actionpack 2.2.3 → 2.3.2

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 (264) hide show
  1. data/CHANGELOG +433 -375
  2. data/MIT-LICENSE +1 -1
  3. data/README +21 -75
  4. data/Rakefile +1 -1
  5. data/lib/action_controller.rb +80 -43
  6. data/lib/action_controller/assertions/model_assertions.rb +1 -0
  7. data/lib/action_controller/assertions/response_assertions.rb +43 -16
  8. data/lib/action_controller/assertions/routing_assertions.rb +1 -1
  9. data/lib/action_controller/assertions/selector_assertions.rb +17 -12
  10. data/lib/action_controller/assertions/tag_assertions.rb +1 -4
  11. data/lib/action_controller/base.rb +153 -82
  12. data/lib/action_controller/benchmarking.rb +9 -9
  13. data/lib/action_controller/caching.rb +9 -11
  14. data/lib/action_controller/caching/actions.rb +11 -18
  15. data/lib/action_controller/caching/fragments.rb +28 -20
  16. data/lib/action_controller/caching/pages.rb +13 -15
  17. data/lib/action_controller/caching/sweeping.rb +2 -2
  18. data/lib/action_controller/cgi_ext.rb +0 -1
  19. data/lib/action_controller/cgi_ext/cookie.rb +2 -0
  20. data/lib/action_controller/cgi_process.rb +54 -162
  21. data/lib/action_controller/cookies.rb +13 -25
  22. data/lib/action_controller/dispatcher.rb +43 -122
  23. data/lib/action_controller/failsafe.rb +52 -0
  24. data/lib/action_controller/flash.rb +38 -47
  25. data/lib/action_controller/helpers.rb +13 -9
  26. data/lib/action_controller/http_authentication.rb +203 -23
  27. data/lib/action_controller/integration.rb +126 -70
  28. data/lib/action_controller/layout.rb +36 -39
  29. data/lib/action_controller/middleware_stack.rb +119 -0
  30. data/lib/action_controller/middlewares.rb +13 -0
  31. data/lib/action_controller/mime_responds.rb +19 -4
  32. data/lib/action_controller/mime_type.rb +8 -0
  33. data/lib/action_controller/params_parser.rb +71 -0
  34. data/lib/action_controller/performance_test.rb +0 -1
  35. data/lib/action_controller/polymorphic_routes.rb +36 -30
  36. data/lib/action_controller/reloader.rb +14 -0
  37. data/lib/action_controller/request.rb +107 -499
  38. data/lib/action_controller/request_forgery_protection.rb +7 -39
  39. data/lib/action_controller/rescue.rb +55 -35
  40. data/lib/action_controller/resources.rb +34 -31
  41. data/lib/action_controller/response.rb +99 -57
  42. data/lib/action_controller/rewindable_input.rb +28 -0
  43. data/lib/action_controller/routing.rb +7 -7
  44. data/lib/action_controller/routing/builder.rb +4 -1
  45. data/lib/action_controller/routing/optimisations.rb +1 -1
  46. data/lib/action_controller/routing/recognition_optimisation.rb +1 -2
  47. data/lib/action_controller/routing/route.rb +15 -5
  48. data/lib/action_controller/routing/route_set.rb +82 -35
  49. data/lib/action_controller/routing/segments.rb +35 -0
  50. data/lib/action_controller/session/abstract_store.rb +181 -0
  51. data/lib/action_controller/session/cookie_store.rb +197 -175
  52. data/lib/action_controller/session/mem_cache_store.rb +36 -83
  53. data/lib/action_controller/session_management.rb +26 -134
  54. data/lib/action_controller/streaming.rb +24 -7
  55. data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
  56. data/lib/action_controller/templates/rescues/template_error.erb +2 -2
  57. data/lib/action_controller/test_case.rb +87 -30
  58. data/lib/action_controller/test_process.rb +145 -104
  59. data/lib/action_controller/uploaded_file.rb +44 -0
  60. data/lib/action_controller/url_rewriter.rb +3 -6
  61. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  62. data/lib/action_controller/vendor/html-scanner/html/selector.rb +1 -1
  63. data/lib/action_controller/vendor/rack-1.0/rack.rb +89 -0
  64. data/lib/action_controller/vendor/rack-1.0/rack/adapter/camping.rb +22 -0
  65. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb +37 -0
  66. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/request.rb +37 -0
  67. data/lib/action_controller/vendor/rack-1.0/rack/auth/basic.rb +58 -0
  68. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb +124 -0
  69. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/nonce.rb +51 -0
  70. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/params.rb +55 -0
  71. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb +40 -0
  72. data/lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb +480 -0
  73. data/lib/action_controller/vendor/rack-1.0/rack/builder.rb +63 -0
  74. data/lib/action_controller/vendor/rack-1.0/rack/cascade.rb +36 -0
  75. data/lib/action_controller/vendor/rack-1.0/rack/chunked.rb +49 -0
  76. data/lib/action_controller/vendor/rack-1.0/rack/commonlogger.rb +61 -0
  77. data/lib/action_controller/vendor/rack-1.0/rack/conditionalget.rb +45 -0
  78. data/lib/action_controller/vendor/rack-1.0/rack/content_length.rb +29 -0
  79. data/lib/action_controller/vendor/rack-1.0/rack/content_type.rb +23 -0
  80. data/lib/action_controller/vendor/rack-1.0/rack/deflater.rb +85 -0
  81. data/lib/action_controller/vendor/rack-1.0/rack/directory.rb +153 -0
  82. data/lib/action_controller/vendor/rack-1.0/rack/file.rb +88 -0
  83. data/lib/action_controller/vendor/rack-1.0/rack/handler.rb +48 -0
  84. data/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb +61 -0
  85. data/lib/action_controller/vendor/rack-1.0/rack/handler/evented_mongrel.rb +8 -0
  86. data/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb +89 -0
  87. data/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb +55 -0
  88. data/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb +84 -0
  89. data/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb +59 -0
  90. data/lib/action_controller/vendor/rack-1.0/rack/handler/swiftiplied_mongrel.rb +8 -0
  91. data/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb +18 -0
  92. data/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb +67 -0
  93. data/lib/action_controller/vendor/rack-1.0/rack/head.rb +19 -0
  94. data/lib/action_controller/vendor/rack-1.0/rack/lint.rb +462 -0
  95. data/lib/action_controller/vendor/rack-1.0/rack/lobster.rb +65 -0
  96. data/lib/action_controller/vendor/rack-1.0/rack/lock.rb +16 -0
  97. data/lib/action_controller/vendor/rack-1.0/rack/methodoverride.rb +27 -0
  98. data/lib/action_controller/vendor/rack-1.0/rack/mime.rb +204 -0
  99. data/lib/action_controller/vendor/rack-1.0/rack/mock.rb +160 -0
  100. data/lib/action_controller/vendor/rack-1.0/rack/recursive.rb +57 -0
  101. data/lib/action_controller/vendor/rack-1.0/rack/reloader.rb +64 -0
  102. data/lib/action_controller/vendor/rack-1.0/rack/request.rb +241 -0
  103. data/lib/action_controller/vendor/rack-1.0/rack/response.rb +179 -0
  104. data/lib/action_controller/vendor/rack-1.0/rack/session/abstract/id.rb +142 -0
  105. data/lib/action_controller/vendor/rack-1.0/rack/session/cookie.rb +91 -0
  106. data/lib/action_controller/vendor/rack-1.0/rack/session/memcache.rb +109 -0
  107. data/lib/action_controller/vendor/rack-1.0/rack/session/pool.rb +100 -0
  108. data/lib/action_controller/vendor/rack-1.0/rack/showexceptions.rb +349 -0
  109. data/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb +106 -0
  110. data/lib/action_controller/vendor/rack-1.0/rack/static.rb +38 -0
  111. data/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb +55 -0
  112. data/lib/action_controller/vendor/rack-1.0/rack/utils.rb +392 -0
  113. data/lib/action_controller/verification.rb +1 -1
  114. data/lib/action_pack.rb +1 -1
  115. data/lib/action_pack/version.rb +2 -2
  116. data/lib/action_view.rb +22 -17
  117. data/lib/action_view/base.rb +53 -79
  118. data/lib/action_view/erb/util.rb +38 -0
  119. data/lib/action_view/helpers.rb +24 -5
  120. data/lib/action_view/helpers/active_record_helper.rb +2 -2
  121. data/lib/action_view/helpers/asset_tag_helper.rb +81 -50
  122. data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
  123. data/lib/action_view/helpers/benchmark_helper.rb +26 -5
  124. data/lib/action_view/helpers/date_helper.rb +82 -7
  125. data/lib/action_view/helpers/form_helper.rb +295 -64
  126. data/lib/action_view/helpers/form_options_helper.rb +160 -18
  127. data/lib/action_view/helpers/form_tag_helper.rb +2 -2
  128. data/lib/action_view/helpers/number_helper.rb +31 -18
  129. data/lib/action_view/helpers/prototype_helper.rb +2 -12
  130. data/lib/action_view/helpers/sanitize_helper.rb +0 -10
  131. data/lib/action_view/helpers/scriptaculous_helper.rb +1 -0
  132. data/lib/action_view/helpers/tag_helper.rb +3 -4
  133. data/lib/action_view/helpers/text_helper.rb +99 -122
  134. data/lib/action_view/helpers/translation_helper.rb +19 -1
  135. data/lib/action_view/helpers/url_helper.rb +25 -2
  136. data/lib/action_view/inline_template.rb +1 -1
  137. data/lib/action_view/locale/en.yml +19 -1
  138. data/lib/action_view/partials.rb +46 -9
  139. data/lib/action_view/paths.rb +28 -84
  140. data/lib/action_view/reloadable_template.rb +117 -0
  141. data/lib/action_view/renderable.rb +28 -35
  142. data/lib/action_view/renderable_partial.rb +3 -4
  143. data/lib/action_view/template.rb +172 -31
  144. data/lib/action_view/template_error.rb +8 -9
  145. data/lib/action_view/template_handler.rb +1 -1
  146. data/lib/action_view/template_handlers.rb +9 -6
  147. data/lib/action_view/template_handlers/erb.rb +2 -39
  148. data/lib/action_view/template_handlers/rjs.rb +1 -0
  149. data/lib/action_view/test_case.rb +27 -1
  150. data/test/abstract_unit.rb +23 -17
  151. data/test/active_record_unit.rb +5 -4
  152. data/test/activerecord/active_record_store_test.rb +139 -106
  153. data/test/activerecord/render_partial_with_record_identification_test.rb +5 -21
  154. data/test/controller/action_pack_assertions_test.rb +25 -23
  155. data/test/controller/addresses_render_test.rb +3 -6
  156. data/test/controller/assert_select_test.rb +83 -70
  157. data/test/controller/base_test.rb +11 -13
  158. data/test/controller/benchmark_test.rb +3 -3
  159. data/test/controller/caching_test.rb +34 -24
  160. data/test/controller/capture_test.rb +3 -6
  161. data/test/controller/content_type_test.rb +3 -6
  162. data/test/controller/cookie_test.rb +31 -66
  163. data/test/controller/deprecation/deprecated_base_methods_test.rb +9 -11
  164. data/test/controller/dispatcher_test.rb +23 -28
  165. data/test/controller/fake_models.rb +8 -0
  166. data/test/controller/filters_test.rb +6 -2
  167. data/test/controller/flash_test.rb +2 -6
  168. data/test/controller/helper_test.rb +15 -1
  169. data/test/controller/html-scanner/document_test.rb +1 -1
  170. data/test/controller/html-scanner/sanitizer_test.rb +1 -1
  171. data/test/controller/http_basic_authentication_test.rb +88 -0
  172. data/test/controller/http_digest_authentication_test.rb +178 -0
  173. data/test/controller/integration_test.rb +56 -52
  174. data/test/controller/layout_test.rb +46 -44
  175. data/test/controller/middleware_stack_test.rb +90 -0
  176. data/test/controller/mime_responds_test.rb +7 -11
  177. data/test/controller/mime_type_test.rb +9 -0
  178. data/test/controller/polymorphic_routes_test.rb +235 -151
  179. data/test/controller/rack_test.rb +52 -81
  180. data/test/controller/redirect_test.rb +6 -14
  181. data/test/controller/render_test.rb +273 -60
  182. data/test/controller/request/json_params_parsing_test.rb +45 -0
  183. data/test/controller/request/multipart_params_parsing_test.rb +223 -0
  184. data/test/controller/request/query_string_parsing_test.rb +120 -0
  185. data/test/controller/request/url_encoded_params_parsing_test.rb +184 -0
  186. data/test/controller/request/xml_params_parsing_test.rb +88 -0
  187. data/test/controller/request_forgery_protection_test.rb +17 -98
  188. data/test/controller/request_test.rb +45 -530
  189. data/test/controller/rescue_test.rb +45 -22
  190. data/test/controller/resources_test.rb +112 -37
  191. data/test/controller/routing_test.rb +1442 -1384
  192. data/test/controller/selector_test.rb +3 -3
  193. data/test/controller/send_file_test.rb +30 -3
  194. data/test/controller/session/cookie_store_test.rb +169 -240
  195. data/test/controller/session/mem_cache_store_test.rb +94 -148
  196. data/test/controller/session/test_session_test.rb +58 -0
  197. data/test/controller/test_test.rb +32 -13
  198. data/test/controller/url_rewriter_test.rb +54 -4
  199. data/test/controller/verification_test.rb +1 -1
  200. data/test/controller/view_paths_test.rb +15 -15
  201. data/test/controller/webservice_test.rb +178 -147
  202. data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
  203. data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
  204. data/test/fixtures/layouts/default_html.html.erb +1 -0
  205. data/test/fixtures/layouts/xhr.html.erb +2 -0
  206. data/test/fixtures/multipart/empty +10 -0
  207. data/test/fixtures/multipart/hello.txt +1 -0
  208. data/test/fixtures/multipart/none +9 -0
  209. data/test/fixtures/public/500.da.html +1 -0
  210. data/test/fixtures/quiz/questions/_question.html.erb +1 -0
  211. data/test/fixtures/replies.yml +1 -1
  212. data/test/fixtures/test/_one.html.erb +1 -0
  213. data/test/fixtures/test/_two.html.erb +1 -0
  214. data/test/fixtures/test/dont_pick_me +1 -0
  215. data/test/fixtures/test/hello.builder +1 -1
  216. data/test/fixtures/test/hello_world.da.html.erb +1 -0
  217. data/test/fixtures/test/hello_world.erb~ +1 -0
  218. data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
  219. data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
  220. data/test/fixtures/test/malformed/malformed.erb~ +1 -0
  221. data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
  222. data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
  223. data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
  224. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
  225. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
  226. data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
  227. data/test/fixtures/test/utf8.html.erb +2 -0
  228. data/test/template/active_record_helper_i18n_test.rb +31 -33
  229. data/test/template/active_record_helper_test.rb +34 -0
  230. data/test/template/asset_tag_helper_test.rb +52 -14
  231. data/test/template/atom_feed_helper_test.rb +3 -5
  232. data/test/template/benchmark_helper_test.rb +50 -24
  233. data/test/template/compiled_templates_test.rb +177 -33
  234. data/test/template/date_helper_i18n_test.rb +88 -81
  235. data/test/template/date_helper_test.rb +427 -43
  236. data/test/template/form_helper_test.rb +243 -44
  237. data/test/template/form_options_helper_test.rb +631 -565
  238. data/test/template/form_tag_helper_test.rb +9 -2
  239. data/test/template/javascript_helper_test.rb +0 -5
  240. data/test/template/number_helper_i18n_test.rb +60 -48
  241. data/test/template/number_helper_test.rb +1 -0
  242. data/test/template/render_test.rb +117 -35
  243. data/test/template/test_test.rb +4 -6
  244. data/test/template/text_helper_test.rb +129 -50
  245. data/test/template/translation_helper_test.rb +23 -19
  246. data/test/template/url_helper_test.rb +35 -2
  247. data/test/view/test_case_test.rb +8 -0
  248. metadata +197 -23
  249. data/lib/action_controller/assertions.rb +0 -69
  250. data/lib/action_controller/caching/sql_cache.rb +0 -18
  251. data/lib/action_controller/cgi_ext/session.rb +0 -53
  252. data/lib/action_controller/components.rb +0 -169
  253. data/lib/action_controller/rack_process.rb +0 -297
  254. data/lib/action_controller/request_profiler.rb +0 -169
  255. data/lib/action_controller/session/active_record_store.rb +0 -340
  256. data/lib/action_controller/session/drb_server.rb +0 -32
  257. data/lib/action_controller/session/drb_store.rb +0 -35
  258. data/test/controller/cgi_test.rb +0 -269
  259. data/test/controller/components_test.rb +0 -156
  260. data/test/controller/http_authentication_test.rb +0 -54
  261. data/test/controller/integration_upload_test.rb +0 -43
  262. data/test/controller/session_fixation_test.rb +0 -89
  263. data/test/controller/session_management_test.rb +0 -178
  264. data/test/fixtures/test/hello_world.js +0 -1
