merb 0.3.7 → 0.4.0

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 (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
+