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.
Files changed (98) hide show
  1. data/CHANGELOG +992 -0
  2. data/CONTRIBUTORS +94 -0
  3. data/LICENSE +20 -0
  4. data/PUBLIC_CHANGELOG +142 -0
  5. data/README +21 -0
  6. data/Rakefile +458 -0
  7. data/TODO +0 -0
  8. data/bin/merb +11 -0
  9. data/bin/merb-specs +5 -0
  10. data/lib/merb-core.rb +598 -0
  11. data/lib/merb-core/autoload.rb +31 -0
  12. data/lib/merb-core/bootloader.rb +717 -0
  13. data/lib/merb-core/config.rb +305 -0
  14. data/lib/merb-core/constants.rb +45 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +568 -0
  16. data/lib/merb-core/controller/exceptions.rb +315 -0
  17. data/lib/merb-core/controller/merb_controller.rb +256 -0
  18. data/lib/merb-core/controller/mime.rb +107 -0
  19. data/lib/merb-core/controller/mixins/authentication.rb +123 -0
  20. data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
  21. data/lib/merb-core/controller/mixins/controller.rb +319 -0
  22. data/lib/merb-core/controller/mixins/render.rb +513 -0
  23. data/lib/merb-core/controller/mixins/responder.rb +469 -0
  24. data/lib/merb-core/controller/template.rb +254 -0
  25. data/lib/merb-core/core_ext.rb +9 -0
  26. data/lib/merb-core/core_ext/hash.rb +7 -0
  27. data/lib/merb-core/core_ext/kernel.rb +340 -0
  28. data/lib/merb-core/dispatch/cookies.rb +130 -0
  29. data/lib/merb-core/dispatch/default_exception/default_exception.rb +93 -0
  30. data/lib/merb-core/dispatch/default_exception/views/_css.html.erb +198 -0
  31. data/lib/merb-core/dispatch/default_exception/views/_javascript.html.erb +73 -0
  32. data/lib/merb-core/dispatch/default_exception/views/index.html.erb +94 -0
  33. data/lib/merb-core/dispatch/dispatcher.rb +176 -0
  34. data/lib/merb-core/dispatch/request.rb +729 -0
  35. data/lib/merb-core/dispatch/router.rb +151 -0
  36. data/lib/merb-core/dispatch/router/behavior.rb +566 -0
  37. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  38. data/lib/merb-core/dispatch/router/resources.rb +191 -0
  39. data/lib/merb-core/dispatch/router/route.rb +511 -0
  40. data/lib/merb-core/dispatch/session.rb +222 -0
  41. data/lib/merb-core/dispatch/session/container.rb +74 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +173 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +68 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +99 -0
  45. data/lib/merb-core/dispatch/session/store_container.rb +150 -0
  46. data/lib/merb-core/dispatch/worker.rb +28 -0
  47. data/lib/merb-core/gem_ext/erubis.rb +77 -0
  48. data/lib/merb-core/logger.rb +203 -0
  49. data/lib/merb-core/plugins.rb +67 -0
  50. data/lib/merb-core/rack.rb +25 -0
  51. data/lib/merb-core/rack/adapter.rb +44 -0
  52. data/lib/merb-core/rack/adapter/ebb.rb +25 -0
  53. data/lib/merb-core/rack/adapter/evented_mongrel.rb +26 -0
  54. data/lib/merb-core/rack/adapter/fcgi.rb +17 -0
  55. data/lib/merb-core/rack/adapter/irb.rb +118 -0
  56. data/lib/merb-core/rack/adapter/mongrel.rb +26 -0
  57. data/lib/merb-core/rack/adapter/runner.rb +28 -0
  58. data/lib/merb-core/rack/adapter/swiftiplied_mongrel.rb +26 -0
  59. data/lib/merb-core/rack/adapter/thin.rb +39 -0
  60. data/lib/merb-core/rack/adapter/thin_turbo.rb +24 -0
  61. data/lib/merb-core/rack/adapter/webrick.rb +36 -0
  62. data/lib/merb-core/rack/application.rb +32 -0
  63. data/lib/merb-core/rack/handler/mongrel.rb +97 -0
  64. data/lib/merb-core/rack/middleware.rb +20 -0
  65. data/lib/merb-core/rack/middleware/conditional_get.rb +29 -0
  66. data/lib/merb-core/rack/middleware/content_length.rb +18 -0
  67. data/lib/merb-core/rack/middleware/csrf.rb +73 -0
  68. data/lib/merb-core/rack/middleware/path_prefix.rb +31 -0
  69. data/lib/merb-core/rack/middleware/profiler.rb +19 -0
  70. data/lib/merb-core/rack/middleware/static.rb +45 -0
  71. data/lib/merb-core/rack/middleware/tracer.rb +20 -0
  72. data/lib/merb-core/server.rb +284 -0
  73. data/lib/merb-core/tasks/audit.rake +68 -0
  74. data/lib/merb-core/tasks/gem_management.rb +229 -0
  75. data/lib/merb-core/tasks/merb.rb +1 -0
  76. data/lib/merb-core/tasks/merb_rake_helper.rb +80 -0
  77. data/lib/merb-core/tasks/stats.rake +71 -0
  78. data/lib/merb-core/test.rb +11 -0
  79. data/lib/merb-core/test/helpers.rb +9 -0
  80. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  81. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  82. data/lib/merb-core/test/helpers/request_helper.rb +393 -0
  83. data/lib/merb-core/test/helpers/route_helper.rb +39 -0
  84. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  85. data/lib/merb-core/test/matchers.rb +9 -0
  86. data/lib/merb-core/test/matchers/controller_matchers.rb +351 -0
  87. data/lib/merb-core/test/matchers/route_matchers.rb +137 -0
  88. data/lib/merb-core/test/matchers/view_matchers.rb +375 -0
  89. data/lib/merb-core/test/run_specs.rb +49 -0
  90. data/lib/merb-core/test/tasks/spectasks.rb +68 -0
  91. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  92. data/lib/merb-core/test/test_ext/object.rb +14 -0
  93. data/lib/merb-core/test/test_ext/string.rb +14 -0
  94. data/lib/merb-core/vendor/facets.rb +2 -0
  95. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  96. data/lib/merb-core/vendor/facets/inflect.rb +342 -0
  97. data/lib/merb-core/version.rb +3 -0
  98. 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>&nbsp;</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