merb 0.3.7 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (173) hide show
  1. data/README +25 -26
  2. data/Rakefile +48 -36
  3. data/app_generators/merb/USAGE +5 -0
  4. data/app_generators/merb/merb_generator.rb +107 -0
  5. data/app_generators/merb/templates/Rakefile +99 -0
  6. data/{examples/skeleton/dist → app_generators/merb/templates}/app/controllers/application.rb +1 -1
  7. data/app_generators/merb/templates/app/controllers/exceptions.rb +13 -0
  8. data/{examples/skeleton/dist → app_generators/merb/templates}/app/helpers/global_helper.rb +0 -0
  9. data/{examples/skeleton/dist/app/mailers → app_generators/merb/templates/app/mailers/views}/layout/application.erb +0 -0
  10. data/app_generators/merb/templates/app/views/exceptions/internal_server_error.html.erb +207 -0
  11. data/app_generators/merb/templates/app/views/exceptions/not_acceptable.html.erb +38 -0
  12. data/app_generators/merb/templates/app/views/exceptions/not_found.html.erb +40 -0
  13. data/app_generators/merb/templates/app/views/layout/application.html.erb +11 -0
  14. data/app_generators/merb/templates/config/boot.rb +11 -0
  15. data/app_generators/merb/templates/config/dependencies.rb +41 -0
  16. data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/development.rb +0 -0
  17. data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/production.rb +0 -0
  18. data/{examples/skeleton/dist/conf → app_generators/merb/templates/config}/environments/test.rb +0 -0
  19. data/app_generators/merb/templates/config/merb.yml +64 -0
  20. data/app_generators/merb/templates/config/merb_init.rb +16 -0
  21. data/app_generators/merb/templates/config/plugins.yml +1 -0
  22. data/app_generators/merb/templates/config/router.rb +32 -0
  23. data/{lib/merb/core_ext/merb_array.rb → app_generators/merb/templates/config/upload.conf} +0 -0
  24. data/app_generators/merb/templates/public/images/merb.jpg +0 -0
  25. data/app_generators/merb/templates/public/merb.fcgi +6 -0
  26. data/app_generators/merb/templates/public/stylesheets/master.css +119 -0
  27. data/app_generators/merb/templates/script/destroy +28 -0
  28. data/app_generators/merb/templates/script/generate +28 -0
  29. data/{examples/skeleton → app_generators/merb/templates}/script/stop_merb +0 -0
  30. data/app_generators/merb/templates/script/win_script.cmd +1 -0
  31. data/app_generators/merb/templates/spec/spec.opts +6 -0
  32. data/app_generators/merb/templates/spec/spec_helper.rb +10 -0
  33. data/app_generators/merb/templates/test/test_helper.rb +13 -0
  34. data/app_generators/merb_plugin/USAGE +5 -0
  35. data/app_generators/merb_plugin/merb_plugin_generator.rb +64 -0
  36. data/app_generators/merb_plugin/templates/LICENSE +20 -0
  37. data/app_generators/merb_plugin/templates/README +4 -0
  38. data/app_generators/merb_plugin/templates/Rakefile +35 -0
  39. data/app_generators/merb_plugin/templates/TODO +5 -0
  40. data/app_generators/merb_plugin/templates/merbtasks.rb +6 -0
  41. data/app_generators/merb_plugin/templates/sampleplugin.rb +10 -0
  42. data/app_generators/merb_plugin/templates/sampleplugin_spec.rb +7 -0
  43. data/app_generators/merb_plugin/templates/spec_helper.rb +2 -0
  44. data/bin/merb +1 -1
  45. data/lib/autotest/discover.rb +3 -0
  46. data/lib/autotest/merb_rspec.rb +79 -0
  47. data/lib/merb.rb +72 -93
  48. data/lib/merb/{merb_abstract_controller.rb → abstract_controller.rb} +28 -5
  49. data/lib/merb/caching/action_cache.rb +65 -29
  50. data/lib/merb/caching/fragment_cache.rb +9 -4
  51. data/lib/merb/caching/store/file_cache.rb +22 -14
  52. data/lib/merb/caching/store/memory_cache.rb +26 -8
  53. data/lib/merb/{merb_constants.rb → constants.rb} +9 -7
  54. data/lib/merb/controller.rb +178 -0
  55. data/lib/merb/core_ext.rb +13 -11
  56. data/lib/merb/core_ext/array.rb +0 -0
  57. data/lib/merb/core_ext/{merb_class.rb → class.rb} +0 -0
  58. data/lib/merb/core_ext/{merb_enumerable.rb → enumerable.rb} +0 -0
  59. data/lib/merb/core_ext/get_args.rb +52 -0
  60. data/lib/merb/core_ext/{merb_hash.rb → hash.rb} +40 -11
  61. data/lib/merb/core_ext/{merb_inflections.rb → inflections.rb} +0 -0
  62. data/lib/merb/core_ext/{merb_inflector.rb → inflector.rb} +1 -1
  63. data/lib/merb/core_ext/{merb_kernel.rb → kernel.rb} +56 -3
  64. data/lib/merb/core_ext/mash.rb +88 -0
  65. data/lib/merb/core_ext/{merb_module.rb → module.rb} +0 -0
  66. data/lib/merb/core_ext/{merb_numeric.rb → numeric.rb} +0 -0
  67. data/lib/merb/core_ext/{merb_object.rb → object.rb} +10 -47
  68. data/lib/merb/core_ext/string.rb +56 -0
  69. data/lib/merb/core_ext/{merb_symbol.rb → symbol.rb} +0 -0
  70. data/lib/merb/dispatcher.rb +109 -0
  71. data/lib/merb/{merb_drb_server.rb → drb_server.rb} +0 -0
  72. data/lib/merb/erubis_ext.rb +10 -0
  73. data/lib/merb/exceptions.rb +173 -0
  74. data/lib/merb/generators/merb_app/merb_app.rb +5 -25
  75. data/lib/merb/generators/merb_generator_helpers.rb +317 -0
  76. data/lib/merb/generators/merb_plugin.rb +19 -0
  77. data/lib/merb/logger.rb +65 -0
  78. data/lib/merb/{merb_mail_controller.rb → mail_controller.rb} +102 -49
  79. data/lib/merb/{merb_mailer.rb → mailer.rb} +31 -27
  80. data/lib/merb/mixins/{basic_authentication_mixin.rb → basic_authentication.rb} +3 -3
  81. data/lib/merb/mixins/{controller_mixin.rb → controller.rb} +131 -112
  82. data/lib/merb/mixins/{erubis_capture_mixin.rb → erubis_capture.rb} +12 -21
  83. data/lib/merb/mixins/{form_control_mixin.rb → form_control.rb} +6 -12
  84. data/lib/merb/mixins/render.rb +401 -0
  85. data/lib/merb/mixins/responder.rb +378 -0
  86. data/lib/merb/mixins/{view_context_mixin.rb → view_context.rb} +65 -10
  87. data/lib/merb/mixins/web_controller.rb +29 -0
  88. data/lib/merb/{merb_handler.rb → mongrel_handler.rb} +59 -38
  89. data/lib/merb/part_controller.rb +19 -0
  90. data/lib/merb/plugins.rb +16 -0
  91. data/lib/merb/rack_adapter.rb +37 -0
  92. data/lib/merb/request.rb +421 -0
  93. data/lib/merb/router.rb +576 -0
  94. data/lib/merb/{merb_server.rb → server.rb} +275 -71
  95. data/lib/merb/session.rb +10 -10
  96. data/lib/merb/session/cookie_store.rb +125 -0
  97. data/lib/merb/session/{merb_mem_cache_session.rb → mem_cache_session.rb} +22 -9
  98. data/lib/merb/session/{merb_memory_session.rb → memory_session.rb} +15 -11
  99. data/lib/merb/template.rb +35 -8
  100. data/lib/merb/template/erubis.rb +16 -10
  101. data/lib/merb/template/haml.rb +33 -20
  102. data/lib/merb/template/markaby.rb +16 -14
  103. data/lib/merb/template/xml_builder.rb +8 -4
  104. data/lib/merb/test/{merb_fake_request.rb → fake_request.rb} +11 -5
  105. data/lib/merb/test/helper.rb +31 -0
  106. data/lib/merb/test/hpricot.rb +136 -0
  107. data/lib/merb/test/{merb_multipart.rb → multipart.rb} +1 -1
  108. data/lib/merb/test/rspec.rb +93 -0
  109. data/lib/merb/{merb_upload_handler.rb → upload_handler.rb} +5 -6
  110. data/lib/merb/{merb_upload_progress.rb → upload_progress.rb} +1 -1
  111. data/lib/merb/{merb_view_context.rb → view_context.rb} +27 -42
  112. data/lib/{merb_tasks.rb → tasks.rb} +0 -0
  113. data/lib/tasks/merb.rake +21 -11
  114. data/merb_default_generators/model/USAGE +0 -0
  115. data/merb_default_generators/model/model_generator.rb +16 -0
  116. data/merb_default_generators/model/templates/new_model_template.erb +5 -0
  117. data/merb_default_generators/resource_controller/USAGE +0 -0
  118. data/merb_default_generators/resource_controller/resource_controller_generator.rb +26 -0
  119. data/merb_default_generators/resource_controller/templates/controller.rb +30 -0
  120. data/merb_default_generators/resource_controller/templates/edit.html.erb +1 -0
  121. data/merb_default_generators/resource_controller/templates/helper.rb +5 -0
  122. data/merb_default_generators/resource_controller/templates/index.html.erb +1 -0
  123. data/merb_default_generators/resource_controller/templates/new.html.erb +1 -0
  124. data/merb_default_generators/resource_controller/templates/show.html.erb +1 -0
  125. data/merb_generators/controller/USAGE +5 -0
  126. data/merb_generators/controller/controller_generator.rb +16 -0
  127. data/merb_generators/controller/templates/controller.rb +8 -0
  128. data/merb_generators/controller/templates/helper.rb +5 -0
  129. data/merb_generators/controller/templates/index.html.erb +3 -0
  130. data/merb_generators/resource/USAGE +0 -0
  131. data/merb_generators/resource/resource_generator.rb +60 -0
  132. data/rspec_generators/merb_controller_test/merb_controller_test_generator.rb +67 -0
  133. data/rspec_generators/merb_controller_test/templates/controller_spec.rb +8 -0
  134. data/rspec_generators/merb_controller_test/templates/edit_spec.rb +12 -0
  135. data/rspec_generators/merb_controller_test/templates/helper_spec.rb +5 -0
  136. data/rspec_generators/merb_controller_test/templates/index_spec.rb +12 -0
  137. data/rspec_generators/merb_controller_test/templates/new_spec.rb +12 -0
  138. data/rspec_generators/merb_controller_test/templates/show_spec.rb +5 -0
  139. data/rspec_generators/merb_model_test/merb_model_test_generator.rb +26 -0
  140. data/rspec_generators/merb_model_test/templates/model_spec_template.erb +7 -0
  141. data/script/destroy +14 -0
  142. data/script/generate +14 -0
  143. data/test_unit_generators/merb_controller_test/merb_controller_test_generator.rb +53 -0
  144. data/test_unit_generators/merb_controller_test/templates/functional_test.rb +17 -0
  145. data/test_unit_generators/merb_controller_test/templates/helper_test.rb +9 -0
  146. data/test_unit_generators/merb_model_test/merb_model_test_generator.rb +29 -0
  147. data/test_unit_generators/merb_model_test/templates/model_test_unit_template.erb +9 -0
  148. metadata +172 -94
  149. data/examples/README_EXAMPLES +0 -10
  150. data/examples/skeleton/Rakefile +0 -68
  151. data/examples/skeleton/dist/app/views/layout/application.herb +0 -12
  152. data/examples/skeleton/dist/conf/database.yml +0 -23
  153. data/examples/skeleton/dist/conf/merb.yml +0 -57
  154. data/examples/skeleton/dist/conf/merb_init.rb +0 -24
  155. data/examples/skeleton/dist/conf/router.rb +0 -22
  156. data/examples/skeleton/dist/conf/upload.conf +0 -5
  157. data/examples/skeleton/dist/schema/migrations/001_add_sessions_table.rb +0 -14
  158. data/examples/skeleton/script/new_migration +0 -21
  159. data/lib/merb/core_ext/merb_string.rb +0 -18
  160. data/lib/merb/merb_controller.rb +0 -206
  161. data/lib/merb/merb_dispatcher.rb +0 -87
  162. data/lib/merb/merb_exceptions.rb +0 -319
  163. data/lib/merb/merb_part_controller.rb +0 -42
  164. data/lib/merb/merb_plugins.rb +0 -293
  165. data/lib/merb/merb_request.rb +0 -165
  166. data/lib/merb/merb_router.rb +0 -309
  167. data/lib/merb/merb_yaml_store.rb +0 -31
  168. data/lib/merb/mixins/render_mixin.rb +0 -283
  169. data/lib/merb/mixins/responder_mixin.rb +0 -159
  170. data/lib/merb/session/merb_ar_session.rb +0 -131
  171. data/lib/merb/vendor/paginator/README.txt +0 -84
  172. data/lib/merb/vendor/paginator/paginator.rb +0 -124
  173. data/lib/tasks/db.rake +0 -55
