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,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rack/body_proxy"
4
+
5
+ module ActionDispatch
6
+ class Executor
7
+ def initialize(app, executor)
8
+ @app, @executor = app, executor
9
+ end
10
+
11
+ def call(env)
12
+ state = @executor.run!
13
+ begin
14
+ response = @app.call(env)
15
+ returned = response << ::Rack::BodyProxy.new(response.pop) { state.complete! }
16
+ ensure
17
+ state.complete! unless returned
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,300 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/keys"
4
+
5
+ module ActionDispatch
6
+ # The flash provides a way to pass temporary primitive-types (String, Array, Hash) between actions. Anything you place in the flash will be exposed
7
+ # to the very next action and then cleared out. This is a great way of doing notices and alerts, such as a create
8
+ # action that sets <tt>flash[:notice] = "Post successfully created"</tt> before redirecting to a display action that can
9
+ # then expose the flash to its template. Actually, that exposure is automatically done.
10
+ #
11
+ # class PostsController < ActionController::Base
12
+ # def create
13
+ # # save post
14
+ # flash[:notice] = "Post successfully created"
15
+ # redirect_to @post
16
+ # end
17
+ #
18
+ # def show
19
+ # # doesn't need to assign the flash notice to the template, that's done automatically
20
+ # end
21
+ # end
22
+ #
23
+ # show.html.erb
24
+ # <% if flash[:notice] %>
25
+ # <div class="notice"><%= flash[:notice] %></div>
26
+ # <% end %>
27
+ #
28
+ # Since the +notice+ and +alert+ keys are a common idiom, convenience accessors are available:
29
+ #
30
+ # flash.alert = "You must be logged in"
31
+ # flash.notice = "Post successfully created"
32
+ #
33
+ # This example places a string in the flash. And of course, you can put as many as you like at a time too. If you want to pass
34
+ # non-primitive types, you will have to handle that in your application. Example: To show messages with links, you will have to
35
+ # use sanitize helper.
36
+ #
37
+ # Just remember: They'll be gone by the time the next action has been performed.
38
+ #
39
+ # See docs on the FlashHash class for more details about the flash.
40
+ class Flash
41
+ KEY = "action_dispatch.request.flash_hash".freeze
42
+
43
+ module RequestMethods
44
+ # Access the contents of the flash. Use <tt>flash["notice"]</tt> to
45
+ # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
46
+ # to put a new one.
47
+ def flash
48
+ flash = flash_hash
49
+ return flash if flash
50
+ self.flash = Flash::FlashHash.from_session_value(session["flash"])
51
+ end
52
+
53
+ def flash=(flash)
54
+ set_header Flash::KEY, flash
55
+ end
56
+
57
+ def flash_hash # :nodoc:
58
+ get_header Flash::KEY
59
+ end
60
+
61
+ def commit_flash # :nodoc:
62
+ session = self.session || {}
63
+ flash_hash = self.flash_hash
64
+
65
+ if flash_hash && (flash_hash.present? || session.key?("flash"))
66
+ session["flash"] = flash_hash.to_session_value
67
+ self.flash = flash_hash.dup
68
+ end
69
+
70
+ if (!session.respond_to?(:loaded?) || session.loaded?) && # reset_session uses {}, which doesn't implement #loaded?
71
+ session.key?("flash") && session["flash"].nil?
72
+ session.delete("flash")
73
+ end
74
+ end
75
+
76
+ def reset_session # :nodoc:
77
+ super
78
+ self.flash = nil
79
+ end
80
+ end
81
+
82
+ class FlashNow #:nodoc:
83
+ attr_accessor :flash
84
+
85
+ def initialize(flash)
86
+ @flash = flash
87
+ end
88
+
89
+ def []=(k, v)
90
+ k = k.to_s
91
+ @flash[k] = v
92
+ @flash.discard(k)
93
+ v
94
+ end
95
+
96
+ def [](k)
97
+ @flash[k.to_s]
98
+ end
99
+
100
+ # Convenience accessor for <tt>flash.now[:alert]=</tt>.
101
+ def alert=(message)
102
+ self[:alert] = message
103
+ end
104
+
105
+ # Convenience accessor for <tt>flash.now[:notice]=</tt>.
106
+ def notice=(message)
107
+ self[:notice] = message
108
+ end
109
+ end
110
+
111
+ class FlashHash
112
+ include Enumerable
113
+
114
+ def self.from_session_value(value) #:nodoc:
115
+ case value
116
+ when FlashHash # Rails 3.1, 3.2
117
+ flashes = value.instance_variable_get(:@flashes)
118
+ if discard = value.instance_variable_get(:@used)
119
+ flashes.except!(*discard)
120
+ end
121
+ new(flashes, flashes.keys)
122
+ when Hash # Rails 4.0
123
+ flashes = value["flashes"]
124
+ if discard = value["discard"]
125
+ flashes.except!(*discard)
126
+ end
127
+ new(flashes, flashes.keys)
128
+ else
129
+ new
130
+ end
131
+ end
132
+
133
+ # Builds a hash containing the flashes to keep for the next request.
134
+ # If there are none to keep, returns +nil+.
135
+ def to_session_value #:nodoc:
136
+ flashes_to_keep = @flashes.except(*@discard)
137
+ return nil if flashes_to_keep.empty?
138
+ { "discard" => [], "flashes" => flashes_to_keep }
139
+ end
140
+
141
+ def initialize(flashes = {}, discard = []) #:nodoc:
142
+ @discard = Set.new(stringify_array(discard))
143
+ @flashes = flashes.stringify_keys
144
+ @now = nil
145
+ end
146
+
147
+ def initialize_copy(other)
148
+ if other.now_is_loaded?
149
+ @now = other.now.dup
150
+ @now.flash = self
151
+ end
152
+ super
153
+ end
154
+
155
+ def []=(k, v)
156
+ k = k.to_s
157
+ @discard.delete k
158
+ @flashes[k] = v
159
+ end
160
+
161
+ def [](k)
162
+ @flashes[k.to_s]
163
+ end
164
+
165
+ def update(h) #:nodoc:
166
+ @discard.subtract stringify_array(h.keys)
167
+ @flashes.update h.stringify_keys
168
+ self
169
+ end
170
+
171
+ def keys
172
+ @flashes.keys
173
+ end
174
+
175
+ def key?(name)
176
+ @flashes.key? name.to_s
177
+ end
178
+
179
+ def delete(key)
180
+ key = key.to_s
181
+ @discard.delete key
182
+ @flashes.delete key
183
+ self
184
+ end
185
+
186
+ def to_hash
187
+ @flashes.dup
188
+ end
189
+
190
+ def empty?
191
+ @flashes.empty?
192
+ end
193
+
194
+ def clear
195
+ @discard.clear
196
+ @flashes.clear
197
+ end
198
+
199
+ def each(&block)
200
+ @flashes.each(&block)
201
+ end
202
+
203
+ alias :merge! :update
204
+
205
+ def replace(h) #:nodoc:
206
+ @discard.clear
207
+ @flashes.replace h.stringify_keys
208
+ self
209
+ end
210
+
211
+ # Sets a flash that will not be available to the next action, only to the current.
212
+ #
213
+ # flash.now[:message] = "Hello current action"
214
+ #
215
+ # This method enables you to use the flash as a central messaging system in your app.
216
+ # When you need to pass an object to the next action, you use the standard flash assign (<tt>[]=</tt>).
217
+ # When you need to pass an object to the current action, you use <tt>now</tt>, and your object will
218
+ # vanish when the current action is done.
219
+ #
220
+ # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>.
221
+ #
222
+ # Also, brings two convenience accessors:
223
+ #
224
+ # flash.now.alert = "Beware now!"
225
+ # # Equivalent to flash.now[:alert] = "Beware now!"
226
+ #
227
+ # flash.now.notice = "Good luck now!"
228
+ # # Equivalent to flash.now[:notice] = "Good luck now!"
229
+ def now
230
+ @now ||= FlashNow.new(self)
231
+ end
232
+
233
+ # Keeps either the entire current flash or a specific flash entry available for the next action:
234
+ #
235
+ # flash.keep # keeps the entire flash
236
+ # flash.keep(:notice) # keeps only the "notice" entry, the rest of the flash is discarded
237
+ def keep(k = nil)
238
+ k = k.to_s if k
239
+ @discard.subtract Array(k || keys)
240
+ k ? self[k] : self
241
+ end
242
+
243
+ # Marks the entire flash or a single flash entry to be discarded by the end of the current action:
244
+ #
245
+ # flash.discard # discard the entire flash at the end of the current action
246
+ # flash.discard(:warning) # discard only the "warning" entry at the end of the current action
247
+ def discard(k = nil)
248
+ k = k.to_s if k
249
+ @discard.merge Array(k || keys)
250
+ k ? self[k] : self
251
+ end
252
+
253
+ # Mark for removal entries that were kept, and delete unkept ones.
254
+ #
255
+ # This method is called automatically by filters, so you generally don't need to care about it.
256
+ def sweep #:nodoc:
257
+ @discard.each { |k| @flashes.delete k }
258
+ @discard.replace @flashes.keys
259
+ end
260
+
261
+ # Convenience accessor for <tt>flash[:alert]</tt>.
262
+ def alert
263
+ self[:alert]
264
+ end
265
+
266
+ # Convenience accessor for <tt>flash[:alert]=</tt>.
267
+ def alert=(message)
268
+ self[:alert] = message
269
+ end
270
+
271
+ # Convenience accessor for <tt>flash[:notice]</tt>.
272
+ def notice
273
+ self[:notice]
274
+ end
275
+
276
+ # Convenience accessor for <tt>flash[:notice]=</tt>.
277
+ def notice=(message)
278
+ self[:notice] = message
279
+ end
280
+
281
+ protected
282
+ def now_is_loaded?
283
+ @now
284
+ end
285
+
286
+ private
287
+ def stringify_array(array) # :doc:
288
+ array.map do |item|
289
+ item.kind_of?(Symbol) ? item.to_s : item
290
+ end
291
+ end
292
+ end
293
+
294
+ def self.new(app) app; end
295
+ end
296
+
297
+ class Request
298
+ prepend Flash::RequestMethods
299
+ end
300
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ # When called, this middleware renders an error page. By default if an HTML
5
+ # response is expected it will render static error pages from the <tt>/public</tt>
6
+ # directory. For example when this middleware receives a 500 response it will
7
+ # render the template found in <tt>/public/500.html</tt>.
8
+ # If an internationalized locale is set, this middleware will attempt to render
9
+ # the template in <tt>/public/500.<locale>.html</tt>. If an internationalized template
10
+ # is not found it will fall back on <tt>/public/500.html</tt>.
11
+ #
12
+ # When a request with a content type other than HTML is made, this middleware
13
+ # will attempt to convert error information into the appropriate response type.
14
+ class PublicExceptions
15
+ attr_accessor :public_path
16
+
17
+ def initialize(public_path)
18
+ @public_path = public_path
19
+ end
20
+
21
+ def call(env)
22
+ request = ActionDispatch::Request.new(env)
23
+ status = request.path_info[1..-1].to_i
24
+ content_type = request.formats.first
25
+ body = { status: status, error: Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) }
26
+
27
+ render(status, content_type, body)
28
+ end
29
+
30
+ private
31
+
32
+ def render(status, content_type, body)
33
+ format = "to_#{content_type.to_sym}" if content_type
34
+ if format && body.respond_to?(format)
35
+ render_format(status, content_type, body.public_send(format))
36
+ else
37
+ render_html(status)
38
+ end
39
+ end
40
+
41
+ def render_format(status, content_type, body)
42
+ [status, { "Content-Type" => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
43
+ "Content-Length" => body.bytesize.to_s }, [body]]
44
+ end
45
+
46
+ def render_html(status)
47
+ path = "#{public_path}/#{status}.#{I18n.locale}.html"
48
+ path = "#{public_path}/#{status}.html" unless (found = File.exist?(path))
49
+
50
+ if found || File.exist?(path)
51
+ render_format(status, "text/html", File.read(path))
52
+ else
53
+ [404, { "X-Cascade" => "pass" }, []]
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionDispatch
4
+ # ActionDispatch::Reloader wraps the request with callbacks provided by ActiveSupport::Reloader
5
+ # callbacks, intended to assist with code reloading during development.
6
+ #
7
+ # By default, ActionDispatch::Reloader is included in the middleware stack
8
+ # only in the development environment; specifically, when +config.cache_classes+
9
+ # is false.
10
+ class Reloader < Executor
11
+ end
12
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ipaddr"
4
+
5
+ module ActionDispatch
6
+ # This middleware calculates the IP address of the remote client that is
7
+ # making the request. It does this by checking various headers that could
8
+ # contain the address, and then picking the last-set address that is not
9
+ # on the list of trusted IPs. This follows the precedent set by e.g.
10
+ # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453],
11
+ # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection]
12
+ # by @gingerlime. A more detailed explanation of the algorithm is given
13
+ # at GetIp#calculate_ip.
14
+ #
15
+ # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2]
16
+ # requires. Some Rack servers simply drop preceding headers, and only report
17
+ # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers].
18
+ # If you are behind multiple proxy servers (like NGINX to HAProxy to Unicorn)
19
+ # then you should test your Rack server to make sure your data is good.
20
+ #
21
+ # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING.
22
+ # This middleware assumes that there is at least one proxy sitting around
23
+ # and setting headers with the client's remote IP address. If you don't use
24
+ # a proxy, because you are hosted on e.g. Heroku without SSL, any client can
25
+ # claim to have any IP address by setting the X-Forwarded-For header. If you
26
+ # care about that, then you need to explicitly drop or ignore those headers
27
+ # sometime before this middleware runs.
28
+ class RemoteIp
29
+ class IpSpoofAttackError < StandardError; end
30
+
31
+ # The default trusted IPs list simply includes IP addresses that are
32
+ # guaranteed by the IP specification to be private addresses. Those will
33
+ # not be the ultimate client IP in production, and so are discarded. See
34
+ # https://en.wikipedia.org/wiki/Private_network for details.
35
+ TRUSTED_PROXIES = [
36
+ "127.0.0.1", # localhost IPv4
37
+ "::1", # localhost IPv6
38
+ "fc00::/7", # private IPv6 range fc00::/7
39
+ "10.0.0.0/8", # private IPv4 range 10.x.x.x
40
+ "172.16.0.0/12", # private IPv4 range 172.16.0.0 .. 172.31.255.255
41
+ "192.168.0.0/16", # private IPv4 range 192.168.x.x
42
+ ].map { |proxy| IPAddr.new(proxy) }
43
+
44
+ attr_reader :check_ip, :proxies
45
+
46
+ # Create a new +RemoteIp+ middleware instance.
47
+ #
48
+ # The +ip_spoofing_check+ option is on by default. When on, an exception
49
+ # is raised if it looks like the client is trying to lie about its own IP
50
+ # address. It makes sense to turn off this check on sites aimed at non-IP
51
+ # clients (like WAP devices), or behind proxies that set headers in an
52
+ # incorrect or confusing way (like AWS ELB).
53
+ #
54
+ # The +custom_proxies+ argument can take an Array of string, IPAddr, or
55
+ # Regexp objects which will be used instead of +TRUSTED_PROXIES+. If a
56
+ # single string, IPAddr, or Regexp object is provided, it will be used in
57
+ # addition to +TRUSTED_PROXIES+. Any proxy setup will put the value you
58
+ # want in the middle (or at the beginning) of the X-Forwarded-For list,
59
+ # with your proxy servers after it. If your proxies aren't removed, pass
60
+ # them in via the +custom_proxies+ parameter. That way, the middleware will
61
+ # ignore those IP addresses, and return the one that you want.
62
+ def initialize(app, ip_spoofing_check = true, custom_proxies = nil)
63
+ @app = app
64
+ @check_ip = ip_spoofing_check
65
+ @proxies = if custom_proxies.blank?
66
+ TRUSTED_PROXIES
67
+ elsif custom_proxies.respond_to?(:any?)
68
+ custom_proxies
69
+ else
70
+ Array(custom_proxies) + TRUSTED_PROXIES
71
+ end
72
+ end
73
+
74
+ # Since the IP address may not be needed, we store the object here
75
+ # without calculating the IP to keep from slowing down the majority of
76
+ # requests. For those requests that do need to know the IP, the
77
+ # GetIp#calculate_ip method will calculate the memoized client IP address.
78
+ def call(env)
79
+ req = ActionDispatch::Request.new env
80
+ req.remote_ip = GetIp.new(req, check_ip, proxies)
81
+ @app.call(req.env)
82
+ end
83
+
84
+ # The GetIp class exists as a way to defer processing of the request data
85
+ # into an actual IP address. If the ActionDispatch::Request#remote_ip method
86
+ # is called, this class will calculate the value and then memoize it.
87
+ class GetIp
88
+ def initialize(req, check_ip, proxies)
89
+ @req = req
90
+ @check_ip = check_ip
91
+ @proxies = proxies
92
+ end
93
+
94
+ # Sort through the various IP address headers, looking for the IP most
95
+ # likely to be the address of the actual remote client making this
96
+ # request.
97
+ #
98
+ # REMOTE_ADDR will be correct if the request is made directly against the
99
+ # Ruby process, on e.g. Heroku. When the request is proxied by another
100
+ # server like HAProxy or NGINX, the IP address that made the original
101
+ # request will be put in an X-Forwarded-For header. If there are multiple
102
+ # proxies, that header may contain a list of IPs. Other proxy services
103
+ # set the Client-Ip header instead, so we check that too.
104
+ #
105
+ # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/],
106
+ # while the first IP in the list is likely to be the "originating" IP,
107
+ # it could also have been set by the client maliciously.
108
+ #
109
+ # In order to find the first address that is (probably) accurate, we
110
+ # take the list of IPs, remove known and trusted proxies, and then take
111
+ # the last address left, which was presumably set by one of those proxies.
112
+ def calculate_ip
113
+ # Set by the Rack web server, this is a single value.
114
+ remote_addr = ips_from(@req.remote_addr).last
115
+
116
+ # Could be a CSV list and/or repeated headers that were concatenated.
117
+ client_ips = ips_from(@req.client_ip).reverse
118
+ forwarded_ips = ips_from(@req.x_forwarded_for).reverse
119
+
120
+ # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set.
121
+ # If they are both set, it means that either:
122
+ #
123
+ # 1) This request passed through two proxies with incompatible IP header
124
+ # conventions.
125
+ # 2) The client passed one of +Client-Ip+ or +X-Forwarded-For+
126
+ # (whichever the proxy servers weren't using) themselves.
127
+ #
128
+ # Either way, there is no way for us to determine which header is the
129
+ # right one after the fact. Since we have no idea, if we are concerned
130
+ # about IP spoofing we need to give up and explode. (If you're not
131
+ # concerned about IP spoofing you can turn the +ip_spoofing_check+
132
+ # option off.)
133
+ should_check_ip = @check_ip && client_ips.last && forwarded_ips.last
134
+ if should_check_ip && !forwarded_ips.include?(client_ips.last)
135
+ # We don't know which came from the proxy, and which from the user
136
+ raise IpSpoofAttackError, "IP spoofing attack?! " \
137
+ "HTTP_CLIENT_IP=#{@req.client_ip.inspect} " \
138
+ "HTTP_X_FORWARDED_FOR=#{@req.x_forwarded_for.inspect}"
139
+ end
140
+
141
+ # We assume these things about the IP headers:
142
+ #
143
+ # - X-Forwarded-For will be a list of IPs, one per proxy, or blank
144
+ # - Client-Ip is propagated from the outermost proxy, or is blank
145
+ # - REMOTE_ADDR will be the IP that made the request to Rack
146
+ ips = [forwarded_ips, client_ips, remote_addr].flatten.compact
147
+
148
+ # If every single IP option is in the trusted list, just return REMOTE_ADDR
149
+ filter_proxies(ips).first || remote_addr
150
+ end
151
+
152
+ # Memoizes the value returned by #calculate_ip and returns it for
153
+ # ActionDispatch::Request to use.
154
+ def to_s
155
+ @ip ||= calculate_ip
156
+ end
157
+
158
+ private
159
+
160
+ def ips_from(header) # :doc:
161
+ return [] unless header
162
+ # Split the comma-separated list into an array of strings.
163
+ ips = header.strip.split(/[,\s]+/)
164
+ ips.select do |ip|
165
+ begin
166
+ # Only return IPs that are valid according to the IPAddr#new method.
167
+ range = IPAddr.new(ip).to_range
168
+ # We want to make sure nobody is sneaking a netmask in.
169
+ range.begin == range.end
170
+ rescue ArgumentError
171
+ nil
172
+ end
173
+ end
174
+ end
175
+
176
+ def filter_proxies(ips) # :doc:
177
+ ips.reject do |ip|
178
+ @proxies.any? { |proxy| proxy === ip }
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end