actionpack 1.9.1 → 1.10.1

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 (123) hide show
  1. data/CHANGELOG +237 -0
  2. data/README +12 -12
  3. data/lib/action_controller.rb +17 -12
  4. data/lib/action_controller/assertions.rb +119 -67
  5. data/lib/action_controller/base.rb +184 -102
  6. data/lib/action_controller/benchmarking.rb +35 -6
  7. data/lib/action_controller/caching.rb +115 -58
  8. data/lib/action_controller/cgi_ext/cgi_methods.rb +54 -21
  9. data/lib/action_controller/cgi_ext/cookie_performance_fix.rb +39 -35
  10. data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +34 -21
  11. data/lib/action_controller/cgi_process.rb +23 -20
  12. data/lib/action_controller/components.rb +11 -2
  13. data/lib/action_controller/dependencies.rb +0 -5
  14. data/lib/action_controller/deprecated_redirects.rb +17 -0
  15. data/lib/action_controller/filters.rb +13 -9
  16. data/lib/action_controller/flash.rb +7 -7
  17. data/lib/action_controller/helpers.rb +1 -14
  18. data/lib/action_controller/layout.rb +40 -29
  19. data/lib/action_controller/macros/auto_complete.rb +52 -0
  20. data/lib/action_controller/macros/in_place_editing.rb +32 -0
  21. data/lib/action_controller/pagination.rb +44 -28
  22. data/lib/action_controller/request.rb +54 -40
  23. data/lib/action_controller/rescue.rb +8 -6
  24. data/lib/action_controller/routing.rb +77 -28
  25. data/lib/action_controller/scaffolding.rb +10 -14
  26. data/lib/action_controller/session/active_record_store.rb +36 -7
  27. data/lib/action_controller/session_management.rb +126 -0
  28. data/lib/action_controller/streaming.rb +14 -5
  29. data/lib/action_controller/templates/rescues/_request_and_response.rhtml +1 -1
  30. data/lib/action_controller/templates/rescues/_trace.rhtml +24 -0
  31. data/lib/action_controller/templates/rescues/diagnostics.rhtml +2 -13
  32. data/lib/action_controller/templates/rescues/template_error.rhtml +4 -2
  33. data/lib/action_controller/templates/scaffolds/list.rhtml +1 -1
  34. data/lib/action_controller/test_process.rb +35 -17
  35. data/lib/action_controller/upload_progress.rb +52 -0
  36. data/lib/action_controller/url_rewriter.rb +21 -16
  37. data/lib/action_controller/vendor/html-scanner/html/document.rb +2 -2
  38. data/lib/action_controller/vendor/html-scanner/html/node.rb +30 -3
  39. data/lib/action_pack/version.rb +9 -0
  40. data/lib/action_view.rb +1 -1
  41. data/lib/action_view/base.rb +204 -60
  42. data/lib/action_view/compiled_templates.rb +70 -0
  43. data/lib/action_view/helpers/active_record_helper.rb +7 -3
  44. data/lib/action_view/helpers/asset_tag_helper.rb +22 -12
  45. data/lib/action_view/helpers/capture_helper.rb +2 -10
  46. data/lib/action_view/helpers/date_helper.rb +21 -13
  47. data/lib/action_view/helpers/form_helper.rb +14 -10
  48. data/lib/action_view/helpers/form_options_helper.rb +4 -4
  49. data/lib/action_view/helpers/form_tag_helper.rb +59 -25
  50. data/lib/action_view/helpers/java_script_macros_helper.rb +188 -0
  51. data/lib/action_view/helpers/javascript_helper.rb +68 -133
  52. data/lib/action_view/helpers/javascripts/controls.js +427 -165
  53. data/lib/action_view/helpers/javascripts/dragdrop.js +256 -277
  54. data/lib/action_view/helpers/javascripts/effects.js +766 -277
  55. data/lib/action_view/helpers/javascripts/prototype.js +906 -218
  56. data/lib/action_view/helpers/javascripts/slider.js +258 -0
  57. data/lib/action_view/helpers/number_helper.rb +4 -3
  58. data/lib/action_view/helpers/pagination_helper.rb +42 -27
  59. data/lib/action_view/helpers/tag_helper.rb +25 -11
  60. data/lib/action_view/helpers/text_helper.rb +119 -13
  61. data/lib/action_view/helpers/upload_progress_helper.rb +2 -2
  62. data/lib/action_view/helpers/url_helper.rb +68 -21
  63. data/lib/action_view/partials.rb +17 -6
  64. data/lib/action_view/template_error.rb +19 -24
  65. data/rakefile +4 -3
  66. data/test/abstract_unit.rb +2 -1
  67. data/test/controller/action_pack_assertions_test.rb +62 -2
  68. data/test/controller/active_record_assertions_test.rb +5 -6
  69. data/test/controller/active_record_store_test.rb +23 -1
  70. data/test/controller/addresses_render_test.rb +4 -0
  71. data/test/controller/{base_tests.rb → base_test.rb} +4 -3
  72. data/test/controller/benchmark_test.rb +36 -0
  73. data/test/controller/caching_filestore.rb +22 -40
  74. data/test/controller/capture_test.rb +10 -1
  75. data/test/controller/cgi_test.rb +145 -23
  76. data/test/controller/components_test.rb +50 -0
  77. data/test/controller/custom_handler_test.rb +3 -3
  78. data/test/controller/fake_controllers.rb +24 -0
  79. data/test/controller/filters_test.rb +6 -6
  80. data/test/controller/flash_test.rb +6 -6
  81. data/test/controller/fragment_store_setting_test.rb +45 -0
  82. data/test/controller/helper_test.rb +1 -3
  83. data/test/controller/new_render_test.rb +119 -7
  84. data/test/controller/redirect_test.rb +11 -1
  85. data/test/controller/render_test.rb +34 -1
  86. data/test/controller/request_test.rb +14 -5
  87. data/test/controller/routing_test.rb +238 -42
  88. data/test/controller/send_file_test.rb +11 -10
  89. data/test/controller/session_management_test.rb +94 -0
  90. data/test/controller/test_test.rb +194 -5
  91. data/test/controller/url_rewriter_test.rb +46 -0
  92. data/test/fixtures/layouts/talk_from_action.rhtml +2 -0
  93. data/test/fixtures/layouts/yield.rhtml +2 -0
  94. data/test/fixtures/multipart/binary_file +0 -0
  95. data/test/fixtures/multipart/large_text_file +10 -0
  96. data/test/fixtures/multipart/mixed_files +0 -0
  97. data/test/fixtures/multipart/single_parameter +5 -0
  98. data/test/fixtures/multipart/text_file +10 -0
  99. data/test/fixtures/test/_customer_greeting.rhtml +1 -0
  100. data/test/fixtures/test/_hash_object.rhtml +1 -0
  101. data/test/fixtures/test/_person.rhtml +2 -0
  102. data/test/fixtures/test/action_talk_to_layout.rhtml +2 -0
  103. data/test/fixtures/test/content_for.rhtml +2 -0
  104. data/test/fixtures/test/potential_conflicts.rhtml +4 -0
  105. data/test/template/active_record_helper_test.rb +15 -8
  106. data/test/template/asset_tag_helper_test.rb +40 -16
  107. data/test/template/compiled_templates_tests.rb +63 -0
  108. data/test/template/date_helper_test.rb +80 -4
  109. data/test/template/form_helper_test.rb +48 -42
  110. data/test/template/form_options_helper_test.rb +40 -40
  111. data/test/template/form_tag_helper_test.rb +21 -15
  112. data/test/template/java_script_macros_helper_test.rb +56 -0
  113. data/test/template/javascript_helper_test.rb +70 -47
  114. data/test/template/number_helper_test.rb +2 -0
  115. data/test/template/tag_helper_test.rb +9 -0
  116. data/test/template/text_helper_test.rb +146 -1
  117. data/test/template/upload_progress_helper_testx.rb +11 -147
  118. data/test/template/url_helper_test.rb +90 -22
  119. data/test/testing_sandbox.rb +26 -0
  120. metadata +37 -7
  121. data/lib/action_controller/auto_complete.rb +0 -47
  122. data/lib/action_controller/deprecated_renders_and_redirects.rb +0 -76
  123. data/lib/action_controller/session.rb +0 -14