@@ -0,0 +1,14 @@
1
+ module ActionController
2
+ class Reloader
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def call(env)
8
+ Dispatcher.reload_application
9
+ @app.call(env)
10
+ ensure
11
+ Dispatcher.cleanup_application
12
+ end
13
+ end
14
+ end
@@ -3,39 +3,42 @@ require 'stringio'
3
3
  require 'strscan'
4
4
 
5
5
  require 'active_support/memoizable'
6
+ require 'action_controller/cgi_ext'
6
7
 
7
8
  module ActionController
8
- # CgiRequest and TestRequest provide concrete implementations.
9
- class AbstractRequest
10
- extend ActiveSupport::Memoizable
9
+ class Request < Rack::Request
11
10
 
12
- def self.relative_url_root=(relative_url_root)
13
- ActiveSupport::Deprecation.warn(
14
- "ActionController::AbstractRequest.relative_url_root= has been renamed." +
15
- "You can now set it with config.action_controller.relative_url_root=", caller)
16
- ActionController::Base.relative_url_root=relative_url_root
11
+ %w[ AUTH_TYPE GATEWAY_INTERFACE
12
+ PATH_TRANSLATED REMOTE_HOST
13
+ REMOTE_IDENT REMOTE_USER REMOTE_ADDR
14
+ SERVER_NAME SERVER_PROTOCOL
15
+
16
+ HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
17
+ HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
18
+ HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
19
+ define_method(env.sub(/^HTTP_/n, '').downcase) do
20
+ @env[env]
21
+ end
22
+ end
23
+
24
+ def key?(key)
25
+ @env.key?(key)
17
26
  end
18
27
 
19
28
  HTTP_METHODS = %w(get head put post delete options)
20
29
  HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
21
30
 
22
- # The hash of environment variables for this request,
23
- # such as { 'RAILS_ENV' => 'production' }.
24
- attr_reader :env
25
-
26
- # The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
27
- # UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
31
+ # Returns the true HTTP request \method as a lowercase symbol, such as
32
+ # <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS
33
+ # constant above, an UnknownHttpMethod exception is raised.
28
34
  def request_method
29
- method = @env['REQUEST_METHOD']
30
- method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
31
-
32
- HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
35
+ @request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}")
33
36
  end
