actionpack 6.0.0

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.

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +311 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +58 -0
  5. data/lib/abstract_controller.rb +27 -0
  6. data/lib/abstract_controller/asset_paths.rb +12 -0
  7. data/lib/abstract_controller/base.rb +267 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/caching/fragments.rb +150 -0
  10. data/lib/abstract_controller/callbacks.rb +224 -0
  11. data/lib/abstract_controller/collector.rb +43 -0
  12. data/lib/abstract_controller/error.rb +6 -0
  13. data/lib/abstract_controller/helpers.rb +194 -0
  14. data/lib/abstract_controller/logger.rb +14 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +20 -0
  16. data/lib/abstract_controller/rendering.rb +127 -0
  17. data/lib/abstract_controller/translation.rb +32 -0
  18. data/lib/abstract_controller/url_for.rb +35 -0
  19. data/lib/action_controller.rb +67 -0
  20. data/lib/action_controller/api.rb +150 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +271 -0
  23. data/lib/action_controller/caching.rb +46 -0
  24. data/lib/action_controller/form_builder.rb +50 -0
  25. data/lib/action_controller/log_subscriber.rb +81 -0
  26. data/lib/action_controller/metal.rb +256 -0
  27. data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
  28. data/lib/action_controller/metal/conditional_get.rb +280 -0
  29. data/lib/action_controller/metal/content_security_policy.rb +52 -0
  30. data/lib/action_controller/metal/cookies.rb +16 -0
  31. data/lib/action_controller/metal/data_streaming.rb +151 -0
  32. data/lib/action_controller/metal/default_headers.rb +17 -0
  33. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  34. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  35. data/lib/action_controller/metal/exceptions.rb +74 -0
  36. data/lib/action_controller/metal/flash.rb +61 -0
  37. data/lib/action_controller/metal/force_ssl.rb +58 -0
  38. data/lib/action_controller/metal/head.rb +60 -0
  39. data/lib/action_controller/metal/helpers.rb +122 -0
  40. data/lib/action_controller/metal/http_authentication.rb +518 -0
  41. data/lib/action_controller/metal/implicit_render.rb +63 -0
  42. data/lib/action_controller/metal/instrumentation.rb +105 -0
  43. data/lib/action_controller/metal/live.rb +314 -0
  44. data/lib/action_controller/metal/mime_responds.rb +324 -0
  45. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  46. data/lib/action_controller/metal/params_wrapper.rb +297 -0
  47. data/lib/action_controller/metal/redirecting.rb +133 -0
  48. data/lib/action_controller/metal/renderers.rb +181 -0
  49. data/lib/action_controller/metal/rendering.rb +122 -0
  50. data/lib/action_controller/metal/request_forgery_protection.rb +456 -0
  51. data/lib/action_controller/metal/rescue.rb +28 -0
  52. data/lib/action_controller/metal/streaming.rb +223 -0
  53. data/lib/action_controller/metal/strong_parameters.rb +1105 -0
  54. data/lib/action_controller/metal/testing.rb +16 -0
  55. data/lib/action_controller/metal/url_for.rb +58 -0
  56. data/lib/action_controller/railtie.rb +89 -0
  57. data/lib/action_controller/railties/helpers.rb +24 -0
  58. data/lib/action_controller/renderer.rb +130 -0
  59. data/lib/action_controller/template_assertions.rb +11 -0
  60. data/lib/action_controller/test_case.rb +626 -0
  61. data/lib/action_dispatch.rb +114 -0
  62. data/lib/action_dispatch/http/cache.rb +226 -0
  63. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  64. data/lib/action_dispatch/http/content_security_policy.rb +284 -0
  65. data/lib/action_dispatch/http/filter_parameters.rb +86 -0
  66. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  67. data/lib/action_dispatch/http/headers.rb +132 -0
  68. data/lib/action_dispatch/http/mime_negotiation.rb +177 -0
  69. data/lib/action_dispatch/http/mime_type.rb +350 -0
  70. data/lib/action_dispatch/http/mime_types.rb +50 -0
  71. data/lib/action_dispatch/http/parameter_filter.rb +12 -0
  72. data/lib/action_dispatch/http/parameters.rb +136 -0
  73. data/lib/action_dispatch/http/rack_cache.rb +63 -0
  74. data/lib/action_dispatch/http/request.rb +427 -0
  75. data/lib/action_dispatch/http/response.rb +534 -0
  76. data/lib/action_dispatch/http/upload.rb +92 -0
  77. data/lib/action_dispatch/http/url.rb +350 -0
  78. data/lib/action_dispatch/journey.rb +7 -0
  79. data/lib/action_dispatch/journey/formatter.rb +189 -0
  80. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  81. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  82. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  83. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  84. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  85. data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
  86. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  87. data/lib/action_dispatch/journey/nodes/node.rb +141 -0
  88. data/lib/action_dispatch/journey/parser.rb +199 -0
  89. data/lib/action_dispatch/journey/parser.y +50 -0
  90. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  91. data/lib/action_dispatch/journey/path/pattern.rb +203 -0
  92. data/lib/action_dispatch/journey/route.rb +204 -0
  93. data/lib/action_dispatch/journey/router.rb +153 -0
  94. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  95. data/lib/action_dispatch/journey/routes.rb +81 -0
  96. data/lib/action_dispatch/journey/scanner.rb +71 -0
  97. data/lib/action_dispatch/journey/visitors.rb +268 -0
  98. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  99. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  100. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  101. data/lib/action_dispatch/middleware/actionable_exceptions.rb +39 -0
  102. data/lib/action_dispatch/middleware/callbacks.rb +34 -0
  103. data/lib/action_dispatch/middleware/cookies.rb +663 -0
  104. data/lib/action_dispatch/middleware/debug_exceptions.rb +185 -0
  105. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  106. data/lib/action_dispatch/middleware/debug_view.rb +68 -0
  107. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -0
  108. data/lib/action_dispatch/middleware/executor.rb +21 -0
  109. data/lib/action_dispatch/middleware/flash.rb +300 -0
  110. data/lib/action_dispatch/middleware/host_authorization.rb +103 -0
  111. data/lib/action_dispatch/middleware/public_exceptions.rb +61 -0
  112. data/lib/action_dispatch/middleware/reloader.rb +12 -0
  113. data/lib/action_dispatch/middleware/remote_ip.rb +181 -0
  114. data/lib/action_dispatch/middleware/request_id.rb +43 -0
  115. data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
  116. data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
  117. data/lib/action_dispatch/middleware/session/cookie_store.rb +113 -0
  118. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
  119. data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
  120. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  121. data/lib/action_dispatch/middleware/stack.rb +148 -0
  122. data/lib/action_dispatch/middleware/static.rb +129 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +24 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +29 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +62 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +38 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +24 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +15 -0
  137. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +165 -0
  138. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  139. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  140. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  141. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  142. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  143. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  144. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  145. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  146. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  147. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  148. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  149. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +203 -0
  150. data/lib/action_dispatch/railtie.rb +58 -0
  151. data/lib/action_dispatch/request/session.rb +242 -0
  152. data/lib/action_dispatch/request/utils.rb +78 -0
  153. data/lib/action_dispatch/routing.rb +261 -0
  154. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  155. data/lib/action_dispatch/routing/inspector.rb +274 -0
  156. data/lib/action_dispatch/routing/mapper.rb +2289 -0
  157. data/lib/action_dispatch/routing/polymorphic_routes.rb +351 -0
  158. data/lib/action_dispatch/routing/redirection.rb +201 -0
  159. data/lib/action_dispatch/routing/route_set.rb +887 -0
  160. data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
  161. data/lib/action_dispatch/routing/url_for.rb +237 -0
  162. data/lib/action_dispatch/system_test_case.rb +168 -0
  163. data/lib/action_dispatch/system_testing/browser.rb +80 -0
  164. data/lib/action_dispatch/system_testing/driver.rb +68 -0
  165. data/lib/action_dispatch/system_testing/server.rb +31 -0
  166. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +97 -0
  167. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +33 -0
  168. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  169. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  170. data/lib/action_dispatch/testing/assertions.rb +24 -0
  171. data/lib/action_dispatch/testing/assertions/response.rb +106 -0
  172. data/lib/action_dispatch/testing/assertions/routing.rb +234 -0
  173. data/lib/action_dispatch/testing/integration.rb +659 -0
  174. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  175. data/lib/action_dispatch/testing/test_process.rb +50 -0
  176. data/lib/action_dispatch/testing/test_request.rb +71 -0
  177. data/lib/action_dispatch/testing/test_response.rb +25 -0
  178. data/lib/action_pack.rb +26 -0
  179. data/lib/action_pack/gem_version.rb +17 -0
  180. data/lib/action_pack/version.rb +10 -0
  181. metadata +329 -0
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/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 are passed to it, where the value or the key can be replaced using
13
+ # String#replace or similar methods.
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 = ActiveSupport::ParameterFilter.new # :nodoc:
32
+ NULL_ENV_FILTER = ActiveSupport::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
+ rescue ActionDispatch::Http::Parameters::ParseError
45
+ @filtered_parameters = {}
46
+ end
47
+
48
+ # Returns a hash of request.env with all sensitive data replaced.
49
+ def filtered_env
50
+ @filtered_env ||= env_filter.filter(@env)
51
+ end
52
+
53
+ # Reconstructs a path with all sensitive GET parameters replaced.
54
+ def filtered_path
55
+ @filtered_path ||= query_string.empty? ? path : "#{path}?#{filtered_query_string}"
56
+ end
57
+
58
+ private
59
+
60
+ def parameter_filter # :doc:
61
+ parameter_filter_for fetch_header("action_dispatch.parameter_filter") {
62
+ return NULL_PARAM_FILTER
63
+ }
64
+ end
65
+
66
+ def env_filter # :doc:
67
+ user_key = fetch_header("action_dispatch.parameter_filter") {
68
+ return NULL_ENV_FILTER
69
+ }
70
+ parameter_filter_for(Array(user_key) + ENV_MATCH)
71
+ end
72
+
73
+ def parameter_filter_for(filters) # :doc:
74
+ ActiveSupport::ParameterFilter.new(filters)
75
+ end
76
+
77
+ KV_RE = "[^&;=]+"
78
+ PAIR_RE = %r{(#{KV_RE})=(#{KV_RE})}
79
+ def filtered_query_string # :doc:
80
+ query_string.gsub(PAIR_RE) do |_|
81
+ parameter_filter.filter($1 => $2).first.join("=")
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ module Http
5
+ module FilterRedirect
6
+ FILTERED = "[FILTERED]" # :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 HTTP_HEADER.match?(key)
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,177 @@
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
+ RESCUABLE_MIME_FORMAT_ERRORS = [
11
+ ActionController::BadRequest,
12
+ ActionDispatch::Http::Parameters::ParseError,
13
+ ]
14
+
15
+ included do
16
+ mattr_accessor :ignore_accept_header, default: false
17
+ end
18
+
19
+ # The MIME type of the HTTP request, such as Mime[:xml].
20
+ def content_mime_type
21
+ fetch_header("action_dispatch.request.content_type") do |k|
22
+ v = if get_header("CONTENT_TYPE") =~ /^([^,\;]*)/
23
+ Mime::Type.lookup($1.strip.downcase)
24
+ else
25
+ nil
26
+ end
27
+ set_header k, v
28
+ end
29
+ end
30
+
31
+ def content_type
32
+ content_mime_type && content_mime_type.to_s
33
+ end
34
+
35
+ def has_content_type? # :nodoc:
36
+ get_header "CONTENT_TYPE"
37
+ end
38
+
39
+ # Returns the accepted MIME type for the request.
40
+ def accepts
41
+ fetch_header("action_dispatch.request.accepts") do |k|
42
+ header = get_header("HTTP_ACCEPT").to_s.strip
43
+
44
+ v = if header.empty?
45
+ [content_mime_type]
46
+ else
47
+ Mime::Type.parse(header)
48
+ end
49
+ set_header k, v
50
+ end
51
+ end
52
+
53
+ # Returns the MIME type for the \format used in the request.
54
+ #
55
+ # GET /posts/5.xml | request.format => Mime[:xml]
56
+ # GET /posts/5.xhtml | request.format => Mime[:html]
57
+ # GET /posts/5 | request.format => Mime[:html] or Mime[:js], or request.accepts.first
58
+ #
59
+ def format(view_path = [])
60
+ formats.first || Mime::NullType.instance
61
+ end
62
+
63
+ def formats
64
+ fetch_header("action_dispatch.request.formats") do |k|
65
+ params_readable = begin
66
+ parameters[:format]
67
+ rescue *RESCUABLE_MIME_FORMAT_ERRORS
68
+ false
69
+ end
70
+
71
+ v = if params_readable
72
+ Array(Mime[parameters[:format]])
73
+ elsif use_accept_header && valid_accept_header
74
+ accepts
75
+ elsif extension_format = format_from_path_extension
76
+ [extension_format]
77
+ elsif xhr?
78
+ [Mime[:js]]
79
+ else
80
+ [Mime[:html]]
81
+ end
82
+
83
+ v = v.select do |format|
84
+ format.symbol || format.ref == "*/*"
85
+ end
86
+
87
+ set_header k, v
88
+ end
89
+ end
90
+
91
+ # Sets the \variant for template.
92
+ def variant=(variant)
93
+ variant = Array(variant)
94
+
95
+ if variant.all? { |v| v.is_a?(Symbol) }
96
+ @variant = ActiveSupport::ArrayInquirer.new(variant)
97
+ else
98
+ raise ArgumentError, "request.variant must be set to a Symbol or an Array of Symbols."
99
+ end
100
+ end
101
+
102
+ def variant
103
+ @variant ||= ActiveSupport::ArrayInquirer.new
104
+ end
105
+
106
+ # Sets the \format by string extension, which can be used to force custom formats
107
+ # that are not controlled by the extension.
108
+ #
109
+ # class ApplicationController < ActionController::Base
110
+ # before_action :adjust_format_for_iphone
111
+ #
112
+ # private
113
+ # def adjust_format_for_iphone
114
+ # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/]
115
+ # end
116
+ # end
117
+ def format=(extension)
118
+ parameters[:format] = extension.to_s
119
+ set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])]
120
+ end
121
+
122
+ # Sets the \formats by string extensions. This differs from #format= by allowing you
123
+ # to set multiple, ordered formats, which is useful when you want to have a fallback.
124
+ #
125
+ # In this example, the :iphone format will be used if it's available, otherwise it'll fallback
126
+ # to the :html format.
127
+ #
128
+ # class ApplicationController < ActionController::Base
129
+ # before_action :adjust_format_for_iphone_with_html_fallback
130
+ #
131
+ # private
132
+ # def adjust_format_for_iphone_with_html_fallback
133
+ # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
134
+ # end
135
+ # end
136
+ def formats=(extensions)
137
+ parameters[:format] = extensions.first.to_s
138
+ set_header "action_dispatch.request.formats", extensions.collect { |extension|
139
+ Mime::Type.lookup_by_extension(extension)
140
+ }
141
+ end
142
+
143
+ # Returns the first MIME type that matches the provided array of MIME types.
144
+ def negotiate_mime(order)
145
+ formats.each do |priority|
146
+ if priority == Mime::ALL
147
+ return order.first
148
+ elsif order.include?(priority)
149
+ return priority
150
+ end
151
+ end
152
+
153
+ order.include?(Mime::ALL) ? format : nil
154
+ end
155
+
156
+ private
157
+
158
+ BROWSER_LIKE_ACCEPTS = /,\s*\*\/\*|\*\/\*\s*,/
159
+
160
+ def valid_accept_header # :doc:
161
+ (xhr? && (accept.present? || content_mime_type)) ||
162
+ (accept.present? && accept !~ BROWSER_LIKE_ACCEPTS)
163
+ end
164
+
165
+ def use_accept_header # :doc:
166
+ !self.class.ignore_accept_header
167
+ end
168
+
169
+ def format_from_path_extension # :doc:
170
+ path = get_header("action_dispatch.original_path") || get_header("PATH_INFO")
171
+ if match = path && path.match(/\.(\w+)\z/)
172
+ Mime[match.captures.first]
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,350 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "singleton"
4
+ require "active_support/core_ext/string/starts_ends_with"
5
+
6
+ module Mime
7
+ class Mimes
8
+ include Enumerable
9
+
10
+ def initialize
11
+ @mimes = []
12
+ @symbols = nil
13
+ end
14
+
15
+ def each
16
+ @mimes.each { |x| yield x }
17
+ end
18
+
19
+ def <<(type)
20
+ @mimes << type
21
+ @symbols = nil
22
+ end
23
+
24
+ def delete_if
25
+ @mimes.delete_if { |x| yield x }.tap { @symbols = nil }
26
+ end
27
+
28
+ def symbols
29
+ @symbols ||= map(&:to_sym)
30
+ end
31
+ end
32
+
33
+ SET = Mimes.new
34
+ EXTENSION_LOOKUP = {}
35
+ LOOKUP = {}
36
+
37
+ class << self
38
+ def [](type)
39
+ return type if type.is_a?(Type)
40
+ Type.lookup_by_extension(type)
41
+ end
42
+
43
+ def fetch(type)
44
+ return type if type.is_a?(Type)
45
+ EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k }
46
+ end
47
+ end
48
+
49
+ # Encapsulates the notion of a MIME type. Can be used at render time, for example, with:
50
+ #
51
+ # class PostsController < ActionController::Base
52
+ # def show
53
+ # @post = Post.find(params[:id])
54
+ #
55
+ # respond_to do |format|
56
+ # format.html
57
+ # format.ics { render body: @post.to_ics, mime_type: Mime::Type.lookup("text/calendar") }
58
+ # format.xml { render xml: @post }
59
+ # end
60
+ # end
61
+ # end
62
+ class Type
63
+ attr_reader :symbol
64
+
65
+ @register_callbacks = []
66
+
67
+ # A simple helper class used in parsing the accept header.
68
+ class AcceptItem #:nodoc:
69
+ attr_accessor :index, :name, :q
70
+ alias :to_s :name
71
+
72
+ def initialize(index, name, q = nil)
73
+ @index = index
74
+ @name = name
75
+ q ||= 0.0 if @name == "*/*" # Default wildcard match to end of list.
76
+ @q = ((q || 1.0).to_f * 100).to_i
77
+ end
78
+
79
+ def <=>(item)
80
+ result = item.q <=> @q
81
+ result = @index <=> item.index if result == 0
82
+ result
83
+ end
84
+ end
85
+
86
+ class AcceptList #:nodoc:
87
+ def self.sort!(list)
88
+ list.sort!
89
+
90
+ text_xml_idx = find_item_by_name list, "text/xml"
91
+ app_xml_idx = find_item_by_name list, Mime[:xml].to_s
92
+
93
+ # Take care of the broken text/xml entry by renaming or deleting it.
94
+ if text_xml_idx && app_xml_idx
95
+ app_xml = list[app_xml_idx]
96
+ text_xml = list[text_xml_idx]
97
+
98
+ app_xml.q = [text_xml.q, app_xml.q].max # Set the q value to the max of the two.
99
+ if app_xml_idx > text_xml_idx # Make sure app_xml is ahead of text_xml in the list.
100
+ list[app_xml_idx], list[text_xml_idx] = text_xml, app_xml
101
+ app_xml_idx, text_xml_idx = text_xml_idx, app_xml_idx
102
+ end
103
+ list.delete_at(text_xml_idx) # Delete text_xml from the list.
104
+ elsif text_xml_idx
105
+ list[text_xml_idx].name = Mime[:xml].to_s
106
+ end
107
+
108
+ # Look for more specific XML-based types and sort them ahead of app/xml.
109
+ if app_xml_idx
110
+ app_xml = list[app_xml_idx]
111
+ idx = app_xml_idx
112
+
113
+ while idx < list.length
114
+ type = list[idx]
115
+ break if type.q < app_xml.q
116
+
117
+ if type.name.ends_with? "+xml"
118
+ list[app_xml_idx], list[idx] = list[idx], app_xml
119
+ app_xml_idx = idx
120
+ end
121
+ idx += 1
122
+ end
123
+ end
124
+
125
+ list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
126
+ list
127
+ end
128
+
129
+ def self.find_item_by_name(array, name)
130
+ array.index { |item| item.name == name }
131
+ end
132
+ end
133
+
134
+ class << self
135
+ TRAILING_STAR_REGEXP = /^(text|application)\/\*/
136
+ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
137
+
138
+ def register_callback(&block)
139
+ @register_callbacks << block
140
+ end
141
+
142
+ def lookup(string)
143
+ LOOKUP[string] || Type.new(string)
144
+ end
145
+
146
+ def lookup_by_extension(extension)
147
+ EXTENSION_LOOKUP[extension.to_s]
148
+ end
149
+
150
+ # Registers an alias that's not used on MIME type lookup, but can be referenced directly. Especially useful for
151
+ # rendering different HTML versions depending on the user agent, like an iPhone.
152
+ def register_alias(string, symbol, extension_synonyms = [])
153
+ register(string, symbol, [], extension_synonyms, true)
154
+ end
155
+
156
+ def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
157
+ new_mime = Type.new(string, symbol, mime_type_synonyms)
158
+
159
+ SET << new_mime
160
+
161
+ ([string] + mime_type_synonyms).each { |str| LOOKUP[str] = new_mime } unless skip_lookup
162
+ ([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = new_mime }
163
+
164
+ @register_callbacks.each do |callback|
165
+ callback.call(new_mime)
166
+ end
167
+ new_mime
168
+ end
169
+
170
+ def parse(accept_header)
171
+ if !accept_header.include?(",")
172
+ accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
173
+ return [] unless accept_header
174
+ parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)].compact
175
+ else
176
+ list, index = [], 0
177
+ accept_header.split(",").each do |header|
178
+ params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
179
+
180
+ next unless params
181
+ params.strip!
182
+ next if params.empty?
183
+
184
+ params = parse_trailing_star(params) || [params]
185
+
186
+ params.each do |m|
187
+ list << AcceptItem.new(index, m.to_s, q)
188
+ index += 1
189
+ end
190
+ end
191
+ AcceptList.sort! list
192
+ end
193
+ end
194
+
195
+ def parse_trailing_star(accept_header)
196
+ parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP
197
+ end
198
+
199
+ # For an input of <tt>'text'</tt>, returns <tt>[Mime[:json], Mime[:xml], Mime[:ics],
200
+ # Mime[:html], Mime[:css], Mime[:csv], Mime[:js], Mime[:yaml], Mime[:text]</tt>.
201
+ #
202
+ # For an input of <tt>'application'</tt>, returns <tt>[Mime[:html], Mime[:js],
203
+ # Mime[:xml], Mime[:yaml], Mime[:atom], Mime[:json], Mime[:rss], Mime[:url_encoded_form]</tt>.
204
+ def parse_data_with_trailing_star(type)
205
+ Mime::SET.select { |m| m =~ type }
206
+ end
207
+
208
+ # This method is opposite of register method.
209
+ #
210
+ # To unregister a MIME type:
211
+ #
212
+ # Mime::Type.unregister(:mobile)
213
+ def unregister(symbol)
214
+ symbol = symbol.downcase
215
+ if mime = Mime[symbol]
216
+ SET.delete_if { |v| v.eql?(mime) }
217
+ LOOKUP.delete_if { |_, v| v.eql?(mime) }
218
+ EXTENSION_LOOKUP.delete_if { |_, v| v.eql?(mime) }
219
+ end
220
+ end
221
+ end
222
+
223
+ attr_reader :hash
224
+
225
+ MIME_NAME = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
226
+ MIME_PARAMETER_KEY = "[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}"
227
+ MIME_PARAMETER_VALUE = "#{Regexp.escape('"')}?[a-zA-Z0-9][a-zA-Z0-9#{Regexp.escape('!#$&-^_.+')}]{0,126}#{Regexp.escape('"')}?"
228
+ MIME_PARAMETER = "\s*\;\s+#{MIME_PARAMETER_KEY}(?:\=#{MIME_PARAMETER_VALUE})?"
229
+ MIME_REGEXP = /\A(?:\*\/\*|#{MIME_NAME}\/(?:\*|#{MIME_NAME})(?:\s*#{MIME_PARAMETER}\s*)*)\z/
230
+
231
+ class InvalidMimeType < StandardError; end
232
+
233
+ def initialize(string, symbol = nil, synonyms = [])
234
+ unless MIME_REGEXP.match?(string)
235
+ raise InvalidMimeType, "#{string.inspect} is not a valid MIME type"
236
+ end
237
+ @symbol, @synonyms = symbol, synonyms
238
+ @string = string
239
+ @hash = [@string, @synonyms, @symbol].hash
240
+ end
241
+
242
+ def to_s
243
+ @string
244
+ end
245
+
246
+ def to_str
247
+ to_s
248
+ end
249
+
250
+ def to_sym
251
+ @symbol
252
+ end
253
+
254
+ def ref
255
+ symbol || to_s
256
+ end
257
+
258
+ def ===(list)
259
+ if list.is_a?(Array)
260
+ (@synonyms + [ self ]).any? { |synonym| list.include?(synonym) }
261
+ else
262
+ super
263
+ end
264
+ end
265
+
266
+ def ==(mime_type)
267
+ return false unless mime_type
268
+ (@synonyms + [ self ]).any? do |synonym|
269
+ synonym.to_s == mime_type.to_s || synonym.to_sym == mime_type.to_sym
270
+ end
271
+ end
272
+
273
+ def eql?(other)
274
+ super || (self.class == other.class &&
275
+ @string == other.string &&
276
+ @synonyms == other.synonyms &&
277
+ @symbol == other.symbol)
278
+ end
279
+
280
+ def =~(mime_type)
281
+ return false unless mime_type
282
+ regexp = Regexp.new(Regexp.quote(mime_type.to_s))
283
+ @synonyms.any? { |synonym| synonym.to_s =~ regexp } || @string =~ regexp
284
+ end
285
+
286
+ def html?
287
+ symbol == :html || @string =~ /html/
288
+ end
289
+
290
+ def all?; false; end
291
+
292
+ protected
293
+
294
+ attr_reader :string, :synonyms
295
+
296
+ private
297
+
298
+ def to_ary; end
299
+ def to_a; end
300
+
301
+ def method_missing(method, *args)
302
+ if method.to_s.ends_with? "?"
303
+ method[0..-2].downcase.to_sym == to_sym
304
+ else
305
+ super
306
+ end
307
+ end
308
+
309
+ def respond_to_missing?(method, include_private = false)
310
+ (method.to_s.ends_with? "?") || super
311
+ end
312
+ end
313
+
314
+ class AllType < Type
315
+ include Singleton
316
+
317
+ def initialize
318
+ super "*/*", nil
319
+ end
320
+
321
+ def all?; true; end
322
+ def html?; true; end
323
+ end
324
+
325
+ # ALL isn't a real MIME type, so we don't register it for lookup with the
326
+ # other concrete types. It's a wildcard match that we use for `respond_to`
327
+ # negotiation internals.
328
+ ALL = AllType.instance
329
+
330
+ class NullType
331
+ include Singleton
332
+
333
+ def nil?
334
+ true
335
+ end
336
+
337
+ def ref; end
338
+
339
+ private
340
+ def respond_to_missing?(method, _)
341
+ method.to_s.ends_with? "?"
342
+ end
343
+
344
+ def method_missing(method, *args)
345
+ false if method.to_s.ends_with? "?"
346
+ end
347
+ end
348
+ end
349
+
350
+ require "action_dispatch/http/mime_types"