@@ -6,17 +6,45 @@ module ActionController #:nodoc:
6
6
  module Benchmarking #:nodoc:
7
7
  def self.append_features(base)
8
8
  super
9
- base.class_eval {
9
+ base.extend(ClassMethods)
10
+ base.class_eval do
10
11
  alias_method :perform_action_without_benchmark, :perform_action
11
12
  alias_method :perform_action, :perform_action_with_benchmark
12
13
 
13
14
  alias_method :render_without_benchmark, :render
14
15
  alias_method :render, :render_with_benchmark
15
- }
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it
21
+ # (unless <tt>use_silence</tt> is set to false).
22
+ #
23
+ # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
24
+ # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
25
+ # will only be conducted if the log level is low enough.
26
+ def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
27
+ if logger && logger.level == log_level
28
+ result = nil
29
+ seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
30
+ logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
31
+ result
32
+ else
33
+ yield
34
+ end
35
+ end
36
+
37
+ # Silences the logger for the duration of the block.
38
+ def silence
39
+ old_logger_level, logger.level = logger.level, Logger::ERROR if logger
40
+ yield
41
+ ensure
42
+ logger.level = old_logger_level if logger
43
+ end
16
44
  end
17
45
 
18
- def render_with_benchmark(options = {}, deprecated_status = nil)
19
- if logger.nil?
46
+ def render_with_benchmark(options = nil, deprecated_status = nil)
47
+ unless logger
20
48
  render_without_benchmark(options, deprecated_status)