34
- memoize :request_method
35
37
 
36
- # The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
37
- # Note, HEAD is returned as <tt>:get</tt> since the two are functionally
38
- # equivalent from the application's perspective.
38
+ # Returns the HTTP request \method used for action processing as a
39
+ # lowercase symbol, such as <tt>:post</tt>. (Unlike #request_method, this
40
+ # method returns <tt>:get</tt> for a HEAD request because the two are
41
+ # functionally equivalent from the application's perspective.)
39
42
  def method
40
43
  request_method == :head ? :get : request_method
41
44
  end
@@ -70,43 +73,46 @@ module ActionController
70
73
  #
71
74
  # request.headers["Content-Type"] # => "text/plain"
72
75
  def headers
73
- ActionController::Http::Headers.new(@env)
76
+ @headers ||= ActionController::Http::Headers.new(@env)
74
77
  end
75
- memoize :headers
76
78
 
77
79
  # Returns the content length of the request as an integer.
78
80
  def content_length
79
- @env['CONTENT_LENGTH'].to_i
81
+ super.to_i
80
82
  end
81
- memoize :content_length
82
83
 
83
84
  # The MIME type of the HTTP request, such as Mime::XML.
84
85
  #
85
86
  # For backward compatibility, the post \format is extracted from the
86
87
  # X-Post-Data-Format HTTP header if present.
