merb-core 0.9.10 → 0.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|