21
49
  else
22
50
  db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
@@ -35,14 +63,15 @@ module ActionController #:nodoc:
35
63
  end
36
64
 
37
65
  def perform_action_with_benchmark
38
- if logger.nil?
66
+ unless logger
39
67
  perform_action_without_benchmark
40
68
  else
41
69
  runtime = [Benchmark::measure{ perform_action_without_benchmark }.real, 0.0001].max
42
70
  log_message = "Completed in #{sprintf("%.5f", runtime)} (#{(1 / runtime).floor} reqs/sec)"
43
71
  log_message << rendering_runtime(runtime) if @rendering_runtime
44
72
  log_message << active_record_runtime(runtime) if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
45
- log_message << " [#{complete_request_uri}]"
73
+ log_message << " | #{headers["Status"]}"
74
+ log_message << " [#{complete_request_uri rescue "unknown"}]"
46
75
  logger.info(log_message)
47
76
  end
48
77
  end
@@ -57,8 +57,7 @@ module ActionController #:nodoc:
57
57
  # By default, the cache extension is .html, which makes it easy for the cached files to be picked up by the web server. If you want
58
58
  # something else, like .php or .shtml, just set Base.page_cache_extension.
59
59
  module Pages
60
- def self.append_features(base) #:nodoc:
61
- super
60
+ def self.included(base) #:nodoc:
62
61
  base.extend(ClassMethods)
63
62
  base.class_eval do
64
63
  @@page_cache_directory = defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/public" : ""
@@ -74,17 +73,21 @@ module ActionController #:nodoc:
74
73
  # expire_page "/lists/show"
75
74
  def expire_page(path)
76
75
  return unless perform_caching
77
- File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
78
- logger.info "Expired page: #{page_cache_file(path)}" unless logger.nil?
76
+
77
+ benchmark "Expired page: #{page_cache_file(path)}" do
78
+ File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
79
+ end
79
80
  end
80
81
 
81
82
  # Manually cache the +content+ in the key determined by +path+. Example:
