merb-core 0.9.2

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 (246) hide show
  1. data/LICENSE +20 -0
  2. data/README +21 -0
  3. data/Rakefile +285 -0
  4. data/TODO +0 -0
  5. data/bin/merb +8 -0
  6. data/bin/merb-specs +5 -0
  7. data/docs/bootloading.dox +57 -0
  8. data/docs/documentation_standards +40 -0
  9. data/docs/new_render_api +51 -0
  10. data/lib/merb-core.rb +304 -0
  11. data/lib/merb-core/autoload.rb +29 -0
  12. data/lib/merb-core/bootloader.rb +601 -0
  13. data/lib/merb-core/config.rb +284 -0
  14. data/lib/merb-core/constants.rb +43 -0
  15. data/lib/merb-core/controller/abstract_controller.rb +531 -0
  16. data/lib/merb-core/controller/exceptions.rb +257 -0
  17. data/lib/merb-core/controller/merb_controller.rb +214 -0
  18. data/lib/merb-core/controller/mime.rb +88 -0
  19. data/lib/merb-core/controller/mixins/controller.rb +262 -0
  20. data/lib/merb-core/controller/mixins/render.rb +324 -0
  21. data/lib/merb-core/controller/mixins/responder.rb +464 -0
  22. data/lib/merb-core/controller/template.rb +205 -0
  23. data/lib/merb-core/core_ext.rb +12 -0
  24. data/lib/merb-core/core_ext/class.rb +192 -0
  25. data/lib/merb-core/core_ext/hash.rb +422 -0
  26. data/lib/merb-core/core_ext/kernel.rb +304 -0
  27. data/lib/merb-core/core_ext/mash.rb +154 -0
  28. data/lib/merb-core/core_ext/object.rb +136 -0
  29. data/lib/merb-core/core_ext/object_space.rb +14 -0
  30. data/lib/merb-core/core_ext/rubygems.rb +28 -0
  31. data/lib/merb-core/core_ext/set.rb +41 -0
  32. data/lib/merb-core/core_ext/string.rb +69 -0
  33. data/lib/merb-core/dispatch/cookies.rb +92 -0
  34. data/lib/merb-core/dispatch/dispatcher.rb +233 -0
  35. data/lib/merb-core/dispatch/exceptions.html.erb +297 -0
  36. data/lib/merb-core/dispatch/request.rb +560 -0
  37. data/lib/merb-core/dispatch/router.rb +141 -0
  38. data/lib/merb-core/dispatch/router/behavior.rb +777 -0
  39. data/lib/merb-core/dispatch/router/cached_proc.rb +52 -0
  40. data/lib/merb-core/dispatch/router/route.rb +212 -0
  41. data/lib/merb-core/dispatch/session.rb +28 -0
  42. data/lib/merb-core/dispatch/session/cookie.rb +166 -0
  43. data/lib/merb-core/dispatch/session/memcached.rb +161 -0
  44. data/lib/merb-core/dispatch/session/memory.rb +234 -0
  45. data/lib/merb-core/gem_ext/erubis.rb +19 -0
  46. data/lib/merb-core/logger.rb +230 -0
  47. data/lib/merb-core/plugins.rb +25 -0
  48. data/lib/merb-core/rack.rb +15 -0
  49. data/lib/merb-core/rack/adapter.rb +42 -0
  50. data/lib/merb-core/rack/adapter/ebb.rb +22 -0
  51. data/lib/merb-core/rack/adapter/evented_mongrel.rb +24 -0
  52. data/lib/merb-core/rack/adapter/fcgi.rb +16 -0
  53. data/lib/merb-core/rack/adapter/irb.rb +108 -0
  54. data/lib/merb-core/rack/adapter/mongrel.rb +25 -0
  55. data/lib/merb-core/rack/adapter/runner.rb +27 -0
  56. data/lib/merb-core/rack/adapter/thin.rb +27 -0
  57. data/lib/merb-core/rack/adapter/webrick.rb +35 -0
  58. data/lib/merb-core/rack/application.rb +77 -0
  59. data/lib/merb-core/rack/handler/mongrel.rb +97 -0
  60. data/lib/merb-core/server.rb +184 -0
  61. data/lib/merb-core/test.rb +10 -0
  62. data/lib/merb-core/test/helpers.rb +9 -0
  63. data/lib/merb-core/test/helpers/controller_helper.rb +8 -0
  64. data/lib/merb-core/test/helpers/multipart_request_helper.rb +175 -0
  65. data/lib/merb-core/test/helpers/request_helper.rb +257 -0
  66. data/lib/merb-core/test/helpers/route_helper.rb +33 -0
  67. data/lib/merb-core/test/helpers/view_helper.rb +121 -0
  68. data/lib/merb-core/test/matchers.rb +9 -0
  69. data/lib/merb-core/test/matchers/controller_matchers.rb +269 -0
  70. data/lib/merb-core/test/matchers/route_matchers.rb +136 -0
  71. data/lib/merb-core/test/matchers/view_matchers.rb +293 -0
  72. data/lib/merb-core/test/run_specs.rb +38 -0
  73. data/lib/merb-core/test/tasks/spectasks.rb +39 -0
  74. data/lib/merb-core/test/test_ext/hpricot.rb +32 -0
  75. data/lib/merb-core/test/test_ext/object.rb +14 -0
  76. data/lib/merb-core/vendor/facets.rb +2 -0
  77. data/lib/merb-core/vendor/facets/dictionary.rb +433 -0
  78. data/lib/merb-core/vendor/facets/inflect.rb +211 -0
  79. data/lib/merb-core/version.rb +11 -0
  80. data/spec/private/config/adapter_spec.rb +32 -0
  81. data/spec/private/config/config_spec.rb +139 -0
  82. data/spec/private/config/environment_spec.rb +13 -0
  83. data/spec/private/config/spec_helper.rb +1 -0
  84. data/spec/private/core_ext/hash_spec.rb +506 -0
  85. data/spec/private/core_ext/kernel_spec.rb +46 -0
  86. data/spec/private/core_ext/object_spec.rb +39 -0
  87. data/spec/private/core_ext/set_spec.rb +26 -0
  88. data/spec/private/core_ext/string_spec.rb +9 -0
  89. data/spec/private/dispatch/cookies_spec.rb +107 -0
  90. data/spec/private/dispatch/dispatch_spec.rb +26 -0
  91. data/spec/private/dispatch/fixture/app/controllers/application.rb +4 -0
  92. data/spec/private/dispatch/fixture/app/controllers/exceptions.rb +27 -0
  93. data/spec/private/dispatch/fixture/app/controllers/foo.rb +21 -0
  94. data/spec/private/dispatch/fixture/app/helpers/global_helpers.rb +8 -0
  95. data/spec/private/dispatch/fixture/app/views/exeptions/client_error.html.erb +37 -0
  96. data/spec/private/dispatch/fixture/app/views/exeptions/internal_server_error.html.erb +216 -0
  97. data/spec/private/dispatch/fixture/app/views/exeptions/not_acceptable.html.erb +38 -0
  98. data/spec/private/dispatch/fixture/app/views/exeptions/not_found.html.erb +40 -0
  99. data/spec/private/dispatch/fixture/app/views/foo/bar.html.erb +0 -0
  100. data/spec/private/dispatch/fixture/app/views/layout/application.html.erb +11 -0
  101. data/spec/private/dispatch/fixture/config/environments/development.rb +6 -0
  102. data/spec/private/dispatch/fixture/config/environments/production.rb +5 -0
  103. data/spec/private/dispatch/fixture/config/environments/test.rb +6 -0
  104. data/spec/private/dispatch/fixture/config/init.rb +45 -0
  105. data/spec/private/dispatch/fixture/config/rack.rb +1 -0
  106. data/spec/private/dispatch/fixture/config/router.rb +35 -0
  107. data/spec/private/dispatch/fixture/log/development.log +1 -0
  108. data/spec/private/dispatch/fixture/log/merb.4000.pid +1 -0
  109. data/spec/private/dispatch/fixture/log/merb_test.log +2040 -0
  110. data/spec/private/dispatch/fixture/log/production.log +1 -0
  111. data/spec/private/dispatch/fixture/merb.4000.pid +1 -0
  112. data/spec/private/dispatch/fixture/public/images/merb.jpg +0 -0
  113. data/spec/private/dispatch/fixture/public/merb.fcgi +4 -0
  114. data/spec/private/dispatch/fixture/public/stylesheets/master.css +119 -0
  115. data/spec/private/dispatch/route_params_spec.rb +24 -0
  116. data/spec/private/dispatch/spec_helper.rb +1 -0
  117. data/spec/private/plugins/plugin_spec.rb +81 -0
  118. data/spec/private/rack/application_spec.rb +43 -0
  119. data/spec/public/DEFINITIONS +11 -0
  120. data/spec/public/abstract_controller/controllers/alt_views/layout/application.erb +1 -0
  121. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_string_controller_layout.erb +1 -0
  122. data/spec/public/abstract_controller/controllers/alt_views/layout/merb/test/fixtures/abstract/render_template_controller_layout.erb +1 -0
  123. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/index.erb +1 -0
  124. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/display_object_with_multiple_roots/show.erb +1 -0
  125. data/spec/public/abstract_controller/controllers/alt_views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
  126. data/spec/public/abstract_controller/controllers/alt_views/partial/basic_partial_with_multiple_roots/_partial.erb +1 -0
  127. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_and_custom_location/index.erb +1 -0
  128. data/spec/public/abstract_controller/controllers/alt_views/render_template_multiple_roots_inherited/index.erb +1 -0
  129. data/spec/public/abstract_controller/controllers/display.rb +54 -0
  130. data/spec/public/abstract_controller/controllers/filters.rb +167 -0
  131. data/spec/public/abstract_controller/controllers/helpers.rb +31 -0
  132. data/spec/public/abstract_controller/controllers/partial.rb +106 -0
  133. data/spec/public/abstract_controller/controllers/render.rb +86 -0
  134. data/spec/public/abstract_controller/controllers/views/helpers/capture/index.erb +1 -0
  135. data/spec/public/abstract_controller/controllers/views/helpers/concat/index.erb +1 -0
  136. data/spec/public/abstract_controller/controllers/views/layout/alt.erb +1 -0
  137. data/spec/public/abstract_controller/controllers/views/layout/custom.erb +1 -0
  138. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object/index.erb +1 -0
  139. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/display_object_with_action/new.erb +1 -0
  140. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template/index.erb +1 -0
  141. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_app_layout/index.erb +0 -0
  142. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_custom_layout/index.erb +1 -0
  143. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/index.erb +1 -0
  144. data/spec/public/abstract_controller/controllers/views/merb/test/fixtures/abstract/render_template_multiple_roots/show.erb +1 -0
  145. data/spec/public/abstract_controller/controllers/views/partial/another_directory/_partial.erb +1 -0
  146. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/_partial.erb +1 -0
  147. data/spec/public/abstract_controller/controllers/views/partial/basic_partial/index.erb +1 -0
  148. data/spec/public/abstract_controller/controllers/views/partial/basic_partial_with_multiple_roots/index.erb +1 -0
  149. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_first.erb +1 -0
  150. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/_second.erb +1 -0
  151. data/spec/public/abstract_controller/controllers/views/partial/nested_partial/index.erb +1 -0
  152. data/spec/public/abstract_controller/controllers/views/partial/partial_in_another_directory/index.erb +1 -0
  153. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/_collection.erb +1 -0
  154. data/spec/public/abstract_controller/controllers/views/partial/partial_with_both/index.erb +1 -0
  155. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/_collection.erb +1 -0
  156. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections/index.erb +1 -0
  157. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/_collection.erb +1 -0
  158. data/spec/public/abstract_controller/controllers/views/partial/partial_with_collections_and_as/index.erb +1 -0
  159. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/_variables.erb +1 -0
  160. data/spec/public/abstract_controller/controllers/views/partial/partial_with_locals/index.erb +1 -0
  161. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/_both.erb +1 -0
  162. data/spec/public/abstract_controller/controllers/views/partial/partial_with_with_and_locals/index.erb +1 -0
  163. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/_with_partial.erb +1 -0
  164. data/spec/public/abstract_controller/controllers/views/partial/with_as_partial/index.erb +1 -0
  165. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/_with_partial.erb +1 -0
  166. data/spec/public/abstract_controller/controllers/views/partial/with_nil_partial/index.erb +1 -0
  167. data/spec/public/abstract_controller/controllers/views/partial/with_partial/_with_partial.erb +1 -0
  168. data/spec/public/abstract_controller/controllers/views/partial/with_partial/index.erb +1 -0
  169. data/spec/public/abstract_controller/controllers/views/test_display/foo.html.erb +1 -0
  170. data/spec/public/abstract_controller/controllers/views/test_render/foo.html.erb +0 -0
  171. data/spec/public/abstract_controller/controllers/views/wonderful/index.erb +1 -0
  172. data/spec/public/abstract_controller/display_spec.rb +33 -0
  173. data/spec/public/abstract_controller/filter_spec.rb +80 -0
  174. data/spec/public/abstract_controller/helper_spec.rb +13 -0
  175. data/spec/public/abstract_controller/partial_spec.rb +53 -0
  176. data/spec/public/abstract_controller/render_spec.rb +70 -0
  177. data/spec/public/abstract_controller/spec_helper.rb +27 -0
  178. data/spec/public/boot_loader/boot_loader_spec.rb +33 -0
  179. data/spec/public/boot_loader/spec_helper.rb +1 -0
  180. data/spec/public/controller/base_spec.rb +31 -0
  181. data/spec/public/controller/controllers/base.rb +41 -0
  182. data/spec/public/controller/controllers/display.rb +40 -0
  183. data/spec/public/controller/controllers/responder.rb +67 -0
  184. data/spec/public/controller/controllers/url.rb +7 -0
  185. data/spec/public/controller/controllers/views/layout/custom.html.erb +1 -0
  186. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.html.erb +1 -0
  187. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/class_provides/index.xml.erb +1 -0
  188. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/display_with_template/index.html.erb +1 -0
  189. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/html_default/index.html.erb +1 -0
  190. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/layout/custom.html.erb +1 -0
  191. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.html.erb +1 -0
  192. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/local_provides/index.xml.erb +1 -0
  193. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.html.erb +1 -0
  194. data/spec/public/controller/controllers/views/merb/test/fixtures/controllers/multi_provides/index.js.erb +1 -0
  195. data/spec/public/controller/display_spec.rb +34 -0
  196. data/spec/public/controller/log/merb.4000.pid +1 -0
  197. data/spec/public/controller/responder_spec.rb +95 -0
  198. data/spec/public/controller/spec_helper.rb +9 -0
  199. data/spec/public/controller/url_spec.rb +152 -0
  200. data/spec/public/directory_structure/directory/app/controllers/application.rb +3 -0
  201. data/spec/public/directory_structure/directory/app/controllers/base.rb +13 -0
  202. data/spec/public/directory_structure/directory/app/controllers/custom.rb +19 -0
  203. data/spec/public/directory_structure/directory/app/views/base/template.html.erb +1 -0
  204. data/spec/public/directory_structure/directory/app/views/wonderful/template.erb +1 -0
  205. data/spec/public/directory_structure/directory/config/router.rb +3 -0
  206. data/spec/public/directory_structure/directory/log/merb.4000.pid +1 -0
  207. data/spec/public/directory_structure/directory/log/merb_test.log +265 -0
  208. data/spec/public/directory_structure/directory/merb.4000.pid +1 -0
  209. data/spec/public/directory_structure/directory_spec.rb +44 -0
  210. data/spec/public/logger/logger_spec.rb +175 -0
  211. data/spec/public/logger/spec_helper.rb +1 -0
  212. data/spec/public/reloading/directory/app/controllers/application.rb +3 -0
  213. data/spec/public/reloading/directory/app/controllers/reload.rb +6 -0
  214. data/spec/public/reloading/directory/config/init.rb +2 -0
  215. data/spec/public/reloading/directory/log/merb.4000.pid +1 -0
  216. data/spec/public/reloading/directory/log/merb_test.log +59 -0
  217. data/spec/public/reloading/directory/merb.4000.pid +1 -0
  218. data/spec/public/reloading/reload_spec.rb +80 -0
  219. data/spec/public/request/multipart_spec.rb +15 -0
  220. data/spec/public/request/request_spec.rb +207 -0
  221. data/spec/public/router/default_spec.rb +21 -0
  222. data/spec/public/router/deferred_spec.rb +22 -0
  223. data/spec/public/router/namespace_spec.rb +113 -0
  224. data/spec/public/router/nested_resources_spec.rb +34 -0
  225. data/spec/public/router/resource_spec.rb +45 -0
  226. data/spec/public/router/resources_spec.rb +57 -0
  227. data/spec/public/router/spec_helper.rb +72 -0
  228. data/spec/public/router/special_spec.rb +44 -0
  229. data/spec/public/router/string_spec.rb +61 -0
  230. data/spec/public/template/template_spec.rb +92 -0
  231. data/spec/public/template/templates/error.html.erb +2 -0
  232. data/spec/public/template/templates/template.html.erb +1 -0
  233. data/spec/public/template/templates/template.html.myt +1 -0
  234. data/spec/public/test/controller_matchers_spec.rb +378 -0
  235. data/spec/public/test/controllers/controller_assertion_mock.rb +7 -0
  236. data/spec/public/test/controllers/dispatch_controller.rb +11 -0
  237. data/spec/public/test/controllers/spec_helper_controller.rb +30 -0
  238. data/spec/public/test/multipart_request_helper_spec.rb +159 -0
  239. data/spec/public/test/multipart_upload_text_file.txt +1 -0
  240. data/spec/public/test/request_helper_spec.rb +153 -0
  241. data/spec/public/test/route_helper_spec.rb +54 -0
  242. data/spec/public/test/route_matchers_spec.rb +133 -0
  243. data/spec/public/test/view_helper_spec.rb +96 -0
  244. data/spec/public/test/view_matchers_spec.rb +107 -0
  245. data/spec/spec_helper.rb +71 -0
  246. metadata +488 -0
