actionpack 5.2.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +429 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +57 -0
- data/lib/abstract_controller.rb +27 -0
- data/lib/abstract_controller/asset_paths.rb +12 -0
- data/lib/abstract_controller/base.rb +265 -0
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/caching/fragments.rb +166 -0
- data/lib/abstract_controller/callbacks.rb +212 -0
- data/lib/abstract_controller/collector.rb +43 -0
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +194 -0
- data/lib/abstract_controller/logger.rb +14 -0
- data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
- data/lib/abstract_controller/rendering.rb +127 -0
- data/lib/abstract_controller/translation.rb +31 -0
- data/lib/abstract_controller/url_for.rb +35 -0
- data/lib/action_controller.rb +66 -0
- data/lib/action_controller/api.rb +149 -0
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/base.rb +276 -0
- data/lib/action_controller/caching.rb +46 -0
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +78 -0
- data/lib/action_controller/metal.rb +256 -0
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +274 -0
- data/lib/action_controller/metal/content_security_policy.rb +52 -0
- data/lib/action_controller/metal/cookies.rb +16 -0
- data/lib/action_controller/metal/data_streaming.rb +152 -0
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
- data/lib/action_controller/metal/exceptions.rb +53 -0
- data/lib/action_controller/metal/flash.rb +61 -0
- data/lib/action_controller/metal/force_ssl.rb +99 -0
- data/lib/action_controller/metal/head.rb +60 -0
- data/lib/action_controller/metal/helpers.rb +123 -0
- data/lib/action_controller/metal/http_authentication.rb +519 -0
- data/lib/action_controller/metal/implicit_render.rb +73 -0
- data/lib/action_controller/metal/instrumentation.rb +107 -0
- data/lib/action_controller/metal/live.rb +312 -0
- data/lib/action_controller/metal/mime_responds.rb +313 -0
- data/lib/action_controller/metal/parameter_encoding.rb +51 -0
- data/lib/action_controller/metal/params_wrapper.rb +293 -0
- data/lib/action_controller/metal/redirecting.rb +133 -0
- data/lib/action_controller/metal/renderers.rb +181 -0
- data/lib/action_controller/metal/rendering.rb +122 -0
- data/lib/action_controller/metal/request_forgery_protection.rb +445 -0
- data/lib/action_controller/metal/rescue.rb +28 -0
- data/lib/action_controller/metal/streaming.rb +223 -0
- data/lib/action_controller/metal/strong_parameters.rb +1086 -0
- data/lib/action_controller/metal/testing.rb +16 -0
- data/lib/action_controller/metal/url_for.rb +58 -0
- data/lib/action_controller/railtie.rb +89 -0
- data/lib/action_controller/railties/helpers.rb +24 -0
- data/lib/action_controller/renderer.rb +117 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +629 -0
- data/lib/action_dispatch.rb +112 -0
- data/lib/action_dispatch/http/cache.rb +222 -0
- data/lib/action_dispatch/http/content_security_policy.rb +272 -0
- data/lib/action_dispatch/http/filter_parameters.rb +84 -0
- data/lib/action_dispatch/http/filter_redirect.rb +37 -0
- data/lib/action_dispatch/http/headers.rb +132 -0
- data/lib/action_dispatch/http/mime_negotiation.rb +175 -0
- data/lib/action_dispatch/http/mime_type.rb +342 -0
- data/lib/action_dispatch/http/mime_types.rb +50 -0
- data/lib/action_dispatch/http/parameter_filter.rb +86 -0
- data/lib/action_dispatch/http/parameters.rb +126 -0
- data/lib/action_dispatch/http/rack_cache.rb +63 -0
- data/lib/action_dispatch/http/request.rb +430 -0
- data/lib/action_dispatch/http/response.rb +519 -0
- data/lib/action_dispatch/http/upload.rb +84 -0
- data/lib/action_dispatch/http/url.rb +350 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/journey/formatter.rb +189 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
- data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
- data/lib/action_dispatch/journey/nodes/node.rb +140 -0
- data/lib/action_dispatch/journey/parser.rb +199 -0
- data/lib/action_dispatch/journey/parser.y +50 -0
- data/lib/action_dispatch/journey/parser_extras.rb +31 -0
- data/lib/action_dispatch/journey/path/pattern.rb +198 -0
- data/lib/action_dispatch/journey/route.rb +203 -0
- data/lib/action_dispatch/journey/router.rb +156 -0
- data/lib/action_dispatch/journey/router/utils.rb +102 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +64 -0
- data/lib/action_dispatch/journey/visitors.rb +268 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/middleware/callbacks.rb +36 -0
- data/lib/action_dispatch/middleware/cookies.rb +685 -0
- data/lib/action_dispatch/middleware/debug_exceptions.rb +205 -0
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +147 -0
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +300 -0
- data/lib/action_dispatch/middleware/public_exceptions.rb +57 -0
- data/lib/action_dispatch/middleware/reloader.rb +12 -0
- data/lib/action_dispatch/middleware/remote_ip.rb +183 -0
- data/lib/action_dispatch/middleware/request_id.rb +43 -0
- data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
- data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
- data/lib/action_dispatch/middleware/session/cookie_store.rb +118 -0
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
- data/lib/action_dispatch/middleware/ssl.rb +150 -0
- data/lib/action_dispatch/middleware/stack.rb +116 -0
- data/lib/action_dispatch/middleware/static.rb +130 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +161 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
- data/lib/action_dispatch/railtie.rb +55 -0
- data/lib/action_dispatch/request/session.rb +234 -0
- data/lib/action_dispatch/request/utils.rb +78 -0
- data/lib/action_dispatch/routing.rb +260 -0
- data/lib/action_dispatch/routing/endpoint.rb +17 -0
- data/lib/action_dispatch/routing/inspector.rb +225 -0
- data/lib/action_dispatch/routing/mapper.rb +2267 -0
- data/lib/action_dispatch/routing/polymorphic_routes.rb +352 -0
- data/lib/action_dispatch/routing/redirection.rb +201 -0
- data/lib/action_dispatch/routing/route_set.rb +890 -0
- data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
- data/lib/action_dispatch/routing/url_for.rb +236 -0
- data/lib/action_dispatch/system_test_case.rb +147 -0
- data/lib/action_dispatch/system_testing/browser.rb +49 -0
- data/lib/action_dispatch/system_testing/driver.rb +59 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
- data/lib/action_dispatch/testing/assertion_response.rb +47 -0
- data/lib/action_dispatch/testing/assertions.rb +24 -0
- data/lib/action_dispatch/testing/assertions/response.rb +107 -0
- data/lib/action_dispatch/testing/assertions/routing.rb +222 -0
- data/lib/action_dispatch/testing/integration.rb +652 -0
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +50 -0
- data/lib/action_dispatch/testing/test_request.rb +71 -0
- data/lib/action_dispatch/testing/test_response.rb +53 -0
- data/lib/action_pack.rb +26 -0
- data/lib/action_pack/gem_version.rb +17 -0
- data/lib/action_pack/version.rb +10 -0
- metadata +318 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_dispatch/http/parameter_filter"
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module Http
|
7
|
+
# Allows you to specify sensitive parameters which will be replaced from
|
8
|
+
# the request log by looking in the query string of the request and all
|
9
|
+
# sub-hashes of the params hash to filter. Filtering only certain sub-keys
|
10
|
+
# from a hash is possible by using the dot notation: 'credit_card.number'.
|
11
|
+
# If a block is given, each key and value of the params hash and all
|
12
|
+
# sub-hashes is passed to it, where the value or the key can be replaced using
|
13
|
+
# String#replace or similar method.
|
14
|
+
#
|
15
|
+
# env["action_dispatch.parameter_filter"] = [:password]
|
16
|
+
# => replaces the value to all keys matching /password/i with "[FILTERED]"
|
17
|
+
#
|
18
|
+
# env["action_dispatch.parameter_filter"] = [:foo, "bar"]
|
19
|
+
# => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
|
20
|
+
#
|
21
|
+
# env["action_dispatch.parameter_filter"] = [ "credit_card.code" ]
|
22
|
+
# => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
|
23
|
+
# change { file: { code: "xxxx"} }
|
24
|
+
#
|
25
|
+
# env["action_dispatch.parameter_filter"] = -> (k, v) do
|
26
|
+
# v.reverse! if k =~ /secret/i
|
27
|
+
# end
|
28
|
+
# => reverses the value to all keys matching /secret/i
|
29
|
+
module FilterParameters
|
30
|
+
ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc:
|
31
|
+
NULL_PARAM_FILTER = ParameterFilter.new # :nodoc:
|
32
|
+
NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc:
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
super
|
36
|
+
@filtered_parameters = nil
|
37
|
+
@filtered_env = nil
|
38
|
+
@filtered_path = nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns a hash of parameters with all sensitive data replaced.
|
42
|
+
def filtered_parameters
|
43
|
+
@filtered_parameters ||= parameter_filter.filter(parameters)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns a hash of request.env with all sensitive data replaced.
|
47
|
+
def filtered_env
|
48
|
+
@filtered_env ||= env_filter.filter(@env)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Reconstructs a path with all sensitive GET parameters replaced.
|
52
|
+
def filtered_path
|
53
|
+
@filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def parameter_filter # :doc:
|
59
|
+
parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
|
60
|
+
return NULL_PARAM_FILTER
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def env_filter # :doc:
|
65
|
+
user_key = fetch_header("action_dispatch.parameter_filter") {
|
66
|
+
return NULL_ENV_FILTER
|
67
|
+
}
|
68
|
+
parameter_filter_for(Array(user_key) + ENV_MATCH)
|
69
|
+
end
|
70
|
+
|
71
|
+
def parameter_filter_for(filters) # :doc:
|
72
|
+
ParameterFilter.new(filters)
|
73
|
+
end
|
74
|
+
|
75
|
+
KV_RE = "[^&;=]+"
|
76
|
+
PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
|
77
|
+
def filtered_query_string # :doc:
|
78
|
+
query_string.gsub(PAIR_RE) do |_|
|
79
|
+
parameter_filter.filter($1 => $2).first.join("=")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionDispatch
|
4
|
+
module Http
|
5
|
+
module FilterRedirect
|
6
|
+
FILTERED = "[FILTERED]".freeze # :nodoc:
|
7
|
+
|
8
|
+
def filtered_location # :nodoc:
|
9
|
+
if location_filter_match?
|
10
|
+
FILTERED
|
11
|
+
else
|
12
|
+
location
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def location_filters
|
19
|
+
if request
|
20
|
+
request.get_header("action_dispatch.redirect_filter") || []
|
21
|
+
else
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def location_filter_match?
|
27
|
+
location_filters.any? do |filter|
|
28
|
+
if String === filter
|
29
|
+
location.include?(filter)
|
30
|
+
elsif Regexp === filter
|
31
|
+
location =~ filter
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionDispatch
|
4
|
+
module Http
|
5
|
+
# Provides access to the request's HTTP headers from the environment.
|
6
|
+
#
|
7
|
+
# env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
|
8
|
+
# headers = ActionDispatch::Http::Headers.from_hash(env)
|
9
|
+
# headers["Content-Type"] # => "text/plain"
|
10
|
+
# headers["User-Agent"] # => "curl/7.43.0"
|
11
|
+
#
|
12
|
+
# Also note that when headers are mapped to CGI-like variables by the Rack
|
13
|
+
# server, both dashes and underscores are converted to underscores. This
|
14
|
+
# ambiguity cannot be resolved at this stage anymore. Both underscores and
|
15
|
+
# dashes have to be interpreted as if they were originally sent as dashes.
|
16
|
+
#
|
17
|
+
# # GET / HTTP/1.1
|
18
|
+
# # ...
|
19
|
+
# # User-Agent: curl/7.43.0
|
20
|
+
# # X_Custom_Header: token
|
21
|
+
#
|
22
|
+
# headers["X_Custom_Header"] # => nil
|
23
|
+
# headers["X-Custom-Header"] # => "token"
|
24
|
+
class Headers
|
25
|
+
CGI_VARIABLES = Set.new(%W[
|
26
|
+
AUTH_TYPE
|
27
|
+
CONTENT_LENGTH
|
28
|
+
CONTENT_TYPE
|
29
|
+
GATEWAY_INTERFACE
|
30
|
+
HTTPS
|
31
|
+
PATH_INFO
|
32
|
+
PATH_TRANSLATED
|
33
|
+
QUERY_STRING
|
34
|
+
REMOTE_ADDR
|
35
|
+
REMOTE_HOST
|
36
|
+
REMOTE_IDENT
|
37
|
+
REMOTE_USER
|
38
|
+
REQUEST_METHOD
|
39
|
+
SCRIPT_NAME
|
40
|
+
SERVER_NAME
|
41
|
+
SERVER_PORT
|
42
|
+
SERVER_PROTOCOL
|
43
|
+
SERVER_SOFTWARE
|
44
|
+
]).freeze
|
45
|
+
|
46
|
+
HTTP_HEADER = /\A[A-Za-z0-9-]+\z/
|
47
|
+
|
48
|
+
include Enumerable
|
49
|
+
|
50
|
+
def self.from_hash(hash)
|
51
|
+
new ActionDispatch::Request.new hash
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(request) # :nodoc:
|
55
|
+
@req = request
|
56
|
+
end
|
57
|
+
|
58
|
+
# Returns the value for the given key mapped to @env.
|
59
|
+
def [](key)
|
60
|
+
@req.get_header env_name(key)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Sets the given value for the key mapped to @env.
|
64
|
+
def []=(key, value)
|
65
|
+
@req.set_header env_name(key), value
|
66
|
+
end
|
67
|
+
|
68
|
+
# Add a value to a multivalued header like Vary or Accept-Encoding.
|
69
|
+
def add(key, value)
|
70
|
+
@req.add_header env_name(key), value
|
71
|
+
end
|
72
|
+
|
73
|
+
def key?(key)
|
74
|
+
@req.has_header? env_name(key)
|
75
|
+
end
|
76
|
+
alias :include? :key?
|
77
|
+
|
78
|
+
DEFAULT = Object.new # :nodoc:
|
79
|
+
|
80
|
+
# Returns the value for the given key mapped to @env.
|
81
|
+
#
|
82
|
+
# If the key is not found and an optional code block is not provided,
|
83
|
+
# raises a <tt>KeyError</tt> exception.
|
84
|
+
#
|
85
|
+
# If the code block is provided, then it will be run and
|
86
|
+
# its result returned.
|
87
|
+
def fetch(key, default = DEFAULT)
|
88
|
+
@req.fetch_header(env_name(key)) do
|
89
|
+
return default unless default == DEFAULT
|
90
|
+
return yield if block_given?
|
91
|
+
raise KeyError, key
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def each(&block)
|
96
|
+
@req.each_header(&block)
|
97
|
+
end
|
98
|
+
|
99
|
+
# Returns a new Http::Headers instance containing the contents of
|
100
|
+
# <tt>headers_or_env</tt> and the original instance.
|
101
|
+
def merge(headers_or_env)
|
102
|
+
headers = @req.dup.headers
|
103
|
+
headers.merge!(headers_or_env)
|
104
|
+
headers
|
105
|
+
end
|
106
|
+
|
107
|
+
# Adds the contents of <tt>headers_or_env</tt> to original instance
|
108
|
+
# entries; duplicate keys are overwritten with the values from
|
109
|
+
# <tt>headers_or_env</tt>.
|
110
|
+
def merge!(headers_or_env)
|
111
|
+
headers_or_env.each do |key, value|
|
112
|
+
@req.set_header env_name(key), value
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def env; @req.env.dup; end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# Converts an HTTP header name to an environment variable name if it is
|
121
|
+
# not contained within the headers hash.
|
122
|
+
def env_name(key)
|
123
|
+
key = key.to_s
|
124
|
+
if key =~ HTTP_HEADER
|
125
|
+
key = key.upcase.tr("-", "_")
|
126
|
+
key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
|
127
|
+
end
|
128
|
+
key
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,175 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/module/attribute_accessors"
|
4
|
+
|
5
|
+
module ActionDispatch
|
6
|
+
module Http
|
7
|
+
module MimeNegotiation
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
mattr_accessor :ignore_accept_header, default: false
|
12
|
+
end
|
13
|
+
|
14
|
+
# The MIME type of the HTTP request, such as Mime[:xml].
|
15
|
+
def content_mime_type
|
16
|
+
fetch_header("action_dispatch.request.content_type") do |k|
|
17
|
+
v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/
|
18
|
+
Mime::Type.lookup($1.strip.downcase)
|
19
|
+
else
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
set_header k, v
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def content_type
|
27
|
+
content_mime_type && content_mime_type.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_content_type? # :nodoc:
|
31
|
+
get_header "CONTENT_TYPE"
|
32
|
+
end
|
33
|
+
|
34
|
+
# Returns the accepted MIME type for the request.
|
35
|
+
def accepts
|
36
|
+
fetch_header("action_dispatch.request.accepts") do |k|
|
37
|
+
header = get_header("HTTP_ACCEPT").to_s.strip
|
38
|
+
|
39
|
+
v = if header.empty?
|
40
|
+
[content_mime_type]
|
41
|
+
else
|
42
|
+
Mime::Type.parse(header)
|
43
|
+
end
|
44
|
+
set_header k, v
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the MIME type for the \format used in the request.
|
49
|
+
#
|
50
|
+
# GET /posts/5.xml | request.format => Mime[:xml]
|
51
|
+
# GET /posts/5.xhtml | request.format => Mime[:html]
|
52
|
+
# GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
|
53
|
+
#
|
54
|
+
def format(view_path = [])
|
55
|
+
formats.first || Mime::NullType.instance
|
56
|
+
end
|
57
|
+
|
58
|
+
def formats
|
59
|
+
fetch_header("action_dispatch.request.formats") do |k|
|
60
|
+
params_readable = begin
|
61
|
+
parameters[:format]
|
62
|
+
rescue ActionController::BadRequest
|
63
|
+
false
|
64
|
+
end
|
65
|
+
|
66
|
+
v = if params_readable
|
67
|
+
Array(Mime[parameters[:format]])
|
68
|
+
elsif use_accept_header && valid_accept_header
|
69
|
+
accepts
|
70
|
+
elsif extension_format = format_from_path_extension
|
71
|
+
[extension_format]
|
72
|
+
elsif xhr?
|
73
|
+
[Mime[:js]]
|
74
|
+
else
|
75
|
+
[Mime[:html]]
|
76
|
+
end
|
77
|
+
|
78
|
+
v = v.select do |format|
|
79
|
+
format.symbol || format.ref == "*/*"
|
80
|
+
end
|
81
|
+
|
82
|
+
set_header k, v
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Sets the \variant for template.
|
87
|
+
def variant=(variant)
|
88
|
+
variant = Array(variant)
|
89
|
+
|
90
|
+
if variant.all? { |v| v.is_a?(Symbol) }
|
91
|
+
@variant = ActiveSupport::ArrayInquirer.new(variant)
|
92
|
+
else
|
93
|
+
raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols. " \
|
94
|
+
"For security reasons, never directly set the variant to a user-provided value, " \
|
95
|
+
"like params[:variant].to_sym. Check user-provided value against a whitelist first, " \
|
96
|
+
"then set the variant: request.variant = :tablet if params[:variant] == 'tablet'"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def variant
|
101
|
+
@variant ||= ActiveSupport::ArrayInquirer.new
|
102
|
+
end
|
103
|
+
|
104
|
+
# Sets the \format by string extension, which can be used to force custom formats
|
105
|
+
# that are not controlled by the extension.
|
106
|
+
#
|
107
|
+
# class ApplicationController < ActionController::Base
|
108
|
+
# before_action :adjust_format_for_iphone
|
109
|
+
#
|
110
|
+
# private
|
111
|
+
# def adjust_format_for_iphone
|
112
|
+
# request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
def format=(extension)
|
116
|
+
parameters[:format] = extension.to_s
|
117
|
+
set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Sets the \formats by string extensions. This differs from #format= by allowing you
|
121
|
+
# to set multiple, ordered formats, which is useful when you want to have a fallback.
|
122
|
+
#
|
123
|
+
# In this example, the :iphone format will be used if it's available, otherwise it'll fallback
|
124
|
+
# to the :html format.
|
125
|
+
#
|
126
|
+
# class ApplicationController < ActionController::Base
|
127
|
+
# before_action :adjust_format_for_iphone_with_html_fallback
|
128
|
+
#
|
129
|
+
# private
|
130
|
+
# def adjust_format_for_iphone_with_html_fallback
|
131
|
+
# request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
|
132
|
+
# end
|
133
|
+
# end
|
134
|
+
def formats=(extensions)
|
135
|
+
parameters[:format] = extensions.first.to_s
|
136
|
+
set_header "action_dispatch.request.formats", extensions.collect { |extension|
|
137
|
+
Mime::Type.lookup_by_extension(extension)
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns the first MIME type that matches the provided array of MIME types.
|
142
|
+
def negotiate_mime(order)
|
143
|
+
formats.each do |priority|
|
144
|
+
if priority == Mime::ALL
|
145
|
+
return order.first
|
146
|
+
elsif order.include?(priority)
|
147
|
+
return priority
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
order.include?(Mime::ALL) ? format : nil
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
|
156
|
+
BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
|
157
|
+
|
158
|
+
def valid_accept_header # :doc:
|
159
|
+
(xhr? && (accept.present? || content_mime_type)) ||
|
160
|
+
(accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
|
161
|
+
end
|
162
|
+
|
163
|
+
def use_accept_header # :doc:
|
164
|
+
!self.class.ignore_accept_header
|
165
|
+
end
|
166
|
+
|
167
|
+
def format_from_path_extension # :doc:
|
168
|
+
path = get_header("action_dispatch.original_path") || get_header("PATH_INFO")
|
169
|
+
if match = path && path.match(/\.(\w+)\z/)
|
170
|
+
Mime[match.captures.first]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
@@ -0,0 +1,342 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# -*- frozen-string-literal: true -*-
|
4
|
+
|
5
|
+
require "singleton"
|
6
|
+
require "active_support/core_ext/string/starts_ends_with"
|
7
|
+
|
8
|
+
module Mime
|
9
|
+
class Mimes
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@mimes = []
|
14
|
+
@symbols = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def each
|
18
|
+
@mimes.each { |x| yield x }
|
19
|
+
end
|
20
|
+
|
21
|
+
def <<(type)
|
22
|
+
@mimes << type
|
23
|
+
@symbols = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete_if
|
27
|
+
@mimes.delete_if { |x| yield x }.tap { @symbols = nil }
|
28
|
+
end
|
29
|
+
|
30
|
+
def symbols
|
31
|
+
@symbols ||= map(&:to_sym)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
SET = Mimes.new
|
36
|
+
EXTENSION_LOOKUP = {}
|
37
|
+
LOOKUP = {}
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def [](type)
|
41
|
+
return type if type.is_a?(Type)
|
42
|
+
Type.lookup_by_extension(type)
|
43
|
+
end
|
44
|
+
|
45
|
+
def fetch(type)
|
46
|
+
return type if type.is_a?(Type)
|
47
|
+
EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Encapsulates the notion of a MIME type. Can be used at render time, for example, with:
|
52
|
+
#
|
53
|
+
# class PostsController < ActionController::Base
|
54
|
+
# def show
|
55
|
+
# @post = Post.find(params[:id])
|
56
|
+
#
|
57
|
+
# respond_to do |format|
|
58
|
+
# format.html
|
59
|
+
# format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
|
60
|
+
# format.xml { render xml: @post }
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
# end
|
64
|
+
class Type
|
65
|
+
attr_reader :symbol
|
66
|
+
|
67
|
+
@register_callbacks = []
|
68
|
+
|
69
|
+
# A simple helper class used in parsing the accept header.
|
70
|
+
class AcceptItem #:nodoc:
|
71
|
+
attr_accessor :index, :name, :q
|
72
|
+
alias :to_s :name
|
73
|
+
|
74
|
+
def initialize(index, name, q = nil)
|
75
|
+
@index = index
|
76
|
+
@name = name
|
77
|
+
q ||= 0.0 if @name == "*/*".freeze # Default wildcard match to end of list.
|
78
|
+
@q = ((q || 1.0).to_f * 100).to_i
|
79
|
+
end
|
80
|
+
|
81
|
+
def <=>(item)
|
82
|
+
result = item.q <=> @q
|
83
|
+
result = @index <=> item.index if result == 0
|
84
|
+
result
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class AcceptList #:nodoc:
|
89
|
+
def self.sort!(list)
|
90
|
+
list.sort!
|
91
|
+
|
92
|
+
text_xml_idx = find_item_by_name list, "text/xml"
|
93
|
+
app_xml_idx = find_item_by_name list, Mime[:xml].to_s
|
94
|
+
|
95
|
+
# Take care of the broken text/xml entry by renaming or deleting it.
|
96
|
+
if text_xml_idx && app_xml_idx
|
97
|
+
app_xml = list[app_xml_idx]
|
98
|
+
text_xml = list[text_xml_idx]
|
99
|
+
|
100
|
+
app_xml.q = [text_xml.q, app_xml.q].max # Set the q value to the max of the two.
|
101
|
+
if app_xml_idx > text_xml_idx # Make sure app_xml is ahead of text_xml in the list.
|
102
|
+
list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml
|
103
|
+
app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx
|
104
|
+
end
|
105
|
+
list.delete_at(text_xml_idx) # Delete text_xml from the list.
|
106
|
+
elsif text_xml_idx
|
107
|
+
list[text_xml_idx].name = Mime[:xml].to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
# Look for more specific XML-based types and sort them ahead of app/xml.
|
111
|
+
if app_xml_idx
|
112
|
+
app_xml = list[app_xml_idx]
|
113
|
+
idx = app_xml_idx
|
114
|
+
|
115
|
+
while idx < list.length
|
116
|
+
type = list[idx]
|
117
|
+
break if type.q < app_xml.q
|
118
|
+
|
119
|
+
if type.name.ends_with? "+xml"
|
120
|
+
list[app_xml_idx], list[idx] = list[idx], app_xml
|
121
|
+
app_xml_idx = idx
|
122
|
+
end
|
123
|
+
idx += 1
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
|
128
|
+
list
|
129
|
+
end
|
130
|
+
|
131
|
+
def self.find_item_by_name(array, name)
|
132
|
+
array.index { |item| item.name == name }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
class << self
|
137
|
+
TRAILING_STAR_REGEXP = /^(text|application)\/\*/
|
138
|
+
PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
|
139
|
+
|
140
|
+
def register_callback(&block)
|
141
|
+
@register_callbacks << block
|
142
|
+
end
|
143
|
+
|
144
|
+
def lookup(string)
|
145
|
+
LOOKUP[string] || Type.new(string)
|
146
|
+
end
|
147
|
+
|
148
|
+
def lookup_by_extension(extension)
|
149
|
+
EXTENSION_LOOKUP[extension.to_s]
|
150
|
+
end
|
151
|
+
|
152
|
+
# Registers an alias that's not used on MIME type lookup, but can be referenced directly. Especially useful for
|
153
|
+
# rendering different HTML versions depending on the user agent, like an iPhone.
|
154
|
+
def register_alias(string, symbol, extension_synonyms = [])
|
155
|
+
register(string, symbol, [], extension_synonyms, true)
|
156
|
+
end
|
157
|
+
|
158
|
+
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
|
159
|
+
new_mime = Type.new(string, symbol, mime_type_synonyms)
|
160
|
+
|
161
|
+
SET << new_mime
|
162
|
+
|
163
|
+
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = new_mime } unless skip_lookup
|
164
|
+
([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = new_mime }
|
165
|
+
|
166
|
+
@register_callbacks.each do |callback|
|
167
|
+
callback.call(new_mime)
|
168
|
+
end
|
169
|
+
new_mime
|
170
|
+
end
|
171
|
+
|
172
|
+
def parse(accept_header)
|
173
|
+
if !accept_header.include?(",")
|
174
|
+
accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
|
175
|
+
parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
|
176
|
+
else
|
177
|
+
list, index = [], 0
|
178
|
+
accept_header.split(",").each do |header|
|
179
|
+
params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
|
180
|
+
|
181
|
+
next unless params
|
182
|
+
params.strip!
|
183
|
+
next if params.empty?
|
184
|
+
|
185
|
+
params = parse_trailing_star(params) || [params]
|
186
|
+
|
187
|
+
params.each do |m|
|
188
|
+
list << AcceptItem.new(index, m.to_s, q)
|
189
|
+
index += 1
|
190
|
+
end
|
191
|
+
end
|
192
|
+
AcceptList.sort! list
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def parse_trailing_star(accept_header)
|
197
|
+
parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP
|
198
|
+
end
|
199
|
+
|
200
|
+
# For an input of <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics],
|
201
|
+
# Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]</tt>.
|
202
|
+
#
|
203
|
+
# For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
|
204
|
+
# Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]</tt>.
|
205
|
+
def parse_data_with_trailing_star(type)
|
206
|
+
Mime::SET.select { |m| m =~ type }
|
207
|
+
end
|
208
|
+
|
209
|
+
# This method is opposite of register method.
|
210
|
+
#
|
211
|
+
# To unregister a MIME type:
|
212
|
+
#
|
213
|
+
# Mime::Type.unregister(:mobile)
|
214
|
+
def unregister(symbol)
|
215
|
+
symbol = symbol.downcase
|
216
|
+
if mime = Mime[symbol]
|
217
|
+
SET.delete_if { |v| v.eql?(mime) }
|
218
|
+
LOOKUP.delete_if { |_, v| v.eql?(mime) }
|
219
|
+
EXTENSION_LOOKUP.delete_if { |_, v| v.eql?(mime) }
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
attr_reader :hash
|
225
|
+
|
226
|
+
def initialize(string, symbol = nil, synonyms = [])
|
227
|
+
@symbol, @synonyms = symbol, synonyms
|
228
|
+
@string = string
|
229
|
+
@hash = [@string, @synonyms, @symbol].hash
|
230
|
+
end
|
231
|
+
|
232
|
+
def to_s
|
233
|
+
@string
|
234
|
+
end
|
235
|
+
|
236
|
+
def to_str
|
237
|
+
to_s
|
238
|
+
end
|
239
|
+
|
240
|
+
def to_sym
|
241
|
+
@symbol
|
242
|
+
end
|
243
|
+
|
244
|
+
def ref
|
245
|
+
symbol || to_s
|
246
|
+
end
|
247
|
+
|
248
|
+
def ===(list)
|
249
|
+
if list.is_a?(Array)
|
250
|
+
(@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
|
251
|
+
else
|
252
|
+
super
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def ==(mime_type)
|
257
|
+
return false unless mime_type
|
258
|
+
(@synonyms + [ self ]).any? do |synonym|
|
259
|
+
synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def eql?(other)
|
264
|
+
super || (self.class == other.class &&
|
265
|
+
@string == other.string &&
|
266
|
+
@synonyms == other.synonyms &&
|
267
|
+
@symbol == other.symbol)
|
268
|
+
end
|
269
|
+
|
270
|
+
def =~(mime_type)
|
271
|
+
return false unless mime_type
|
272
|
+
regexp = Regexp.new(Regexp.quote(mime_type.to_s))
|
273
|
+
@synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp
|
274
|
+
end
|
275
|
+
|
276
|
+
def html?
|
277
|
+
symbol == :html || @string =~ /html/
|
278
|
+
end
|
279
|
+
|
280
|
+
def all?; false; end
|
281
|
+
|
282
|
+
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
283
|
+
# Workaround for Ruby 2.2 "private attribute?" warning.
|
284
|
+
protected
|
285
|
+
|
286
|
+
attr_reader :string, :synonyms
|
287
|
+
|
288
|
+
private
|
289
|
+
|
290
|
+
def to_ary; end
|
291
|
+
def to_a; end
|
292
|
+
|
293
|
+
def method_missing(method, *args)
|
294
|
+
if method.to_s.ends_with? "?"
|
295
|
+
method[0..-2].downcase.to_sym == to_sym
|
296
|
+
else
|
297
|
+
super
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def respond_to_missing?(method, include_private = false)
|
302
|
+
(method.to_s.ends_with? "?") || super
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
class AllType < Type
|
307
|
+
include Singleton
|
308
|
+
|
309
|
+
def initialize
|
310
|
+
super "*/*", :all
|
311
|
+
end
|
312
|
+
|
313
|
+
def all?; true; end
|
314
|
+
def html?; true; end
|
315
|
+
end
|
316
|
+
|
317
|
+
# ALL isn't a real MIME type, so we don't register it for lookup with the
|
318
|
+
# other concrete types. It's a wildcard match that we use for `respond_to`
|
319
|
+
# negotiation internals.
|
320
|
+
ALL = AllType.instance
|
321
|
+
|
322
|
+
class NullType
|
323
|
+
include Singleton
|
324
|
+
|
325
|
+
def nil?
|
326
|
+
true
|
327
|
+
end
|
328
|
+
|
329
|
+
def ref; end
|
330
|
+
|
331
|
+
private
|
332
|
+
def respond_to_missing?(method, _)
|
333
|
+
method.to_s.ends_with? "?"
|
334
|
+
end
|
335
|
+
|
336
|
+
def method_missing(method, *args)
|
337
|
+
false if method.to_s.ends_with? "?"
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
require "action_dispatch/http/mime_types"
|