87
88
  def content_type
88
- Mime::Type.lookup(content_type_without_parameters)
89
+ @content_type ||= begin
90
+ if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
91
+ Mime::Type.lookup($1.strip.downcase)
92
+ else
93
+ nil
94
+ end
95
+ end
89
96
  end
90
- memoize :content_type
91
97
 
92
98
  # Returns the accepted MIME type for the request.
93
99
  def accepts
94
- header = @env['HTTP_ACCEPT'].to_s.strip
100
+ @accepts ||= begin
101
+ header = @env['HTTP_ACCEPT'].to_s.strip
95
102
 
96
- if header.empty?
97
- [content_type, Mime::ALL].compact
98
- else
99
- Mime::Type.parse(header)
103
+ if header.empty?
104
+ [content_type, Mime::ALL].compact
105
+ else
106
+ Mime::Type.parse(header)
107
+ end
100
108
  end
101
109
  end
102
- memoize :accepts
103
110
 
104
111
  def if_modified_since
105
112
  if since = env['HTTP_IF_MODIFIED_SINCE']
106
113
  Time.rfc2822(since) rescue nil
107
114
  end
108
115
  end
109
- memoize :if_modified_since
110
116
 
111
117
  def if_none_match
112
118
  env['HTTP_IF_NONE_MATCH']
