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.

Files changed (170) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +429 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +57 -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 +265 -0
  8. data/lib/abstract_controller/caching.rb +66 -0
  9. data/lib/abstract_controller/caching/fragments.rb +166 -0
  10. data/lib/abstract_controller/callbacks.rb +212 -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 +31 -0
  18. data/lib/abstract_controller/url_for.rb +35 -0
  19. data/lib/action_controller.rb +66 -0
  20. data/lib/action_controller/api.rb +149 -0
  21. data/lib/action_controller/api/api_rendering.rb +16 -0
  22. data/lib/action_controller/base.rb +276 -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 +78 -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 +274 -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 +152 -0
  32. data/lib/action_controller/metal/etag_with_flash.rb +18 -0
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
  34. data/lib/action_controller/metal/exceptions.rb +53 -0
  35. data/lib/action_controller/metal/flash.rb +61 -0
  36. data/lib/action_controller/metal/force_ssl.rb +99 -0
  37. data/lib/action_controller/metal/head.rb +60 -0
  38. data/lib/action_controller/metal/helpers.rb +123 -0
  39. data/lib/action_controller/metal/http_authentication.rb +519 -0
  40. data/lib/action_controller/metal/implicit_render.rb +73 -0
  41. data/lib/action_controller/metal/instrumentation.rb +107 -0
  42. data/lib/action_controller/metal/live.rb +312 -0
  43. data/lib/action_controller/metal/mime_responds.rb +313 -0
  44. data/lib/action_controller/metal/parameter_encoding.rb +51 -0
  45. data/lib/action_controller/metal/params_wrapper.rb +293 -0
  46. data/lib/action_controller/metal/redirecting.rb +133 -0
  47. data/lib/action_controller/metal/renderers.rb +181 -0
  48. data/lib/action_controller/metal/rendering.rb +122 -0
  49. data/lib/action_controller/metal/request_forgery_protection.rb +445 -0
  50. data/lib/action_controller/metal/rescue.rb +28 -0
  51. data/lib/action_controller/metal/streaming.rb +223 -0
  52. data/lib/action_controller/metal/strong_parameters.rb +1086 -0
  53. data/lib/action_controller/metal/testing.rb +16 -0
  54. data/lib/action_controller/metal/url_for.rb +58 -0
  55. data/lib/action_controller/railtie.rb +89 -0
  56. data/lib/action_controller/railties/helpers.rb +24 -0
  57. data/lib/action_controller/renderer.rb +117 -0
  58. data/lib/action_controller/template_assertions.rb +11 -0
  59. data/lib/action_controller/test_case.rb +629 -0
  60. data/lib/action_dispatch.rb +112 -0
  61. data/lib/action_dispatch/http/cache.rb +222 -0
  62. data/lib/action_dispatch/http/content_security_policy.rb +272 -0
  63. data/lib/action_dispatch/http/filter_parameters.rb +84 -0
  64. data/lib/action_dispatch/http/filter_redirect.rb +37 -0
  65. data/lib/action_dispatch/http/headers.rb +132 -0
  66. data/lib/action_dispatch/http/mime_negotiation.rb +175 -0
  67. data/lib/action_dispatch/http/mime_type.rb +342 -0
  68. data/lib/action_dispatch/http/mime_types.rb +50 -0
  69. data/lib/action_dispatch/http/parameter_filter.rb +86 -0
  70. data/lib/action_dispatch/http/parameters.rb +126 -0
  71. data/lib/action_dispatch/http/rack_cache.rb +63 -0
  72. data/lib/action_dispatch/http/request.rb +430 -0
  73. data/lib/action_dispatch/http/response.rb +519 -0
  74. data/lib/action_dispatch/http/upload.rb +84 -0
  75. data/lib/action_dispatch/http/url.rb +350 -0
  76. data/lib/action_dispatch/journey.rb +7 -0
  77. data/lib/action_dispatch/journey/formatter.rb +189 -0
  78. data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
  79. data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
  80. data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
  81. data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
  82. data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
  83. data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
  84. data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
  85. data/lib/action_dispatch/journey/nodes/node.rb +140 -0
  86. data/lib/action_dispatch/journey/parser.rb +199 -0
  87. data/lib/action_dispatch/journey/parser.y +50 -0
  88. data/lib/action_dispatch/journey/parser_extras.rb +31 -0
  89. data/lib/action_dispatch/journey/path/pattern.rb +198 -0
  90. data/lib/action_dispatch/journey/route.rb +203 -0
  91. data/lib/action_dispatch/journey/router.rb +156 -0
  92. data/lib/action_dispatch/journey/router/utils.rb +102 -0
  93. data/lib/action_dispatch/journey/routes.rb +82 -0
  94. data/lib/action_dispatch/journey/scanner.rb +64 -0
  95. data/lib/action_dispatch/journey/visitors.rb +268 -0
  96. data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
  97. data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
  98. data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
  99. data/lib/action_dispatch/middleware/callbacks.rb +36 -0
  100. data/lib/action_dispatch/middleware/cookies.rb +685 -0
  101. data/lib/action_dispatch/middleware/debug_exceptions.rb +205 -0
  102. data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
  103. data/lib/action_dispatch/middleware/exception_wrapper.rb +147 -0
  104. data/lib/action_dispatch/middleware/executor.rb +21 -0
  105. data/lib/action_dispatch/middleware/flash.rb +300 -0
  106. data/lib/action_dispatch/middleware/public_exceptions.rb +57 -0
  107. data/lib/action_dispatch/middleware/reloader.rb +12 -0
  108. data/lib/action_dispatch/middleware/remote_ip.rb +183 -0
  109. data/lib/action_dispatch/middleware/request_id.rb +43 -0
  110. data/lib/action_dispatch/middleware/session/abstract_store.rb +92 -0
  111. data/lib/action_dispatch/middleware/session/cache_store.rb +54 -0
  112. data/lib/action_dispatch/middleware/session/cookie_store.rb +118 -0
  113. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +28 -0
  114. data/lib/action_dispatch/middleware/show_exceptions.rb +62 -0
  115. data/lib/action_dispatch/middleware/ssl.rb +150 -0
  116. data/lib/action_dispatch/middleware/stack.rb +116 -0
  117. data/lib/action_dispatch/middleware/static.rb +130 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
  119. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
  120. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
  121. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  122. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
  123. data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
  124. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +16 -0
  125. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
  126. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
  127. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
  128. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +161 -0
  129. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
  130. data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +32 -0
  132. data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
  133. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +6 -0
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
  139. data/lib/action_dispatch/railtie.rb +55 -0
  140. data/lib/action_dispatch/request/session.rb +234 -0
  141. data/lib/action_dispatch/request/utils.rb +78 -0
  142. data/lib/action_dispatch/routing.rb +260 -0
  143. data/lib/action_dispatch/routing/endpoint.rb +17 -0
  144. data/lib/action_dispatch/routing/inspector.rb +225 -0
  145. data/lib/action_dispatch/routing/mapper.rb +2267 -0
  146. data/lib/action_dispatch/routing/polymorphic_routes.rb +352 -0
  147. data/lib/action_dispatch/routing/redirection.rb +201 -0
  148. data/lib/action_dispatch/routing/route_set.rb +890 -0
  149. data/lib/action_dispatch/routing/routes_proxy.rb +69 -0
  150. data/lib/action_dispatch/routing/url_for.rb +236 -0
  151. data/lib/action_dispatch/system_test_case.rb +147 -0
  152. data/lib/action_dispatch/system_testing/browser.rb +49 -0
  153. data/lib/action_dispatch/system_testing/driver.rb +59 -0
  154. data/lib/action_dispatch/system_testing/server.rb +31 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
  157. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
  158. data/lib/action_dispatch/testing/assertion_response.rb +47 -0
  159. data/lib/action_dispatch/testing/assertions.rb +24 -0
  160. data/lib/action_dispatch/testing/assertions/response.rb +107 -0
  161. data/lib/action_dispatch/testing/assertions/routing.rb +222 -0
  162. data/lib/action_dispatch/testing/integration.rb +652 -0
  163. data/lib/action_dispatch/testing/request_encoder.rb +55 -0
  164. data/lib/action_dispatch/testing/test_process.rb +50 -0
  165. data/lib/action_dispatch/testing/test_request.rb +71 -0
  166. data/lib/action_dispatch/testing/test_response.rb +53 -0
  167. data/lib/action_pack.rb +26 -0
  168. data/lib/action_pack/gem_version.rb +17 -0
  169. data/lib/action_pack/version.rb +10 -0
  170. metadata +318 -0
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "abstract_controller/collector"
4
+
5
+ module ActionController #:nodoc:
6
+ module MimeResponds
7
+ # Without web-service support, an action which collects the data for displaying a list of people
8
+ # might look something like this:
9
+ #
10
+ # def index
11
+ # @people = Person.all
12
+ # end
13
+ #
14
+ # That action implicitly responds to all formats, but formats can also be whitelisted:
15
+ #
16
+ # def index
17
+ # @people = Person.all
18
+ # respond_to :html, :js
19
+ # end
20
+ #
21
+ # Here's the same action, with web-service support baked in:
22
+ #
23
+ # def index
24
+ # @people = Person.all
25
+ #
26
+ # respond_to do |format|
27
+ # format.html
28
+ # format.js
29
+ # format.xml { render xml: @people }
30
+ # end
31
+ # end
32
+ #
33
+ # What that says is, "if the client wants HTML or JS in response to this action, just respond as we
34
+ # would have before, but if the client wants XML, return them the list of people in XML format."
35
+ # (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
36
+ #
37
+ # Supposing you have an action that adds a new person, optionally creating their company
38
+ # (by name) if it does not already exist, without web-services, it might look like this:
39
+ #
40
+ # def create
41
+ # @company = Company.find_or_create_by(name: params[:company][:name])
42
+ # @person = @company.people.create(params[:person])
43
+ #
44
+ # redirect_to(person_list_url)
45
+ # end
46
+ #
47
+ # Here's the same action, with web-service support baked in:
48
+ #
49
+ # def create
50
+ # company = params[:person].delete(:company)
51
+ # @company = Company.find_or_create_by(name: company[:name])
52
+ # @person = @company.people.create(params[:person])
53
+ #
54
+ # respond_to do |format|
55
+ # format.html { redirect_to(person_list_url) }
56
+ # format.js
57
+ # format.xml { render xml: @person.to_xml(include: @company) }
58
+ # end
59
+ # end
60
+ #
61
+ # If the client wants HTML, we just redirect them back to the person list. If they want JavaScript,
62
+ # then it is an Ajax request and we render the JavaScript template associated with this action.
63
+ # Lastly, if the client wants XML, we render the created person as XML, but with a twist: we also
64
+ # include the person's company in the rendered XML, so you get something like this:
65
+ #
66
+ # <person>
67
+ # <id>...</id>
68
+ # ...
69
+ # <company>
70
+ # <id>...</id>
71
+ # <name>...</name>
72
+ # ...
73
+ # </company>
74
+ # </person>
75
+ #
76
+ # Note, however, the extra bit at the top of that action:
77
+ #
78
+ # company = params[:person].delete(:company)
79
+ # @company = Company.find_or_create_by(name: company[:name])
80
+ #
81
+ # This is because the incoming XML document (if a web-service request is in process) can only contain a
82
+ # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
83
+ #
84
+ # person[name]=...&person[company][name]=...&...
85
+ #
86
+ # And, like this (xml-encoded):
87
+ #
88
+ # <person>
89
+ # <name>...</name>
90
+ # <company>
91
+ # <name>...</name>
92
+ # </company>
93
+ # </person>
94
+ #
95
+ # In other words, we make the request so that it operates on a single entity's person. Then, in the action,
96
+ # we extract the company data from the request, find or create the company, and then create the new person
97
+ # with the remaining data.
98
+ #
99
+ # Note that you can define your own XML parameter parser which would allow you to describe multiple entities
100
+ # in a single request (i.e., by wrapping them all in a single root node), but if you just go with the flow
101
+ # and accept Rails' defaults, life will be much easier.
102
+ #
103
+ # If you need to use a MIME type which isn't supported by default, you can register your own handlers in
104
+ # +config/initializers/mime_types.rb+ as follows.
105
+ #
106
+ # Mime::Type.register "image/jpg", :jpg
107
+ #
108
+ # Respond to also allows you to specify a common block for different formats by using +any+:
109
+ #
110
+ # def index
111
+ # @people = Person.all
112
+ #
113
+ # respond_to do |format|
114
+ # format.html
115
+ # format.any(:xml, :json) { render request.format.to_sym => @people }
116
+ # end
117
+ # end
118
+ #
119
+ # In the example above, if the format is xml, it will render:
120
+ #
121
+ # render xml: @people
122
+ #
123
+ # Or if the format is json:
124
+ #
125
+ # render json: @people
126
+ #
127
+ # Formats can have different variants.
128
+ #
129
+ # The request variant is a specialization of the request format, like <tt>:tablet</tt>,
130
+ # <tt>:phone</tt>, or <tt>:desktop</tt>.
131
+ #
132
+ # We often want to render different html/json/xml templates for phones,
133
+ # tablets, and desktop browsers. Variants make it easy.
134
+ #
135
+ # You can set the variant in a +before_action+:
136
+ #
137
+ # request.variant = :tablet if request.user_agent =~ /iPad/
138
+ #
139
+ # Respond to variants in the action just like you respond to formats:
140
+ #
141
+ # respond_to do |format|
142
+ # format.html do |variant|
143
+ # variant.tablet # renders app/views/projects/show.html+tablet.erb
144
+ # variant.phone { extra_setup; render ... }
145
+ # variant.none { special_setup } # executed only if there is no variant set
146
+ # end
147
+ # end
148
+ #
149
+ # Provide separate templates for each format and variant:
150
+ #
151
+ # app/views/projects/show.html.erb
152
+ # app/views/projects/show.html+tablet.erb
153
+ # app/views/projects/show.html+phone.erb
154
+ #
155
+ # When you're not sharing any code within the format, you can simplify defining variants
156
+ # using the inline syntax:
157
+ #
158
+ # respond_to do |format|
159
+ # format.js { render "trash" }
160
+ # format.html.phone { redirect_to progress_path }
161
+ # format.html.none { render "trash" }
162
+ # end
163
+ #
164
+ # Variants also support common +any+/+all+ block that formats have.
165
+ #
166
+ # It works for both inline:
167
+ #
168
+ # respond_to do |format|
169
+ # format.html.any { render html: "any" }
170
+ # format.html.phone { render html: "phone" }
171
+ # end
172
+ #
173
+ # and block syntax:
174
+ #
175
+ # respond_to do |format|
176
+ # format.html do |variant|
177
+ # variant.any(:tablet, :phablet){ render html: "any" }
178
+ # variant.phone { render html: "phone" }
179
+ # end
180
+ # end
181
+ #
182
+ # You can also set an array of variants:
183
+ #
184
+ # request.variant = [:tablet, :phone]
185
+ #
186
+ # This will work similarly to formats and MIME types negotiation. If there
187
+ # is no +:tablet+ variant declared, the +:phone+ variant will be used:
188
+ #
189
+ # respond_to do |format|
190
+ # format.html.none
191
+ # format.html.phone # this gets rendered
192
+ # end
193
+ def respond_to(*mimes)
194
+ raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
195
+
196
+ collector = Collector.new(mimes, request.variant)
197
+ yield collector if block_given?
198
+
199
+ if format = collector.negotiate_format(request)
200
+ _process_format(format)
201
+ _set_rendered_content_type format
202
+ response = collector.response
203
+ response.call if response
204
+ else
205
+ raise ActionController::UnknownFormat
206
+ end
207
+ end
208
+
209
+ # A container for responses available from the current controller for
210
+ # requests for different mime-types sent to a particular action.
211
+ #
212
+ # The public controller methods +respond_to+ may be called with a block
213
+ # that is used to define responses to different mime-types, e.g.
214
+ # for +respond_to+ :
215
+ #
216
+ # respond_to do |format|
217
+ # format.html
218
+ # format.xml { render xml: @people }
219
+ # end
220
+ #
221
+ # In this usage, the argument passed to the block (+format+ above) is an
222
+ # instance of the ActionController::MimeResponds::Collector class. This
223
+ # object serves as a container in which available responses can be stored by
224
+ # calling any of the dynamically generated, mime-type-specific methods such
225
+ # as +html+, +xml+ etc on the Collector. Each response is represented by a
226
+ # corresponding block if present.
227
+ #
228
+ # A subsequent call to #negotiate_format(request) will enable the Collector
229
+ # to determine which specific mime-type it should respond with for the current
230
+ # request, with this response then being accessible by calling #response.
231
+ class Collector
232
+ include AbstractController::Collector
233
+ attr_accessor :format
234
+
235
+ def initialize(mimes, variant = nil)
236
+ @responses = {}
237
+ @variant = variant
238
+
239
+ mimes.each { |mime| @responses[Mime[mime]] = nil }
240
+ end
241
+
242
+ def any(*args, &block)
243
+ if args.any?
244
+ args.each { |type| send(type, &block) }
245
+ else
246
+ custom(Mime::ALL, &block)
247
+ end
248
+ end
249
+ alias :all :any
250
+
251
+ def custom(mime_type, &block)
252
+ mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type)
253
+ @responses[mime_type] ||= if block_given?
254
+ block
255
+ else
256
+ VariantCollector.new(@variant)
257
+ end
258
+ end
259
+
260
+ def response
261
+ response = @responses.fetch(format, @responses[Mime::ALL])
262
+ if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax
263
+ response.variant
264
+ elsif response.nil? || response.arity == 0 # `format.html` - just a format, call its block
265
+ response
266
+ else # `format.html{ |variant| variant.phone }` - variant block syntax
267
+ variant_collector = VariantCollector.new(@variant)
268
+ response.call(variant_collector) # call format block with variants collector
269
+ variant_collector.variant
270
+ end
271
+ end
272
+
273
+ def negotiate_format(request)
274
+ @format = request.negotiate_mime(@responses.keys)
275
+ end
276
+
277
+ class VariantCollector #:nodoc:
278
+ def initialize(variant = nil)
279
+ @variant = variant
280
+ @variants = {}
281
+ end
282
+
283
+ def any(*args, &block)
284
+ if block_given?
285
+ if args.any? && args.none? { |a| a == @variant }
286
+ args.each { |v| @variants[v] = block }
287
+ else
288
+ @variants[:any] = block
289
+ end
290
+ end
291
+ end
292
+ alias :all :any
293
+
294
+ def method_missing(name, *args, &block)
295
+ @variants[name] = block if block_given?
296
+ end
297
+
298
+ def variant
299
+ if @variant.empty?
300
+ @variants[:none] || @variants[:any]
301
+ else
302
+ @variants[variant_key]
303
+ end
304
+ end
305
+
306
+ private
307
+ def variant_key
308
+ @variant.find { |variant| @variants.key?(variant) } || :any
309
+ end
310
+ end
311
+ end
312
+ end
313
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ # Specify binary encoding for parameters for a given action.
5
+ module ParameterEncoding
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def inherited(klass) # :nodoc:
10
+ super
11
+ klass.setup_param_encode
12
+ end
13
+
14
+ def setup_param_encode # :nodoc:
15
+ @_parameter_encodings = {}
16
+ end
17
+
18
+ def binary_params_for?(action) # :nodoc:
19
+ @_parameter_encodings[action.to_s]
20
+ end
21
+
22
+ # Specify that a given action's parameters should all be encoded as
23
+ # ASCII-8BIT (it "skips" the encoding default of UTF-8).
24
+ #
25
+ # For example, a controller would use it like this:
26
+ #
27
+ # class RepositoryController < ActionController::Base
28
+ # skip_parameter_encoding :show
29
+ #
30
+ # def show
31
+ # @repo = Repository.find_by_filesystem_path params[:file_path]
32
+ #
33
+ # # `repo_name` is guaranteed to be UTF-8, but was ASCII-8BIT, so
34
+ # # tag it as such
35
+ # @repo_name = params[:repo_name].force_encoding 'UTF-8'
36
+ # end
37
+ #
38
+ # def index
39
+ # @repositories = Repository.all
40
+ # end
41
+ # end
42
+ #
43
+ # The show action in the above controller would have all parameter values
44
+ # encoded as ASCII-8BIT. This is useful in the case where an application
45
+ # must handle data but encoding of the data is unknown, like file system data.
46
+ def skip_parameter_encoding(action)
47
+ @_parameter_encodings[action.to_s] = true
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/slice"
4
+ require "active_support/core_ext/hash/except"
5
+ require "active_support/core_ext/module/anonymous"
6
+ require "action_dispatch/http/mime_type"
7
+
8
+ module ActionController
9
+ # Wraps the parameters hash into a nested hash. This will allow clients to
10
+ # submit requests without having to specify any root elements.
11
+ #
12
+ # This functionality is enabled in +config/initializers/wrap_parameters.rb+
13
+ # and can be customized.
14
+ #
15
+ # You could also turn it on per controller by setting the format array to
16
+ # a non-empty array:
17
+ #
18
+ # class UsersController < ApplicationController
19
+ # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
20
+ # end
21
+ #
22
+ # If you enable +ParamsWrapper+ for +:json+ format, instead of having to
23
+ # send JSON parameters like this:
24
+ #
25
+ # {"user": {"name": "Konata"}}
26
+ #
27
+ # You can send parameters like this:
28
+ #
29
+ # {"name": "Konata"}
30
+ #
31
+ # And it will be wrapped into a nested hash with the key name matching the
32
+ # controller's name. For example, if you're posting to +UsersController+,
33
+ # your new +params+ hash will look like this:
34
+ #
35
+ # {"name" => "Konata", "user" => {"name" => "Konata"}}
36
+ #
37
+ # You can also specify the key in which the parameters should be wrapped to,
38
+ # and also the list of attributes it should wrap by using either +:include+ or
39
+ # +:exclude+ options like this:
40
+ #
41
+ # class UsersController < ApplicationController
42
+ # wrap_parameters :person, include: [:username, :password]
43
+ # end
44
+ #
45
+ # On Active Record models with no +:include+ or +:exclude+ option set,
46
+ # it will only wrap the parameters returned by the class method
47
+ # <tt>attribute_names</tt>.
48
+ #
49
+ # If you're going to pass the parameters to an +ActiveModel+ object (such as
50
+ # <tt>User.new(params[:user])</tt>), you might consider passing the model class to
51
+ # the method instead. The +ParamsWrapper+ will actually try to determine the
52
+ # list of attribute names from the model and only wrap those attributes:
53
+ #
54
+ # class UsersController < ApplicationController
55
+ # wrap_parameters Person
56
+ # end
57
+ #
58
+ # You still could pass +:include+ and +:exclude+ to set the list of attributes
59
+ # you want to wrap.
60
+ #
61
+ # By default, if you don't specify the key in which the parameters would be
62
+ # wrapped to, +ParamsWrapper+ will actually try to determine if there's
63
+ # a model related to it or not. This controller, for example:
64
+ #
65
+ # class Admin::UsersController < ApplicationController
66
+ # end
67
+ #
68
+ # will try to check if <tt>Admin::User</tt> or +User+ model exists, and use it to
69
+ # determine the wrapper key respectively. If both models don't exist,
70
+ # it will then fallback to use +user+ as the key.
71
+ module ParamsWrapper
72
+ extend ActiveSupport::Concern
73
+
74
+ EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
75
+
76
+ require "mutex_m"
77
+
78
+ class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
79
+ include Mutex_m
80
+
81
+ def self.from_hash(hash)
82
+ name = hash[:name]
83
+ format = Array(hash[:format])
84
+ include = hash[:include] && Array(hash[:include]).collect(&:to_s)
85
+ exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s)
86
+ new name, format, include, exclude, nil, nil
87
+ end
88
+
89
+ def initialize(name, format, include, exclude, klass, model) # :nodoc:
90
+ super
91
+ @include_set = include
92
+ @name_set = name
93
+ end
94
+
95
+ def model
96
+ super || synchronize { super || self.model = _default_wrap_model }
97
+ end
98
+
99
+ def include
100
+ return super if @include_set
101
+
102
+ m = model
103
+ synchronize do
104
+ return super if @include_set
105
+
106
+ @include_set = true
107
+
108
+ unless super || exclude
109
+ if m.respond_to?(:attribute_names) && m.attribute_names.any?
110
+ if m.respond_to?(:stored_attributes) && !m.stored_attributes.empty?
111
+ self.include = m.attribute_names + m.stored_attributes.values.flatten.map(&:to_s)
112
+ else
113
+ self.include = m.attribute_names
114
+ end
115
+
116
+ if m.respond_to?(:nested_attributes_options) && m.nested_attributes_options.keys.any?
117
+ self.include += m.nested_attributes_options.keys.map do |key|
118
+ key.to_s.concat("_attributes")
119
+ end
120
+ end
121
+
122
+ self.include
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def name
129
+ return super if @name_set
130
+
131
+ m = model
132
+ synchronize do
133
+ return super if @name_set
134
+
135
+ @name_set = true
136
+
137
+ unless super || klass.anonymous?
138
+ self.name = m ? m.to_s.demodulize.underscore :
139
+ klass.controller_name.singularize
140
+ end
141
+ end
142
+ end
143
+
144
+ private
145
+ # Determine the wrapper model from the controller's name. By convention,
146
+ # this could be done by trying to find the defined model that has the
147
+ # same singular name as the controller. For example, +UsersController+
148
+ # will try to find if the +User+ model exists.
149
+ #
150
+ # This method also does namespace lookup. Foo::Bar::UsersController will
151
+ # try to find Foo::Bar::User, Foo::User and finally User.
152
+ def _default_wrap_model
153
+ return nil if klass.anonymous?
154
+ model_name = klass.name.sub(/Controller$/, "").classify
155
+
156
+ begin
157
+ if model_klass = model_name.safe_constantize
158
+ model_klass
159
+ else
160
+ namespaces = model_name.split("::")
161
+ namespaces.delete_at(-2)
162
+ break if namespaces.last == model_name
163
+ model_name = namespaces.join("::")
164
+ end
165
+ end until model_klass
166
+
167
+ model_klass
168
+ end
169
+ end
170
+
171
+ included do
172
+ class_attribute :_wrapper_options, default: Options.from_hash(format: [])
173
+ end
174
+
175
+ module ClassMethods
176
+ def _set_wrapper_options(options)
177
+ self._wrapper_options = Options.from_hash(options)
178
+ end
179
+
180
+ # Sets the name of the wrapper key, or the model which +ParamsWrapper+
181
+ # would use to determine the attribute names from.
182
+ #
183
+ # ==== Examples
184
+ # wrap_parameters format: :xml
185
+ # # enables the parameter wrapper for XML format
186
+ #
187
+ # wrap_parameters :person
188
+ # # wraps parameters into +params[:person]+ hash
189
+ #
190
+ # wrap_parameters Person
191
+ # # wraps parameters by determining the wrapper key from Person class
192
+ # (+person+, in this case) and the list of attribute names
193
+ #
194
+ # wrap_parameters include: [:username, :title]
195
+ # # wraps only +:username+ and +:title+ attributes from parameters.
196
+ #
197
+ # wrap_parameters false
198
+ # # disables parameters wrapping for this controller altogether.
199
+ #
200
+ # ==== Options
201
+ # * <tt>:format</tt> - The list of formats in which the parameters wrapper
202
+ # will be enabled.
203
+ # * <tt>:include</tt> - The list of attribute names which parameters wrapper
204
+ # will wrap into a nested hash.
205
+ # * <tt>:exclude</tt> - The list of attribute names which parameters wrapper
206
+ # will exclude from a nested hash.
207
+ def wrap_parameters(name_or_model_or_options, options = {})
208
+ model = nil
209
+
210
+ case name_or_model_or_options
211
+ when Hash
212
+ options = name_or_model_or_options
213
+ when false
214
+ options = options.merge(format: [])
215
+ when Symbol, String
216
+ options = options.merge(name: name_or_model_or_options)
217
+ else
218
+ model = name_or_model_or_options
219
+ end
220
+
221
+ opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
222
+ opts.model = model
223
+ opts.klass = self
224
+
225
+ self._wrapper_options = opts
226
+ end
227
+
228
+ # Sets the default wrapper key or model which will be used to determine
229
+ # wrapper key and attribute names. Called automatically when the
230
+ # module is inherited.
231
+ def inherited(klass)
232
+ if klass._wrapper_options.format.any?
233
+ params = klass._wrapper_options.dup
234
+ params.klass = klass
235
+ klass._wrapper_options = params
236
+ end
237
+ super
238
+ end
239
+ end
240
+
241
+ # Performs parameters wrapping upon the request. Called automatically
242
+ # by the metal call stack.
243
+ def process_action(*args)
244
+ if _wrapper_enabled?
245
+ wrapped_hash = _wrap_parameters request.request_parameters
246
+ wrapped_keys = request.request_parameters.keys
247
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters.slice(*wrapped_keys)
248
+
249
+ # This will make the wrapped hash accessible from controller and view.
250
+ request.parameters.merge! wrapped_hash
251
+ request.request_parameters.merge! wrapped_hash
252
+
253
+ # This will display the wrapped hash in the log file.
254
+ request.filtered_parameters.merge! wrapped_filtered_hash
255
+ end
256
+ super
257
+ end
258
+
259
+ private
260
+
261
+ # Returns the wrapper key which will be used to store wrapped parameters.
262
+ def _wrapper_key
263
+ _wrapper_options.name
264
+ end
265
+
266
+ # Returns the list of enabled formats.
267
+ def _wrapper_formats
268
+ _wrapper_options.format
269
+ end
270
+
271
+ # Returns the list of parameters which will be selected for wrapped.
272
+ def _wrap_parameters(parameters)
273
+ { _wrapper_key => _extract_parameters(parameters) }
274
+ end
275
+
276
+ def _extract_parameters(parameters)
277
+ if include_only = _wrapper_options.include
278
+ parameters.slice(*include_only)
279
+ else
280
+ exclude = _wrapper_options.exclude || []
281
+ parameters.except(*(exclude + EXCLUDE_PARAMETERS))
282
+ end
283
+ end
284
+
285
+ # Checks if we should perform parameters wrapping.
286
+ def _wrapper_enabled?
287
+ return false unless request.has_content_type?
288
+
289
+ ref = request.content_mime_type.ref
290
+ _wrapper_formats.include?(ref) && _wrapper_key && !request.parameters.key?(_wrapper_key)
291
+ end
292
+ end
293
+ end