@@ -0,0 +1,29 @@
1
+ module Merb
2
+ module WebControllerMixin
3
+
4
+ def request
5
+ @web_controller.request
6
+ end
7
+
8
+ def params
9
+ @web_controller.params
10
+ end
11
+
12
+ def cookies
13
+ @web_controller.cookies
14
+ end
15
+
16
+ def headers
17
+ @web_controller.headers
18
+ end
19
+
20
+ def session
21
+ @web_controller.session
22
+ end
23
+
24
+ def response
25
+ @web_controller.response
26
+ end
27
+
28
+ end
29
+ end
@@ -1,7 +1,7 @@
1
1
  class Mongrel::HttpResponse
2
2
  NO_CLOSE_STATUS_FORMAT = "HTTP/1.1 %d %s\r\n".freeze
3
3
  def send_status_no_connection_close(content_length=@body.length)
4
- if not @status_sent
4
+ unless @status_sent
5
5
  @header['Content-Length'] = content_length unless @status == 304
6
6
  write(NO_CLOSE_STATUS_FORMAT % [@status, Mongrel::HTTP_STATUS_CODES[@status]])
7
7
  @status_sent = true
@@ -10,7 +10,18 @@ class Mongrel::HttpResponse
10
10
  end
11
11
 
12
12
  class MerbHandler < Mongrel::HttpHandler