@@ -125,15 +131,15 @@ module ActionController
125
131
  # supplied, both must match, or the request is not considered fresh.
126
132
  def fresh?(response)
127
133
  case
128
- when if_modified_since && if_none_match
129
- not_modified?(response.last_modified) && etag_matches?(response.etag)
130
- when if_modified_since
131
- not_modified?(response.last_modified)
132
- when if_none_match
133
- etag_matches?(response.etag)
134
- else
135
- false
136
- end
134
+ when if_modified_since && if_none_match
135
+ not_modified?(response.last_modified) && etag_matches?(response.etag)
136
+ when if_modified_since
137
+ not_modified?(response.last_modified)
138
+ when if_none_match
139
+ etag_matches?(response.etag)
140
+ else
141
+ false
142
+ end
137
143
  end
138
144
 
139
145
  # Returns the Mime type for the \format used in the request.
@@ -209,7 +215,7 @@ module ActionController
209
215
  # delimited list in the case of multiple chained proxies; the last
210
216
  # address which is not trusted is the originating IP.
211
217
  def remote_ip
212
- remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].split(',').collect(&:strip)
218
+ remote_addr_list = @env['REMOTE_ADDR'] && @env['REMOTE_ADDR'].scan(/[^,\s]+/)
213
219
 
214
220
  unless remote_addr_list.blank?
215
221
  not_trusted_addrs = remote_addr_list.reject {|addr| addr =~ TRUSTED_PROXIES}
@@ -218,7 +224,7 @@ module ActionController
218
224
  remote_ips = @env['HTTP_X_FORWARDED_FOR'] && @env['HTTP_X_FORWARDED_FOR'].split(',')
219
225
 
220
226
  if @env.include? 'HTTP_CLIENT_IP'
221
- if remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
227
+ if ActionController::Base.ip_spoofing_check && remote_ips && !remote_ips.include?(@env['HTTP_CLIENT_IP'])
222
228
  # We don't know which came from the proxy, and which from the user
223
229
  raise ActionControllerError.new(<<EOM)
224
230
  IP spoofing attack?!
@@ -240,26 +246,21 @@ EOM
240
246
 
241
247
  @env['REMOTE_ADDR']
242
248
  end
243
- memoize :remote_ip
244
249
 
245
250
  # Returns the lowercase name of the HTTP server software.
246
251
  def server_software