@@ -0,0 +1,297 @@
1
+ <html>
2
+ <head>
3
+ <meta http-equiv="Content-type" content="text/html; charset=utf-8">
4
+ <title><%= @exception_name %></title>
5
+ <style type="text/css" media="screen">
6
+ body {
7
+ font-family:arial;
8
+ font-size:11px;
9
+ }
10
+ h1 {
11
+ font-size:48px;
12
+ letter-spacing:-4px;
13
+ margin:0;
14
+ line-height:36px;
15
+ color:#333;
16
+ }
17
+ h1 sup {
18
+ font-size: 0.5em;
19
+ }
20
+ h1 sup.error_500, h1 sup.error_400 {
21
+ color:#990E05;
22
+ }
23
+ h1 sup.error_100, h1 sup.error_200 {
24
+ color:#00BF10;
25
+ }
26
+ h1 sup.error_300 {
27
+ /* pretty sure you cant 'see' status 300
28
+ errors but if you could I think they
29
+ would be blue */
30
+ color:#1B2099;
31
+ }
32
+ h2 {
33
+ font-size:36px;
34
+ letter-spacing:-3px;
35
+ margin:0;
36
+ line-height:28px;
37
+ color:#444;
38
+ }
39
+ h3 {
40
+ cursor: pointer;
41
+ color: #006;
42
+ text-decoration: underline;
43
+ }
44
+ a, a:visited {
45
+ color:#00BF10;
46
+ }
47
+ .internalError {
48
+ width:800px;
49
+ margin:50px auto;
50
+ }
51
+ .header {
52
+ border-bottom:10px solid #333;
53
+ margin-bottom:1px;
54
+ background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7");
55
+ padding:20px;
56
+ }
57
+ table.listing {
58
+ border-collapse: collapse;
59
+ font-size: 12px;
60
+ width: 100%;
61
+ }
62
+
63
+ table.listing th {
64
+ background-color: #000;
65
+ color: #fff;
66
+ }
67
+
68
+ table.listing td, table.listing th {
69
+ padding: 5px;
70
+ text-align: left;
71
+ vertical-align: top;
72
+ }
73
+
74
+ table.listing tr.odd {
75
+ background-color: #ccc;
76
+ }
77
+
78
+ table.listing tr.even {
79
+ background-color: #aaa;
80
+ }
81
+
82
+ table.listing td[colspan=2] {
83
+ text-align: center;
84
+ }
85
+
86
+ table.trace {
87
+ width:100%;
88
+ font-family:courier, monospace;
89
+ letter-spacing:-1px;
90
+ border-collapse: collapse;
91
+ border-spacing:0;
92
+ }
93
+ table.trace tr td{
94
+ padding:0;
95
+ height:26px;
96
+ font-size:13px;
97
+ vertical-align:middle;
98
+ }
99
+ table.trace tr.file{
100
+ border-top:2px solid #fff;
101
+ background-color:#F3F3F3;
102
+ }
103
+ table.trace tr.source {
104
+ background-color:#F8F8F8;
105
+ display:none;
106
+ }
107
+ table.trace .open tr.source {
108
+ display:table-row;
109
+ }
110
+ table.trace tr.file td.expand {
111
+ width:23px;
112
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAXCAIAAABvSEP3AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdVJREFUeNqMVL+TwUAYxaRIOlEhlZHGDAUzzOQ61+AqXMV1lJSU7q/QRqm8KFUcJTNn5qJkaPyoKKVz7y4mF8na5Kt29tt9+/Z97/u81+vVQ4r9frdarS6Xi7ETDIZisRjxMGPfmk4niNPpZE+xLAugbPaZ53nzvtfMBe/3+/3dbuehBrAKhZdUKkVAWa9Xsiybv0CPZDJZLr/qa5/BwgwRjYqOKIvFYjQa/aNommZh0Ww2K5UqzwfoQOPxaLPZ3FAmk0+7lplMpt1u53J5OpBOR0eZEE9wHJfP5zud93g88QhluwWbjW+5VOmKBgKBer3eaDTDYeGBQF8+x7rqIYoiPgixWJazpA6HA+MSxRArkUgMh0M409g8Ho8+9wYxxCqVSq1W26EDHGM2m4HOHQrEc38f/Yn7cLmlIRhBENzcx8cVRZnPZ/YUep2BWkjTIfA+PKVpZAXR5QxsjiqCKvGEqqp443w+0dvy17swqD0HB3S73V5PpkNg1qBqt8kwGCjmPkinM0QJbIoEa7U6UG6ToVgs4V9G2g0ESoP5Aoi7KYX5oCgf8IKbkvn9/mr1LRQKESamzgJy0g0tSZIuB3nuGqRU9Vv9C4sKkUhEkp4soxvxI8AAhWrrtXa3X8EAAAAASUVORK5CYII=);
113
+ background-position:top left;
114
+ background-repeat:no-repeat;
115
+ }
116
+ table.trace tr.file td.expand div {
117
+ width:23px;
118
+ }
119
+ table.trace .open tr.file td.expand {
120
+ width:19px;
121
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXZJREFUeNrslK1ywkAUhcMOBomEOiSdqLxEBJX0NaijOsjyHGGmCGyQQYaiiiw4gktkcOmZbpsuuzQ/M5XnqJ2d3S/n3nM3rTzPLUP7/Tt0+pLcGQwG3W53OLyHzPMtjYL7q9UqSRLrD4E1Gj1orCvKYuFHUWTVkOM44/HjDcp8/lL4r6NerzeZPMm1KFw0QkDn83m5fP2lHA4fNQvRtNvtjsfDd0WzmSfb2e/fdTqdOvdh/HLJZLOn0+d2HJ+KRGzbdl23EpFlmed5cp2maRzHQq1lvQ5KMi6EUZBGfup6E1pTfd+vrGW7jbQ2C9hTt9BpqNyIWaAwAy6xg2eBz5iRC/NomiZhGN5sqmnkauo0BUGgVQoBjQ80oCACgNQdZHfTYBkF2mxCtWWAqunWpahxIDUt3QYUxIFQpJHyIWpXjinabKbbwItMHT+NyjchrP8QKaSQQgoppJBCCimkkEIKKaSQQgoppJBCCimkkEIKKaSo+hRgAEFD17X08O2NAAAAAElFTkSuQmCC);
122
+ background-position:top left;
123
+ background-repeat:no-repeat;
124
+ }
125
+ table.trace .open tr.file td.expand div {
126
+ width:19px;
127
+ }
128
+ table.trace tr.source td.collapse {
129
+ width:19px;
130
+ background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVxJREFUeNrs0zFygkAUBmBlUkgJHdABlQwVkVJKKUxBYWbkALTxMJwhltyDFkss03IF8pudIcwaDaDl/6pd2P327b7d+eHwMXs4lNkzggoVKlSoUKFChQoVKlSoUKFChQoVKlSoUKFChQqVEYqm6ft9+qiSJEkYho7jTlcw2fd9NOI4nq4gEdFwXXe1Cqco63VkWVbXRTqLhTpOwQRpF7quR1E0TgGhqvLKUFCyoQqG/rks3O6kZKW/eRFpevOCoGTXVTcMQ5EyxyDEkML1c5RzuZOICIyXqn7JBVez6282MWrx731HOv2qB8Hri2lamNk0DfpVVdV1Peodappmmua8bdvzuc7zfNprzrLMth1FnGh/X8MjCAIQv/cFz/+65PcDh7rbvYv2ZUfdj+PxsyzLgVl0hKwgTqeqKApx2LeOc7t98zyv/1FWOgvx9RPii23bmL9cetJ8Ed8CDAC6aFW8bCzFhwAAAABJRU5ErkJggg==);
131
+ background-position:bottom left;
132
+ background-repeat:no-repeat;
133
+ background-color:#6F706F;
134
+ }
135
+ table.trace tr td.path {
136
+ padding-left:10px;
137
+ }
138
+ table.trace tr td.code {
139
+ padding-left:35px;
140
+ white-space: pre;
141
+ line-height:9px;
142
+ padding-bottom:10px;
143
+ }
144
+ table.trace tr td.code em {
145
+ font-weight:bold;
146
+ color:#00BF10;
147
+ }
148
+ table.trace tr td.code a {
149
+ width: 20px;
150
+ float: left;
151
+ }
152
+ table.trace tr td.code .more {
153
+ color:#666;
154
+ }
155
+ table.trace tr td.line {
156
+ width:30px;
157
+ text-align:right;
158
+ padding-right:4px;
159
+ }
160
+ .footer {
161
+ margin-top:5px;
162
+ font-size:11px;
163
+ color:#444;
164
+ text-align:right;
165
+ }
166
+ </style>
167
+ </head>
168
+ <body>
169
+ <div class="internalError">
170
+
171
+ <div class="header">
172
+ <h1><%= @exception_name %> <sup class="error_<%= @exception.class::STATUS %>"><%= @exception.class::STATUS %></sup></h1>
173
+ <% if show_details = ::Merb::Config[:exception_details] -%>
174
+ <h2><%= html_escape(@exception.message) %></h2>
175
+ <% else -%>
176
+ <h2>Sorry about that...</h2>
177
+ <% end -%>
178
+
179
+ <h3>Parameters</h3>
180
+ <table class="listing" style="display: none">
181
+ <thead>
182
+ <tr><th width="25%">Parameter</th><th width="75%">Value</th></tr>
183
+ </thead>
184
+ <tbody>
185
+ <% params[:original_params].each_with_index do |param_value, i| %>
186
+ <tr class="<%= i % 2 == 0 ? "even" : "odd" %>"><td><%= param_value.first %></td><td><%= param_value.last.inspect %></td></tr>
187
+ <% end %>
188
+ <%= "<tr class='odd'><td colspan='2'>None</td></tr>" if params[:original_params].empty? %>
189
+ </tbody>
190
+ </table>
191
+
192
+ <h3>Session</h3>
193
+ <table class="listing" style="display: none">
194
+ <thead>
195
+ <tr><th width="25%">Key</th><th width="75%">Value</th></tr>
196
+ </thead>
197
+ <tbody>
198
+ <% params[:original_session].each_with_index do |param_value, i| %>
199
+ <tr class="<%= i % 2 == 0 ? "even" : "odd" %>"><td><%= param_value.first %></td><td><%= param_value.last.inspect %></td></tr>
200
+ <% end %>
201
+ <%= "<tr class='odd'><td colspan='2'>None</td></tr>" if params[:original_session].empty? %>
202
+ </tbody>
203
+ </table>
204
+
205
+ <h3>Cookies</h3>
206
+ <table class="listing" style="display: none">
207
+ <thead>
208
+ <tr><th width="25%">Cookie</th><th width="75%">Value</th></tr>
209
+ </thead>
210
+ <tbody>
211
+ <% params[:original_cookies].each_with_index do |param_value, i| %>
212
+ <tr class="<%= i % 2 == 0 ? "even" : "odd" %>"><td><%= param_value.first %></td><td><%= param_value.last.inspect %></td></tr>
213
+ <% end %>
214
+ <%= "<tr class='odd'><td colspan='2'>None</td></tr>" if params[:original_cookies].empty? %>
215
+ </tbody>
216
+ </table>
217
+
218
+ <h3>Named Routes</h3>
219
+ <table class="listing" style="display: none">
220
+ <% unless Merb::Router.named_routes.empty? %>
221
+ <thead>
222
+ <tr><th width="25%">Name</th><th width="75%">Route</th></tr>
223
+ </thead>
224
+ <tbody>
225
+ <% Merb::Router.named_routes.each_with_index do |name_route, i| %>
226
+ <tr class="<%= i % 2 == 0 ? "even" : "odd" %>"><td><%= name_route.first %></td><td><%= name_route.last %></td></tr>
227
+ <% end %>
228
+ </tbody>
229
+ <% else %>
230
+ <tr><td colspan="2">None</td></tr>
231
+ <% end %>
232
+ </table>
233
+ </div>
234
+
235
+ <% if show_details %>
236
+ <table class="trace">
237
+ <% @exception.backtrace.each_with_index do |line, index| %>
238
+ <tbody class="close">
239
+ <tr class="file">
240
+ <td class="expand"><div>&nbsp;</div></td>
241
+ <td class="path">
242
+ <%= (line.match(/^([^:]+)/)[1] rescue 'unknown').sub(/\/((opt|usr)\/local\/lib\/(ruby\/)?(gems\/)?(1.8\/)?(gems\/)?|.+\/app\/)/, '') %>
243
+ <% unless line.match(/\.erb:/) %>
244
+ in "<strong><%= line.match(/:in `(.+)'$/)[1] rescue '?' %></strong>"
245
+ <% else %>
246
+ (<strong>ERB Template</strong>)
247
+ <% end %>
248
+ </td>
249
+ <td class="line">
250
+ <a href="txmt://open?url=file://<%=file = (line.match(/^([^:]+)/)[1] rescue 'unknown')%>&amp;line=<%= lineno = line.match(/:([0-9]+):/)[1] rescue '?' %>"><%=lineno%></a>&nbsp;
251
+ </td>
252
+ </tr>
253
+ <tr class="source">
254
+ <td class="collapse">
255
+ </td>
256
+ <td class="code" colspan="2"><% (__caller_lines__(file, lineno, 5) rescue []).each do |llineno, lcode, lcurrent| %>
257
+ <a href="txmt://open?url=file://<%=file%>&amp;line=<%=llineno%>"><%= llineno %></a><%='<em>' if llineno==lineno.to_i %><%= lcode.size > 90 ? Erubis::XmlHelper.escape_xml(lcode[0..90])+'<span class="more">......</span>' : Erubis::XmlHelper.escape_xml(lcode) %><%='</em>' if llineno==lineno.to_i %>
258
+ <% end %>
259
+
260
+ </td>
261
+ </tr>
262
+ </tbody>
263
+ <% end %>
264
+ </table>
265
+ <script type="text/javascript" charset="utf-8">
266
+ els = document.getElementsByTagName('td');
267
+ for(i=0; i<els.length; i++){
268
+ // swap the open & closed classes
269
+ if(els[i].className=='expand' || els[i].className=='collapse'){
270
+ els[i].onclick = function(e){
271
+ tbody = this.parentNode.parentNode;
272
+ if(tbody.className=='open'){
273
+ tbody.className='closed';
274
+ }else{
275
+ tbody.className='open';
276
+ }
277
+ }
278
+ }
279
+ }
280
+ els = document.getElementsByTagName("h3");
281
+ for(i=0; i<els.length; i++) {
282
+ els[i].onclick = function(e) {
283
+ var tag = this.nextSibling;
284
+ while(tag.nodeType != 1) {
285
+ tag = tag.nextSibling;
286
+ }
287
+ tag.style.display = tag.style.display == "none" ? "" : "none";
288
+ }
289
+ }
290
+ </script>
291
+ <% end %>
292
+ <div class="footer">
293
+ lots of love, from <a href="#">merb</a>
294
+ </div>
295
+ </div>
296
+ </body>
297
+ </html>
@@ -0,0 +1,560 @@
1
+ module Merb
2
+
3
+ class Request
4
+ # def env def session def route_params
5
+ attr_accessor :env, :session, :route_params
6
+
7
+ # by setting these to false, auto-parsing is disabled; this way you can do your own parsing instead
8
+ cattr_accessor :parse_multipart_params, :parse_json_params, :parse_xml_params
9
+ self.parse_multipart_params = true
10
+ self.parse_json_params = true
11
+ self.parse_xml_params = true
12
+
13
+ # Initial the request object.
14
+ #
15
+ # ==== Parameters
16
+ # http_request<~params:~[], ~body:IO>::
17
+ # An object like an HTTP Request.
18
+ def initialize(rack_env)
19
+ @env = rack_env
20
+ @body = rack_env['rack.input']
21
+ @route_params = {}
22
+ end
23
+
24
+ METHODS = %w{get post put delete head}
25
+
26
+ # ==== Returns
27
+ # Symbol:: The name of the request method, e.g. :get.
28
+ #
29
+ # ==== Notes
30
+ # If the method is post, then the +_method+ param will be checked for
31
+ # masquerading method.
32
+ def method
33
+ @method ||= begin
34
+ request_method = @env['REQUEST_METHOD'].downcase.to_sym
35
+ case request_method
36
+ when :get, :head, :put, :delete
37
+ request_method
38
+ when :post
39
+ if self.class.parse_multipart_params
40
+ m = body_and_query_params.merge(multipart_params)['_method']
41
+ else
42
+ m = body_and_query_params['_method']
43
+ end
44
+ m.downcase! if m
45
+ METHODS.include?(m) ? m.to_sym : :post
46
+ else
47
+ raise "Unknown REQUEST_METHOD: #{@env['REQUEST_METHOD']}"
48
+ end
49
+ end
50
+ end
51
+
52
+ # create predicate methods for querying the REQUEST_METHOD
53
+ # get? post? head? put? etc
54
+ METHODS.each do |m|
55
+ class_eval "def #{m}?() method == :#{m} end"
56
+ end
57
+
58
+ private
59
+
60
+ # ==== Returns
61
+ # Hash:: Parameters passed from the URL like ?blah=hello.
62
+ def query_params
63
+ @query_params ||= self.class.query_parse(query_string || '')
64
+ end
65
+
66
+ # Parameters passed in the body of the request. Ajax calls from
67
+ # prototype.js and other libraries pass content this way.
68
+ #
69
+ # ==== Returns
70
+ # Hash:: The parameters passed in the body.
71
+ def body_params
72
+ @body_params ||= begin
73
+ if content_type && content_type.match(Merb::Const::FORM_URL_ENCODED_REGEXP) # or content_type.nil?
74
+ self.class.query_parse(raw_post)
75
+ end
76
+ end
77
+ end
78
+
79
+ # ==== Returns
80
+ # Hash::
81
+ # The parameters gathered from the query string and the request body,
82
+ # with parameters in the body taking precedence.
83
+ def body_and_query_params
84
+ # ^-- FIXME a better name for this method
85
+ @body_and_query_params ||= begin
86
+ h = query_params
87
+ h.merge!(body_params) if body_params
88
+ h.to_mash
89
+ end
90
+ end
91
+
92
+ # ==== Raises
93
+ # ControllerExceptions::MultiPartParseError::
94
+ # Unable to parse the multipart form data.
95
+ #
96
+ # ==== Returns
97
+ # Hash:: The parsed multipart parameters.
98
+ def multipart_params
99
+ @multipart_params ||=
100
+ begin
101
+ # if the content-type is multipart and there's stuff in the body,
102
+ # parse the multipart. Otherwise return {}
103
+ if (Merb::Const::MULTIPART_REGEXP =~ content_type && @body.size > 0)
104
+ self.class.parse_multipart(@body, $1, content_length)
105
+ else
106
+ {}
107
+ end
108
+ rescue ControllerExceptions::MultiPartParseError => e
109
+ @multipart_params = {}
110
+ raise e
111
+ end
112
+ end
113
+
114
+ # ==== Returns
115
+ # Hash:: Parameters from body if this is a JSON request.
116
+ def json_params
117
+ @json_params ||= begin
118
+ if Merb::Const::JSON_MIME_TYPE_REGEXP.match(content_type)
119
+ JSON.parse(raw_post)
120
+ end
121
+ end
122
+ end
123
+
124
+ # ==== Returns
125
+ # Hash:: Parameters from body if this is an XML request.
126
+ def xml_params
127
+ @xml_params ||= begin
128
+ if Merb::Const::XML_MIME_TYPE_REGEXP.match(content_type)
129
+ Hash.from_xml(raw_post) rescue Mash.new
130
+ end
131
+ end
132
+ end
133
+
134
+ public
135
+ # ==== Returns
136
+ # Hash:: All request parameters.
137
+ #
138
+ # ==== Notes
139
+ # The order of precedence for the params is XML, JSON, multipart, body and
140
+ # request string.
141
+ def params
142
+ @params ||= begin
143
+ h = body_and_query_params.merge(route_params)
144
+ h.merge!(multipart_params) if self.class.parse_multipart_params && multipart_params
145
+ h.merge!(json_params) if self.class.parse_json_params && json_params
146
+ h.merge!(xml_params) if self.class.parse_xml_params && xml_params
147
+ h
148
+ end
149
+ end
150
+
151
+ # Resets the params to a nil value.
152
+ def reset_params!
153
+ @params = nil
154
+ end
155
+
156
+ # ==== Returns
157
+ # Hash:: The cookies for this request.
158
+ def cookies
159
+ @cookies ||= self.class.query_parse(@env[Merb::Const::HTTP_COOKIE], ';,')
160
+ end
161
+
162
+ # ==== Returns
163
+ # String:: The raw post.
164
+ def raw_post
165
+ @body.rewind if @body.respond_to?(:rewind)
166
+ @raw_post ||= @body.read
167
+ end
168
+
169
+ # ==== Returns
170
+ # Boolean:: If the request is an XML HTTP request.
171
+ def xml_http_request?
172
+ not /XMLHttpRequest/i.match(@env['HTTP_X_REQUESTED_WITH']).nil?
173
+ end
174
+ alias xhr? :xml_http_request?
175
+ alias ajax? :xml_http_request?
176
+
177
+ # ==== Returns
178
+ # String:: The remote IP address.
179
+ def remote_ip
180
+ return @env['HTTP_CLIENT_IP'] if @env.include?('HTTP_CLIENT_IP')
181
+
182
+ if @env.include?(Merb::Const::HTTP_X_FORWARDED_FOR) then
183
+ remote_ips = @env[Merb::Const::HTTP_X_FORWARDED_FOR].split(',').reject do |ip|
184
+ ip =~ /^unknown$|^(127|10|172\.16|192\.168)\./i
185
+ end
186
+
187
+ return remote_ips.first.strip unless remote_ips.empty?
188
+ end
189
+
190
+ return @env[Merb::Const::REMOTE_ADDR]
191
+ end
192
+
193
+ # ==== Returns
194
+ # String::
195
+ # The protocol, i.e. either "https://" or "http://" depending on the
196
+ # HTTPS header.
197
+ def protocol
198
+ ssl? ? 'https://' : 'http://'
199
+ end
200
+
201
+ # ==== Returns
202
+ # Boolean::: True if the request is an SSL request.
203
+ def ssl?
204
+ @env['HTTPS'] == 'on' || @env['HTTP_X_FORWARDED_PROTO'] == 'https'
205
+ end
206
+
207
+ # ==== Returns
208
+ # String:: The HTTP referer.
209
+ def referer
210
+ @env['HTTP_REFERER']
211
+ end
212
+
213
+ # ==== Returns
214
+ # String:: The request URI.
215
+ def uri
216
+ @env['REQUEST_URI'] || @env['REQUEST_PATH']
217
+ end
218
+
219
+ # ==== Returns
220
+ # String:: The HTTP user agent.
221
+ def user_agent
222
+ @env['HTTP_USER_AGENT']
223
+ end
224
+
225
+ # ==== Returns
226
+ # String:: The server name.
227
+ def server_name
228
+ @env['SERVER_NAME']
229
+ end
230
+
231
+ # ==== Returns
232
+ # String:: The accepted encodings.
233
+ def accept_encoding
234
+ @env['HTTP_ACCEPT_ENCODING']
235
+ end
236
+
237
+ # ==== Returns
238
+ # String:: The script name.
239
+ def script_name
240
+ @env['SCRIPT_NAME']
241
+ end
242
+
243
+ # ==== Returns
244
+ # String:: HTTP cache control.
245
+ def cache_control
246
+ @env['HTTP_CACHE_CONTROL']
247
+ end
248
+
249
+ # ==== Returns
250
+ # String:: The accepted language.
251
+ def accept_language
252
+ @env['HTTP_ACCEPT_LANGUAGE']
253
+ end
254
+
255
+ # ==== Returns
256
+ # String:: The server software.
257
+ def server_software
258
+ @env['SERVER_SOFTWARE']
259
+ end
260
+
261
+ # ==== Returns
262
+ # String:: Value of HTTP_KEEP_ALIVE.
263
+ def keep_alive
264
+ @env['HTTP_KEEP_ALIVE']
265
+ end
266
+
267
+ # ==== Returns
268
+ # String:: The accepted character sets.
269
+ def accept_charset
270
+ @env['HTTP_ACCEPT_CHARSET']
271
+ end
272
+
273
+ # ==== Returns
274
+ # String:: The HTTP version
275
+ def version
276
+ @env['HTTP_VERSION']
277
+ end
278
+
279
+ # ==== Returns
280
+ # String:: The gateway.
281
+ def gateway
282
+ @env['GATEWAY_INTERFACE']
283
+ end
284
+
285
+ # ==== Returns
286
+ # String:: The accepted response types. Defaults to "*/*".
287
+ def accept
288
+ @env['HTTP_ACCEPT'].blank? ? "*/*" : @env['HTTP_ACCEPT']
289
+ end
290
+
291
+ # ==== Returns
292
+ # String:: The HTTP connection.
293
+ def connection
294
+ @env['HTTP_CONNECTION']
295
+ end
296
+
297
+ # ==== Returns
298
+ # String:: The query string.
299
+ def query_string
300
+ @env['QUERY_STRING']
301
+ end
302
+
303
+ # ==== Returns
304
+ # String:: The request content type.
305
+ def content_type
306
+ @env['CONTENT_TYPE']
307
+ end
308
+
309
+ # ==== Returns
310
+ # Fixnum:: The request content length.
311
+ def content_length
312
+ @content_length ||= @env[Merb::Const::CONTENT_LENGTH].to_i
313
+ end
314
+
315
+ # ==== Returns
316
+ # String::
317
+ # The URI without the query string. Strips trailing "/" and reduces
318
+ # duplicate "/" to a single "/".
319
+ def path
320
+ path = (uri.empty? ? '/' : uri.split('?').first).squeeze("/")
321
+ path = path[0..-2] if (path[-1] == ?/) && path.size > 1
322
+ path
323
+ end
324
+
325
+ # ==== Returns
326
+ # String:: The path info.
327
+ def path_info
328
+ @path_info ||= self.class.unescape(@env['PATH_INFO'])
329
+ end
330
+
331
+ # ==== Returns
332
+ # Fixnum:: The server port.
333
+ def port
334
+ @env['SERVER_PORT'].to_i
335
+ end
336
+
337
+ # ==== Returns
338
+ # String:: The full hostname including the port.
339
+ def host
340
+ @env['HTTP_X_FORWARDED_HOST'] || @env['HTTP_HOST']
341
+ end
342
+
343
+ # ==== Parameters
344
+ # tld_length<Fixnum>::
345
+ # Number of domains levels to inlclude in the top level domain. Defaults
346
+ # to 1.
347
+ #
348
+ # ==== Returns
349
+ # Array:: All the subdomain parts of the host.
350
+ def subdomains(tld_length = 1)
351
+ parts = host.split('.')
352
+ parts[0..-(tld_length+2)]
353
+ end
354
+
355
+ # ==== Parameters
356
+ # tld_length<Fixnum>::
357
+ # Number of domains levels to inlclude in the top level domain. Defaults
358
+ # to 1.
359
+ #
360
+ # ==== Returns
361
+ # String:: The full domain name without the port number.
362
+ def domain(tld_length = 1)
363
+ host.split('.').last(1 + tld_length).join('.').sub(/:\d+$/,'')
364
+ end
365
+
366
+ class << self
367
+
368
+ # ==== Parameters
369
+ # value<Array, Hash, ~to_s>:: The value for the query string.
370
+ # prefix<~to_s>:: The prefix to add to the query string keys.
371
+ #
372
+ # ==== Returns
373
+ # String:: The query string.
374
+ #
375
+ # ==== Alternatives
376
+ # If the value is a string, the prefix will be used as the key.
377
+ #
378
+ # ==== Examples
379
+ # params_to_query_string(10, "page")
380
+ # # => "page=10"
381
+ # params_to_query_string({ :page => 10, :word => "ruby" })
382
+ # # => "page=10&word=ruby"
383
+ # params_to_query_string({ :page => 10, :word => "ruby" }, "search")
384
+ # # => "search[page]=10&search[word]=ruby"
385
+ # params_to_query_string([ "ice-cream", "cake" ], "shopping_list")
386
+ # # => "shopping_list[]=ice-cream&shopping_list[]=cake"
387
+ def params_to_query_string(value, prefix = nil)
388
+ case value
389
+ when Array
390
+ value.map { |v|
391
+ params_to_query_string(v, "#{prefix}[]")
392
+ } * "&"
393
+ when Hash
394
+ value.map { |k, v|
395
+ params_to_query_string(v, prefix ? "#{prefix}[#{Merb::Request.escape(k)}]" : Merb::Request.escape(k))
396
+ } * "&"
397
+ else
398
+ "#{prefix}=#{Merb::Request.escape(value)}"
399
+ end
400
+ end
401
+
402
+ # ==== Parameters
403
+ # s<String>:: String to URL escape.
404
+ #
405
+ # ==== returns
406
+ # String:: The escaped string.
407
+ def escape(s)
408
+ s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
409
+ '%'+$1.unpack('H2'*$1.size).join('%').upcase
410
+ }.tr(' ', '+')
411
+ end
412
+
413
+ # ==== Parameter
414
+ # s<String>:: String to URL unescape.
415
+ #
416
+ # ==== returns
417
+ # String:: The unescaped string.
418
+ def unescape(s)
419
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
420
+ [$1.delete('%')].pack('H*')
421
+ }
422
+ end
423
+
424
+ # ==== Parameters
425
+ # qs<String>:: The query string.
426
+ # d<String>:: The query string divider. Defaults to "&".
427
+ #
428
+ # ==== Returns
429
+ # Mash:: The parsed query string.
430
+ #
431
+ # ==== Examples
432
+ # query_parse("bar=nik&post[body]=heya")
433
+ # # => { :bar => "nik", :post => { :body => "heya" } }
434
+ def query_parse(qs, d = '&;')
435
+ (qs||'').split(/[#{d}] */n).inject({}) { |h,p|
436
+ key, value = unescape(p).split('=',2)
437
+ normalize_params(h, key, value)
438
+ }.to_mash
439
+ end
440
+
441
+ NAME_REGEX = /Content-Disposition:.* name="?([^\";]*)"?/ni.freeze
442
+ CONTENT_TYPE_REGEX = /Content-Type: (.*)\r\n/ni.freeze
443
+ FILENAME_REGEX = /Content-Disposition:.* filename="?([^\";]*)"?/ni.freeze
444
+ CRLF = "\r\n".freeze
445
+ EOL = CRLF
446
+
447
+ # ==== Parameters
448
+ # request<IO>:: The raw request.
449
+ # boundary<String>:: The boundary string.
450
+ # content_length<Fixnum>:: The length of the content.
451
+ #
452
+ # ==== Raises
453
+ # ControllerExceptions::MultiPartParseError:: Failed to parse request.
454
+ #
455
+ # ==== Returns
456
+ # Hash:: The parsed request.
457
+ def parse_multipart(request, boundary, content_length)
458
+ boundary = "--#{boundary}"
459
+ paramhsh = {}
460
+ buf = ""
461
+ input = request
462
+ input.binmode if defined? input.binmode
463
+ boundary_size = boundary.size + EOL.size
464
+ bufsize = 16384
465
+ content_length -= boundary_size
466
+ status = input.read(boundary_size)
467
+ raise ControllerExceptions::MultiPartParseError, "bad content body:\n'#{status}' should == '#{boundary + EOL}'" unless status == boundary + EOL
468
+ rx = /(?:#{EOL})?#{Regexp.quote(boundary,'n')}(#{EOL}|--)/
469
+ loop {
470
+ head = nil
471
+ body = ''
472
+ filename = content_type = name = nil
473
+ read_size = 0
474
+ until head && buf =~ rx
475
+ i = buf.index("\r\n\r\n")
476
+ if( i == nil && read_size == 0 && content_length == 0 )
477
+ content_length = -1
478
+ break
479
+ end
480
+ if !head && i
481
+ head = buf.slice!(0, i+2) # First \r\n
482
+ buf.slice!(0, 2) # Second \r\n
483
+ filename = head[FILENAME_REGEX, 1]
484
+ content_type = head[CONTENT_TYPE_REGEX, 1]
485
+ name = head[NAME_REGEX, 1]
486
+
487
+ if filename && !filename.empty?
488
+ body = Tempfile.new(:Merb)
489
+ body.binmode if defined? body.binmode
490
+ end
491
+ next
492
+ end
493
+
494
+ # Save the read body part.
495
+ if head && (boundary_size+4 < buf.size)
496
+ body << buf.slice!(0, buf.size - (boundary_size+4))
497
+ end
498
+
499
+ read_size = bufsize < content_length ? bufsize : content_length
500
+ if( read_size > 0 )
501
+ c = input.read(read_size)
502
+ raise ControllerExceptions::MultiPartParseError, "bad content body" if c.nil? || c.empty?
503
+ buf << c
504
+ content_length -= c.size
505
+ end
506
+ end
507
+
508
+ # Save the rest.
509
+ if i = buf.index(rx)
510
+ body << buf.slice!(0, i)
511
+ buf.slice!(0, boundary_size+2)
512
+
513
+ content_length = -1 if $1 == "--"
514
+ end
515
+
516
+ if filename && !filename.empty?
517
+ body.rewind
518
+ data = {
519
+ :filename => File.basename(filename),
520
+ :content_type => content_type,
521
+ :tempfile => body,
522
+ :size => File.size(body.path)
523
+ }
524
+ else
525
+ data = body
526
+ end
527
+ paramhsh = normalize_params(paramhsh,name,data)
528
+ break if buf.empty? || content_length == -1
529
+ }
530
+ paramhsh
531
+ end
532
+
533
+ # Converts a query string snippet to a hash and adds it to existing
534
+ # parameters.
535
+ #
536
+ # ==== Parameters
537
+ # parms<Hash>:: Parameters to add the normalized parameters to.
538
+ # name<String>:: The key of the parameter to normalize.
539
+ # val<String>:: The value of the parameter.
540
+ #
541
+ # ==== Returns
542
+ # Hash:: Normalized parameters
543
+ def normalize_params(parms, name, val=nil)
544
+ name =~ %r([\[\]]*([^\[\]]+)\]*)
545
+ key = $1 || ''
546
+ after = $' || ''
547
+
548
+ if after == ""
549
+ parms[key] = val
550
+ elsif after == "[]"
551
+ (parms[key] ||= []) << val
552
+ else
553
+ parms[key] ||= {}
554
+ parms[key] = normalize_params(parms[key], after, val)
555
+ end
556
+ parms
557
+ end
558
+ end
559
+ end
560
+ end