merb-core 0.9.10 → 0.9.11
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +6 -0
- data/lib/merb-core.rb +11 -4
- data/lib/merb-core/autoload.rb +1 -0
- data/lib/merb-core/bootloader.rb +44 -11
- data/lib/merb-core/config.rb +7 -0
- data/lib/merb-core/controller/abstract_controller.rb +1 -1
- data/lib/merb-core/controller/merb_controller.rb +0 -1
- data/lib/merb-core/controller/mixins/controller.rb +1 -1
- data/lib/merb-core/core_ext/kernel.rb +30 -24
- data/lib/merb-core/dispatch/cookies.rb +2 -2
- data/lib/merb-core/dispatch/request.rb +8 -229
- data/lib/merb-core/dispatch/request_parsers.rb +236 -0
- data/lib/merb-core/dispatch/router/route.rb +1 -1
- data/lib/merb-core/dispatch/session/cookie.rb +2 -2
- data/lib/merb-core/rack.rb +0 -1
- data/lib/merb-core/rack/adapter/abstract.rb +3 -1
- data/lib/merb-core/rack/middleware/static.rb +2 -2
- data/lib/merb-core/server.rb +1 -1
- data/lib/merb-core/tasks/gem_management.rb +2 -1
- data/lib/merb-core/test/helpers.rb +2 -1
- data/lib/merb-core/test/helpers/cookie_jar.rb +106 -0
- data/lib/merb-core/test/helpers/mock_request_helper.rb +2 -2
- data/lib/merb-core/test/helpers/request_helper.rb +48 -32
- data/lib/merb-core/test/matchers/request_matchers.rb +20 -0
- data/lib/merb-core/test/matchers/route_matchers.rb +1 -1
- data/lib/merb-core/test/test_ext/rspec.rb +5 -2
- data/lib/merb-core/version.rb +1 -1
- metadata +4 -3
- data/lib/merb-core/rack/middleware/csrf.rb +0 -73
data/Rakefile
CHANGED
@@ -230,6 +230,12 @@ end
|
|
230
230
|
setup_specs("mri", "spec")
|
231
231
|
setup_specs("jruby", "jruby -S spec")
|
232
232
|
|
233
|
+
task "specs:core_ext" do
|
234
|
+
require "lib/merb-core/test/run_specs"
|
235
|
+
run_specs("spec/public/core_ext/*_spec.rb", "spec", "-c -f o")
|
236
|
+
end
|
237
|
+
|
238
|
+
task "spec" => ["specs:mri"]
|
233
239
|
task "specs" => ["specs:mri"]
|
234
240
|
task "specs:private" => ["specs:mri:private"]
|
235
241
|
task "specs:public" => ["specs:mri:public"]
|
data/lib/merb-core.rb
CHANGED
@@ -110,12 +110,14 @@ module Merb
|
|
110
110
|
#
|
111
111
|
# @api public
|
112
112
|
def start(argv = ARGV)
|
113
|
-
Merb::Config[:
|
113
|
+
Merb::Config[:original_log_stream] = Merb::Config[:log_stream]
|
114
|
+
Merb::Config[:log_stream] ||= STDOUT
|
114
115
|
if Hash === argv
|
115
116
|
Merb::Config.setup(argv)
|
116
|
-
|
117
|
+
elsif !argv.nil?
|
117
118
|
Merb::Config.parse_args(argv)
|
118
119
|
end
|
120
|
+
|
119
121
|
Merb::Config[:log_stream] = STDOUT
|
120
122
|
|
121
123
|
Merb.environment = Merb::Config[:environment]
|
@@ -277,7 +279,7 @@ module Merb
|
|
277
279
|
#
|
278
280
|
# @api public
|
279
281
|
def root=(value)
|
280
|
-
@root =
|
282
|
+
@root = value
|
281
283
|
end
|
282
284
|
|
283
285
|
# ==== Parameters
|
@@ -400,7 +402,12 @@ module Merb
|
|
400
402
|
Merb.logger.fatal!
|
401
403
|
|
402
404
|
print_colorized_backtrace(e) if e && Merb::Config[:verbose]
|
403
|
-
|
405
|
+
|
406
|
+
if Merb::Config[:show_ugly_backtraces]
|
407
|
+
raise e
|
408
|
+
else
|
409
|
+
exit(1)
|
410
|
+
end
|
404
411
|
end
|
405
412
|
|
406
413
|
# Print a colorized backtrace to the merb logger.
|
data/lib/merb-core/autoload.rb
CHANGED
@@ -14,6 +14,7 @@ module Merb
|
|
14
14
|
autoload :Rack, "merb-core/rack"
|
15
15
|
autoload :RenderMixin, "merb-core/controller/mixins/render"
|
16
16
|
autoload :Request, "merb-core/dispatch/request"
|
17
|
+
autoload :Parse, "merb-core/dispatch/request_parsers.rb"
|
17
18
|
autoload :ResponderMixin, "merb-core/controller/mixins/responder"
|
18
19
|
autoload :Router, "merb-core/dispatch/router"
|
19
20
|
autoload :Test, "merb-core/test"
|
data/lib/merb-core/bootloader.rb
CHANGED
@@ -167,10 +167,28 @@ module Merb
|
|
167
167
|
before_load_callbacks << block
|
168
168
|
end
|
169
169
|
|
170
|
+
# Execute a block of code before master process is shut down.
|
171
|
+
# Only makes sense on platforms where Merb server can use forking.
|
172
|
+
#
|
173
|
+
# ==== Parameters
|
174
|
+
# &block::
|
175
|
+
# A block to be added to the callbacks that will be executed
|
176
|
+
# before master process is shut down.
|
177
|
+
#
|
178
|
+
# @api public
|
170
179
|
def before_master_shutdown(&block)
|
171
180
|
before_master_shutdown_callbacks << block
|
172
181
|
end
|
173
182
|
|
183
|
+
# Execute a block of code before worker process is shut down.
|
184
|
+
# Only makes sense on platforms where Merb server can use forking.
|
185
|
+
#
|
186
|
+
# ==== Parameters
|
187
|
+
# &block::
|
188
|
+
# A block to be added to the callbacks that will be executed
|
189
|
+
# before worker process is shut down.
|
190
|
+
#
|
191
|
+
# @api public
|
174
192
|
def before_worker_shutdown(&block)
|
175
193
|
before_worker_shutdown_callbacks << block
|
176
194
|
end
|
@@ -201,7 +219,8 @@ class Merb::BootLoader::Logger < Merb::BootLoader
|
|
201
219
|
end
|
202
220
|
end
|
203
221
|
|
204
|
-
Merb::Config[:log_stream] =
|
222
|
+
Merb::Config[:log_stream] =
|
223
|
+
Merb::Config[:original_log_stream] || Merb.log_stream
|
205
224
|
|
206
225
|
print_warnings
|
207
226
|
|
@@ -366,6 +385,7 @@ class Merb::BootLoader::Dependencies < Merb::BootLoader
|
|
366
385
|
load_initfile
|
367
386
|
load_env_config
|
368
387
|
end
|
388
|
+
expand_ruby_path
|
369
389
|
enable_json_gem unless Merb::disabled?(:json)
|
370
390
|
load_dependencies
|
371
391
|
update_logger
|
@@ -497,6 +517,27 @@ class Merb::BootLoader::Dependencies < Merb::BootLoader
|
|
497
517
|
end
|
498
518
|
nil
|
499
519
|
end
|
520
|
+
|
521
|
+
# Expands Ruby path with framework directories (for models, lib, etc). Only run once.
|
522
|
+
#
|
523
|
+
# ==== Returns
|
524
|
+
# nil
|
525
|
+
#
|
526
|
+
# @api private
|
527
|
+
def self.expand_ruby_path
|
528
|
+
# Add models, controllers, helpers and lib to the load path
|
529
|
+
unless @ran
|
530
|
+
Merb.logger.info "Expanding RUBY_PATH..." if Merb::Config[:verbose]
|
531
|
+
|
532
|
+
$LOAD_PATH.unshift Merb.dir_for(:model)
|
533
|
+
$LOAD_PATH.unshift Merb.dir_for(:controller)
|
534
|
+
$LOAD_PATH.unshift Merb.dir_for(:lib)
|
535
|
+
$LOAD_PATH.unshift Merb.dir_for(:helper)
|
536
|
+
end
|
537
|
+
|
538
|
+
@ran = true
|
539
|
+
nil
|
540
|
+
end
|
500
541
|
end
|
501
542
|
|
502
543
|
class Merb::BootLoader::MixinSession < Merb::BootLoader
|
@@ -564,20 +605,12 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
|
|
564
605
|
#
|
565
606
|
# @api plugin
|
566
607
|
def run
|
567
|
-
# Add models, controllers, helpers and lib to the load path
|
568
|
-
unless @ran
|
569
|
-
$LOAD_PATH.unshift Merb.dir_for(:model)
|
570
|
-
$LOAD_PATH.unshift Merb.dir_for(:controller)
|
571
|
-
$LOAD_PATH.unshift Merb.dir_for(:lib)
|
572
|
-
$LOAD_PATH.unshift Merb.dir_for(:helper)
|
573
|
-
end
|
574
|
-
|
575
|
-
@ran = true
|
576
608
|
# process name you see in ps output
|
577
609
|
$0 = "merb#{" : " + Merb::Config[:name] if Merb::Config[:name]} : master"
|
578
610
|
|
579
611
|
# Log the process configuration user defined signal 1 (SIGUSR1) is received.
|
580
612
|
Merb.trap("USR1") do
|
613
|
+
require "yaml"
|
581
614
|
Merb.logger.fatal! "Configuration:\n#{Merb::Config.to_hash.merge(:pid => $$).to_yaml}\n\n"
|
582
615
|
end
|
583
616
|
|
@@ -797,7 +830,7 @@ class Merb::BootLoader::LoadClasses < Merb::BootLoader
|
|
797
830
|
# Ignore the file for syntax errors. The next time
|
798
831
|
# the file is changed, it'll be reloaded again
|
799
832
|
begin
|
800
|
-
|
833
|
+
require file
|
801
834
|
rescue SyntaxError => e
|
802
835
|
Merb.logger.error "Cannot load #{file} because of syntax error: #{e.message}"
|
803
836
|
ensure
|
data/lib/merb-core/config.rb
CHANGED
@@ -350,6 +350,13 @@ module Merb
|
|
350
350
|
begin
|
351
351
|
require "ruby-debug"
|
352
352
|
Debugger.start
|
353
|
+
|
354
|
+
# Load up any .rdebugrc files we find
|
355
|
+
[".", ENV["HOME"], ENV["HOMEPATH"]].each do |script_dir|
|
356
|
+
script_file = "#{script_dir}/.rdebugrc"
|
357
|
+
Debugger.run_script script_file, StringIO.new if File.exists?(script_file)
|
358
|
+
end
|
359
|
+
|
353
360
|
if Debugger.respond_to?(:settings)
|
354
361
|
Debugger.settings[:autoeval] = true
|
355
362
|
end
|
@@ -661,7 +661,7 @@ class Merb::AbstractController
|
|
661
661
|
# get appended
|
662
662
|
filters << [filter, opts]
|
663
663
|
when Symbol, String
|
664
|
-
if existing_filter = filters.find {|f| f.first.to_s
|
664
|
+
if existing_filter = filters.find {|f| f.first.to_s == filter.to_s}
|
665
665
|
filters[ filters.index(existing_filter) ] = [filter, opts]
|
666
666
|
else
|
667
667
|
filters << [filter, opts]
|
@@ -113,7 +113,6 @@ class Merb::Controller < Merb::AbstractController
|
|
113
113
|
callables.flatten.reject{|action| action =~ /^_.*/}
|
114
114
|
end
|
115
115
|
|
116
|
-
|
117
116
|
# The location to look for a template for a particular controller, context,
|
118
117
|
# and mime-type. This is overridden from AbstractController, which defines a
|
119
118
|
# version of this that does not involve mime-types.
|
@@ -133,7 +133,7 @@ module Merb
|
|
133
133
|
default_redirect_options = { :message => nil, :permanent => false }
|
134
134
|
opts = default_redirect_options.merge(opts)
|
135
135
|
if opts[:message]
|
136
|
-
notice = Merb::
|
136
|
+
notice = Merb::Parse.escape([Marshal.dump(opts[:message])].pack("m"))
|
137
137
|
url = url =~ /\?/ ? "#{url}&_message=#{notice}" : "#{url}?_message=#{notice}"
|
138
138
|
end
|
139
139
|
self.status = opts[:permanent] ? 301 : 302
|
@@ -2,7 +2,7 @@ require 'rubygems/dependency'
|
|
2
2
|
|
3
3
|
module Gem
|
4
4
|
class Dependency
|
5
|
-
attr_accessor :require_block
|
5
|
+
attr_accessor :require_block, :require_as
|
6
6
|
end
|
7
7
|
end
|
8
8
|
|
@@ -18,18 +18,16 @@ module Kernel
|
|
18
18
|
#
|
19
19
|
# @api private
|
20
20
|
def track_dependency(name, *ver, &blk)
|
21
|
-
|
22
|
-
|
21
|
+
options = ver.pop if ver.last.is_a?(Hash)
|
22
|
+
new_dep = Gem::Dependency.new(name, ver.empty? ? nil : ver)
|
23
|
+
new_dep.require_block = blk
|
24
|
+
new_dep.require_as = (options && options[:require_as]) || name
|
23
25
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
else
|
30
|
-
Merb::BootLoader::Dependencies.dependencies << dep
|
31
|
-
end
|
32
|
-
return dep
|
26
|
+
deps = Merb::BootLoader::Dependencies.dependencies
|
27
|
+
|
28
|
+
deps.reject! {|current| current.name == new_dep.name }
|
29
|
+
deps << new_dep
|
30
|
+
new_dep
|
33
31
|
end
|
34
32
|
|
35
33
|
# Loads the given string as a gem. Execution is deferred until
|
@@ -51,7 +49,7 @@ module Kernel
|
|
51
49
|
#
|
52
50
|
# @api public
|
53
51
|
def dependency(name, *ver, &blk)
|
54
|
-
immediate = ver.last.
|
52
|
+
immediate = ver.last.delete(:immediate) if ver.last.is_a?(Hash)
|
55
53
|
if immediate || Merb::BootLoader.finished?(Merb::BootLoader::Dependencies)
|
56
54
|
load_dependency(name, *ver, &blk)
|
57
55
|
else
|
@@ -78,20 +76,21 @@ module Kernel
|
|
78
76
|
#
|
79
77
|
# @api private
|
80
78
|
def load_dependency(name, *ver, &blk)
|
81
|
-
dep = name.is_a?(Gem::Dependency) ? name : track_dependency(name, *ver)
|
79
|
+
dep = name.is_a?(Gem::Dependency) ? name : track_dependency(name, *ver, &blk)
|
82
80
|
gem(dep)
|
83
81
|
rescue Gem::LoadError => e
|
84
82
|
Merb.fatal! "The gem #{name}, #{ver.inspect} was not found", e
|
85
83
|
ensure
|
86
|
-
|
84
|
+
begin
|
85
|
+
require dep.require_as
|
86
|
+
rescue LoadError => e
|
87
|
+
Merb.fatal! "The file #{dep.require_as} was not found", e
|
88
|
+
end
|
89
|
+
|
90
|
+
if block = dep.require_block
|
87
91
|
block.call
|
88
|
-
else
|
89
|
-
begin
|
90
|
-
require dep.name
|
91
|
-
rescue LoadError => e
|
92
|
-
Merb.fatal! "The file #{dep.name} was not found", e
|
93
|
-
end
|
94
92
|
end
|
93
|
+
|
95
94
|
Merb.logger.verbose!("loading gem '#{dep.name}' ...")
|
96
95
|
return dep # ensure needs explicit return
|
97
96
|
end
|
@@ -299,11 +298,18 @@ module Kernel
|
|
299
298
|
lines = File.read(file).split("\n")
|
300
299
|
first_line = (f = line - size - 1) < 0 ? 0 : f
|
301
300
|
|
302
|
-
|
303
|
-
|
301
|
+
if first_line.zero?
|
302
|
+
new_size = line - 1
|
303
|
+
lines = lines[first_line, size + new_size + 1]
|
304
|
+
else
|
305
|
+
new_size = nil
|
306
|
+
lines = lines[first_line, size * 2 + 1]
|
307
|
+
end
|
304
308
|
|
305
309
|
lines && lines.each_with_index do |str, index|
|
306
|
-
|
310
|
+
line_n = index + line
|
311
|
+
line_n = (new_size.nil?) ? line_n - size : line_n - new_size
|
312
|
+
yield line_n, str.chomp
|
307
313
|
end
|
308
314
|
end
|
309
315
|
end
|
@@ -68,7 +68,7 @@ module Merb
|
|
68
68
|
options["expires"] = expiry.gmtime.strftime(Merb::Const::COOKIE_EXPIRATION_FORMAT)
|
69
69
|
end
|
70
70
|
secure = options.delete("secure")
|
71
|
-
kookie = "#{name}=#{Merb::
|
71
|
+
kookie = "#{name}=#{Merb::Parse.escape(value)}; "
|
72
72
|
# WebKit in particular doens't like empty cookie options - skip them.
|
73
73
|
options.each { |k, v| kookie << "#{k}=#{v}; " unless v.blank? }
|
74
74
|
kookie << 'secure' if secure
|
@@ -116,7 +116,7 @@ module Merb
|
|
116
116
|
# a Hash of key => value pairs.
|
117
117
|
def cookies
|
118
118
|
@cookies ||= begin
|
119
|
-
values =
|
119
|
+
values = Merb::Parse.query(@env[Merb::Const::HTTP_COOKIE], ';,')
|
120
120
|
cookies = Merb::Cookies.new(values)
|
121
121
|
cookies.update(default_cookies) if respond_to?(:default_cookies)
|
122
122
|
cookies
|
@@ -179,7 +179,7 @@ module Merb
|
|
179
179
|
#
|
180
180
|
# @api private
|
181
181
|
def query_params
|
182
|
-
@query_params ||=
|
182
|
+
@query_params ||= Merb::Parse.query(query_string || '')
|
183
183
|
end
|
184
184
|
|
185
185
|
# Parameters passed in the body of the request. Ajax calls from
|
@@ -192,7 +192,7 @@ module Merb
|
|
192
192
|
def body_params
|
193
193
|
@body_params ||= begin
|
194
194
|
if content_type && content_type.match(Merb::Const::FORM_URL_ENCODED_REGEXP) # or content_type.nil?
|
195
|
-
|
195
|
+
Merb::Parse.query(raw_post)
|
196
196
|
end
|
197
197
|
end
|
198
198
|
end
|
@@ -226,7 +226,7 @@ module Merb
|
|
226
226
|
# if the content-type is multipart
|
227
227
|
# parse the multipart. Otherwise return {}
|
228
228
|
if (Merb::Const::MULTIPART_REGEXP =~ content_type)
|
229
|
-
|
229
|
+
Merb::Parse.multipart(@body, $1, content_length)
|
230
230
|
else
|
231
231
|
{}
|
232
232
|
end
|
@@ -299,8 +299,8 @@ module Merb
|
|
299
299
|
def message
|
300
300
|
return {} unless params[:_message]
|
301
301
|
begin
|
302
|
-
Marshal.load(Merb::
|
303
|
-
rescue ArgumentError
|
302
|
+
Marshal.load(Merb::Parse.unescape(params[:_message]).unpack("m").first)
|
303
|
+
rescue ArgumentError, TypeError
|
304
304
|
{}
|
305
305
|
end
|
306
306
|
end
|
@@ -539,7 +539,7 @@ module Merb
|
|
539
539
|
#
|
540
540
|
# @api public
|
541
541
|
def path_info
|
542
|
-
@path_info ||=
|
542
|
+
@path_info ||= Merb::Parse.unescape(@env[Merb::Const::PATH_INFO])
|
543
543
|
end
|
544
544
|
|
545
545
|
# ==== Returns
|
@@ -555,7 +555,8 @@ module Merb
|
|
555
555
|
#
|
556
556
|
# @api public
|
557
557
|
def host
|
558
|
-
@env[Merb::Const::HTTP_X_FORWARDED_HOST] || @env[Merb::Const::HTTP_HOST]
|
558
|
+
@env[Merb::Const::HTTP_X_FORWARDED_HOST] || @env[Merb::Const::HTTP_HOST] ||
|
559
|
+
@env[Merb::Const::SERVER_NAME]
|
559
560
|
end
|
560
561
|
|
561
562
|
# ==== Parameters
|
@@ -603,227 +604,5 @@ module Merb
|
|
603
604
|
end
|
604
605
|
end
|
605
606
|
|
606
|
-
class << self
|
607
|
-
|
608
|
-
# ==== Parameters
|
609
|
-
# value<Array, Hash, Dictionary ~to_s>:: The value for the query string.
|
610
|
-
# prefix<~to_s>:: The prefix to add to the query string keys.
|
611
|
-
#
|
612
|
-
# ==== Returns
|
613
|
-
# String:: The query string.
|
614
|
-
#
|
615
|
-
# ==== Alternatives
|
616
|
-
# If the value is a string, the prefix will be used as the key.
|
617
|
-
#
|
618
|
-
# ==== Examples
|
619
|
-
# params_to_query_string(10, "page")
|
620
|
-
# # => "page=10"
|
621
|
-
# params_to_query_string({ :page => 10, :word => "ruby" })
|
622
|
-
# # => "page=10&word=ruby"
|
623
|
-
# params_to_query_string({ :page => 10, :word => "ruby" }, "search")
|
624
|
-
# # => "search[page]=10&search[word]=ruby"
|
625
|
-
# params_to_query_string([ "ice-cream", "cake" ], "shopping_list")
|
626
|
-
# # => "shopping_list[]=ice-cream&shopping_list[]=cake"
|
627
|
-
#
|
628
|
-
# @api private
|
629
|
-
def params_to_query_string(value, prefix = nil)
|
630
|
-
case value
|
631
|
-
when Array
|
632
|
-
value.map { |v|
|
633
|
-
params_to_query_string(v, "#{prefix}[]")
|
634
|
-
} * "&"
|
635
|
-
when Hash, Dictionary
|
636
|
-
value.map { |k, v|
|
637
|
-
params_to_query_string(v, prefix ? "#{prefix}[#{Merb::Request.escape(k)}]" : Merb::Request.escape(k))
|
638
|
-
} * "&"
|
639
|
-
else
|
640
|
-
"#{prefix}=#{Merb::Request.escape(value)}"
|
641
|
-
end
|
642
|
-
end
|
643
|
-
|
644
|
-
# ==== Parameters
|
645
|
-
# s<String>:: String to URL escape.
|
646
|
-
#
|
647
|
-
# ==== returns
|
648
|
-
# String:: The escaped string.
|
649
|
-
#
|
650
|
-
# @api private
|
651
|
-
def escape(s)
|
652
|
-
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
653
|
-
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
654
|
-
}.tr(' ', '+')
|
655
|
-
end
|
656
|
-
|
657
|
-
# ==== Parameter
|
658
|
-
# s<String>:: String to URL unescape.
|
659
|
-
#
|
660
|
-
# ==== returns
|
661
|
-
# String:: The unescaped string.
|
662
|
-
#
|
663
|
-
# @api private
|
664
|
-
def unescape(s)
|
665
|
-
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
666
|
-
[$1.delete('%')].pack('H*')
|
667
|
-
}
|
668
|
-
end
|
669
|
-
|
670
|
-
# ==== Parameters
|
671
|
-
# query_string<String>:: The query string.
|
672
|
-
# delimiter<String>:: The query string divider. Defaults to "&".
|
673
|
-
# preserve_order<Boolean>:: Preserve order of args. Defaults to false.
|
674
|
-
#
|
675
|
-
# ==== Returns
|
676
|
-
# Mash:: The parsed query string (Dictionary if preserve_order is set).
|
677
|
-
#
|
678
|
-
# ==== Examples
|
679
|
-
# query_parse("bar=nik&post[body]=heya")
|
680
|
-
# # => { :bar => "nik", :post => { :body => "heya" } }
|
681
|
-
#
|
682
|
-
# @api private
|
683
|
-
def query_parse(query_string, delimiter = '&;', preserve_order = false)
|
684
|
-
query = preserve_order ? Dictionary.new : {}
|
685
|
-
for pair in (query_string || '').split(/[#{delimiter}] */n)
|
686
|
-
key, value = unescape(pair).split('=',2)
|
687
|
-
next if key.nil?
|
688
|
-
if key.include?('[')
|
689
|
-
normalize_params(query, key, value)
|
690
|
-
else
|
691
|
-
query[key] = value
|
692
|
-
end
|
693
|
-
end
|
694
|
-
preserve_order ? query : query.to_mash
|
695
|
-
end
|
696
|
-
|
697
|
-
NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
|
698
|
-
CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
|
699
|
-
FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
|
700
|
-
CRLF = "\r\n".freeze
|
701
|
-
EOL = CRLF
|
702
|
-
|
703
|
-
# ==== Parameters
|
704
|
-
# request<IO>:: The raw request.
|
705
|
-
# boundary<String>:: The boundary string.
|
706
|
-
# content_length<Fixnum>:: The length of the content.
|
707
|
-
#
|
708
|
-
# ==== Raises
|
709
|
-
# ControllerExceptions::MultiPartParseError:: Failed to parse request.
|
710
|
-
#
|
711
|
-
# ==== Returns
|
712
|
-
# Hash:: The parsed request.
|
713
|
-
#
|
714
|
-
# @api private
|
715
|
-
def parse_multipart(request, boundary, content_length)
|
716
|
-
boundary = "--#{boundary}"
|
717
|
-
paramhsh = {}
|
718
|
-
buf = ""
|
719
|
-
input = request
|
720
|
-
input.binmode if defined? input.binmode
|
721
|
-
boundary_size = boundary.size + EOL.size
|
722
|
-
bufsize = 16384
|
723
|
-
content_length -= boundary_size
|
724
|
-
status = input.read(boundary_size)
|
725
|
-
return {} if status == nil || status.empty?
|
726
|
-
raise ControllerExceptions::MultiPartParseError, "bad content body:\n'#{status}' should == '#{boundary + EOL}'" unless status == boundary + EOL
|
727
|
-
rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
|
728
|
-
loop {
|
729
|
-
head = nil
|
730
|
-
body = ''
|
731
|
-
filename = content_type = name = nil
|
732
|
-
read_size = 0
|
733
|
-
until head && buf =~ rx
|
734
|
-
i = buf.index("\r\n\r\n")
|
735
|
-
if( i == nil && read_size == 0 && content_length == 0 )
|
736
|
-
content_length = -1
|
737
|
-
break
|
738
|
-
end
|
739
|
-
if !head && i
|
740
|
-
head = buf.slice!(0, i+2) # First \r\n
|
741
|
-
buf.slice!(0, 2) # Second \r\n
|
742
|
-
filename = head[FILENAME_REGEX, 1]
|
743
|
-
content_type = head[CONTENT_TYPE_REGEX, 1]
|
744
|
-
name = head[NAME_REGEX, 1]
|
745
|
-
|
746
|
-
if filename && !filename.empty?
|
747
|
-
body = Tempfile.new(:Merb)
|
748
|
-
body.binmode if defined? body.binmode
|
749
|
-
end
|
750
|
-
next
|
751
|
-
end
|
752
|
-
|
753
|
-
# Save the read body part.
|
754
|
-
if head && (boundary_size+4 < buf.size)
|
755
|
-
body << buf.slice!(0, buf.size - (boundary_size+4))
|
756
|
-
end
|
757
|
-
|
758
|
-
read_size = bufsize < content_length ? bufsize : content_length
|
759
|
-
if( read_size > 0 )
|
760
|
-
c = input.read(read_size)
|
761
|
-
raise ControllerExceptions::MultiPartParseError, "bad content body" if c.nil? || c.empty?
|
762
|
-
buf << c
|
763
|
-
content_length -= c.size
|
764
|
-
end
|
765
|
-
end
|
766
|
-
|
767
|
-
# Save the rest.
|
768
|
-
if i = buf.index(rx)
|
769
|
-
body << buf.slice!(0, i)
|
770
|
-
buf.slice!(0, boundary_size+2)
|
771
|
-
|
772
|
-
content_length = -1 if $1 == "--"
|
773
|
-
end
|
774
|
-
|
775
|
-
if filename && !filename.empty?
|
776
|
-
body.rewind
|
777
|
-
data = {
|
778
|
-
:filename => File.basename(filename),
|
779
|
-
:content_type => content_type,
|
780
|
-
:tempfile => body,
|
781
|
-
:size => File.size(body.path)
|
782
|
-
}
|
783
|
-
else
|
784
|
-
data = body
|
785
|
-
end
|
786
|
-
paramhsh = normalize_params(paramhsh,name,data)
|
787
|
-
break if buf.empty? || content_length == -1
|
788
|
-
}
|
789
|
-
paramhsh
|
790
|
-
end
|
791
|
-
|
792
|
-
# Converts a query string snippet to a hash and adds it to existing
|
793
|
-
# parameters.
|
794
|
-
#
|
795
|
-
# ==== Parameters
|
796
|
-
# parms<Hash>:: Parameters to add the normalized parameters to.
|
797
|
-
# name<String>:: The key of the parameter to normalize.
|
798
|
-
# val<String>:: The value of the parameter.
|
799
|
-
#
|
800
|
-
# ==== Returns
|
801
|
-
# Hash:: Normalized parameters
|
802
|
-
#
|
803
|
-
# @api private
|
804
|
-
def normalize_params(parms, name, val=nil)
|
805
|
-
name =~ %r([\[\]]*([^\[\]]+)\]*)
|
806
|
-
key = $1 || ''
|
807
|
-
after = $' || ''
|
808
|
-
|
809
|
-
if after == ""
|
810
|
-
parms[key] = val
|
811
|
-
elsif after == "[]"
|
812
|
-
(parms[key] ||= []) << val
|
813
|
-
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$)
|
814
|
-
child_key = $1
|
815
|
-
parms[key] ||= []
|
816
|
-
if parms[key].last.is_a?(Hash) && !parms[key].last.key?(child_key)
|
817
|
-
parms[key].last.update(child_key => val)
|
818
|
-
else
|
819
|
-
parms[key] << { child_key => val }
|
820
|
-
end
|
821
|
-
else
|
822
|
-
parms[key] ||= {}
|
823
|
-
parms[key] = normalize_params(parms[key], after, val)
|
824
|
-
end
|
825
|
-
parms
|
826
|
-
end
|
827
|
-
end
|
828
607
|
end
|
829
608
|
end
|