actionpack 1.12.5 → 1.13.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- data/CHANGELOG +517 -15
- data/MIT-LICENSE +1 -1
- data/README +18 -20
- data/Rakefile +7 -4
- data/examples/address_book_controller.rb +3 -3
- data/examples/blog_controller.cgi +3 -3
- data/examples/debate_controller.cgi +5 -5
- data/lib/action_controller.rb +2 -2
- data/lib/action_controller/assertions.rb +73 -311
- data/lib/action_controller/{deprecated_assertions.rb → assertions/deprecated_assertions.rb} +32 -8
- data/lib/action_controller/assertions/dom_assertions.rb +25 -0
- data/lib/action_controller/assertions/model_assertions.rb +12 -0
- data/lib/action_controller/assertions/response_assertions.rb +140 -0
- data/lib/action_controller/assertions/routing_assertions.rb +82 -0
- data/lib/action_controller/assertions/selector_assertions.rb +571 -0
- data/lib/action_controller/assertions/tag_assertions.rb +117 -0
- data/lib/action_controller/base.rb +334 -163
- data/lib/action_controller/benchmarking.rb +3 -6
- data/lib/action_controller/caching.rb +83 -22
- data/lib/action_controller/cgi_ext/cgi_ext.rb +0 -7
- data/lib/action_controller/cgi_ext/cgi_methods.rb +167 -173
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +43 -22
- data/lib/action_controller/cgi_process.rb +50 -27
- data/lib/action_controller/components.rb +21 -25
- data/lib/action_controller/cookies.rb +10 -9
- data/lib/action_controller/{dependencies.rb → deprecated_dependencies.rb} +9 -27
- data/lib/action_controller/filters.rb +448 -225
- data/lib/action_controller/flash.rb +24 -20
- data/lib/action_controller/helpers.rb +2 -5
- data/lib/action_controller/integration.rb +40 -16
- data/lib/action_controller/layout.rb +11 -8
- data/lib/action_controller/macros/auto_complete.rb +3 -2
- data/lib/action_controller/macros/in_place_editing.rb +3 -2
- data/lib/action_controller/mime_responds.rb +41 -29
- data/lib/action_controller/mime_type.rb +68 -10
- data/lib/action_controller/pagination.rb +4 -3
- data/lib/action_controller/request.rb +22 -14
- data/lib/action_controller/rescue.rb +25 -22
- data/lib/action_controller/resources.rb +302 -0
- data/lib/action_controller/response.rb +20 -2
- data/lib/action_controller/response.rb.rej +17 -0
- data/lib/action_controller/routing.rb +1165 -567
- data/lib/action_controller/scaffolding.rb +30 -31
- data/lib/action_controller/session/active_record_store.rb +2 -0
- data/lib/action_controller/session/drb_store.rb +4 -0
- data/lib/action_controller/session/mem_cache_store.rb +4 -0
- data/lib/action_controller/session_management.rb +6 -9
- data/lib/action_controller/status_codes.rb +89 -0
- data/lib/action_controller/streaming.rb +6 -15
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +5 -5
- data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -2
- data/lib/action_controller/templates/rescues/routing_error.rhtml +4 -4
- data/lib/action_controller/templates/rescues/template_error.rhtml +1 -1
- data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
- data/lib/action_controller/test_process.rb +52 -30
- data/lib/action_controller/url_rewriter.rb +63 -29
- data/lib/action_controller/vendor/html-scanner/html/document.rb +1 -0
- data/lib/action_controller/vendor/html-scanner/html/node.rb +3 -4
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +822 -0
- data/lib/action_controller/verification.rb +22 -11
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/version.rb +2 -2
- data/lib/action_view.rb +1 -1
- data/lib/action_view/base.rb +46 -43
- data/lib/action_view/compiled_templates.rb +1 -1
- data/lib/action_view/helpers/active_record_helper.rb +54 -17
- data/lib/action_view/helpers/asset_tag_helper.rb +97 -46
- data/lib/action_view/helpers/capture_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +258 -136
- data/lib/action_view/helpers/debug_helper.rb +1 -1
- data/lib/action_view/helpers/deprecated_helper.rb +34 -0
- data/lib/action_view/helpers/form_helper.rb +75 -35
- data/lib/action_view/helpers/form_options_helper.rb +7 -5
- data/lib/action_view/helpers/form_tag_helper.rb +44 -6
- data/lib/action_view/helpers/java_script_macros_helper.rb +59 -46
- data/lib/action_view/helpers/javascript_helper.rb +71 -10
- data/lib/action_view/helpers/javascripts/controls.js +41 -23
- data/lib/action_view/helpers/javascripts/dragdrop.js +105 -76
- data/lib/action_view/helpers/javascripts/effects.js +293 -163
- data/lib/action_view/helpers/javascripts/prototype.js +897 -389
- data/lib/action_view/helpers/javascripts/prototype.js.rej +561 -0
- data/lib/action_view/helpers/number_helper.rb +111 -65
- data/lib/action_view/helpers/prototype_helper.rb +84 -109
- data/lib/action_view/helpers/scriptaculous_helper.rb +5 -0
- data/lib/action_view/helpers/tag_helper.rb +69 -16
- data/lib/action_view/helpers/text_helper.rb +149 -112
- data/lib/action_view/helpers/url_helper.rb +200 -107
- data/lib/action_view/template_error.rb +66 -42
- data/test/abstract_unit.rb +4 -2
- data/test/active_record_unit.rb +84 -56
- data/test/activerecord/active_record_assertions_test.rb +26 -18
- data/test/activerecord/active_record_store_test.rb +4 -36
- data/test/activerecord/pagination_test.rb +1 -6
- data/test/controller/action_pack_assertions_test.rb +230 -113
- data/test/controller/addresses_render_test.rb +2 -6
- data/test/controller/assert_select_test.rb +576 -0
- data/test/controller/base_test.rb +73 -3
- data/test/controller/caching_test.rb +228 -0
- data/test/controller/capture_test.rb +12 -10
- data/test/controller/cgi_test.rb +89 -12
- data/test/controller/components_test.rb +24 -2
- data/test/controller/content_type_test.rb +139 -0
- data/test/controller/controller_fixtures/app/controllers/admin/user_controller.rb +0 -0
- data/test/controller/controller_fixtures/app/controllers/user_controller.rb +0 -0
- data/test/controller/controller_fixtures/vendor/plugins/bad_plugin/lib/plugin_controller.rb +0 -0
- data/test/controller/cookie_test.rb +33 -25
- data/test/controller/deprecated_instance_variables_test.rb +48 -0
- data/test/controller/deprecation/deprecated_base_methods_test.rb +60 -0
- data/test/controller/fake_controllers.rb +0 -1
- data/test/controller/filters_test.rb +301 -16
- data/test/controller/flash_test.rb +19 -2
- data/test/controller/helper_test.rb +2 -2
- data/test/controller/integration_test.rb +154 -0
- data/test/controller/layout_test.rb +115 -1
- data/test/controller/mime_responds_test.rb +94 -0
- data/test/controller/mime_type_test.rb +9 -0
- data/test/controller/new_render_test.rb +161 -11
- data/test/controller/raw_post_test.rb +52 -15
- data/test/controller/redirect_test.rb +27 -14
- data/test/controller/render_test.rb +76 -29
- data/test/controller/request_test.rb +55 -4
- data/test/controller/resources_test.rb +274 -0
- data/test/controller/routing_test.rb +1533 -824
- data/test/controller/selector_test.rb +628 -0
- data/test/controller/send_file_test.rb +9 -1
- data/test/controller/session_management_test.rb +51 -0
- data/test/controller/test_test.rb +113 -29
- data/test/controller/url_rewriter_test.rb +86 -17
- data/test/controller/verification_test.rb +19 -17
- data/test/controller/webservice_test.rb +0 -7
- data/test/fixtures/content_type/render_default_content_types_for_respond_to.rhtml +1 -0
- data/test/fixtures/content_type/render_default_for_rhtml.rhtml +1 -0
- data/test/fixtures/content_type/render_default_for_rjs.rjs +1 -0
- data/test/fixtures/content_type/render_default_for_rxml.rxml +1 -0
- data/test/fixtures/deprecated_instance_variables/_cookies_ivar.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_cookies_method.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_flash_ivar.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_flash_method.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_headers_ivar.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_headers_method.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_params_ivar.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_params_method.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_request_ivar.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_request_method.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_response_ivar.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_response_method.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_session_ivar.rhtml +1 -0
- data/test/fixtures/deprecated_instance_variables/_session_method.rhtml +1 -0
- data/test/fixtures/multipart/binary_file +0 -0
- data/test/fixtures/public/javascripts/application.js +1 -0
- data/test/fixtures/test/_hello.rxml +1 -0
- data/test/fixtures/test/hello_world_container.rxml +3 -0
- data/test/fixtures/topic.rb +2 -2
- data/test/template/active_record_helper_test.rb +83 -12
- data/test/template/asset_tag_helper_test.rb +75 -95
- data/test/template/compiled_templates_test.rb +1 -0
- data/test/template/date_helper_test.rb +873 -181
- data/test/template/deprecated_helper_test.rb +36 -0
- data/test/template/deprecated_instance_variables_test.rb +43 -0
- data/test/template/form_helper_test.rb +77 -1
- data/test/template/form_options_helper_test.rb +4 -0
- data/test/template/form_tag_helper_test.rb +66 -2
- data/test/template/java_script_macros_helper_test.rb +4 -1
- data/test/template/javascript_helper_test.rb +29 -0
- data/test/template/number_helper_test.rb +63 -27
- data/test/template/prototype_helper_test.rb +77 -34
- data/test/template/tag_helper_test.rb +34 -6
- data/test/template/text_helper_test.rb +69 -34
- data/test/template/url_helper_test.rb +168 -16
- data/test/testing_sandbox.rb +7 -22
- metadata +66 -20
- data/filler.txt +0 -50
- data/lib/action_controller/code_generation.rb +0 -235
- data/lib/action_controller/vendor/xml_simple.rb +0 -1019
- data/test/controller/caching_filestore.rb +0 -74
- data/test/fixtures/application_root/app/controllers/a_class_that_contains_a_controller/poorly_placed_controller.rb +0 -7
- data/test/fixtures/application_root/app/controllers/module_that_holds_controllers/nested_controller.rb +0 -3
- data/test/fixtures/application_root/app/models/a_class_that_contains_a_controller.rb +0 -7
- data/test/fixtures/dont_load.rb +0 -3
@@ -8,11 +8,8 @@ module ActionController #:nodoc:
|
|
8
8
|
base.extend(ClassMethods)
|
9
9
|
|
10
10
|
base.class_eval do
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
alias_method :render_without_benchmark, :render
|
15
|
-
alias_method :render, :render_with_benchmark
|
11
|
+
alias_method_chain :perform_action, :benchmark
|
12
|
+
alias_method_chain :render, :benchmark
|
16
13
|
end
|
17
14
|
end
|
18
15
|
|
@@ -68,7 +65,7 @@ module ActionController #:nodoc:
|
|
68
65
|
else
|
69
66
|
runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
|
70
67
|
log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
|
71
|
-
log_message << rendering_runtime(runtime) if @rendering_runtime
|
68
|
+
log_message << rendering_runtime(runtime) if defined?(@rendering_runtime)
|
72
69
|
log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
|
73
70
|
log_message << " | #{headers["Status"]}"
|
74
71
|
log_message << " [#{complete_request_uri rescue "unknown"}]"
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'uri'
|
2
3
|
|
3
4
|
module ActionController #:nodoc:
|
4
5
|
# Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
|
@@ -117,24 +118,24 @@ module ActionController #:nodoc:
|
|
117
118
|
return unless perform_caching
|
118
119
|
if options[:action].is_a?(Array)
|
119
120
|
options[:action].dup.each do |action|
|
120
|
-
self.class.expire_page(url_for(options.merge(
|
121
|
+
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :action => action)))
|
121
122
|
end
|
122
123
|
else
|
123
|
-
self.class.expire_page(url_for(options.merge(
|
124
|
+
self.class.expire_page(url_for(options.merge(:only_path => true, :skip_relative_url_root => true)))
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
127
|
-
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of
|
128
|
+
# Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of response.body is used
|
128
129
|
# If no options are provided, the current +options+ for this action is used. Example:
|
129
130
|
# cache_page "I'm the cached content", :controller => "lists", :action => "show"
|
130
131
|
def cache_page(content = nil, options = {})
|
131
132
|
return unless perform_caching && caching_allowed
|
132
|
-
self.class.cache_page(content ||
|
133
|
+
self.class.cache_page(content || response.body, url_for(options.merge(:only_path => true, :skip_relative_url_root => true, :format => params[:format])))
|
133
134
|
end
|
134
135
|
|
135
136
|
private
|
136
137
|
def caching_allowed
|
137
|
-
|
138
|
+
request.get? && response.headers['Status'].to_i == 200
|
138
139
|
end
|
139
140
|
end
|
140
141
|
|
@@ -155,9 +156,12 @@ module ActionController #:nodoc:
|
|
155
156
|
# the current host and the path. So a page that is accessed at http://david.somewhere.com/lists/show/1 will result in a fragment named
|
156
157
|
# "david.somewhere.com/lists/show/1". This allows the cacher to differentiate between "david.somewhere.com/lists/" and
|
157
158
|
# "jamis.somewhere.com/lists/" -- which is a helpful way of assisting the subdomain-as-account-key pattern.
|
159
|
+
#
|
160
|
+
# Different representations of the same resource, e.g. <tt>http://david.somewhere.com/lists</tt> and <tt>http://david.somewhere.com/lists.xml</tt>
|
161
|
+
# are treated like separate requests and so are cached separately. Keep in mind when expiring an action cache that <tt>:action => 'lists'</tt> is not the same
|
162
|
+
# as <tt>:action => 'list', :format => :xml</tt>.
|
158
163
|
module Actions
|
159
|
-
def self.
|
160
|
-
super
|
164
|
+
def self.included(base) #:nodoc:
|
161
165
|
base.extend(ClassMethods)
|
162
166
|
base.send(:attr_accessor, :rendered_action_cache)
|
163
167
|
end
|
@@ -173,22 +177,24 @@ module ActionController #:nodoc:
|
|
173
177
|
return unless perform_caching
|
174
178
|
if options[:action].is_a?(Array)
|
175
179
|
options[:action].dup.each do |action|
|
176
|
-
expire_fragment(
|
180
|
+
expire_fragment(ActionCachePath.path_for(self, options.merge({ :action => action })))
|
177
181
|
end
|
178
182
|
else
|
179
|
-
expire_fragment(
|
183
|
+
expire_fragment(ActionCachePath.path_for(self, options))
|
180
184
|
end
|
181
185
|
end
|
182
186
|
|
183
187
|
class ActionCacheFilter #:nodoc:
|
184
|
-
def initialize(*actions)
|
188
|
+
def initialize(*actions, &block)
|
185
189
|
@actions = actions
|
186
190
|
end
|
187
191
|
|
188
192
|
def before(controller)
|
189
193
|
return unless @actions.include?(controller.action_name.intern)
|
190
|
-
|
194
|
+
action_cache_path = ActionCachePath.new(controller)
|
195
|
+
if cache = controller.read_fragment(action_cache_path.path)
|
191
196
|
controller.rendered_action_cache = true
|
197
|
+
set_content_type!(action_cache_path)
|
192
198
|
controller.send(:render_text, cache)
|
193
199
|
false
|
194
200
|
end
|
@@ -196,8 +202,60 @@ module ActionController #:nodoc:
|
|
196
202
|
|
197
203
|
def after(controller)
|
198
204
|
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
|
199
|
-
controller.write_fragment(
|
205
|
+
controller.write_fragment(ActionCachePath.path_for(controller), controller.response.body)
|
200
206
|
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def set_content_type!(action_cache_path)
|
211
|
+
if extention = action_cache_path.extension
|
212
|
+
content_type = Mime::EXTENSION_LOOKUP[extention]
|
213
|
+
action_cache_path.controller.response.content_type = content_type.to_s
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
class ActionCachePath
|
220
|
+
attr_reader :controller, :options
|
221
|
+
|
222
|
+
class << self
|
223
|
+
def path_for(*args, &block)
|
224
|
+
new(*args).path
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def initialize(controller, options = {})
|
229
|
+
@controller = controller
|
230
|
+
@options = options
|
231
|
+
end
|
232
|
+
|
233
|
+
def path
|
234
|
+
return @path if @path
|
235
|
+
@path = controller.url_for(options).split('://').last
|
236
|
+
normalize!
|
237
|
+
add_extension!
|
238
|
+
URI.unescape(@path)
|
239
|
+
end
|
240
|
+
|
241
|
+
def extension
|
242
|
+
@extension ||= extract_extension(controller.request.path)
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
def normalize!
|
247
|
+
@path << 'index' if @path.last == '/'
|
248
|
+
end
|
249
|
+
|
250
|
+
def add_extension!
|
251
|
+
@path << ".#{extension}" if extension
|
252
|
+
end
|
253
|
+
|
254
|
+
def extract_extension(file_path)
|
255
|
+
# Don't want just what comes after the last '.' to accomodate multi part extensions
|
256
|
+
# such as tar.gz.
|
257
|
+
file_path[/^[^.]+\.(.+)$/, 1]
|
258
|
+
end
|
201
259
|
end
|
202
260
|
end
|
203
261
|
|
@@ -208,7 +266,7 @@ module ActionController #:nodoc:
|
|
208
266
|
# <b>Hello <%= @name %></b>
|
209
267
|
# <% cache do %>
|
210
268
|
# All the topics in the system:
|
211
|
-
# <%=
|
269
|
+
# <%= render :partial => "topic", :collection => Topic.find(:all) %>
|
212
270
|
# <% end %>
|
213
271
|
#
|
214
272
|
# This cache will bind to the name of action that called it. So you would be able to invalidate it using
|
@@ -246,8 +304,7 @@ module ActionController #:nodoc:
|
|
246
304
|
# ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
|
247
305
|
# ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
|
248
306
|
module Fragments
|
249
|
-
def self.
|
250
|
-
super
|
307
|
+
def self.included(base) #:nodoc:
|
251
308
|
base.class_eval do
|
252
309
|
@@fragment_cache_store = MemoryStore.new
|
253
310
|
cattr_reader :fragment_cache_store
|
@@ -306,7 +363,12 @@ module ActionController #:nodoc:
|
|
306
363
|
# Name can take one of three forms:
|
307
364
|
# * String: This would normally take the form of a path like "pages/45/notes"
|
308
365
|
# * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
|
309
|
-
# * Regexp: Will destroy all the matched fragments, example:
|
366
|
+
# * Regexp: Will destroy all the matched fragments, example:
|
367
|
+
# %r{pages/\d*/notes}
|
368
|
+
# Ensure you do not specify start and finish in the regex (^$) because
|
369
|
+
# the actual filename matched looks like ./cache/filename/path.cache
|
370
|
+
# Regexp expiration is not supported on caches which can't iterate over
|
371
|
+
# all keys, such as memcached.
|
310
372
|
def expire_fragment(name, options = nil)
|
311
373
|
return unless perform_caching
|
312
374
|
|
@@ -327,6 +389,7 @@ module ActionController #:nodoc:
|
|
327
389
|
def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
|
328
390
|
expire_fragment(matcher, options)
|
329
391
|
end
|
392
|
+
deprecate :expire_matched_fragments => :expire_fragment
|
330
393
|
|
331
394
|
|
332
395
|
class UnthreadedMemoryStore #:nodoc:
|
@@ -430,7 +493,7 @@ module ActionController #:nodoc:
|
|
430
493
|
if f =~ matcher
|
431
494
|
begin
|
432
495
|
File.delete(f)
|
433
|
-
rescue
|
496
|
+
rescue SystemCallError => e
|
434
497
|
# If there's no cache, then there's nothing to complain about
|
435
498
|
end
|
436
499
|
end
|
@@ -493,8 +556,7 @@ module ActionController #:nodoc:
|
|
493
556
|
#
|
494
557
|
# In the example above, four actions are cached and three actions are responsible for expiring those caches.
|
495
558
|
module Sweeping
|
496
|
-
def self.
|
497
|
-
super
|
559
|
+
def self.included(base) #:nodoc:
|
498
560
|
base.extend(ClassMethods)
|
499
561
|
end
|
500
562
|
|
@@ -503,8 +565,7 @@ module ActionController #:nodoc:
|
|
503
565
|
return unless perform_caching
|
504
566
|
configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}
|
505
567
|
sweepers.each do |sweeper|
|
506
|
-
|
507
|
-
|
568
|
+
ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
|
508
569
|
sweeper_instance = Object.const_get(Inflector.classify(sweeper)).instance
|
509
570
|
|
510
571
|
if sweeper_instance.is_a?(Sweeper)
|
@@ -523,7 +584,7 @@ module ActionController #:nodoc:
|
|
523
584
|
|
524
585
|
# ActiveRecord::Observer will mark this class as reloadable even though it should not be.
|
525
586
|
# However, subclasses of ActionController::Caching::Sweeper should be Reloadable
|
526
|
-
include Reloadable::
|
587
|
+
include Reloadable::Deprecated
|
527
588
|
|
528
589
|
def before(controller)
|
529
590
|
self.controller = controller
|
@@ -27,13 +27,6 @@ class CGI #:nodoc:
|
|
27
27
|
def request_parameters
|
28
28
|
CGIMethods.parse_request_parameters(params, env_table)
|
29
29
|
end
|
30
|
-
|
31
|
-
def redirect(where)
|
32
|
-
header({
|
33
|
-
"Status" => "302 Moved",
|
34
|
-
"location" => "#{where}"
|
35
|
-
})
|
36
|
-
end
|
37
30
|
|
38
31
|
def session(parameters = nil)
|
39
32
|
parameters = {} if parameters.nil?
|
@@ -1,217 +1,211 @@
|
|
1
1
|
require 'cgi'
|
2
|
-
require 'action_controller/vendor/xml_simple'
|
3
2
|
require 'action_controller/vendor/xml_node'
|
3
|
+
require 'strscan'
|
4
4
|
|
5
5
|
# Static methods for parsing the query and request parameters that can be used in
|
6
6
|
# a CGI extension class or testing in isolation.
|
7
7
|
class CGIMethods #:nodoc:
|
8
|
-
|
9
|
-
#
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
k = CGI.unescape(k) if k
|
22
|
-
v = CGI.unescape(v) if v
|
23
|
-
|
24
|
-
unless k.include?(?[)
|
25
|
-
parsed_params[k] = v
|
26
|
-
else
|
27
|
-
keys = split_key(k)
|
28
|
-
last_key = keys.pop
|
29
|
-
last_key = keys.pop if (use_array = last_key.empty?)
|
30
|
-
parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
|
31
|
-
|
32
|
-
if use_array then (parent[last_key] ||= []) << v
|
33
|
-
else parent[last_key] = v
|
34
|
-
end
|
35
|
-
end
|
36
|
-
}
|
37
|
-
|
38
|
-
parsed_params
|
8
|
+
class << self
|
9
|
+
# DEPRECATED: Use parse_form_encoded_parameters
|
10
|
+
def parse_query_parameters(query_string)
|
11
|
+
pairs = query_string.split('&').collect do |chunk|
|
12
|
+
next if chunk.empty?
|
13
|
+
key, value = chunk.split('=', 2)
|
14
|
+
next if key.empty?
|
15
|
+
value = (value.nil? || value.empty?) ? nil : CGI.unescape(value)
|
16
|
+
[ CGI.unescape(key), value ]
|
17
|
+
end.compact
|
18
|
+
|
19
|
+
FormEncodedPairParser.new(pairs).result
|
39
20
|
end
|
40
21
|
|
41
|
-
#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
22
|
+
# DEPRECATED: Use parse_form_encoded_parameters
|
23
|
+
def parse_request_parameters(params)
|
24
|
+
parser = FormEncodedPairParser.new
|
25
|
+
|
26
|
+
params = params.dup
|
27
|
+
until params.empty?
|
28
|
+
for key, value in params
|
29
|
+
if key.blank?
|
30
|
+
params.delete key
|
31
|
+
elsif !key.include?('[')
|
32
|
+
# much faster to test for the most common case first (GET)
|
33
|
+
# and avoid the call to build_deep_hash
|
34
|
+
parser.result[key] = get_typed_value(value[0])
|
35
|
+
params.delete key
|
36
|
+
elsif value.is_a?(Array)
|
37
|
+
parser.parse(key, get_typed_value(value.shift))
|
38
|
+
params.delete key if value.empty?
|
39
|
+
else
|
40
|
+
raise TypeError, "Expected array, found #{value.inspect}"
|
41
|
+
end
|
55
42
|
end
|
56
43
|
end
|
57
44
|
|
58
|
-
|
45
|
+
parser.result
|
59
46
|
end
|
60
47
|
|
61
|
-
def
|
62
|
-
|
48
|
+
def parse_formatted_request_parameters(mime_type, raw_post_data)
|
49
|
+
case strategy = ActionController::Base.param_parsers[mime_type]
|
63
50
|
when Proc
|
64
51
|
strategy.call(raw_post_data)
|
65
52
|
when :xml_simple
|
66
|
-
raw_post_data.blank? ?
|
67
|
-
typecast_xml_value(XmlSimple.xml_in(raw_post_data,
|
68
|
-
'forcearray' => false,
|
69
|
-
'forcecontent' => true,
|
70
|
-
'keeproot' => true,
|
71
|
-
'contentkey' => '__content__'))
|
53
|
+
raw_post_data.blank? ? {} : Hash.from_xml(raw_post_data)
|
72
54
|
when :yaml
|
73
55
|
YAML.load(raw_post_data)
|
74
56
|
when :xml_node
|
75
57
|
node = XmlNode.from_xml(raw_post_data)
|
76
58
|
{ node.node_name => node }
|
77
59
|
end
|
78
|
-
|
79
|
-
dasherize_keys(params || {})
|
80
|
-
rescue Object => e
|
60
|
+
rescue Exception => e # YAML, XML or Ruby code block errors
|
81
61
|
{ "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace,
|
82
62
|
"raw_post_data" => raw_post_data, "format" => mime_type }
|
83
63
|
end
|
84
64
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
when
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
65
|
+
private
|
66
|
+
def get_typed_value(value)
|
67
|
+
case value
|
68
|
+
when String
|
69
|
+
value
|
70
|
+
when NilClass
|
71
|
+
''
|
72
|
+
when Array
|
73
|
+
value.map { |v| get_typed_value(v) }
|
74
|
+
else
|
75
|
+
# Uploaded file provides content type and filename.
|
76
|
+
if value.respond_to?(:content_type) &&
|
77
|
+
!value.content_type.blank? &&
|
78
|
+
!value.original_filename.blank?
|
79
|
+
unless value.respond_to?(:full_original_filename)
|
80
|
+
class << value
|
81
|
+
alias_method :full_original_filename, :original_filename
|
82
|
+
|
83
|
+
# Take the basename of the upload's original filename.
|
84
|
+
# This handles the full Windows paths given by Internet Explorer
|
85
|
+
# (and perhaps other broken user agents) without affecting
|
86
|
+
# those which give the lone filename.
|
87
|
+
# The Windows regexp is adapted from Perl's File::Basename.
|
88
|
+
def original_filename
|
89
|
+
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
|
90
|
+
md.captures.first
|
91
|
+
else
|
92
|
+
File.basename full_original_filename
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Return the same value after overriding original_filename.
|
99
|
+
value
|
100
|
+
|
101
|
+
# Multipart values may have content type, but no filename.
|
102
|
+
elsif value.respond_to?(:read)
|
103
|
+
result = value.read
|
104
|
+
value.rewind
|
105
|
+
result
|
106
|
+
|
107
|
+
# Unknown value, neither string nor multipart.
|
108
|
+
else
|
109
|
+
raise "Unknown form value: #{value.inspect}"
|
110
|
+
end
|
109
111
|
end
|
110
|
-
else
|
111
|
-
raise "can't typecast #{value.inspect}"
|
112
112
|
end
|
113
|
-
|
113
|
+
end
|
114
114
|
|
115
|
-
|
115
|
+
class FormEncodedPairParser < StringScanner
|
116
|
+
attr_reader :top, :parent, :result
|
116
117
|
|
117
|
-
def
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
gsub(/'/, "'").
|
122
|
-
gsub(/&/, "&")
|
118
|
+
def initialize(pairs = [])
|
119
|
+
super('')
|
120
|
+
@result = {}
|
121
|
+
pairs.each { |key, value| parse(key, value) }
|
123
122
|
end
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
123
|
+
|
124
|
+
KEY_REGEXP = %r{([^\[\]=&]+)}
|
125
|
+
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
|
126
|
+
|
127
|
+
# Parse the query string
|
128
|
+
def parse(key, value)
|
129
|
+
self.string = key
|
130
|
+
@top, @parent = result, nil
|
131
|
+
|
132
|
+
# First scan the bare key
|
133
|
+
key = scan(KEY_REGEXP) or return
|
134
|
+
key = post_key_check(key)
|
135
|
+
|
136
|
+
# Then scan as many nestings as present
|
137
|
+
until eos?
|
138
|
+
r = scan(BRACKETED_KEY_REGEXP) or return
|
139
|
+
key = self[1]
|
140
|
+
key = post_key_check(key)
|
136
141
|
end
|
142
|
+
|
143
|
+
bind(key, value)
|
137
144
|
end
|
138
145
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
146
|
+
private
|
147
|
+
# After we see a key, we must look ahead to determine our next action. Cases:
|
148
|
+
#
|
149
|
+
# [] follows the key. Then the value must be an array.
|
150
|
+
# = follows the key. (A value comes next)
|
151
|
+
# & or the end of string follows the key. Then the key is a flag.
|
152
|
+
# otherwise, a hash follows the key.
|
153
|
+
def post_key_check(key)
|
154
|
+
if scan(/\[\]/) # a[b][] indicates that b is an array
|
155
|
+
container(key, Array)
|
156
|
+
nil
|
157
|
+
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
|
158
|
+
container(key, Hash)
|
159
|
+
nil
|
160
|
+
else # End of key? We do nothing.
|
161
|
+
key
|
162
|
+
end
|
152
163
|
end
|
153
|
-
end
|
154
164
|
|
155
|
-
|
156
|
-
#
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
165
|
+
# Add a container to the stack.
|
166
|
+
#
|
167
|
+
def container(key, klass)
|
168
|
+
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
|
169
|
+
value = bind(key, klass.new)
|
170
|
+
type_conflict! klass, value unless value.is_a?(klass)
|
171
|
+
push(value)
|
172
|
+
end
|
173
|
+
|
174
|
+
# Push a value onto the 'stack', which is actually only the top 2 items.
|
175
|
+
def push(value)
|
176
|
+
@parent, @top = @top, value
|
177
|
+
end
|
178
|
+
|
179
|
+
# Bind a key (which may be nil for items in an array) to the provided value.
|
180
|
+
def bind(key, value)
|
181
|
+
if top.is_a? Array
|
182
|
+
if key
|
183
|
+
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
|
184
|
+
top[-1][key] = value
|
185
|
+
else
|
186
|
+
top << {key => value}.with_indifferent_access
|
187
|
+
push top.last
|
176
188
|
end
|
189
|
+
else
|
190
|
+
top << value
|
177
191
|
end
|
192
|
+
elsif top.is_a? Hash
|
193
|
+
key = CGI.unescape(key)
|
194
|
+
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
|
195
|
+
return top[key] ||= value
|
196
|
+
else
|
197
|
+
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
|
178
198
|
end
|
179
199
|
|
180
|
-
|
181
|
-
value
|
182
|
-
|
183
|
-
elsif value.respond_to?(:read)
|
184
|
-
# Value as part of a multipart request
|
185
|
-
value.read
|
186
|
-
elsif value.class == Array
|
187
|
-
value.collect { |v| CGIMethods.get_typed_value(v) }
|
188
|
-
else
|
189
|
-
# other value (neither string nor a multipart request)
|
190
|
-
value.to_s
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
|
195
|
-
def CGIMethods.get_levels(key)
|
196
|
-
all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
|
197
|
-
if main.nil?
|
198
|
-
[]
|
199
|
-
elsif trailing
|
200
|
-
[key]
|
201
|
-
elsif bracketed
|
202
|
-
[main] + bracketed.slice(1...-1).split('][')
|
203
|
-
else
|
204
|
-
[main]
|
200
|
+
return value
|
205
201
|
end
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
{ levels.first => CGIMethods.build_deep_hash(value, nil, levels[1..-1]) }
|
213
|
-
else
|
214
|
-
hash.update({ levels.first => CGIMethods.build_deep_hash(value, hash[levels.first], levels[1..-1]) })
|
202
|
+
|
203
|
+
def type_conflict!(klass, value)
|
204
|
+
raise TypeError,
|
205
|
+
"Conflicting types for parameter containers. " +
|
206
|
+
"Expected an instance of #{klass}, but found an instance of #{value.class}. " +
|
207
|
+
"This can be caused by passing Array and Hash based paramters qs[]=value&qs[key]=value. "
|
215
208
|
end
|
209
|
+
|
216
210
|
end
|
217
211
|
end
|