82
83
  # cache_page "I'm the cached content", "/lists/show"
83
84
  def cache_page(content, path)
84
85
  return unless perform_caching
85
- FileUtils.makedirs(File.dirname(page_cache_path(path)))
86
- File.open(page_cache_path(path), "w+") { |f| f.write(content) }
87
- logger.info "Cached page: #{page_cache_file(path)}" unless logger.nil?
86
+
87
+ benchmark "Cached page: #{page_cache_file(path)}" do
88
+ FileUtils.makedirs(File.dirname(page_cache_path(path)))
89
+ File.open(page_cache_path(path), "wb+") { |f| f.write(content) }
90
+ end
88
91
  end
89
92
 
90
93
  # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
@@ -98,7 +101,7 @@ module ActionController #:nodoc:
98
101
 
99
102
  private
100
103
  def page_cache_file(path)
101
- name = ((path.empty? || path == "/") ? "/index" : path)
104
+ name = ((path.empty? || path == "/") ? "/index" : URI.unescape(path))
102
105
  name << page_cache_extension unless (name.split('/').last || name).include? '.'
103
106
  return name
104
107
  end
@@ -232,27 +235,32 @@ module ActionController #:nodoc:
232
235
  # up a lot of memory since each process keeps all the caches in memory.
233
236
  # * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache
234
237
  # around for all processes, but requires that you run and manage a separate DRb process.
235
- # * MemCachedStore: Works like DRbStore, but uses Danga's MemCached instead.
238
+ # * MemCacheStore: Works like DRbStore, but uses Danga's MemCache instead.
236
239
  #
237
240
  # Configuration examples (MemoryStore is the default):
238
241
  #
239
- # ActionController::Base.fragment_cache_store =
240
- # ActionController::Caching::Fragments::MemoryStore.new
241
- #
242
- # ActionController::Base.fragment_cache_store =
243
- # ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory")
244
- #
245
- # ActionController::Base.fragment_cache_store =
246
- # ActionController::Caching::Fragments::DRbStore.new("druby://localhost:9192")
247
- #
248
- # ActionController::Base.fragment_cache_store =
249
- # ActionController::Caching::Fragments::FileStore.new("localhost")
242
+ # ActionController::Base.fragment_cache_store = :memory_store
243
+ # ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
244
+ # ActionController::Base.fragment_cache_store = :drb_store, "druby://localhost:9192"
245
+ # ActionController::Base.fragment_cache_store = :mem_cache_store, "localhost"
246
+ # ActionController::Base.fragment_cache_store = MyOwnStore.new("parameter")
250
247
  module Fragments
251
248
  def self.append_features(base) #:nodoc:
252
249
  super
253
250
  base.class_eval do
254
251
  @@fragment_cache_store = MemoryStore.new
255
- cattr_accessor :fragment_cache_store
252
+ cattr_reader :fragment_cache_store
253
+
254
+ def self.fragment_cache_store=(store_option)
255
+ store, *parameters = *([ store_option ].flatten)
256
+ @@fragment_cache_store = if store.is_a?(Symbol)
257
+ store_class_name = (store == :drb_store ? "DRbStore" : store.to_s.camelize)
258
+ store_class = ActionController::Caching::Fragments.const_get(store_class_name)
259
+ parameters.empty? ? store.new : store_class.new(*parameters)
260
+ else
261
+ store
262
+ end
263
+ end
256
264
  end
257
265
  end
258
266
 
@@ -261,7 +269,7 @@ module ActionController #:nodoc:
261
269
  end
262
270
 
263
271
  # Called by CacheHelper#cache
264
- def cache_erb_fragment(block, name = {}, options = {})
272
+ def cache_erb_fragment(block, name = {}, options = nil)
265
273
  unless perform_caching then block.call; return end
266
274
 
267
275
  buffer = eval("_erbout", block.binding)
@@ -275,24 +283,22 @@ module ActionController #:nodoc:
275
283
  end
276
284
  end
277
285
 