13
- @@file_only_methods = ["GET","HEAD"]
13
+ @@file_only_methods = ["GET","HEAD"]
14
+ @@path_prefix = nil
15
+ @@path_prefix_original = nil
16
+
17
+ class << self
18
+ # Use :path_prefix in merb.yml to set this
19
+ def path_prefix() @@path_prefix_original end
20
+ def path_prefix=(prefix)
21
+ @@path_prefix_original = prefix
22
+ @@path_prefix = (prefix.is_a?(String) ? /^#{prefix.escape_regexp}/ : prefix)
23
+ end
24
+ end
14
25
 
15
26
  # Take the name of a directory and use that as the doc root or public
16
27
  # directory of your site. This is set to the root of your merb app + '/public'
@@ -28,58 +39,59 @@ class MerbHandler < Mongrel::HttpHandler
28
39
  # into Merb::RouteMatcher to let it decide which controller and method will
29
40
  # serve the request.
30
41
  # 4. After the controller has done its thing, we check for the X-SENDFILE
31
- # header. if you set this header to the path to a file in your controller
42
+ # header. if you set this header to the path of a file in your controller
32
43
  # then mongrel will serve the file directly and your controller can go on
33
44
  # processing other requests.
34
- def process(request, response)
35
-
36
- start = Time.now
45
+ def process(request, response)
46
+ start = Time.now
37
47
  benchmarks = {}
38
48
 
39
- if response.socket.closed?
40
- return
41
- end
49
+ return if response.socket.closed?
42
50
 
43
- MERB_LOGGER.info("\nRequest: REQUEST_URI: #{request.params[Mongrel::Const::REQUEST_URI]} (#{Time.now.strftime("%Y-%m-%d %H:%M:%S")})")
51
+ MERB_LOGGER.info("\nRequest: REQUEST_URI: #{
52
+ request.params[Mongrel::Const::REQUEST_URI]} (#{Time.now.strftime("%Y-%m-%d %H:%M:%S")})")
53
+
54
+ # Truncate the request URI if there's a path prefix so that an app can be
55
+ # hosted inside a subdirectory, for example.
56
+ if @@path_prefix
57
+ if request.params[Mongrel::Const::PATH_INFO] =~ @@path_prefix
58
+ MERB_LOGGER.info("Path prefix #{@@path_prefix.inspect} removed from PATH_INFO and REQUEST_URI.")
59
+ request.params[Mongrel::Const::PATH_INFO].sub!(@@path_prefix, '')
60
+ request.params[Mongrel::Const::REQUEST_URI].sub!(@@path_prefix, '')
61
+ path_info = request.params[Mongrel::Const::PATH_INFO]
62
+ else
63
+ raise "Path prefix is set to '#{@@path_prefix.inspect}', but is not in the REQUEST_URI. "
64
+ end
65
+ else
66
+ path_info = request.params[Mongrel::Const::PATH_INFO]
67
+ end
44
68
 
45
69
  # Rails style page caching. Check the public dir first for .html pages and
46
70
  # serve directly. Otherwise fall back to Merb routing and request
47
71
  # dispatching.
48
- path_info = request.params[Mongrel::Const::PATH_INFO]
49
72
  page_cached = path_info + ".html"
50
73
  get_or_head = @@file_only_methods.include? request.params[Mongrel::Const::REQUEST_METHOD]
51
74
 
52
- if get_or_head and @files.can_serve(path_info)
75
+ if get_or_head && @files.can_serve(path_info)
53
76
  # File exists as-is so serve it up
54
77
  MERB_LOGGER.info("Serving static file: #{path_info}")
55
78
  @files.process(request,response)
56
- elsif get_or_head and @files.can_serve(page_cached)
79
+ elsif get_or_head && @files.can_serve(page_cached)
57
80
  # Possible cached page, serve it up
58
81
  MERB_LOGGER.info("Serving static file: #{page_cached}")
59
82
  request.params[Mongrel::Const::PATH_INFO] = page_cached
60
83
  @files.process(request,response)
61
84
  else
62
- begin
63
- # Let Merb:Dispatcher find the route and call the filter chain and action
64
- controller = nil
65
- controller, action = Merb::Dispatcher.handle(request, response)
85
+ # Let Merb:Dispatcher find the route and call the filter chain and action
86
+ controller, action = Merb::Dispatcher.handle(request, response)
87
+ benchmarks.merge!(controller._benchmarks)
88
+ benchmarks[:controller] = controller.class.to_s
89
+ benchmarks[:action] = action
66
90
 
67
- benchmarks.merge!(controller._benchmarks)
68
- benchmarks[:controller] = controller.class.to_s
69
- benchmarks[:action] = action
70
-
71
- MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{benchmarks[:setup_time]} seconds")
72
- rescue Object => e
73
- response.start(500) do |head,out|
74
- head["Content-Type"] = "text/html"
75
- MERB_LOGGER.info(Merb.exception(e))
76
- out << Merb.html_exception(e)
77
- end
78
- return
79
- end
91
+ MERB_LOGGER.info("Routing to controller: #{controller.class} action: #{action}\nRoute Recognition & Parsing HTTP Input took: #{benchmarks[:setup_time]} seconds")
80
92
 
81
93
  sendfile, clength = nil
82
- response.status = controller.status
94
+ response.status = controller.status
83
95
  # Check for the X-SENDFILE header from your Merb::Controller and serve
84
96
  # the file directly instead of buffering.
85
97
  controller.headers.each do |k, v|
@@ -96,8 +108,9 @@ class MerbHandler < Mongrel::HttpHandler
96
108
 
97
109
  if sendfile
98
110
  benchmarks[:sendfile_time] = Time.now - start
99
- MERB_LOGGER.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{benchmarks[:sendfile_time]} seconds")
100
- file_status = File.stat(sendfile)
111
+ MERB_LOGGER.info("X-SENDFILE: #{sendfile}\nComplete Request took: #{
112
+ benchmarks[:sendfile_time]} seconds")
113
+ file_status = File.stat(sendfile)
101
114
  response.status = 200
102
115
  # Set the last modified times as well and etag for all files
103
116
  response.header[Mongrel::Const::LAST_MODIFIED] = file_status.mtime.httpdate
@@ -129,9 +142,17 @@ class MerbHandler < Mongrel::HttpHandler
129
142
  total_request_time = Time.now - start
130
143
  benchmarks[:total_request_time] = total_request_time
131
144
 
132
- MERB_LOGGER.info("Request Times: #{benchmarks.inspect}")
133
- MERB_LOGGER.info("Response status: #{response.status}\nComplete Request took: #{total_request_time} seconds, #{1.0/total_request_time} Requests/Second\n\n")
145
+ MERB_LOGGER.info("Request Times: #{benchmarks.inspect}\nResponse status: #{response.status}\nComplete Request took: #{total_request_time} seconds, #{1.0/total_request_time} Requests/Second\n\n")
134
146
  end
135
- end
136
-
137
- end
147
+ rescue Object => e
148
+ # if an exception is raised here then something is
149
+ # wrong with the dispatcher code so we shouldn't pass the
150
+ # exception back in or we might end up in a loop
151
+ response.send_status(500)
152
+ response.send_header
153
+ response.write("500 Internal Server Error")
154
+ MERB_LOGGER.error(Merb.exception(e))
155
+ ensure
156
+ MERB_LOGGER.flush
157
+ end
158
+ end
@@ -0,0 +1,19 @@
1
+ module Merb
2
+ class PartController < AbstractController
3
+ self._template_root = File.expand_path(self._template_root / "../app/parts/views")
4
+ include Merb::WebControllerMixin
5
+
6
+ def initialize(web_controller)
7
+ @web_controller = web_controller
8
+ super
9
+ end
10
+
11
+ def dispatch(action=:to_s)
12
+ old_action = params[:action]
13
+ params[:action] = action
14
+ super(action)
15
+ params[:action] = old_action
16
+ @_body
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ module Merb
2
+ module Plugins
3
+ def self.config
4
+ @config ||= File.exists?(MERB_ROOT / "config" / "plugins.yml") ? YAML.load(File.read(MERB_ROOT / "config" / "plugins.yml")) || {} : {}
5
+ end
6
+
7
+ @rakefiles = []
8
+ def self.rakefiles
9
+ @rakefiles
10
+ end
11
+
12
+ def self.add_rakefiles(*rakefiles)
13
+ @rakefiles += rakefiles
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,37 @@
1
+ module Merb
2
+ module Rack
3
+
4
+ class RequestWrapper
5
+ def initialize(env)
6
+ @env = env
7
+ end
8
+
9
+ def params
10
+ @env
11
+ end
12
+
13
+ def body
14
+ @env['rack.input']
15
+ end
16
+ end
17
+
18
+ class Adapter
19
+ def call(env)
20
+ env["PATH_INFO"] ||= ""
21
+ env["SCRIPT_NAME"] ||= ""
22
+ if env["REQUEST_URI"] =~ %r{(https?://)[^/](.*)}
23
+ env["REQUEST_URI"] = $2
24
+ end
25
+ request = RequestWrapper.new(env)
26
+ response = StringIO.new
27
+ begin
28
+ controller, action = ::Merb::Dispatcher.handle(request, response)
29
+ rescue Object => e
30
+ return [500, {"Content-Type"=>"text/html"}, "Internal Server Error"]
31
+ end
32
+
33
+ [controller.status, controller.headers, controller.body]
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,421 @@
1
+ module Merb
2
+ class Request
3
+ attr_accessor :env, :session
4
+
5
+ # by setting these to false, auto-parsing is disabled; this way you can do your own parsing instead
6
+ cattr_accessor :parse_multipart_params, :parse_json_params, :parse_xml_params
7
+ self.parse_multipart_params = true
8
+ self.parse_json_params = true
9
+ self.parse_xml_params = true
10
+
11
+ def initialize(http_request)
12
+ @env = http_request.params
13
+ @body = http_request.body
14
+ end
15
+
16
+ METHODS = %w{get post put delete head}
17
+
18
+ def method
19
+ @method ||= begin
20
+ request_method = @env['REQUEST_METHOD'].downcase.to_sym
21
+ case request_method
22
+ when :get, :head, :put, :delete
23
+ request_method
24
+ when :post
25
+ m = body_and_query_params['_method']
26
+ m.downcase! if m
27
+ METHODS.include?(m) ? m.to_sym : :post
28
+ else
29
+ raise "Unknown REQUEST_METHOD: #{@env['REQUEST_METHOD']}"
30
+ end
31
+ end
32
+ end
33
+
34
+ # create predicate methods for querying the REQUEST_METHOD
35
+ # get? post? head? put? etc
36
+ METHODS.each do |m|
37
+ define_method("#{m}?") { method == m.to_sym }
38
+ end
39
+
40
+ private
41
+
42
+ # FIXME: symbolize_keys on params is a potential problem. symbols are
43
+ # not garbage collected so a malicious user could send many large query
44
+ # keys to Merb forcing it to eat up memeory.
45
+
46
+ # A hash of parameters passed from the URL like ?blah=hello
47
+ def query_params
48
+ @query_params ||= self.class.query_parse(query_string || '')
49
+ end
50
+
51
+ # A hash of parameters passed in the body of the request.
52
+ #
53
+ # Ajax calls from prototype.js and other libraries
54
+ # pass content this way.
55
+ def body_params
56
+ @body_params ||= begin
57
+ if content_type && content_type.match(Merb::Const::FORM_URL_ENCODED_REGEXP) # or content_type.nil?
58
+ self.class.query_parse(raw_post)
59
+ end
60
+ end
61
+ end
62
+
63
+ def body_and_query_params
64
+ # ^-- FIXME a better name for this method
65
+ @body_and_query_params ||= begin
66
+ h = query_params
67
+ h.merge!(body_params) if body_params
68
+ h
69
+ end
70
+ end
71
+
72
+ def multipart_params
73
+ @multipart_params ||= begin
74
+ if Merb::Const::MULTIPART_REGEXP =~ content_type
75
+ self.class.parse_multipart(@body, $1, content_length)
76
+ end
77
+ end
78
+ end
79
+
80
+ def json_params
81
+ @json_params ||= begin
82
+ if [Merb::Const::APPLICATION_JSON, Merb::Const::TEXT_JSON].include?(content_type)
83
+ JSON.parse(raw_post)
84
+ end
85
+ end
86
+ end
87
+
88
+ def xml_params
89
+ @xml_params ||= begin
90
+ if [Merb::Const::APPLICATION_XML, Merb::Const::TEXT_XML].include?(content_type)
91
+ Hash.from_xml(raw_post) rescue Mash.new
92
+ end
93
+ end
94
+ end
95
+
96
+ public
97
+
98
+ def params
99
+ @params ||= begin
100
+ h = route_params.to_mash.merge(body_and_query_params)
101
+ h.merge!(multipart_params) if self.class.parse_multipart_params && multipart_params
102
+ h.merge!(json_params) if self.class.parse_json_params && json_params
103
+ h.merge!(xml_params) if self.class.parse_xml_params && xml_params
104
+ h
105
+ end
106
+ end
107
+
108
+ def cookies
109
+ @cookies ||= self.class.query_parse(@env[Merb::Const::HTTP_COOKIE], ';,')
110
+ end
111
+
112
+ def route
113
+ @route ||= Merb::Router.routes[route_index]
114
+ end
115
+
116
+ # returns two objects, route_index and route_params
117
+ def route_match
118
+ @route_match ||= Merb::Router.match(self, body_and_query_params)
119
+ end
120
+ private :route_match
121
+
122
+ def route_index
123
+ route_match.first
124
+ end
125
+
126
+ def route_params
127
+ route_match.last
128
+ end
129
+
130
+ def controller_name
131
+ route_params[:controller]
132
+ end
133
+
134
+ def controller_class
135
+ begin
136
+ cnt = controller_name.to_const_string
137
+ rescue ::String::InvalidPathConversion
138
+ raise ControllerExceptions::NotFound
139
+ end
140
+ if !Controller._subclasses.include?(cnt)
141
+ raise ControllerExceptions::NotFound, "Controller '#{cnt}' not found"
142
+ end
143
+
144
+ begin
145
+ if cnt == "Application"
146
+ raise ControllerExceptions::NotFound, "The 'Application' controller has no public actions"
147
+ end
148
+ return Object.full_const_get(cnt)
149
+ rescue NameError
150
+ raise ControllerExceptions::NotFound
151
+ end
152
+ end
153
+
154
+ def action
155
+ route_params[:action]
156
+ end
157
+
158
+ def raw_post
159
+ @body.rewind
160
+ @body.read
161
+ end
162
+
163
+ # Returns true if the request is an Ajax request.
164
+ #
165
+ # Also aliased as the more memorable ajax? and xhr?.
166
+ def xml_http_request?
167
+ not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
168
+ end
169
+ alias xhr? :xml_http_request?
170
+ alias ajax? :xml_http_request?
171
+
172
+ # returns the remote IP address if it can find it.
173
+ def remote_ip
174
+ return @env['HTTP_CLIENT_IP'] if @env.include?('HTTP_CLIENT_IP')
175
+
176
+ if @env.include?(Merb::Const::HTTP_X_FORWARDED_FOR) then
177
+ remote_ips = @env[Merb::Const::HTTP_X_FORWARDED_FOR].split(',').reject do |ip|
178
+ ip =~ /^unknown$|^(127|10|172\.16|192\.168)\./i
179
+ end
180
+
181
+ return remote_ips.first.strip unless remote_ips.empty?
182
+ end
183
+
184
+ return @env[Merb::Const::REMOTE_ADDR]
185
+ end
186
+
187
+ # returns either 'https://' or 'http://' depending on
188
+ # the HTTPS header
189
+ def protocol
190
+ ssl? ? 'https://' : 'http://'
191
+ end
192
+
193
+ # returns true if the request is an SSL request
194
+ def ssl?
195
+ @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
196
+ end
197
+
198
+ # returns the request HTTP_REFERER.
199
+ def referer
200
+ @env['HTTP_REFERER']
201
+ end
202
+
203
+ # returns he request uri.
204
+ def uri
205
+ @env['REQUEST_URI']
206
+ end
207
+
208
+ def user_agent
209
+ @env['HTTP_USER_AGENT']
210
+ end
211
+
212
+ def server_name
213
+ @env['SERVER_NAME']
214
+ end
215
+
216
+ def accept_encoding
217
+ @env['HTTP_ACCEPT_ENCODING']
218
+ end
219
+
220
+ def script_name
221
+ @env['SCRIPT_NAME']
222
+ end
223
+
224
+ def cache_control
225
+ @env['HTTP_CACHE_CONTROL']
226
+ end
227
+
228
+ def accept_language
229
+ @env['HTTP_ACCEPT_LANGUAGE']
230
+ end
231
+
232
+ def host
233
+ @env['HTTP_HOST']
234
+ end
235
+
236
+ def server_software
237
+ @env['SERVER_SOFTWARE']
238
+ end
239
+
240
+ def keep_alive
241
+ @env['HTTP_KEEP_ALIVE']
242
+ end
243
+
244
+ def accept_charset
245
+ @env['HTTP_ACCEPT_CHARSET']
246
+ end
247
+
248
+ def version
249
+ @env['HTTP_VERSION']
250
+ end
251
+
252
+ def gateway
253
+ @env['GATEWAY_INTERFACE']
254
+ end
255
+
256
+ def accept
257
+ @env['HTTP_ACCEPT']
258
+ end
259
+
260
+ def connection
261
+ @env['HTTP_CONNECTION']
262
+ end
263
+
264
+ def query_string
265
+ @env['QUERY_STRING']
266
+ end
267
+
268
+ def content_type
269
+ @env['CONTENT_TYPE']
270
+ end
271
+
272
+ def content_length
273
+ @content_length ||= @env[Merb::Const::CONTENT_LENGTH].to_i
274
+ end
275
+
276
+ # Returns the uri without the query string. Strips trailing '/' and reduces
277
+ # duplicate '/' to a single '/'
278
+ def path
279
+ path = (uri ? uri.split('?').first : '').sub(/\/+/, '/')
280
+ path = path[0..-2] if (path[-1] == ?/) && path.size > 1
281
+ path
282
+ end
283
+
284
+ # returns the PATH_INFO
285
+ def path_info
286
+ @path_info ||= Mongrel::HttpRequest.unescape(@env['PATH_INFO'])
287
+ end
288
+
289
+ # returns the port the server is running on
290
+ def port
291
+ @env['SERVER_PORT'].to_i
292
+ end
293
+
294
+ # returns the full hostname including port
295
+ def host
296
+ @env['HTTP_X_FORWARDED_HOST'] || @env['HTTP_HOST']
297
+ end
298
+
299
+ # returns an array of all the subdomain parts of the host.
300
+ def subdomains(tld_length = 1)
301
+ parts = host.split('.')
302
+ parts[0..-(tld_length+2)]
303
+ end
304
+
305
+ # returns the full domain name without the port number.
306
+ def domain(tld_length = 1)
307
+ host.split('.').last(1 + tld_length).join('.').sub(/:\d+$/,'')
308
+ end
309
+
310
+ class << self
311
+ # parses a query string or the payload of a POST
312
+ # request into the params hash. So for example:
313
+ # /foo?bar=nik&post[title]=heya&post[body]=whatever
314
+ # parses into:
315
+ # {:bar => 'nik', :post => {:title => 'heya', :body => 'whatever'}}
316
+ def query_parse(qs, d = '&;')
317
+ (qs||'').split(/[#{d}] */n).inject({}) { |h,p|
318
+ normalize_params(h, *Mongrel::HttpRequest.unescape(p).split('=',2))
319
+ }
320
+ end
321
+
322
+ NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
323
+ CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
324
+ FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
325
+ CRLF = "\r\n".freeze
326
+ EOL = CRLF
327
+ def parse_multipart(request,boundary, content_length)
328
+ boundary = "--#{boundary}"
329
+ paramhsh = {}
330
+ buf = ""
331
+ input = request
332
+ input.binmode if defined? input.binmode
333
+ boundary_size = boundary.size + EOL.size
334
+ bufsize = 16384
335
+ content_length -= boundary_size
336
+ status = input.read(boundary_size)
337
+ raise EOFError, "bad content body" unless status == boundary + EOL
338
+ rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
339
+ loop {
340
+ head = nil
341
+ body = ''
342
+ filename = content_type = name = nil
343
+ read_size = 0
344
+ until head && buf =~ rx
345
+ i = buf.index("\r\n\r\n")
346
+ if( i == nil && read_size == 0 && content_length == 0 )
347
+ content_length = -1
348
+ break
349
+ end
350
+ if !head && i
351
+ head = buf.slice!(0, i+2) # First \r\n
352
+ buf.slice!(0, 2) # Second \r\n
353
+ filename = head[FILENAME_REGEX, 1]
354
+ content_type = head[CONTENT_TYPE_REGEX, 1]
355
+ name = head[NAME_REGEX, 1]
356
+
357
+ if filename && !filename.empty?
358
+ body = Tempfile.new(:Merb)
359
+ body.binmode if defined? body.binmode
360
+ end
361
+ next
362
+ end
363
+
364
+ # Save the read body part.
365
+ if head && (boundary_size+4 < buf.size)
366
+ body << buf.slice!(0, buf.size - (boundary_size+4))
367
+ end
368
+
369
+ read_size = bufsize < content_length ? bufsize : content_length
370
+ if( read_size > 0 )
371
+ c = input.read(read_size)
372
+ raise EOFError, "bad content body" if c.nil? || c.empty?
373
+ buf << c
374
+ content_length -= c.size
375
+ end
376
+ end
377
+
378
+ # Save the rest.
379
+ if i = buf.index(rx)
380
+ body << buf.slice!(0, i)
381
+ buf.slice!(0, boundary_size+2)
382
+
383
+ content_length = -1 if $1 == "--"
384
+ end
385
+
386
+ if filename && !filename.empty?
387
+ body.rewind
388
+ data = {
389
+ :filename => File.basename(filename),
390
+ :content_type => content_type,
391
+ :tempfile => body,
392
+ :size => File.size(body)
393
+ }
394
+ else
395
+ data = body
396
+ end
397
+ paramhsh = normalize_params(paramhsh,name,data)
398
+ break if buf.empty? || content_length == -1
399
+ }
400
+ paramhsh
401
+ end
402
+
403
+ def normalize_params(parms, name, val=nil)
404
+ name =~ %r([\[\]]*([^\[\]]+)\]*)
405
+ key = $1 || ''
406
+ after = $' || ''
407
+
408
+ if after == ""
409
+ parms[key] = val
410
+ elsif after == "[]"
411
+ (parms[key] ||= []) << val
412
+ else
413
+ parms[key] ||= {}
414
+ parms[key] = normalize_params(parms[key], after, val)
415
+ end
416
+ parms
417
+ end
418
+ end
419
+ end
420
+ end
421
+