merb-core 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
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