247
252
  (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
248
253
  end
249
- memoize :server_software
250
-
251
254
 
252
255
  # Returns the complete URL used for this request.
253
256
  def url
254
257
  protocol + host_with_port + request_uri
255
258
  end
256
- memoize :url
257
259
 
258
260
  # Returns 'https://' if this is an SSL request and 'http://' otherwise.
259
261
  def protocol
260
262
  ssl? ? 'https://' : 'http://'
261
263
  end
262
- memoize :protocol
263
264
 
264
265
  # Is this an SSL request?
265
266
  def ssl?
@@ -271,7 +272,7 @@ EOM
271
272
  if forwarded = env["HTTP_X_FORWARDED_HOST"]
272
273
  forwarded.split(/,\s?/).last
273
274
  else
274
- env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
275
+ env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
275
276
  end
276
277
  end
277
278
 
@@ -279,14 +280,12 @@ EOM
279
280
  def host
280
281
  raw_host_with_port.sub(/:\d+$/, '')
281
282
  end
282
- memoize :host
283
283
 
284
284
  # Returns a \host:\port string for this request, such as "example.com" or
285
285
  # "example.com:8080".
286
286
  def host_with_port
287
287
  "#{host}#{port_string}"
288
288
  end
289
- memoize :host_with_port
290
289
 
291
290
  # Returns the port number of this request as an integer.
292
291
  def port
@@ -296,7 +295,6 @@ EOM
296
295
  standard_port
297
296
  end
298
297
  end
299
- memoize :port
300
298
 
301
299
  # Returns the standard \port number for this request's protocol.
302
300
  def standard_port
@@ -305,15 +303,6 @@ EOM
305
303
  else 80
306
304
  end
307
305
  end
308
-
309
- # Returns the value of ActionController::Base.relative_url_root. This method is
310
- # deprecated as the value is an application wide setting, not something which
311
- # changes per request.
312
- def relative_url_root
313
- ActiveSupport::Deprecation.warn(
314
- "relative_url_root is now set application-wide, use ActionController::Base.relative_url_root instead.", caller)
315
- ActionController::Base.relative_url_root
316
- end
317
306
 
318
307
  # Returns a \port suffix like ":8080" if the \port number of this request
319
308
  # is not the default HTTP \port 80 or HTTPS \port 443.
@@ -341,13 +330,8 @@ EOM
341
330
 
342
331
  # Returns the query string, accounting for server idiosyncrasies.
343
332
  def query_string
344
- if uri = @env['REQUEST_URI']
345
- uri.split('?', 2)[1] || ''
346
- else
347
- @env['QUERY_STRING'] || ''
348
- end
333
+ @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
349
334
  end
350
- memoize :query_string
351
335
 
352
336
  # Returns the request URI, accounting for server idiosyncrasies.
353
337
  # WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
@@ -373,36 +357,33 @@ EOM
373
357
  end
374
358
  end
375
359
  end
376
- memoize :request_uri
377
360
 
378
361
  # Returns the interpreted \path to requested resource after all the installation
379
362
  # directory of this application was taken into account.
380
363
  def path
381
- path = (uri = request_uri) ? uri.split('?').first.to_s : ''
382
-
383
- # Cut off the path to the installation directory if given
384
- path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
385
- path || ''
364
+ path = request_uri.to_s[/\A[^\?]*/]
365
+ path.sub!(/\A#{ActionController::Base.relative_url_root}/, '')
366
+ path
386
367
  end
387
- memoize :path
388
368
 
389
369
  # Read the request \body. This is useful for web services that need to
390
370
  # work with raw requests directly.
391
371
  def raw_post
392
- unless env.include? 'RAW_POST_DATA'
393
- env['RAW_POST_DATA'] = body.read(content_length)
372
+ unless @env.include? 'RAW_POST_DATA'
373
+ @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
394
374
  body.rewind if body.respond_to?(:rewind)
395
375
  end
396
- env['RAW_POST_DATA']
376
+ @env['RAW_POST_DATA']
397
377
  end
398
378
 
399
379
  # Returns both GET and POST \parameters in a single hash.
400
380
  def parameters
401
381
  @parameters ||= request_parameters.merge(query_parameters).update(path_parameters).with_indifferent_access
402
382
  end
383
+ alias_method :params, :parameters
403
384
 
404
385
  def path_parameters=(parameters) #:nodoc:
405
- @path_parameters = parameters
386
+ @env["rack.routing_args"] = parameters
406
387
  @symbolized_path_parameters = @parameters = nil
407
388
  end
408
389
 
@@ -418,464 +399,91 @@ EOM
418
399
  #
419
400
  # See <tt>symbolized_path_parameters</tt> for symbolized keys.
420
401
  def path_parameters
421
- @path_parameters ||= {}
402
+ @env["rack.routing_args"] ||= {}
422
403
  end
423
404
 
424
405
  # The request body is an IO input stream. If the RAW_POST_DATA environment
425
406
  # variable is already set, wrap it in a StringIO.
426
407
  def body
427
- if raw_post = env['RAW_POST_DATA']
408
+ if raw_post = @env['RAW_POST_DATA']
428
409
  raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
429
410
  StringIO.new(raw_post)
430
411
  else
431
- body_stream
412
+ @env['rack.input']
432
413
  end
433
414
  end
434
415
 
435
- def remote_addr
436
- @env['REMOTE_ADDR']
437
- end
438
-
439
- def referrer
440
- @env['HTTP_REFERER']
416
+ def form_data?
417
+ FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
441
418
  end
442
- alias referer referrer
443
419
 
444
-
445
- def query_parameters
446
- @query_parameters ||= self.class.parse_query_parameters(query_string)
420
+ # Override Rack's GET method to support indifferent access
421
+ def GET
422
+ @env["action_controller.request.query_parameters"] ||= normalize_parameters(super)
447
423
  end
424
+ alias_method :query_parameters, :GET
448
425
 
449
- def request_parameters
450
- @request_parameters ||= parse_formatted_request_parameters
426
+ # Override Rack's POST method to support indifferent access
427
+ def POST
428
+ @env["action_controller.request.request_parameters"] ||= normalize_parameters(super)
451
429
  end
452
-
453
-
454
- #--
455
- # Must be implemented in the concrete request
456
- #++
430
+ alias_method :request_parameters, :POST
457
431
 
458
432
  def body_stream #:nodoc:
433
+ @env['rack.input']
459
434
  end
460
435
 
461
- def cookies #:nodoc:
462
- end
463
-
464
- def session #:nodoc:
436
+ def session
437
+ @env['rack.session'] ||= {}
465
438
  end
466
439
 
467
440
  def session=(session) #:nodoc:
468
- @session = session
441
+ @env['rack.session'] = session
469
442
  end
470
443
 
471
- def reset_session #:nodoc:
444
+ def reset_session
445
+ @env['rack.session.options'].delete(:id)
446
+ @env['rack.session'] = {}
472
447
  end
473
448
 
474
- protected
475
- # The raw content type string. Use when you need parameters such as
476
- # charset or boundary which aren't included in the content_type MIME type.
477
- # Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
478
- def content_type_with_parameters
479
- content_type_from_legacy_post_data_format_header ||
480
- env['CONTENT_TYPE'].to_s
481
- end
482
-
483
- # The raw content type string with its parameters stripped off.
484
- def content_type_without_parameters
485
- self.class.extract_content_type_without_parameters(content_type_with_parameters)
486
- end
487
- memoize :content_type_without_parameters
488
-
489
- private
490
- def content_type_from_legacy_post_data_format_header
491
- if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
492
- case x_post_format.to_s.downcase
493
- when 'yaml'; 'application/x-yaml'
494
- when 'xml'; 'application/xml'
495
- end
496
- end
497
- end
498
-
499
- def parse_formatted_request_parameters
500
- return {} if content_length.zero?
501
-
502
- content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
503
-
504
- # Don't parse params for unknown requests.
505
- return {} if content_type.blank?
506
-
507
- mime_type = Mime::Type.lookup(content_type)
508
- strategy = ActionController::Base.param_parsers[mime_type]
509
-
510
- # Only multipart form parsing expects a stream.
511
- body = (strategy && strategy != :multipart_form) ? raw_post : self.body
512
-
513
- case strategy
514
- when Proc
515
- strategy.call(body)
516
- when :url_encoded_form
517
- self.class.clean_up_ajax_request_body! body
518
- self.class.parse_query_parameters(body)
519
- when :multipart_form
520
- self.class.parse_multipart_form_parameters(body, boundary, content_length, env)
521
- when :xml_simple, :xml_node
522
- body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
523
- when :yaml
524
- YAML.load(body)
525
- when :json
526
- if body.blank?
527
- {}
528
- else
529
- data = ActiveSupport::JSON.decode(body)
530
- data = {:_json => data} unless data.is_a?(Hash)
531
- data.with_indifferent_access
532
- end
533
- else
534
- {}
535
- end
536
- rescue Exception => e # YAML, XML or Ruby code block errors
537
- raise
538
- { "body" => body,
539
- "content_type" => content_type_with_parameters,
540
- "content_length" => content_length,
541
- "exception" => "#{e.message} (#{e.class})",
542
- "backtrace" => e.backtrace }
543
- end
544
-
545
- def named_host?(host)
546
- !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
547
- end
548
-
549
- class << self
550
- def parse_query_parameters(query_string)
551
- return {} if query_string.blank?
552
-
553
- pairs = query_string.split('&').collect do |chunk|
554
- next if chunk.empty?
555
- key, value = chunk.split('=', 2)
556
- next if key.empty?
557
- value = value.nil? ? nil : CGI.unescape(value)
558
- [ CGI.unescape(key), value ]
559
- end.compact
560
-
561
- UrlEncodedPairParser.new(pairs).result
562
- end
563
-
564
- def parse_request_parameters(params)
565
- parser = UrlEncodedPairParser.new
566
-
567
- params = params.dup
568
- until params.empty?
569
- for key, value in params
570
- if key.blank?
571
- params.delete key
572
- elsif !key.include?('[')
573
- # much faster to test for the most common case first (GET)
574
- # and avoid the call to build_deep_hash
575
- parser.result[key] = get_typed_value(value[0])
576
- params.delete key
577
- elsif value.is_a?(Array)
578
- parser.parse(key, get_typed_value(value.shift))
579
- params.delete key if value.empty?
580
- else
581
- raise TypeError, "Expected array, found #{value.inspect}"
582
- end
583
- end
584
- end
585
-
586
- parser.result
587
- end
588
-
589
- def parse_multipart_form_parameters(body, boundary, body_size, env)
590
- parse_request_parameters(read_multipart(body, boundary, body_size, env))
591
- end
592
-
593
- def extract_multipart_boundary(content_type_with_parameters)
594
- if content_type_with_parameters =~ MULTIPART_BOUNDARY
595
- ['multipart/form-data', $1.dup]
596
- else
597
- extract_content_type_without_parameters(content_type_with_parameters)
598
- end
599
- end
600
-
601
- def extract_content_type_without_parameters(content_type_with_parameters)
602
- $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
603
- end
604
-
605
- def clean_up_ajax_request_body!(body)
606
- body.chop! if body[-1] == 0
607
- body.gsub!(/&_=$/, '')
608
- end
609
-
610
-
611
- private
612
- def get_typed_value(value)
613
- case value
614
- when String
615
- value
616
- when NilClass
617
- ''
618
- when Array
619
- value.map { |v| get_typed_value(v) }
620
- else
621
- if value.respond_to? :original_filename
622
- # Uploaded file
623
- if value.original_filename
624
- value
625
- # Multipart param
626
- else
627
- result = value.read
628
- value.rewind
629
- result
630
- end
631
- # Unknown value, neither string nor multipart.
632
- else
633
- raise "Unknown form value: #{value.inspect}"
634
- end
635
- end
636
- end
637
-
638
- MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
639
-
640
- EOL = "\015\012"
641
-
642
- def read_multipart(body, boundary, body_size, env)
643
- params = Hash.new([])
644
- boundary = "--" + boundary
645
- quoted_boundary = Regexp.quote(boundary)
646
- buf = ""
647
- bufsize = 10 * 1024
648
- boundary_end=""
649
-
650
- # start multipart/form-data
651
- body.binmode if defined? body.binmode
652
- case body
653
- when File
654
- body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
655
- when StringIO
656
- body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
657
- end
658
- boundary_size = boundary.size + EOL.size
659
- body_size -= boundary_size
660
- status = body.read(boundary_size)
661
- if nil == status
662
- raise EOFError, "no content body"
663
- elsif boundary + EOL != status
664
- raise EOFError, "bad content body"
665
- end
666
-
667
- loop do
668
- head = nil
669
- content =
670
- if 10240 < body_size
671
- UploadedTempfile.new("CGI")
672
- else
673
- UploadedStringIO.new
674
- end
675
- content.binmode if defined? content.binmode
676
-
677
- until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
678
-
679
- if (not head) and /#{EOL}#{EOL}/n.match(buf)
680
- buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
681
- head = $1.dup
682
- ""
683
- end
684
- next
685
- end
686
-
687
- if head and ( (EOL + boundary + EOL).size < buf.size )
688
- content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
689
- buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
690
- end
691
-
692
- c = if bufsize < body_size
693
- body.read(bufsize)
694
- else
695
- body.read(body_size)
696
- end
697
- if c.nil? || c.empty?
698
- raise EOFError, "bad content body"
699
- end
700
- buf.concat(c)
701
- body_size -= c.size
702
- end
703
-
704
- buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
705
- content.print $1
706
- if "--" == $2
707
- body_size = -1
708
- end
709
- boundary_end = $2.dup
710
- ""
711
- end
712
-
713
- content.rewind
714
-
715
- head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
716
- if filename = $1 || $2
717
- if /Mac/ni.match(env['HTTP_USER_AGENT']) and
718
- /Mozilla/ni.match(env['HTTP_USER_AGENT']) and
719
- (not /MSIE/ni.match(env['HTTP_USER_AGENT']))
720
- filename = CGI.unescape(filename)
721
- end
722
- content.original_path = filename.dup
723
- end
724
-
725
- head =~ /Content-Type: ([^\r]*)/ni
726
- content.content_type = $1.dup if $1
727
-
728
- head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
729
- name = $1.dup if $1
730
-
731
- if params.has_key?(name)
732
- params[name].push(content)
733
- else
734
- params[name] = [content]
735
- end
736
- break if body_size == -1
737
- end
738
- raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
739
-
740
- begin
741
- body.rewind if body.respond_to?(:rewind)
742
- rescue Errno::ESPIPE
743
- # Handles exceptions raised by input streams that cannot be rewound
744
- # such as when using plain CGI under Apache
745
- end
746
-
747
- params
748
- end
449
+ def session_options
450
+ @env['rack.session.options'] ||= {}
749
451
  end
750
- end
751
452
 
752
- class UrlEncodedPairParser < StringScanner #:nodoc:
753
- attr_reader :top, :parent, :result
754
-
755
- def initialize(pairs = [])
756
- super('')
757
- @result = {}
758
- pairs.each { |key, value| parse(key, value) }
453
+ def session_options=(options)
454
+ @env['rack.session.options'] = options
759
455
  end
760
456
 
761
- KEY_REGEXP = %r{([^\[\]=&]+)}
762
- BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
763
-
764
- # Parse the query string
765
- def parse(key, value)
766
- self.string = key
767
- @top, @parent = result, nil
768
-
769
- # First scan the bare key
770
- key = scan(KEY_REGEXP) or return
771
- key = post_key_check(key)
772
-
773
- # Then scan as many nestings as present
774
- until eos?
775
- r = scan(BRACKETED_KEY_REGEXP) or return
776
- key = self[1]
777
- key = post_key_check(key)
778
- end
779
-
780
- bind(key, value)
457
+ def server_port
458
+ @env['SERVER_PORT'].to_i
781
459
  end
782
460
 
783
461
  private
784
- # After we see a key, we must look ahead to determine our next action. Cases:
785
- #
786
- # [] follows the key. Then the value must be an array.
787
- # = follows the key. (A value comes next)
788
- # & or the end of string follows the key. Then the key is a flag.
789
- # otherwise, a hash follows the key.
790
- def post_key_check(key)
791
- if scan(/\[\]/) # a[b][] indicates that b is an array
792
- container(key, Array)
793
- nil
794
- elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
795
- container(key, Hash)
796
- nil
797
- else # End of key? We do nothing.
798
- key
799
- end
800
- end
801
-
802
- # Add a container to the stack.
803
- def container(key, klass)
804
- type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
805
- value = bind(key, klass.new)
806
- type_conflict! klass, value unless value.is_a?(klass)
807
- push(value)
808
- end
809
-
810
- # Push a value onto the 'stack', which is actually only the top 2 items.
811
- def push(value)
812
- @parent, @top = @top, value
462
+ def named_host?(host)
463
+ !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
813
464
  end
814
465
 
815
- # Bind a key (which may be nil for items in an array) to the provided value.
816
- def bind(key, value)
817
- if top.is_a? Array
818
- if key
819
- if top[-1].is_a?(Hash) && ! top[-1].key?(key)
820
- top[-1][key] = value
821
- else
822
- top << {key => value}.with_indifferent_access
823
- push top.last
824
- value = top[key]
825
- end
466
+ # Convert nested Hashs to HashWithIndifferentAccess and replace
467
+ # file upload hashs with UploadedFile objects
468
+ def normalize_parameters(value)
469
+ case value
470
+ when Hash
471
+ if value.has_key?(:tempfile)
472
+ upload = value[:tempfile]
473
+ upload.extend(UploadedFile)
474
+ upload.original_path = value[:filename]
475
+ upload.content_type = value[:type]
476
+ upload
826
477
  else
827
- top << value
478
+ h = {}
479
+ value.each { |k, v| h[k] = normalize_parameters(v) }
480
+ h.with_indifferent_access
828
481
  end
829
- elsif top.is_a? Hash
830
- key = CGI.unescape(key)
831
- parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
832
- top[key] ||= value
833
- return top[key]
482
+ when Array
483
+ value.map { |e| normalize_parameters(e) }
834
484
  else
835
- raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
485
+ value
836
486
  end
837
-
838
- return value
839
487
  end
840
-
841
- def type_conflict!(klass, value)
842
- raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)"
843
- end
844
- end
845
-
846
- module UploadedFile
847
- def self.included(base)
848
- base.class_eval do
849
- attr_accessor :original_path, :content_type
850
- alias_method :local_path, :path
851
- end
852
- end
853
-
854
- # Take the basename of the upload's original filename.
855
- # This handles the full Windows paths given by Internet Explorer
856
- # (and perhaps other broken user agents) without affecting
857
- # those which give the lone filename.
858
- # The Windows regexp is adapted from Perl's File::Basename.
859
- def original_filename
860
- unless defined? @original_filename
861
- @original_filename =
862
- unless original_path.blank?
863
- if original_path =~ /^(?:.*[:\\\/])?(.*)/m
864
- $1
865
- else
866
- File.basename original_path
867
- end
868
- end
869
- end
870
- @original_filename
871
- end
872
- end
873
-
874
- class UploadedStringIO < StringIO
875
- include UploadedFile
876
- end
877
-
878
- class UploadedTempfile < Tempfile
879
- include UploadedFile
880
488
  end
881
489
  end