278
- def write_fragment(name, content, options = {})
286
+ def write_fragment(name, content, options = nil)
279
287
  return unless perform_caching
280
288
 
281
289
  key = fragment_cache_key(name)
282
- fragment_cache_store.write(key, content, options)
283
- logger.info "Cached fragment: #{key}" unless logger.nil?
290
+ self.class.benchmark "Cached fragment: #{key}" do
291
+ fragment_cache_store.write(key, content, options)
292
+ end
284
293
  content
285
294
  end
286
295
 
287
- def read_fragment(name, options = {})
296
+ def read_fragment(name, options = nil)
288
297
  return unless perform_caching
289
298
 
290
299
  key = fragment_cache_key(name)
291
- if cache = fragment_cache_store.read(key, options)
292
- logger.info "Fragment hit: #{key}" unless logger.nil?
293
- cache
294
- else
295
- false
300
+ self.class.benchmark "Fragment read: #{key}" do
301
+ fragment_cache_store.read(key, options)
296
302
  end
297
303
  end
298
304
 
@@ -300,79 +306,120 @@ module ActionController #:nodoc:
300
306
  # * String: This would normally take the form of a path like "pages/45/notes"
301
307
  # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
302
308
  # * Regexp: Will destroy all the matched fragments, example: %r{pages/\d*/notes}
303
- def expire_fragment(name, options = {})
309
+ def expire_fragment(name, options = nil)
304
310
  return unless perform_caching
305
311
 
306
312
  key = fragment_cache_key(name)
307
313
 
308
314
  if key.is_a?(Regexp)
309
- fragment_cache_store.delete_matched(key, options)
310
- logger.info "Expired fragments matching: #{key.source}" unless logger.nil?
315
+ self.class.benchmark "Expired fragments matching: #{key.source}" do
316
+ fragment_cache_store.delete_matched(key, options)
317
+ end
311
318
  else
312
- fragment_cache_store.delete(key, options)
313
- logger.info "Expired fragment: #{key}" unless logger.nil?
319
+ self.class.benchmark "Expired fragment: #{key}" do
320
+ fragment_cache_store.delete(key, options)
321
+ end
314
322
  end
315
323
  end
316
324
 
317
325
  # Deprecated -- just call expire_fragment with a regular expression
318
- def expire_matched_fragments(matcher = /.*/, options = {}) #:nodoc:
326
+ def expire_matched_fragments(matcher = /.*/, options = nil) #:nodoc:
319
327
  expire_fragment(matcher, options)
320
328
  end
321
329
 
322
- class MemoryStore #:nodoc:
323
- def initialize
324
- @data, @mutex = { }, Mutex.new
330
+
331
+ class UnthreadedMemoryStore #:nodoc:
332
+ def initialize #:nodoc:
333
+ @data = {}
325
334
  end
326
335
 
327
- def read(name, options = {}) #:nodoc:
328
- @mutex.synchronize { @data[name] } rescue nil
336
+ def read(name, options=nil) #:nodoc:
337
+ @data[name]
329
338
  end
330
339
 
331
- def write(name, value, options = {}) #:nodoc:
332
- @mutex.synchronize { @data[name] = value }
340
+ def write(name, value, options=nil) #:nodoc:
341
+ @data[name] = value
333
342
  end
334
343
 
335
- def delete(name, options = {}) #:nodoc:
336
- @mutex.synchronize { @data.delete(name) }
344
+ def delete(name, options=nil) #:nodoc:
345
+ @data.delete(name)
337
346
  end
338
347
 
