wycats-merb-core 0.9.8
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/CHANGELOG +992 -0
- data/CONTRIBUTORS +94 -0
- data/LICENSE +20 -0
- data/PUBLIC_CHANGELOG +142 -0
- data/README +21 -0
- data/Rakefile +458 -0
- data/TODO +0 -0
- data/bin/merb +11 -0
- data/bin/merb-specs +5 -0
- data/lib/merb-core.rb +598 -0
- data/lib/merb-core/autoload.rb +31 -0
- data/lib/merb-core/bootloader.rb +717 -0
- data/lib/merb-core/config.rb +305 -0
- data/lib/merb-core/constants.rb +45 -0
- data/lib/merb-core/controller/abstract_controller.rb +568 -0
- data/lib/merb-core/controller/exceptions.rb +315 -0
- data/lib/merb-core/controller/merb_controller.rb +256 -0
- data/lib/merb-core/controller/mime.rb +107 -0
- data/lib/merb-core/controller/mixins/authentication.rb +123 -0
- data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
- data/lib/merb-core/controller/mixins/controller.rb +319 -0
- data/lib/merb-core/controller/mixins/render.rb +513 -0
- data/lib/merb-core/controller/mixins/responder.rb +469 -0
- data/lib/merb-core/controller/template.rb +254 -0
- data/lib/merb-core/core_ext.rb +9 -0
- data/lib/merb-core/core_ext/hash.rb +7 -0
- data/lib/merb-core/core_ext/kernel.rb +340 -0
- data/lib/merb-core/dispatch/cookies.rb +130 -0
- data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
- data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
- data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
- data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
- data/lib/merb-core/dispatch/dispatcher.rb +176 -0
- data/lib/merb-core/dispatch/request.rb +729 -0
- data/lib/merb-core/dispatch/router.rb +151 -0
- data/lib/merb-core/dispatch/router/behavior.rb +566 -0
- data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
- data/lib/merb-core/dispatch/router/resources.rb +191 -0
- data/lib/merb-core/dispatch/router/route.rb +511 -0
- data/lib/merb-core/dispatch/session.rb +222 -0
- data/lib/merb-core/dispatch/session/container.rb +74 -0
- data/lib/merb-core/dispatch/session/cookie.rb +173 -0
- data/lib/merb-core/dispatch/session/memcached.rb +68 -0
- data/lib/merb-core/dispatch/session/memory.rb +99 -0
- data/lib/merb-core/dispatch/session/store_container.rb +150 -0
- data/lib/merb-core/dispatch/worker.rb +28 -0
- data/lib/merb-core/gem_ext/erubis.rb +77 -0
- data/lib/merb-core/logger.rb +203 -0
- data/lib/merb-core/plugins.rb +67 -0
- data/lib/merb-core/rack.rb +25 -0
- data/lib/merb-core/rack/adapter.rb +44 -0
- data/lib/merb-core/rack/adapter/ebb.rb +25 -0
- data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
- data/lib/merb-core/rack/adapter/irb.rb +118 -0
- data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/runner.rb +28 -0
- data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
- data/lib/merb-core/rack/adapter/thin.rb +39 -0
- data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
- data/lib/merb-core/rack/adapter/webrick.rb +36 -0
- data/lib/merb-core/rack/application.rb +32 -0
- data/lib/merb-core/rack/handler/mongrel.rb +97 -0
- data/lib/merb-core/rack/middleware.rb +20 -0
- data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
- data/lib/merb-core/rack/middleware/content_length.rb +18 -0
- data/lib/merb-core/rack/middleware/csrf.rb +73 -0
- data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
- data/lib/merb-core/rack/middleware/profiler.rb +19 -0
- data/lib/merb-core/rack/middleware/static.rb +45 -0
- data/lib/merb-core/rack/middleware/tracer.rb +20 -0
- data/lib/merb-core/server.rb +284 -0
- data/lib/merb-core/tasks/audit.rake +68 -0
- data/lib/merb-core/tasks/gem_management.rb +229 -0
- data/lib/merb-core/tasks/merb.rb +1 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
- data/lib/merb-core/tasks/stats.rake +71 -0
- data/lib/merb-core/test.rb +11 -0
- data/lib/merb-core/test/helpers.rb +9 -0
- data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
- data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
- data/lib/merb-core/test/helpers/request_helper.rb +393 -0
- data/lib/merb-core/test/helpers/route_helper.rb +39 -0
- data/lib/merb-core/test/helpers/view_helper.rb +121 -0
- data/lib/merb-core/test/matchers.rb +9 -0
- data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
- data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
- data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
- data/lib/merb-core/test/run_specs.rb +49 -0
- data/lib/merb-core/test/tasks/spectasks.rb +68 -0
- data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
- data/lib/merb-core/test/test_ext/object.rb +14 -0
- data/lib/merb-core/test/test_ext/string.rb +14 -0
- data/lib/merb-core/vendor/facets.rb +2 -0
- data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
- data/lib/merb-core/vendor/facets/inflect.rb +342 -0
- data/lib/merb-core/version.rb +3 -0
- metadata +253 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
<html>
|
2
|
+
<head>
|
3
|
+
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
4
|
+
<title><%= humanize_exception(@exceptions.first) %></title>
|
5
|
+
<%= partial :css %>
|
6
|
+
</head>
|
7
|
+
<body>
|
8
|
+
<% if @show_details %>
|
9
|
+
<% if @exceptions.size > 1 %>
|
10
|
+
<div class="internalError">
|
11
|
+
<div class="header">
|
12
|
+
<h1>Error Stack</h1>
|
13
|
+
<ul>
|
14
|
+
<% @exceptions.each_with_index do |exception,i| %>
|
15
|
+
<li>
|
16
|
+
<a href="#exception_<%= i %>"><%= humanize_exception(exception) %></a>
|
17
|
+
<%= escape_html(exception.message.split("\n",2).first) %>
|
18
|
+
</li>
|
19
|
+
<% end %>
|
20
|
+
</ul>
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
<% end %>
|
24
|
+
|
25
|
+
<div class="internalError">
|
26
|
+
<div class="header">
|
27
|
+
<h1>Request Details</h1>
|
28
|
+
<h3>Parameters</h3>
|
29
|
+
<%= listing("Parameter", "Value", request.params) %>
|
30
|
+
|
31
|
+
<% if request.session? %>
|
32
|
+
<h3>Session</h3>
|
33
|
+
<%= listing("Key", "Value", request.session) %>
|
34
|
+
<% end %>
|
35
|
+
|
36
|
+
<h3>Cookies</h3>
|
37
|
+
<%= listing("Cookie", "Value", request.cookies) %>
|
38
|
+
|
39
|
+
<h3>Named Routes</h3>
|
40
|
+
<%= listing("Name", "Route", Merb::Router.named_routes) %>
|
41
|
+
</div>
|
42
|
+
</div>
|
43
|
+
<% end %>
|
44
|
+
|
45
|
+
<% @exceptions.each_with_index do |exception,i| %>
|
46
|
+
<div class="internalError" id="exception_<%= i %>">
|
47
|
+
<div class="header">
|
48
|
+
<h1>
|
49
|
+
<%= humanize_exception(exception) %>
|
50
|
+
<sup class="error_<%= exception.class.status %>"><%= exception.class.status %></sup>
|
51
|
+
</h1>
|
52
|
+
<%= error_codes(exception) %>
|
53
|
+
<p class="options">
|
54
|
+
<label class="all">All<input type="checkbox" autocomplete="off"></label>
|
55
|
+
<span class="all">
|
56
|
+
<label class="app">App<input type="checkbox" checked="checked" autocomplete="off"/></label>
|
57
|
+
<label class="framework">Framework<input type="checkbox" autocomplete="off"/></label>
|
58
|
+
<label class="gem">Gem<input type="checkbox" autocomplete="off"/></label>
|
59
|
+
<label class="other">Other<input type="checkbox" autocomplete="off"/></label>
|
60
|
+
</span>
|
61
|
+
</p>
|
62
|
+
|
63
|
+
<% if @show_details %>
|
64
|
+
<table class="trace">
|
65
|
+
<% exception.backtrace.each_with_index do |line, index| %>
|
66
|
+
<% type, shortname, filename, lineno, location = frame_details(line) %>
|
67
|
+
<tbody class="close <%= type %>" <%= "style='display:none'" unless type == "app" %>>
|
68
|
+
<tr class="file">
|
69
|
+
<td class="expand"><div> </div></td>
|
70
|
+
<td class="path">
|
71
|
+
<%= shortname %>
|
72
|
+
<% if filename && filename.match(/\.erb$/) %>
|
73
|
+
(<strong>ERB Template</strong>)
|
74
|
+
<% else %>
|
75
|
+
in <strong><%= location ? location.match(/in (`.+')$/)[1] : 'main' %></strong>
|
76
|
+
<% end %>
|
77
|
+
</td>
|
78
|
+
<td class="line">
|
79
|
+
<%= textmate_url(filename, lineno) %>
|
80
|
+
</td>
|
81
|
+
</tr>
|
82
|
+
<%= render_source(filename, lineno) %>
|
83
|
+
</tbody>
|
84
|
+
<% end %>
|
85
|
+
</table>
|
86
|
+
<% end %>
|
87
|
+
<div class="footer">
|
88
|
+
lots of love, from <a href="http://www.merbivore.com">merb</a>
|
89
|
+
</div>
|
90
|
+
</div>
|
91
|
+
<% end %>
|
92
|
+
<%= partial :javascript %>
|
93
|
+
</body>
|
94
|
+
</html>
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require Merb.framework_root / "merb-core" / "dispatch" / "default_exception" / "default_exception"
|
2
|
+
|
3
|
+
module Merb
|
4
|
+
class Dispatcher
|
5
|
+
class << self
|
6
|
+
include Merb::ControllerExceptions
|
7
|
+
|
8
|
+
attr_accessor :use_mutex
|
9
|
+
|
10
|
+
@@work_queue = Queue.new
|
11
|
+
|
12
|
+
def work_queue
|
13
|
+
@@work_queue
|
14
|
+
end
|
15
|
+
|
16
|
+
Merb::Dispatcher.use_mutex = ::Merb::Config[:use_mutex]
|
17
|
+
|
18
|
+
# Dispatch the rack environment. ControllerExceptions are rescued here
|
19
|
+
# and redispatched.
|
20
|
+
#
|
21
|
+
# ==== Parameters
|
22
|
+
# rack_env<Rack::Environment>::
|
23
|
+
# The rack environment, which is used to instantiate a Merb::Request
|
24
|
+
#
|
25
|
+
# ==== Returns
|
26
|
+
# Merb::Controller::
|
27
|
+
# The Merb::Controller that was dispatched to
|
28
|
+
def handle(request)
|
29
|
+
request.handle
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class Request
|
35
|
+
include Merb::ControllerExceptions
|
36
|
+
|
37
|
+
@@mutex = Mutex.new
|
38
|
+
|
39
|
+
def handle
|
40
|
+
start = Time.now
|
41
|
+
Merb.logger.info "Started request handling: #{start.to_s}"
|
42
|
+
|
43
|
+
find_route!
|
44
|
+
return redirect if redirects?
|
45
|
+
|
46
|
+
klass = controller
|
47
|
+
Merb.logger.debug("Routed to: #{params.inspect}")
|
48
|
+
|
49
|
+
unless klass < Controller
|
50
|
+
raise NotFound,
|
51
|
+
"Controller '#{klass}' not found.\n" \
|
52
|
+
"If Merb tries to find a controller for static files, " \
|
53
|
+
"you may need to check your Rackup file, see the Problems " \
|
54
|
+
"section at: http://wiki.merbivore.com/pages/rack-middleware"
|
55
|
+
end
|
56
|
+
|
57
|
+
if klass.abstract?
|
58
|
+
raise NotFound, "The '#{klass}' controller has no public actions"
|
59
|
+
end
|
60
|
+
|
61
|
+
controller = dispatch_action(klass, params[:action])
|
62
|
+
controller._benchmarks[:dispatch_time] = Time.now - start
|
63
|
+
Merb.logger.info controller._benchmarks.inspect
|
64
|
+
Merb.logger.flush
|
65
|
+
controller
|
66
|
+
rescue Object => exception
|
67
|
+
dispatch_exception(exception)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set up a faux controller to do redirection from the router
|
71
|
+
#
|
72
|
+
# ==== Parameters
|
73
|
+
# request<Merb::Request>::
|
74
|
+
# The Merb::Request object that was created in #handle
|
75
|
+
# status<Integer>::
|
76
|
+
# The status code to return with the controller
|
77
|
+
# url<String>::
|
78
|
+
# The URL to return
|
79
|
+
#
|
80
|
+
# ==== Example
|
81
|
+
# r.match("/my/old/crusty/url").redirect("http://example.com/index.html")
|
82
|
+
#
|
83
|
+
# ==== Returns
|
84
|
+
# Merb::Controller::
|
85
|
+
# Merb::Controller set with redirect headers and a 301/302 status
|
86
|
+
def redirect(url = nil, opts = {})
|
87
|
+
controller = Merb::Controller.new(self)
|
88
|
+
|
89
|
+
unless url
|
90
|
+
status, url = redirect_status, redirect_url
|
91
|
+
end
|
92
|
+
|
93
|
+
Merb.logger.info("Dispatcher redirecting to: #{url} (#{status})")
|
94
|
+
Merb.logger.flush
|
95
|
+
|
96
|
+
controller.redirect(url, opts)
|
97
|
+
controller.status = status if status
|
98
|
+
controller.body = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
|
99
|
+
controller
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
# Setup the controller and call the chosen action
|
104
|
+
#
|
105
|
+
# ==== Parameters
|
106
|
+
# klass<Merb::Controller>:: The controller class to dispatch to.
|
107
|
+
# action<Symbol>:: The action to dispatch.
|
108
|
+
# request<Merb::Request>::
|
109
|
+
# The Merb::Request object that was created in #handle
|
110
|
+
# status<Integer>:: The status code to respond with.
|
111
|
+
#
|
112
|
+
# ==== Returns
|
113
|
+
# Merb::Controller::
|
114
|
+
# The Merb::Controller that was dispatched to.
|
115
|
+
def dispatch_action(klass, action, status=200)
|
116
|
+
# build controller
|
117
|
+
controller = klass.new(self, status)
|
118
|
+
if Dispatcher.use_mutex
|
119
|
+
@@mutex.synchronize { controller._dispatch(action) }
|
120
|
+
else
|
121
|
+
controller._dispatch(action)
|
122
|
+
end
|
123
|
+
controller
|
124
|
+
end
|
125
|
+
|
126
|
+
# Re-route the current request to the Exception controller if it is
|
127
|
+
# available, and try to render the exception nicely.
|
128
|
+
#
|
129
|
+
# You can handle exceptions by implementing actions for specific
|
130
|
+
# exceptions such as not_found or for entire classes of exceptions
|
131
|
+
# such as client_error. You can also implement handlers for
|
132
|
+
# exceptions outside the Merb exception hierarchy (e.g.
|
133
|
+
# StandardError is caught in standard_error).
|
134
|
+
#
|
135
|
+
# ==== Parameters
|
136
|
+
# request<Merb::Request>::
|
137
|
+
# The request object associated with the failed request.
|
138
|
+
# exception<Object>::
|
139
|
+
# The exception object that was created when trying to dispatch the
|
140
|
+
# original controller.
|
141
|
+
#
|
142
|
+
# ==== Returns
|
143
|
+
# Exceptions::
|
144
|
+
# The Merb::Controller that was dispatched to.
|
145
|
+
def dispatch_exception(exception)
|
146
|
+
if(exception.is_a?(Merb::ControllerExceptions::Base) &&
|
147
|
+
!exception.is_a?(Merb::ControllerExceptions::ServerError))
|
148
|
+
Merb.logger.info(Merb.exception(exception))
|
149
|
+
else
|
150
|
+
Merb.logger.error(Merb.exception(exception))
|
151
|
+
end
|
152
|
+
|
153
|
+
self.exceptions = [exception]
|
154
|
+
|
155
|
+
begin
|
156
|
+
e = exceptions.first
|
157
|
+
|
158
|
+
if action_name = e.action_name
|
159
|
+
dispatch_action(Exceptions, action_name, e.class.status)
|
160
|
+
else
|
161
|
+
Merb::Dispatcher::DefaultException.new(self, e.class.status)._dispatch
|
162
|
+
end
|
163
|
+
rescue Object => dispatch_issue
|
164
|
+
if e.same?(dispatch_issue) || exceptions.size > 5
|
165
|
+
Merb::Dispatcher::DefaultException.new(self, e.class.status)._dispatch
|
166
|
+
else
|
167
|
+
Merb.logger.error("Dispatching #{e.class} raised another error.")
|
168
|
+
Merb.logger.error(Merb.exception(dispatch_issue))
|
169
|
+
|
170
|
+
exceptions.unshift dispatch_issue
|
171
|
+
retry
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
@@ -0,0 +1,729 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Merb
|
4
|
+
|
5
|
+
class Request
|
6
|
+
# def env def exceptions def route_params
|
7
|
+
attr_accessor :env, :exceptions, :route
|
8
|
+
attr_reader :route_params
|
9
|
+
|
10
|
+
# by setting these to false, auto-parsing is disabled; this way you can
|
11
|
+
# do your own parsing instead
|
12
|
+
cattr_accessor :parse_multipart_params, :parse_json_params,
|
13
|
+
:parse_xml_params
|
14
|
+
self.parse_multipart_params = true
|
15
|
+
self.parse_json_params = true
|
16
|
+
self.parse_xml_params = true
|
17
|
+
|
18
|
+
# Flash, and some older browsers can't use arbitrary
|
19
|
+
# request methods -- i.e., are limited to GET/POST.
|
20
|
+
# These user-agents can make POST requests in combination
|
21
|
+
# with these overrides to participate fully in REST.
|
22
|
+
# Common examples are _method or fb_sig_request_method
|
23
|
+
# in the params, or an X-HTTP-Method-Override header
|
24
|
+
cattr_accessor :http_method_overrides
|
25
|
+
self.http_method_overrides = []
|
26
|
+
|
27
|
+
# Initialize the request object.
|
28
|
+
#
|
29
|
+
# ==== Parameters
|
30
|
+
# http_request<~params:~[], ~body:IO>::
|
31
|
+
# An object like an HTTP Request.
|
32
|
+
def initialize(rack_env)
|
33
|
+
@env = rack_env
|
34
|
+
@body = rack_env['rack.input']
|
35
|
+
@route_params = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def controller
|
39
|
+
unless params[:controller]
|
40
|
+
raise ControllerExceptions::NotFound,
|
41
|
+
"Route matched, but route did not specify a controller.\n" +
|
42
|
+
"Did you forgot to add :controller => \"people\" or :controller " +
|
43
|
+
"segment to route definition?\nHere is what's specified:\n" +
|
44
|
+
route.inspect
|
45
|
+
end
|
46
|
+
path = [params[:namespace], params[:controller]].compact.join("/")
|
47
|
+
controller = path.snake_case.to_const_string
|
48
|
+
|
49
|
+
begin
|
50
|
+
Object.full_const_get(controller)
|
51
|
+
rescue NameError => e
|
52
|
+
msg = "Controller class not found for controller `#{path}'"
|
53
|
+
Merb.logger.warn!(msg)
|
54
|
+
raise ControllerExceptions::NotFound, msg
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
METHODS = %w{get post put delete head options}
|
59
|
+
|
60
|
+
# ==== Returns
|
61
|
+
# <Symbol>:: The name of the request method, e.g. :get.
|
62
|
+
#
|
63
|
+
# ==== Notes
|
64
|
+
# If the method is post, then the blocks specified in
|
65
|
+
# http_method_overrides will be checked for the masquerading method.
|
66
|
+
# The block will get the controller yielded to it. The first matching workaround wins.
|
67
|
+
# To disable this behavior, set http_method_overrides = []
|
68
|
+
def method
|
69
|
+
@method ||= begin
|
70
|
+
request_method = @env['REQUEST_METHOD'].downcase.to_sym
|
71
|
+
case request_method
|
72
|
+
when :get, :head, :put, :delete, :options
|
73
|
+
request_method
|
74
|
+
when :post
|
75
|
+
m = nil
|
76
|
+
self.class.http_method_overrides.each do |o|
|
77
|
+
m ||= o.call(self); break if m
|
78
|
+
end
|
79
|
+
m.downcase! if m
|
80
|
+
METHODS.include?(m) ? m.to_sym : :post
|
81
|
+
else
|
82
|
+
raise "Unknown REQUEST_METHOD: #{@env['REQUEST_METHOD']}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# create predicate methods for querying the REQUEST_METHOD
|
88
|
+
# get? post? head? put? etc
|
89
|
+
METHODS.each do |m|
|
90
|
+
class_eval "def #{m}?() method == :#{m} end"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Find route using requested URI and merges route
|
94
|
+
# parameters (:action, :controller and named segments)
|
95
|
+
# into request params hash.
|
96
|
+
def find_route!
|
97
|
+
@route, @route_params = Merb::Router.route_for(self)
|
98
|
+
params.merge! @route_params
|
99
|
+
end
|
100
|
+
|
101
|
+
# Redirect status of route matched this request.
|
102
|
+
#
|
103
|
+
# ==== Returns
|
104
|
+
# Integer::
|
105
|
+
# The URL to redirect to if the route redirects
|
106
|
+
def redirect_status
|
107
|
+
route.redirect_status
|
108
|
+
end
|
109
|
+
|
110
|
+
# Returns redirect url of route matched this request.
|
111
|
+
#
|
112
|
+
# ==== Returns
|
113
|
+
# <String>:: redirect url of route matched this request
|
114
|
+
def redirect_url
|
115
|
+
route.redirect_url
|
116
|
+
end
|
117
|
+
|
118
|
+
# Returns true if matched route does immediate redirection.
|
119
|
+
#
|
120
|
+
# ==== Returns
|
121
|
+
# <Boolean>:: if matched route does immediate redirection.
|
122
|
+
def redirects?
|
123
|
+
route.redirects?
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
# ==== Returns
|
129
|
+
# Hash:: Parameters passed from the URL like ?blah=hello.
|
130
|
+
def query_params
|
131
|
+
@query_params ||= self.class.query_parse(query_string || '')
|
132
|
+
end
|
133
|
+
|
134
|
+
# Parameters passed in the body of the request. Ajax calls from
|
135
|
+
# prototype.js and other libraries pass content this way.
|
136
|
+
#
|
137
|
+
# ==== Returns
|
138
|
+
# Hash:: The parameters passed in the body.
|
139
|
+
def body_params
|
140
|
+
@body_params ||= begin
|
141
|
+
if content_type && content_type.match(Merb::Const::FORM_URL_ENCODED_REGEXP) # or content_type.nil?
|
142
|
+
self.class.query_parse(raw_post)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# ==== Returns
|
148
|
+
# Mash::
|
149
|
+
# The parameters gathered from the query string and the request body,
|
150
|
+
# with parameters in the body taking precedence.
|
151
|
+
def body_and_query_params
|
152
|
+
# ^-- FIXME a better name for this method
|
153
|
+
@body_and_query_params ||= begin
|
154
|
+
h = query_params
|
155
|
+
h.merge!(body_params) if body_params
|
156
|
+
h.to_mash
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
# ==== Raises
|
161
|
+
# ControllerExceptions::MultiPartParseError::
|
162
|
+
# Unable to parse the multipart form data.
|
163
|
+
#
|
164
|
+
# ==== Returns
|
165
|
+
# Hash:: The parsed multipart parameters.
|
166
|
+
def multipart_params
|
167
|
+
@multipart_params ||=
|
168
|
+
begin
|
169
|
+
# if the content-type is multipart
|
170
|
+
# parse the multipart. Otherwise return {}
|
171
|
+
if (Merb::Const::MULTIPART_REGEXP =~ content_type)
|
172
|
+
self.class.parse_multipart(@body, $1, content_length)
|
173
|
+
else
|
174
|
+
{}
|
175
|
+
end
|
176
|
+
rescue ControllerExceptions::MultiPartParseError => e
|
177
|
+
@multipart_params = {}
|
178
|
+
raise e
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# ==== Returns
|
183
|
+
# Hash:: Parameters from body if this is a JSON request.
|
184
|
+
#
|
185
|
+
# ==== Notes
|
186
|
+
# If the JSON object parses as a Hash, it will be merged with the
|
187
|
+
# parameters hash. If it parses to anything else (such as an Array, or
|
188
|
+
# if it inflates to an Object) it will be accessible via the inflated_object
|
189
|
+
# parameter.
|
190
|
+
def json_params
|
191
|
+
@json_params ||= begin
|
192
|
+
if Merb::Const::JSON_MIME_TYPE_REGEXP.match(content_type)
|
193
|
+
jobj = JSON.parse(raw_post) rescue Mash.new
|
194
|
+
jobj.kind_of?(Hash) ? jobj : { :inflated_object => jobj }
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# ==== Returns
|
200
|
+
# Hash:: Parameters from body if this is an XML request.
|
201
|
+
def xml_params
|
202
|
+
@xml_params ||= begin
|
203
|
+
if Merb::Const::XML_MIME_TYPE_REGEXP.match(content_type)
|
204
|
+
Hash.from_xml(raw_post) rescue Mash.new
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
public
|
210
|
+
|
211
|
+
# ==== Returns
|
212
|
+
# Mash:: All request parameters.
|
213
|
+
#
|
214
|
+
# ==== Notes
|
215
|
+
# The order of precedence for the params is XML, JSON, multipart, body and
|
216
|
+
# request string.
|
217
|
+
def params
|
218
|
+
@params ||= begin
|
219
|
+
h = body_and_query_params.merge(route_params)
|
220
|
+
h.merge!(multipart_params) if self.class.parse_multipart_params && multipart_params
|
221
|
+
h.merge!(json_params) if self.class.parse_json_params && json_params
|
222
|
+
h.merge!(xml_params) if self.class.parse_xml_params && xml_params
|
223
|
+
h
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def message
|
228
|
+
return {} unless params[:_message]
|
229
|
+
begin
|
230
|
+
Marshal.load(Merb::Request.unescape(params[:_message]).unpack("m").first)
|
231
|
+
rescue ArgumentError
|
232
|
+
{}
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
# Resets the params to a nil value.
|
237
|
+
def reset_params!
|
238
|
+
@params = nil
|
239
|
+
end
|
240
|
+
|
241
|
+
# ==== Returns
|
242
|
+
# String:: The raw post.
|
243
|
+
def raw_post
|
244
|
+
@body.rewind if @body.respond_to?(:rewind)
|
245
|
+
@raw_post ||= @body.read
|
246
|
+
end
|
247
|
+
|
248
|
+
# ==== Returns
|
249
|
+
# Boolean:: If the request is an XML HTTP request.
|
250
|
+
def xml_http_request?
|
251
|
+
not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
|
252
|
+
end
|
253
|
+
alias xhr? :xml_http_request?
|
254
|
+
alias ajax? :xml_http_request?
|
255
|
+
|
256
|
+
# ==== Returns
|
257
|
+
# String:: The remote IP address.
|
258
|
+
def remote_ip
|
259
|
+
return @env['HTTP_CLIENT_IP'] if @env.include?('HTTP_CLIENT_IP')
|
260
|
+
|
261
|
+
if @env.include?(Merb::Const::HTTP_X_FORWARDED_FOR) then
|
262
|
+
remote_ips = @env[Merb::Const::HTTP_X_FORWARDED_FOR].split(',').reject do |ip|
|
263
|
+
ip =~ /^unknown$|^(127|10|172\.16|192\.168)\./i
|
264
|
+
end
|
265
|
+
|
266
|
+
return remote_ips.first.strip unless remote_ips.empty?
|
267
|
+
end
|
268
|
+
|
269
|
+
return @env[Merb::Const::REMOTE_ADDR]
|
270
|
+
end
|
271
|
+
|
272
|
+
# ==== Parameters
|
273
|
+
# name<~to_sym, Hash>:: The name of the URL to generate.
|
274
|
+
# rparams<Hash>:: Parameters for the route generation.
|
275
|
+
#
|
276
|
+
# ==== Returns
|
277
|
+
# String:: The generated URL.
|
278
|
+
#
|
279
|
+
# ==== Alternatives
|
280
|
+
# If a hash is used as the first argument, a default route will be
|
281
|
+
# generated based on it and rparams.
|
282
|
+
# ====
|
283
|
+
# TODO: Update this documentation
|
284
|
+
def generate_url(name, *args)
|
285
|
+
unless Symbol === name
|
286
|
+
args.unshift(name)
|
287
|
+
name = :default
|
288
|
+
end
|
289
|
+
|
290
|
+
unless route = Merb::Router.named_routes[name]
|
291
|
+
raise Merb::Router::GenerationError, "Named route not found: #{name}"
|
292
|
+
end
|
293
|
+
|
294
|
+
route.generate(args, params)
|
295
|
+
end
|
296
|
+
|
297
|
+
|
298
|
+
# ==== Parameters
|
299
|
+
# name<~to_sym, Hash>:: The name of the URL to generate.
|
300
|
+
# rparams<Hash>:: Parameters for the route generation.
|
301
|
+
#
|
302
|
+
# ==== Returns
|
303
|
+
# String:: The generated url with protocol + hostname + URL.
|
304
|
+
#
|
305
|
+
# ==== Options
|
306
|
+
#
|
307
|
+
# :protocol and :host options are special: use them to explicitly
|
308
|
+
# specify protocol and host of resulting url. If you omit them,
|
309
|
+
# protocol and host of request are used.
|
310
|
+
#
|
311
|
+
# ==== Alternatives
|
312
|
+
# If a hash is used as the first argument, a default route will be
|
313
|
+
# generated based on it and rparams.
|
314
|
+
def generate_absolute_url(name, rparams={})
|
315
|
+
if rparams.is_a?(Hash)
|
316
|
+
tprotocol = rparams.delete(:protocol)
|
317
|
+
thost = rparams.delete(:host)
|
318
|
+
end
|
319
|
+
|
320
|
+
(tprotocol || protocol) + "://" +
|
321
|
+
(thost || host) +
|
322
|
+
generate_url(name, rparams)
|
323
|
+
end
|
324
|
+
|
325
|
+
# ==== Returns
|
326
|
+
# String::
|
327
|
+
# The protocol, i.e. either "https" or "http" depending on the
|
328
|
+
# HTTPS header.
|
329
|
+
def protocol
|
330
|
+
ssl? ? 'https' : 'http'
|
331
|
+
end
|
332
|
+
|
333
|
+
# ==== Returns
|
334
|
+
# Boolean::: True if the request is an SSL request.
|
335
|
+
def ssl?
|
336
|
+
@env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
|
337
|
+
end
|
338
|
+
|
339
|
+
# ==== Returns
|
340
|
+
# String:: The HTTP referer.
|
341
|
+
def referer
|
342
|
+
@env['HTTP_REFERER']
|
343
|
+
end
|
344
|
+
|
345
|
+
# ==== Returns
|
346
|
+
# String:: The full URI, including protocol and host
|
347
|
+
def full_uri
|
348
|
+
protocol + host + uri
|
349
|
+
end
|
350
|
+
|
351
|
+
# ==== Returns
|
352
|
+
# String:: The request URI.
|
353
|
+
def uri
|
354
|
+
@env['REQUEST_PATH'] || @env['REQUEST_URI'] || path_info
|
355
|
+
end
|
356
|
+
|
357
|
+
# ==== Returns
|
358
|
+
# String:: The HTTP user agent.
|
359
|
+
def user_agent
|
360
|
+
@env['HTTP_USER_AGENT']
|
361
|
+
end
|
362
|
+
|
363
|
+
# ==== Returns
|
364
|
+
# String:: The server name.
|
365
|
+
def server_name
|
366
|
+
@env['SERVER_NAME']
|
367
|
+
end
|
368
|
+
|
369
|
+
# ==== Returns
|
370
|
+
# String:: The accepted encodings.
|
371
|
+
def accept_encoding
|
372
|
+
@env['HTTP_ACCEPT_ENCODING']
|
373
|
+
end
|
374
|
+
|
375
|
+
# ==== Returns
|
376
|
+
# String:: The script name.
|
377
|
+
def script_name
|
378
|
+
@env['SCRIPT_NAME']
|
379
|
+
end
|
380
|
+
|
381
|
+
# ==== Returns
|
382
|
+
# String:: HTTP cache control.
|
383
|
+
def cache_control
|
384
|
+
@env['HTTP_CACHE_CONTROL']
|
385
|
+
end
|
386
|
+
|
387
|
+
# ==== Returns
|
388
|
+
# String:: The accepted language.
|
389
|
+
def accept_language
|
390
|
+
@env['HTTP_ACCEPT_LANGUAGE']
|
391
|
+
end
|
392
|
+
|
393
|
+
# ==== Returns
|
394
|
+
# String:: The server software.
|
395
|
+
def server_software
|
396
|
+
@env['SERVER_SOFTWARE']
|
397
|
+
end
|
398
|
+
|
399
|
+
# ==== Returns
|
400
|
+
# String:: Value of HTTP_KEEP_ALIVE.
|
401
|
+
def keep_alive
|
402
|
+
@env['HTTP_KEEP_ALIVE']
|
403
|
+
end
|
404
|
+
|
405
|
+
# ==== Returns
|
406
|
+
# String:: The accepted character sets.
|
407
|
+
def accept_charset
|
408
|
+
@env['HTTP_ACCEPT_CHARSET']
|
409
|
+
end
|
410
|
+
|
411
|
+
# ==== Returns
|
412
|
+
# String:: The HTTP version
|
413
|
+
def version
|
414
|
+
@env['HTTP_VERSION']
|
415
|
+
end
|
416
|
+
|
417
|
+
# ==== Returns
|
418
|
+
# String:: The gateway.
|
419
|
+
def gateway
|
420
|
+
@env['GATEWAY_INTERFACE']
|
421
|
+
end
|
422
|
+
|
423
|
+
# ==== Returns
|
424
|
+
# String:: The accepted response types. Defaults to "*/*".
|
425
|
+
def accept
|
426
|
+
@env['HTTP_ACCEPT'].blank? ? "*/*" : @env['HTTP_ACCEPT']
|
427
|
+
end
|
428
|
+
|
429
|
+
# ==== Returns
|
430
|
+
# String:: The HTTP connection.
|
431
|
+
def connection
|
432
|
+
@env['HTTP_CONNECTION']
|
433
|
+
end
|
434
|
+
|
435
|
+
# ==== Returns
|
436
|
+
# String:: The query string.
|
437
|
+
def query_string
|
438
|
+
@env['QUERY_STRING']
|
439
|
+
end
|
440
|
+
|
441
|
+
# ==== Returns
|
442
|
+
# String:: The request content type.
|
443
|
+
def content_type
|
444
|
+
@env['CONTENT_TYPE']
|
445
|
+
end
|
446
|
+
|
447
|
+
# ==== Returns
|
448
|
+
# Fixnum:: The request content length.
|
449
|
+
def content_length
|
450
|
+
@content_length ||= @env[Merb::Const::CONTENT_LENGTH].to_i
|
451
|
+
end
|
452
|
+
|
453
|
+
# ==== Returns
|
454
|
+
# String::
|
455
|
+
# The URI without the query string. Strips trailing "/" and reduces
|
456
|
+
# duplicate "/" to a single "/".
|
457
|
+
def path
|
458
|
+
path = (uri.empty? ? '/' : uri.split('?').first).squeeze("/")
|
459
|
+
path = path[0..-2] if (path[-1] == ?/) && path.size > 1
|
460
|
+
path
|
461
|
+
end
|
462
|
+
|
463
|
+
# ==== Returns
|
464
|
+
# String:: The path info.
|
465
|
+
def path_info
|
466
|
+
@path_info ||= self.class.unescape(@env['PATH_INFO'])
|
467
|
+
end
|
468
|
+
|
469
|
+
# ==== Returns
|
470
|
+
# Fixnum:: The server port.
|
471
|
+
def port
|
472
|
+
@env['SERVER_PORT'].to_i
|
473
|
+
end
|
474
|
+
|
475
|
+
# ==== Returns
|
476
|
+
# String:: The full hostname including the port.
|
477
|
+
def host
|
478
|
+
@env['HTTP_X_FORWARDED_HOST'] || @env['HTTP_HOST']
|
479
|
+
end
|
480
|
+
|
481
|
+
# ==== Parameters
|
482
|
+
# tld_length<Fixnum>::
|
483
|
+
# Number of domains levels to inlclude in the top level domain. Defaults
|
484
|
+
# to 1.
|
485
|
+
#
|
486
|
+
# ==== Returns
|
487
|
+
# Array:: All the subdomain parts of the host.
|
488
|
+
def subdomains(tld_length = 1)
|
489
|
+
parts = host.split('.')
|
490
|
+
parts[0..-(tld_length+2)]
|
491
|
+
end
|
492
|
+
|
493
|
+
# ==== Parameters
|
494
|
+
# tld_length<Fixnum>::
|
495
|
+
# Number of domains levels to inlclude in the top level domain. Defaults
|
496
|
+
# to 1.
|
497
|
+
#
|
498
|
+
# ==== Returns
|
499
|
+
# String:: The full domain name without the port number.
|
500
|
+
def domain(tld_length = 1)
|
501
|
+
host.split('.').last(1 + tld_length).join('.').sub(/:\d+$/,'')
|
502
|
+
end
|
503
|
+
|
504
|
+
# ==== Returns
|
505
|
+
# Value of If-None-Match request header.
|
506
|
+
def if_none_match
|
507
|
+
@env[Merb::Const::HTTP_IF_NONE_MATCH]
|
508
|
+
end
|
509
|
+
|
510
|
+
# ==== Returns
|
511
|
+
# Value of If-Modified-Since request header.
|
512
|
+
def if_modified_since
|
513
|
+
if time = @env[Merb::Const::HTTP_IF_MODIFIED_SINCE]
|
514
|
+
Time.rfc2822(time)
|
515
|
+
end
|
516
|
+
end
|
517
|
+
|
518
|
+
class << self
|
519
|
+
|
520
|
+
# ==== Parameters
|
521
|
+
# value<Array, Hash, Dictionary ~to_s>:: The value for the query string.
|
522
|
+
# prefix<~to_s>:: The prefix to add to the query string keys.
|
523
|
+
#
|
524
|
+
# ==== Returns
|
525
|
+
# String:: The query string.
|
526
|
+
#
|
527
|
+
# ==== Alternatives
|
528
|
+
# If the value is a string, the prefix will be used as the key.
|
529
|
+
#
|
530
|
+
# ==== Examples
|
531
|
+
# params_to_query_string(10, "page")
|
532
|
+
# # => "page=10"
|
533
|
+
# params_to_query_string({ :page => 10, :word => "ruby" })
|
534
|
+
# # => "page=10&word=ruby"
|
535
|
+
# params_to_query_string({ :page => 10, :word => "ruby" }, "search")
|
536
|
+
# # => "search[page]=10&search[word]=ruby"
|
537
|
+
# params_to_query_string([ "ice-cream", "cake" ], "shopping_list")
|
538
|
+
# # => "shopping_list[]=ice-cream&shopping_list[]=cake"
|
539
|
+
def params_to_query_string(value, prefix = nil)
|
540
|
+
case value
|
541
|
+
when Array
|
542
|
+
value.map { |v|
|
543
|
+
params_to_query_string(v, "#{prefix}[]")
|
544
|
+
} * "&"
|
545
|
+
when Hash, Dictionary
|
546
|
+
value.map { |k, v|
|
547
|
+
params_to_query_string(v, prefix ? "#{prefix}[#{Merb::Request.escape(k)}]" : Merb::Request.escape(k))
|
548
|
+
} * "&"
|
549
|
+
else
|
550
|
+
"#{prefix}=#{Merb::Request.escape(value)}"
|
551
|
+
end
|
552
|
+
end
|
553
|
+
|
554
|
+
# ==== Parameters
|
555
|
+
# s<String>:: String to URL escape.
|
556
|
+
#
|
557
|
+
# ==== returns
|
558
|
+
# String:: The escaped string.
|
559
|
+
def escape(s)
|
560
|
+
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
561
|
+
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
562
|
+
}.tr(' ', '+')
|
563
|
+
end
|
564
|
+
|
565
|
+
# ==== Parameter
|
566
|
+
# s<String>:: String to URL unescape.
|
567
|
+
#
|
568
|
+
# ==== returns
|
569
|
+
# String:: The unescaped string.
|
570
|
+
def unescape(s)
|
571
|
+
s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
|
572
|
+
[$1.delete('%')].pack('H*')
|
573
|
+
}
|
574
|
+
end
|
575
|
+
|
576
|
+
# ==== Parameters
|
577
|
+
# query_string<String>:: The query string.
|
578
|
+
# delimiter<String>:: The query string divider. Defaults to "&".
|
579
|
+
# preserve_order<Boolean>:: Preserve order of args. Defaults to false.
|
580
|
+
#
|
581
|
+
# ==== Returns
|
582
|
+
# Mash:: The parsed query string (Dictionary if preserve_order is set).
|
583
|
+
#
|
584
|
+
# ==== Examples
|
585
|
+
# query_parse("bar=nik&post[body]=heya")
|
586
|
+
# # => { :bar => "nik", :post => { :body => "heya" } }
|
587
|
+
def query_parse(query_string, delimiter = '&;', preserve_order = false)
|
588
|
+
query = preserve_order ? Dictionary.new : {}
|
589
|
+
for pair in (query_string || '').split(/[#{delimiter}] */n)
|
590
|
+
key, value = unescape(pair).split('=',2)
|
591
|
+
next if key.nil?
|
592
|
+
if key.include?('[')
|
593
|
+
normalize_params(query, key, value)
|
594
|
+
else
|
595
|
+
query[key] = value
|
596
|
+
end
|
597
|
+
end
|
598
|
+
preserve_order ? query : query.to_mash
|
599
|
+
end
|
600
|
+
|
601
|
+
NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
|
602
|
+
CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
|
603
|
+
FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
|
604
|
+
CRLF = "\r\n".freeze
|
605
|
+
EOL = CRLF
|
606
|
+
|
607
|
+
# ==== Parameters
|
608
|
+
# request<IO>:: The raw request.
|
609
|
+
# boundary<String>:: The boundary string.
|
610
|
+
# content_length<Fixnum>:: The length of the content.
|
611
|
+
#
|
612
|
+
# ==== Raises
|
613
|
+
# ControllerExceptions::MultiPartParseError:: Failed to parse request.
|
614
|
+
#
|
615
|
+
# ==== Returns
|
616
|
+
# Hash:: The parsed request.
|
617
|
+
def parse_multipart(request, boundary, content_length)
|
618
|
+
boundary = "--#{boundary}"
|
619
|
+
paramhsh = {}
|
620
|
+
buf = ""
|
621
|
+
input = request
|
622
|
+
input.binmode if defined? input.binmode
|
623
|
+
boundary_size = boundary.size + EOL.size
|
624
|
+
bufsize = 16384
|
625
|
+
content_length -= boundary_size
|
626
|
+
status = input.read(boundary_size)
|
627
|
+
return {} if status == nil || status.empty?
|
628
|
+
raise ControllerExceptions::MultiPartParseError, "bad content body:\n'#{status}' should == '#{boundary + EOL}'" unless status == boundary + EOL
|
629
|
+
rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
|
630
|
+
loop {
|
631
|
+
head = nil
|
632
|
+
body = ''
|
633
|
+
filename = content_type = name = nil
|
634
|
+
read_size = 0
|
635
|
+
until head && buf =~ rx
|
636
|
+
i = buf.index("\r\n\r\n")
|
637
|
+
if( i == nil && read_size == 0 && content_length == 0 )
|
638
|
+
content_length = -1
|
639
|
+
break
|
640
|
+
end
|
641
|
+
if !head && i
|
642
|
+
head = buf.slice!(0, i+2) # First \r\n
|
643
|
+
buf.slice!(0, 2) # Second \r\n
|
644
|
+
filename = head[FILENAME_REGEX, 1]
|
645
|
+
content_type = head[CONTENT_TYPE_REGEX, 1]
|
646
|
+
name = head[NAME_REGEX, 1]
|
647
|
+
|
648
|
+
if filename && !filename.empty?
|
649
|
+
body = Tempfile.new(:Merb)
|
650
|
+
body.binmode if defined? body.binmode
|
651
|
+
end
|
652
|
+
next
|
653
|
+
end
|
654
|
+
|
655
|
+
# Save the read body part.
|
656
|
+
if head && (boundary_size+4 < buf.size)
|
657
|
+
body << buf.slice!(0, buf.size - (boundary_size+4))
|
658
|
+
end
|
659
|
+
|
660
|
+
read_size = bufsize < content_length ? bufsize : content_length
|
661
|
+
if( read_size > 0 )
|
662
|
+
c = input.read(read_size)
|
663
|
+
raise ControllerExceptions::MultiPartParseError, "bad content body" if c.nil? || c.empty?
|
664
|
+
buf << c
|
665
|
+
content_length -= c.size
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
# Save the rest.
|
670
|
+
if i = buf.index(rx)
|
671
|
+
body << buf.slice!(0, i)
|
672
|
+
buf.slice!(0, boundary_size+2)
|
673
|
+
|
674
|
+
content_length = -1 if $1 == "--"
|
675
|
+
end
|
676
|
+
|
677
|
+
if filename && !filename.empty?
|
678
|
+
body.rewind
|
679
|
+
data = {
|
680
|
+
:filename => File.basename(filename),
|
681
|
+
:content_type => content_type,
|
682
|
+
:tempfile => body,
|
683
|
+
:size => File.size(body.path)
|
684
|
+
}
|
685
|
+
else
|
686
|
+
data = body
|
687
|
+
end
|
688
|
+
paramhsh = normalize_params(paramhsh,name,data)
|
689
|
+
break if buf.empty? || content_length == -1
|
690
|
+
}
|
691
|
+
paramhsh
|
692
|
+
end
|
693
|
+
|
694
|
+
# Converts a query string snippet to a hash and adds it to existing
|
695
|
+
# parameters.
|
696
|
+
#
|
697
|
+
# ==== Parameters
|
698
|
+
# parms<Hash>:: Parameters to add the normalized parameters to.
|
699
|
+
# name<String>:: The key of the parameter to normalize.
|
700
|
+
# val<String>:: The value of the parameter.
|
701
|
+
#
|
702
|
+
# ==== Returns
|
703
|
+
# Hash:: Normalized parameters
|
704
|
+
def normalize_params(parms, name, val=nil)
|
705
|
+
name =~ %r([\[\]]*([^\[\]]+)\]*)
|
706
|
+
key = $1 || ''
|
707
|
+
after = $' || ''
|
708
|
+
|
709
|
+
if after == ""
|
710
|
+
parms[key] = val
|
711
|
+
elsif after == "[]"
|
712
|
+
(parms[key] ||= []) << val
|
713
|
+
elsif after =~ %r(^\[\]\[([^\[\]]+)\]$)
|
714
|
+
child_key = $1
|
715
|
+
parms[key] ||= []
|
716
|
+
if parms[key].last.is_a?(Hash) && !parms[key].last.key?(child_key)
|
717
|
+
parms[key].last.update(child_key => val)
|
718
|
+
else
|
719
|
+
parms[key] << { child_key => val }
|
720
|
+
end
|
721
|
+
else
|
722
|
+
parms[key] ||= {}
|
723
|
+
parms[key] = normalize_params(parms[key], after, val)
|
724
|
+
end
|
725
|
+
parms
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
729
|
+
end
|