339
- def delete_matched(matcher, options) #:nodoc:
340
- @mutex.synchronize { @data.delete_if { |k,v| k =~ matcher } }
348
+ def delete_matched(matcher, options=nil) #:nodoc:
349
+ @data.delete_if { |k,v| k =~ matcher }
350
+ end
351
+ end
352
+
353
+ module ThreadSafety #:nodoc:
354
+ def read(name, options=nil) #:nodoc:
355
+ @mutex.synchronize { super }
356
+ end
357
+
358
+ def write(name, value, options=nil) #:nodoc:
359
+ @mutex.synchronize { super }
360
+ end
361
+
362
+ def delete(name, options=nil) #:nodoc:
363
+ @mutex.synchronize { super }
364
+ end
365
+
366
+ def delete_matched(matcher, options=nil) #:nodoc:
367
+ @mutex.synchronize { super }
368
+ end
369
+ end
370
+
371
+ class MemoryStore < UnthreadedMemoryStore #:nodoc:
372
+ def initialize #:nodoc:
373
+ super
374
+ if ActionController::Base.allow_concurrency
375
+ @mutex = Mutex.new
376
+ MemoryStore.send(:include, ThreadSafety)
377
+ end
341
378
  end
342
379
  end
343
380
 
344
381
  class DRbStore < MemoryStore #:nodoc:
382
+ attr_reader :address
383
+
345
384
  def initialize(address = 'druby://localhost:9192')
346
- @data, @mutex = DRbObject.new(nil, address), Mutex.new
385
+ super()
386
+ @address = address
387
+ @data = DRbObject.new(nil, address)
347
388
  end
348
389
  end
349
390
 
350
391
  class MemCacheStore < MemoryStore #:nodoc:
392
+ attr_reader :address
393
+
351
394
  def initialize(address = 'localhost')
352
- @data, @mutex = MemCache.new(address), Mutex.new
395
+ super()
396
+ @address = address
397
+ @data = MemCache.new(address)
353
398
  end
354
399
  end
355
400
 
356
- class FileStore #:nodoc:
401
+ class UnthreadedFileStore #:nodoc:
402
+ attr_reader :cache_path
403
+
357
404
  def initialize(cache_path)
358
405
  @cache_path = cache_path
359
406
  end
360
407
 
361
- def write(name, value, options = {}) #:nodoc:
408
+ def write(name, value, options = nil) #:nodoc:
362
409
  ensure_cache_path(File.dirname(real_file_path(name)))
363
- File.open(real_file_path(name), "w+") { |f| f.write(value) }
410
+ File.open(real_file_path(name), "wb+") { |f| f.write(value) }
364
411
  rescue => e
365
- Base.logger.info "Couldn't create cache directory: #{name} (#{e.message})" unless Base.logger.nil?
412
+ Base.logger.error "Couldn't create cache directory: #{name} (#{e.message})" if Base.logger
366
413
  end
367
414
 
368
- def read(name, options = {}) #:nodoc:
415
+ def read(name, options = nil) #:nodoc:
369
416
  IO.read(real_file_path(name)) rescue nil
370
417
  end
371
418
 
372
419
  def delete(name, options) #:nodoc:
373
420
  File.delete(real_file_path(name))
374
421
  rescue SystemCallError => e
375
- Base.logger.info "Couldn't expire cache #{name} (#{e.message})" unless Base.logger.nil?
422
+ # If there's no cache, then there's nothing to complain about
376
423
  end
377
424
 
378
425
  def delete_matched(matcher, options) #:nodoc:
@@ -381,7 +428,7 @@ module ActionController #:nodoc:
381
428
  begin
382
429
  File.delete(f)
383
430
  rescue Object => e
384
- Base.logger.info "Couldn't expire cache: #{f} (#{e.message})" unless Base.logger.nil?
431
+ # If there's no cache, then there's nothing to complain about
385
432
  end
386
433
  end
387
434
  end
@@ -407,7 +454,17 @@ module ActionController #:nodoc:
407
454
  end
408
455
  end
409
456
  end
410
- end
457
+ end
458
+
459
+ class FileStore < UnthreadedFileStore #:nodoc:
460
+ def initialize(cache_path)
461
+ super(cache_path)
462
+ if ActionController::Base.allow_concurrency
463
+ @mutex = Mutex.new
464
+ FileStore.send(:include, ThreadSafety)
465
+ end
466
+ end
467
+ end
411
468
  end
412
469
 
413
470
  # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
@@ -11,23 +11,30 @@ class CGIMethods #:nodoc:
11
11
  parsed_params = {}
12
12
 
13
13
  query_string.split(/[&;]/).each { |p|
14
+ # Ignore repeated delimiters.
15
+ next if p.empty?
16
+
14
17
  k, v = p.split('=',2)
15
- v = nil if (!v.nil? && v.empty?)
18
+ v = nil if (v && v.empty?)
16
19
 
17
- k = CGI.unescape(k) unless k.nil?
18
- v = CGI.unescape(v) unless v.nil?
20
+ k = CGI.unescape(k) if k
21
+ v = CGI.unescape(v) if v
19
22
 
20
- keys = split_key(k)
21
- last_key = keys.pop
22
- last_key = keys.pop if (use_array = last_key.empty?)
23
- parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
24
-
25
- if use_array then (parent[last_key] ||= []) << v
26
- else parent[last_key] = v
23
+ unless k.include?(?[)
24
+ parsed_params[k] = v
25
+ else
26
+ keys = split_key(k)
27
+ last_key = keys.pop
28
+ last_key = keys.pop if (use_array = last_key.empty?)
29
+ parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
30
+
31
+ if use_array then (parent[last_key] ||= []) << v
32
+ else parent[last_key] = v
33
+ end
27
34
  end
28
35
  }
29
36
 
30
- return parsed_params
37
+ parsed_params
31
38
  end
32
39
 
33
40
  # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
@@ -38,14 +45,16 @@ class CGIMethods #:nodoc:
38
45
 
39
46
  for key, value in params
40
47
  value = [value] if key =~ /.*\[\]$/
41
- CGIMethods.build_deep_hash(
42
- CGIMethods.get_typed_value(value[0]),
43
- parsed_params,
44
- CGIMethods.get_levels(key)
45
- )
48
+ unless key.include?('[')
49
+ # much faster to test for the most common case first (GET)
50
+ # and avoid the call to build_deep_hash
51
+ parsed_params[key] = get_typed_value(value[0])
52
+ else
53
+ build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key))
54
+ end
46
55
  end
47
56
 
48
- return parsed_params
57
+ parsed_params
49
58
  end
50
59
 
51
60
  def self.parse_formatted_request_parameters(format, raw_post_data)
@@ -72,23 +81,47 @@ class CGIMethods #:nodoc:
72
81
  keys.concat($2[1..-2].split(']['))
73
82
  keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
74
83
 
75
- return keys
84
+ keys
76
85
  else
77
- return [key]
86
+ [key]
78
87
  end
79
88
  end
80
89
 
81
90
  def CGIMethods.get_typed_value(value)
82
- if value.respond_to?(:content_type) && !value.content_type.empty?
91
+ # test most frequent case first
92
+ if value.is_a?(String)
93
+ value
94
+ elsif value.respond_to?(:content_type) && ! value.content_type.blank?
83
95
  # Uploaded file
96
+ unless value.respond_to?(:full_original_filename)
97
+ class << value
98
+ alias_method :full_original_filename, :original_filename
99
+
100
+ # Take the basename of the upload's original filename.
101
+ # This handles the full Windows paths given by Internet Explorer
102
+ # (and perhaps other broken user agents) without affecting
103
+ # those which give the lone filename.
104
+ # The Windows regexp is adapted from Perl's File::Basename.
105
+ def original_filename
106
+ if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
107
+ md.captures.first
108
+ else
109
+ File.basename full_original_filename
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ # Return the same value after overriding original_filename.
84
116
  value
117
+
85
118
  elsif value.respond_to?(:read)
86
119
  # Value as part of a multipart request
87
120
  value.read
88
121
  elsif value.class == Array
89
122
  value.collect { |v| CGIMethods.get_typed_value(v) }
90
123
  else
91
- # Standard value (not a multipart request)
124
+ # other value (neither string nor a multipart request)
92
125
  value.to_s
93
126
  end
94
127
  end