actionpack 2.2.3 → 2.3.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionpack might be problematic. Click here for more details.

Files changed (264) hide show
  1. data/CHANGELOG +433 -375
  2. data/MIT-LICENSE +1 -1
  3. data/README +21 -75
  4. data/Rakefile +1 -1
  5. data/lib/action_controller.rb +80 -43
  6. data/lib/action_controller/assertions/model_assertions.rb +1 -0
  7. data/lib/action_controller/assertions/response_assertions.rb +43 -16
  8. data/lib/action_controller/assertions/routing_assertions.rb +1 -1
  9. data/lib/action_controller/assertions/selector_assertions.rb +17 -12
  10. data/lib/action_controller/assertions/tag_assertions.rb +1 -4
  11. data/lib/action_controller/base.rb +153 -82
  12. data/lib/action_controller/benchmarking.rb +9 -9
  13. data/lib/action_controller/caching.rb +9 -11
  14. data/lib/action_controller/caching/actions.rb +11 -18
  15. data/lib/action_controller/caching/fragments.rb +28 -20
  16. data/lib/action_controller/caching/pages.rb +13 -15
  17. data/lib/action_controller/caching/sweeping.rb +2 -2
  18. data/lib/action_controller/cgi_ext.rb +0 -1
  19. data/lib/action_controller/cgi_ext/cookie.rb +2 -0
  20. data/lib/action_controller/cgi_process.rb +54 -162
  21. data/lib/action_controller/cookies.rb +13 -25
  22. data/lib/action_controller/dispatcher.rb +43 -122
  23. data/lib/action_controller/failsafe.rb +52 -0
  24. data/lib/action_controller/flash.rb +38 -47
  25. data/lib/action_controller/helpers.rb +13 -9
  26. data/lib/action_controller/http_authentication.rb +203 -23
  27. data/lib/action_controller/integration.rb +126 -70
  28. data/lib/action_controller/layout.rb +36 -39
  29. data/lib/action_controller/middleware_stack.rb +119 -0
  30. data/lib/action_controller/middlewares.rb +13 -0
  31. data/lib/action_controller/mime_responds.rb +19 -4
  32. data/lib/action_controller/mime_type.rb +8 -0
  33. data/lib/action_controller/params_parser.rb +71 -0
  34. data/lib/action_controller/performance_test.rb +0 -1
  35. data/lib/action_controller/polymorphic_routes.rb +36 -30
  36. data/lib/action_controller/reloader.rb +14 -0
  37. data/lib/action_controller/request.rb +107 -499
  38. data/lib/action_controller/request_forgery_protection.rb +7 -39
  39. data/lib/action_controller/rescue.rb +55 -35
  40. data/lib/action_controller/resources.rb +34 -31
  41. data/lib/action_controller/response.rb +99 -57
  42. data/lib/action_controller/rewindable_input.rb +28 -0
  43. data/lib/action_controller/routing.rb +7 -7
  44. data/lib/action_controller/routing/builder.rb +4 -1
  45. data/lib/action_controller/routing/optimisations.rb +1 -1
  46. data/lib/action_controller/routing/recognition_optimisation.rb +1 -2
  47. data/lib/action_controller/routing/route.rb +15 -5
  48. data/lib/action_controller/routing/route_set.rb +82 -35
  49. data/lib/action_controller/routing/segments.rb +35 -0
  50. data/lib/action_controller/session/abstract_store.rb +181 -0
  51. data/lib/action_controller/session/cookie_store.rb +197 -175
  52. data/lib/action_controller/session/mem_cache_store.rb +36 -83
  53. data/lib/action_controller/session_management.rb +26 -134
  54. data/lib/action_controller/streaming.rb +24 -7
  55. data/lib/action_controller/templates/rescues/diagnostics.erb +2 -2
  56. data/lib/action_controller/templates/rescues/template_error.erb +2 -2
  57. data/lib/action_controller/test_case.rb +87 -30
  58. data/lib/action_controller/test_process.rb +145 -104
  59. data/lib/action_controller/uploaded_file.rb +44 -0
  60. data/lib/action_controller/url_rewriter.rb +3 -6
  61. data/lib/action_controller/vendor/html-scanner.rb +16 -0
  62. data/lib/action_controller/vendor/html-scanner/html/selector.rb +1 -1
  63. data/lib/action_controller/vendor/rack-1.0/rack.rb +89 -0
  64. data/lib/action_controller/vendor/rack-1.0/rack/adapter/camping.rb +22 -0
  65. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb +37 -0
  66. data/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/request.rb +37 -0
  67. data/lib/action_controller/vendor/rack-1.0/rack/auth/basic.rb +58 -0
  68. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb +124 -0
  69. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/nonce.rb +51 -0
  70. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/params.rb +55 -0
  71. data/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb +40 -0
  72. data/lib/action_controller/vendor/rack-1.0/rack/auth/openid.rb +480 -0
  73. data/lib/action_controller/vendor/rack-1.0/rack/builder.rb +63 -0
  74. data/lib/action_controller/vendor/rack-1.0/rack/cascade.rb +36 -0
  75. data/lib/action_controller/vendor/rack-1.0/rack/chunked.rb +49 -0
  76. data/lib/action_controller/vendor/rack-1.0/rack/commonlogger.rb +61 -0
  77. data/lib/action_controller/vendor/rack-1.0/rack/conditionalget.rb +45 -0
  78. data/lib/action_controller/vendor/rack-1.0/rack/content_length.rb +29 -0
  79. data/lib/action_controller/vendor/rack-1.0/rack/content_type.rb +23 -0
  80. data/lib/action_controller/vendor/rack-1.0/rack/deflater.rb +85 -0
  81. data/lib/action_controller/vendor/rack-1.0/rack/directory.rb +153 -0
  82. data/lib/action_controller/vendor/rack-1.0/rack/file.rb +88 -0
  83. data/lib/action_controller/vendor/rack-1.0/rack/handler.rb +48 -0
  84. data/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb +61 -0
  85. data/lib/action_controller/vendor/rack-1.0/rack/handler/evented_mongrel.rb +8 -0
  86. data/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb +89 -0
  87. data/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb +55 -0
  88. data/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb +84 -0
  89. data/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb +59 -0
  90. data/lib/action_controller/vendor/rack-1.0/rack/handler/swiftiplied_mongrel.rb +8 -0
  91. data/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb +18 -0
  92. data/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb +67 -0
  93. data/lib/action_controller/vendor/rack-1.0/rack/head.rb +19 -0
  94. data/lib/action_controller/vendor/rack-1.0/rack/lint.rb +462 -0
  95. data/lib/action_controller/vendor/rack-1.0/rack/lobster.rb +65 -0
  96. data/lib/action_controller/vendor/rack-1.0/rack/lock.rb +16 -0
  97. data/lib/action_controller/vendor/rack-1.0/rack/methodoverride.rb +27 -0
  98. data/lib/action_controller/vendor/rack-1.0/rack/mime.rb +204 -0
  99. data/lib/action_controller/vendor/rack-1.0/rack/mock.rb +160 -0
  100. data/lib/action_controller/vendor/rack-1.0/rack/recursive.rb +57 -0
  101. data/lib/action_controller/vendor/rack-1.0/rack/reloader.rb +64 -0
  102. data/lib/action_controller/vendor/rack-1.0/rack/request.rb +241 -0
  103. data/lib/action_controller/vendor/rack-1.0/rack/response.rb +179 -0
  104. data/lib/action_controller/vendor/rack-1.0/rack/session/abstract/id.rb +142 -0
  105. data/lib/action_controller/vendor/rack-1.0/rack/session/cookie.rb +91 -0
  106. data/lib/action_controller/vendor/rack-1.0/rack/session/memcache.rb +109 -0
  107. data/lib/action_controller/vendor/rack-1.0/rack/session/pool.rb +100 -0
  108. data/lib/action_controller/vendor/rack-1.0/rack/showexceptions.rb +349 -0
  109. data/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb +106 -0
  110. data/lib/action_controller/vendor/rack-1.0/rack/static.rb +38 -0
  111. data/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb +55 -0
  112. data/lib/action_controller/vendor/rack-1.0/rack/utils.rb +392 -0
  113. data/lib/action_controller/verification.rb +1 -1
  114. data/lib/action_pack.rb +1 -1
  115. data/lib/action_pack/version.rb +2 -2
  116. data/lib/action_view.rb +22 -17
  117. data/lib/action_view/base.rb +53 -79
  118. data/lib/action_view/erb/util.rb +38 -0
  119. data/lib/action_view/helpers.rb +24 -5
  120. data/lib/action_view/helpers/active_record_helper.rb +2 -2
  121. data/lib/action_view/helpers/asset_tag_helper.rb +81 -50
  122. data/lib/action_view/helpers/atom_feed_helper.rb +1 -1
  123. data/lib/action_view/helpers/benchmark_helper.rb +26 -5
  124. data/lib/action_view/helpers/date_helper.rb +82 -7
  125. data/lib/action_view/helpers/form_helper.rb +295 -64
  126. data/lib/action_view/helpers/form_options_helper.rb +160 -18
  127. data/lib/action_view/helpers/form_tag_helper.rb +2 -2
  128. data/lib/action_view/helpers/number_helper.rb +31 -18
  129. data/lib/action_view/helpers/prototype_helper.rb +2 -12
  130. data/lib/action_view/helpers/sanitize_helper.rb +0 -10
  131. data/lib/action_view/helpers/scriptaculous_helper.rb +1 -0
  132. data/lib/action_view/helpers/tag_helper.rb +3 -4
  133. data/lib/action_view/helpers/text_helper.rb +99 -122
  134. data/lib/action_view/helpers/translation_helper.rb +19 -1
  135. data/lib/action_view/helpers/url_helper.rb +25 -2
  136. data/lib/action_view/inline_template.rb +1 -1
  137. data/lib/action_view/locale/en.yml +19 -1
  138. data/lib/action_view/partials.rb +46 -9
  139. data/lib/action_view/paths.rb +28 -84
  140. data/lib/action_view/reloadable_template.rb +117 -0
  141. data/lib/action_view/renderable.rb +28 -35
  142. data/lib/action_view/renderable_partial.rb +3 -4
  143. data/lib/action_view/template.rb +172 -31
  144. data/lib/action_view/template_error.rb +8 -9
  145. data/lib/action_view/template_handler.rb +1 -1
  146. data/lib/action_view/template_handlers.rb +9 -6
  147. data/lib/action_view/template_handlers/erb.rb +2 -39
  148. data/lib/action_view/template_handlers/rjs.rb +1 -0
  149. data/lib/action_view/test_case.rb +27 -1
  150. data/test/abstract_unit.rb +23 -17
  151. data/test/active_record_unit.rb +5 -4
  152. data/test/activerecord/active_record_store_test.rb +139 -106
  153. data/test/activerecord/render_partial_with_record_identification_test.rb +5 -21
  154. data/test/controller/action_pack_assertions_test.rb +25 -23
  155. data/test/controller/addresses_render_test.rb +3 -6
  156. data/test/controller/assert_select_test.rb +83 -70
  157. data/test/controller/base_test.rb +11 -13
  158. data/test/controller/benchmark_test.rb +3 -3
  159. data/test/controller/caching_test.rb +34 -24
  160. data/test/controller/capture_test.rb +3 -6
  161. data/test/controller/content_type_test.rb +3 -6
  162. data/test/controller/cookie_test.rb +31 -66
  163. data/test/controller/deprecation/deprecated_base_methods_test.rb +9 -11
  164. data/test/controller/dispatcher_test.rb +23 -28
  165. data/test/controller/fake_models.rb +8 -0
  166. data/test/controller/filters_test.rb +6 -2
  167. data/test/controller/flash_test.rb +2 -6
  168. data/test/controller/helper_test.rb +15 -1
  169. data/test/controller/html-scanner/document_test.rb +1 -1
  170. data/test/controller/html-scanner/sanitizer_test.rb +1 -1
  171. data/test/controller/http_basic_authentication_test.rb +88 -0
  172. data/test/controller/http_digest_authentication_test.rb +178 -0
  173. data/test/controller/integration_test.rb +56 -52
  174. data/test/controller/layout_test.rb +46 -44
  175. data/test/controller/middleware_stack_test.rb +90 -0
  176. data/test/controller/mime_responds_test.rb +7 -11
  177. data/test/controller/mime_type_test.rb +9 -0
  178. data/test/controller/polymorphic_routes_test.rb +235 -151
  179. data/test/controller/rack_test.rb +52 -81
  180. data/test/controller/redirect_test.rb +6 -14
  181. data/test/controller/render_test.rb +273 -60
  182. data/test/controller/request/json_params_parsing_test.rb +45 -0
  183. data/test/controller/request/multipart_params_parsing_test.rb +223 -0
  184. data/test/controller/request/query_string_parsing_test.rb +120 -0
  185. data/test/controller/request/url_encoded_params_parsing_test.rb +184 -0
  186. data/test/controller/request/xml_params_parsing_test.rb +88 -0
  187. data/test/controller/request_forgery_protection_test.rb +17 -98
  188. data/test/controller/request_test.rb +45 -530
  189. data/test/controller/rescue_test.rb +45 -22
  190. data/test/controller/resources_test.rb +112 -37
  191. data/test/controller/routing_test.rb +1442 -1384
  192. data/test/controller/selector_test.rb +3 -3
  193. data/test/controller/send_file_test.rb +30 -3
  194. data/test/controller/session/cookie_store_test.rb +169 -240
  195. data/test/controller/session/mem_cache_store_test.rb +94 -148
  196. data/test/controller/session/test_session_test.rb +58 -0
  197. data/test/controller/test_test.rb +32 -13
  198. data/test/controller/url_rewriter_test.rb +54 -4
  199. data/test/controller/verification_test.rb +1 -1
  200. data/test/controller/view_paths_test.rb +15 -15
  201. data/test/controller/webservice_test.rb +178 -147
  202. data/test/fixtures/alternate_helpers/foo_helper.rb +3 -0
  203. data/test/fixtures/layout_tests/alt/layouts/alt.rhtml +0 -0
  204. data/test/fixtures/layouts/default_html.html.erb +1 -0
  205. data/test/fixtures/layouts/xhr.html.erb +2 -0
  206. data/test/fixtures/multipart/empty +10 -0
  207. data/test/fixtures/multipart/hello.txt +1 -0
  208. data/test/fixtures/multipart/none +9 -0
  209. data/test/fixtures/public/500.da.html +1 -0
  210. data/test/fixtures/quiz/questions/_question.html.erb +1 -0
  211. data/test/fixtures/replies.yml +1 -1
  212. data/test/fixtures/test/_one.html.erb +1 -0
  213. data/test/fixtures/test/_two.html.erb +1 -0
  214. data/test/fixtures/test/dont_pick_me +1 -0
  215. data/test/fixtures/test/hello.builder +1 -1
  216. data/test/fixtures/test/hello_world.da.html.erb +1 -0
  217. data/test/fixtures/test/hello_world.erb~ +1 -0
  218. data/test/fixtures/test/hello_world.pt-BR.html.erb +1 -0
  219. data/test/fixtures/test/malformed/malformed.en.html.erb~ +1 -0
  220. data/test/fixtures/test/malformed/malformed.erb~ +1 -0
  221. data/test/fixtures/test/malformed/malformed.html.erb~ +1 -0
  222. data/test/fixtures/test/render_explicit_html_template.js.rjs +1 -0
  223. data/test/fixtures/test/render_implicit_html_template.js.rjs +1 -0
  224. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.da.html.erb +1 -0
  225. data/test/fixtures/test/render_implicit_html_template_from_xhr_request.html.erb +1 -0
  226. data/test/fixtures/test/render_implicit_js_template_without_layout.js.erb +1 -0
  227. data/test/fixtures/test/utf8.html.erb +2 -0
  228. data/test/template/active_record_helper_i18n_test.rb +31 -33
  229. data/test/template/active_record_helper_test.rb +34 -0
  230. data/test/template/asset_tag_helper_test.rb +52 -14
  231. data/test/template/atom_feed_helper_test.rb +3 -5
  232. data/test/template/benchmark_helper_test.rb +50 -24
  233. data/test/template/compiled_templates_test.rb +177 -33
  234. data/test/template/date_helper_i18n_test.rb +88 -81
  235. data/test/template/date_helper_test.rb +427 -43
  236. data/test/template/form_helper_test.rb +243 -44
  237. data/test/template/form_options_helper_test.rb +631 -565
  238. data/test/template/form_tag_helper_test.rb +9 -2
  239. data/test/template/javascript_helper_test.rb +0 -5
  240. data/test/template/number_helper_i18n_test.rb +60 -48
  241. data/test/template/number_helper_test.rb +1 -0
  242. data/test/template/render_test.rb +117 -35
  243. data/test/template/test_test.rb +4 -6
  244. data/test/template/text_helper_test.rb +129 -50
  245. data/test/template/translation_helper_test.rb +23 -19
  246. data/test/template/url_helper_test.rb +35 -2
  247. data/test/view/test_case_test.rb +8 -0
  248. metadata +197 -23
  249. data/lib/action_controller/assertions.rb +0 -69
  250. data/lib/action_controller/caching/sql_cache.rb +0 -18
  251. data/lib/action_controller/cgi_ext/session.rb +0 -53
  252. data/lib/action_controller/components.rb +0 -169
  253. data/lib/action_controller/rack_process.rb +0 -297
  254. data/lib/action_controller/request_profiler.rb +0 -169
  255. data/lib/action_controller/session/active_record_store.rb +0 -340
  256. data/lib/action_controller/session/drb_server.rb +0 -32
  257. data/lib/action_controller/session/drb_store.rb +0 -35
  258. data/test/controller/cgi_test.rb +0 -269
  259. data/test/controller/components_test.rb +0 -156
  260. data/test/controller/http_authentication_test.rb +0 -54
  261. data/test/controller/integration_upload_test.rb +0 -43
  262. data/test/controller/session_fixation_test.rb +0 -89
  263. data/test/controller/session_management_test.rb +0 -178
  264. data/test/fixtures/test/hello_world.js +0 -1
@@ -0,0 +1,142 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+ # bugrep: Andreas Zehnder
3
+
4
+ require 'time'
5
+ require 'rack/request'
6
+ require 'rack/response'
7
+
8
+ module Rack
9
+
10
+ module Session
11
+
12
+ module Abstract
13
+
14
+ # ID sets up a basic framework for implementing an id based sessioning
15
+ # service. Cookies sent to the client for maintaining sessions will only
16
+ # contain an id reference. Only #get_session and #set_session are
17
+ # required to be overwritten.
18
+ #
19
+ # All parameters are optional.
20
+ # * :key determines the name of the cookie, by default it is
21
+ # 'rack.session'
22
+ # * :path, :domain, :expire_after, :secure, and :httponly set the related
23
+ # cookie options as by Rack::Response#add_cookie
24
+ # * :defer will not set a cookie in the response.
25
+ # * :renew (implementation dependent) will prompt the generation of a new
26
+ # session id, and migration of data to be referenced at the new id. If
27
+ # :defer is set, it will be overridden and the cookie will be set.
28
+ # * :sidbits sets the number of bits in length that a generated session
29
+ # id will be.
30
+ #
31
+ # These options can be set on a per request basis, at the location of
32
+ # env['rack.session.options']. Additionally the id of the session can be
33
+ # found within the options hash at the key :id. It is highly not
34
+ # recommended to change its value.
35
+ #
36
+ # Is Rack::Utils::Context compatible.
37
+
38
+ class ID
39
+ DEFAULT_OPTIONS = {
40
+ :path => '/',
41
+ :domain => nil,
42
+ :expire_after => nil,
43
+ :secure => false,
44
+ :httponly => true,
45
+ :defer => false,
46
+ :renew => false,
47
+ :sidbits => 128
48
+ }
49
+
50
+ attr_reader :key, :default_options
51
+ def initialize(app, options={})
52
+ @app = app
53
+ @key = options[:key] || "rack.session"
54
+ @default_options = self.class::DEFAULT_OPTIONS.merge(options)
55
+ end
56
+
57
+ def call(env)
58
+ context(env)
59
+ end
60
+
61
+ def context(env, app=@app)
62
+ load_session(env)
63
+ status, headers, body = app.call(env)
64
+ commit_session(env, status, headers, body)
65
+ end
66
+
67
+ private
68
+
69
+ # Generate a new session id using Ruby #rand. The size of the
70
+ # session id is controlled by the :sidbits option.
71
+ # Monkey patch this to use custom methods for session id generation.
72
+
73
+ def generate_sid
74
+ "%0#{@default_options[:sidbits] / 4}x" %
75
+ rand(2**@default_options[:sidbits] - 1)
76
+ end
77
+
78
+ # Extracts the session id from provided cookies and passes it and the
79
+ # environment to #get_session. It then sets the resulting session into
80
+ # 'rack.session', and places options and session metadata into
81
+ # 'rack.session.options'.
82
+
83
+ def load_session(env)
84
+ request = Rack::Request.new(env)
85
+ session_id = request.cookies[@key]
86
+
87
+ begin
88
+ session_id, session = get_session(env, session_id)
89
+ env['rack.session'] = session
90
+ rescue
91
+ env['rack.session'] = Hash.new
92
+ end
93
+
94
+ env['rack.session.options'] = @default_options.
95
+ merge(:id => session_id)
96
+ end
97
+
98
+ # Acquires the session from the environment and the session id from
99
+ # the session options and passes them to #set_session. If successful
100
+ # and the :defer option is not true, a cookie will be added to the
101
+ # response with the session's id.
102
+
103
+ def commit_session(env, status, headers, body)
104
+ session = env['rack.session']
105
+ options = env['rack.session.options']
106
+ session_id = options[:id]
107
+
108
+ if not session_id = set_session(env, session_id, session, options)
109
+ env["rack.errors"].puts("Warning! #{self.class.name} failed to save session. Content dropped.")
110
+ [status, headers, body]
111
+ elsif options[:defer] and not options[:renew]
112
+ env["rack.errors"].puts("Defering cookie for #{session_id}") if $VERBOSE
113
+ [status, headers, body]
114
+ else
115
+ cookie = Hash.new
116
+ cookie[:value] = session_id
117
+ cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
118
+ response = Rack::Response.new(body, status, headers)
119
+ response.set_cookie(@key, cookie.merge(options))
120
+ response.to_a
121
+ end
122
+ end
123
+
124
+ # All thread safety and session retrival proceedures should occur here.
125
+ # Should return [session_id, session].
126
+ # If nil is provided as the session id, generation of a new valid id
127
+ # should occur within.
128
+
129
+ def get_session(env, sid)
130
+ raise '#get_session not implemented.'
131
+ end
132
+
133
+ # All thread safety and session storage proceedures should occur here.
134
+ # Should return true or false dependant on whether or not the session
135
+ # was saved or not.
136
+ def set_session(env, sid, session, options)
137
+ raise '#set_session not implemented.'
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,91 @@
1
+ require 'openssl'
2
+ require 'rack/request'
3
+ require 'rack/response'
4
+
5
+ module Rack
6
+
7
+ module Session
8
+
9
+ # Rack::Session::Cookie provides simple cookie based session management.
10
+ # The session is a Ruby Hash stored as base64 encoded marshalled data
11
+ # set to :key (default: rack.session).
12
+ # When the secret key is set, cookie data is checked for data integrity.
13
+ #
14
+ # Example:
15
+ #
16
+ # use Rack::Session::Cookie, :key => 'rack.session',
17
+ # :domain => 'foo.com',
18
+ # :path => '/',
19
+ # :expire_after => 2592000,
20
+ # :secret => 'change_me'
21
+ #
22
+ # All parameters are optional.
23
+
24
+ class Cookie
25
+
26
+ def initialize(app, options={})
27
+ @app = app
28
+ @key = options[:key] || "rack.session"
29
+ @secret = options[:secret]
30
+ @default_options = {:domain => nil,
31
+ :path => "/",
32
+ :expire_after => nil}.merge(options)
33
+ end
34
+
35
+ def call(env)
36
+ load_session(env)
37
+ status, headers, body = @app.call(env)
38
+ commit_session(env, status, headers, body)
39
+ end
40
+
41
+ private
42
+
43
+ def load_session(env)
44
+ request = Rack::Request.new(env)
45
+ session_data = request.cookies[@key]
46
+
47
+ if @secret && session_data
48
+ session_data, digest = session_data.split("--")
49
+ session_data = nil unless digest == generate_hmac(session_data)
50
+ end
51
+
52
+ begin
53
+ session_data = session_data.unpack("m*").first
54
+ session_data = Marshal.load(session_data)
55
+ env["rack.session"] = session_data
56
+ rescue
57
+ env["rack.session"] = Hash.new
58
+ end
59
+
60
+ env["rack.session.options"] = @default_options.dup
61
+ end
62
+
63
+ def commit_session(env, status, headers, body)
64
+ session_data = Marshal.dump(env["rack.session"])
65
+ session_data = [session_data].pack("m*")
66
+
67
+ if @secret
68
+ session_data = "#{session_data}--#{generate_hmac(session_data)}"
69
+ end
70
+
71
+ if session_data.size > (4096 - @key.size)
72
+ env["rack.errors"].puts("Warning! Rack::Session::Cookie data size exceeds 4K. Content dropped.")
73
+ [status, headers, body]
74
+ else
75
+ options = env["rack.session.options"]
76
+ cookie = Hash.new
77
+ cookie[:value] = session_data
78
+ cookie[:expires] = Time.now + options[:expire_after] unless options[:expire_after].nil?
79
+ response = Rack::Response.new(body, status, headers)
80
+ response.set_cookie(@key, cookie.merge(options))
81
+ response.to_a
82
+ end
83
+ end
84
+
85
+ def generate_hmac(data)
86
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, @secret, data)
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,109 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+
3
+ require 'rack/session/abstract/id'
4
+ require 'memcache'
5
+
6
+ module Rack
7
+ module Session
8
+ # Rack::Session::Memcache provides simple cookie based session management.
9
+ # Session data is stored in memcached. The corresponding session key is
10
+ # maintained in the cookie.
11
+ # You may treat Session::Memcache as you would Session::Pool with the
12
+ # following caveats.
13
+ #
14
+ # * Setting :expire_after to 0 would note to the Memcache server to hang
15
+ # onto the session data until it would drop it according to it's own
16
+ # specifications. However, the cookie sent to the client would expire
17
+ # immediately.
18
+ #
19
+ # Note that memcache does drop data before it may be listed to expire. For
20
+ # a full description of behaviour, please see memcache's documentation.
21
+
22
+ class Memcache < Abstract::ID
23
+ attr_reader :mutex, :pool
24
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \
25
+ :namespace => 'rack:session',
26
+ :memcache_server => 'localhost:11211'
27
+
28
+ def initialize(app, options={})
29
+ super
30
+
31
+ @mutex = Mutex.new
32
+ @pool = MemCache.
33
+ new @default_options[:memcache_server], @default_options
34
+ raise 'No memcache servers' unless @pool.servers.any?{|s|s.alive?}
35
+ end
36
+
37
+ def generate_sid
38
+ loop do
39
+ sid = super
40
+ break sid unless @pool.get(sid, true)
41
+ end
42
+ end
43
+
44
+ def get_session(env, sid)
45
+ session = @pool.get(sid) if sid
46
+ @mutex.lock if env['rack.multithread']
47
+ unless sid and session
48
+ env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
49
+ session = {}
50
+ sid = generate_sid
51
+ ret = @pool.add sid, session
52
+ raise "Session collision on '#{sid.inspect}'" unless /^STORED/ =~ ret
53
+ end
54
+ session.instance_variable_set('@old', {}.merge(session))
55
+ return [sid, session]
56
+ rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
57
+ warn "#{self} is unable to find server."
58
+ warn $!.inspect
59
+ return [ nil, {} ]
60
+ ensure
61
+ @mutex.unlock if env['rack.multithread']
62
+ end
63
+
64
+ def set_session(env, session_id, new_session, options)
65
+ expiry = options[:expire_after]
66
+ expiry = expiry.nil? ? 0 : expiry + 1
67
+
68
+ @mutex.lock if env['rack.multithread']
69
+ session = @pool.get(session_id) || {}
70
+ if options[:renew] or options[:drop]
71
+ @pool.delete session_id
72
+ return false if options[:drop]
73
+ session_id = generate_sid
74
+ @pool.add session_id, 0 # so we don't worry about cache miss on #set
75
+ end
76
+ old_session = new_session.instance_variable_get('@old') || {}
77
+ session = merge_sessions session_id, old_session, new_session, session
78
+ @pool.set session_id, session, expiry
79
+ return session_id
80
+ rescue MemCache::MemCacheError, Errno::ECONNREFUSED # MemCache server cannot be contacted
81
+ warn "#{self} is unable to find server."
82
+ warn $!.inspect
83
+ return false
84
+ ensure
85
+ @mutex.unlock if env['rack.multithread']
86
+ end
87
+
88
+ private
89
+
90
+ def merge_sessions sid, old, new, cur=nil
91
+ cur ||= {}
92
+ unless Hash === old and Hash === new
93
+ warn 'Bad old or new sessions provided.'
94
+ return cur
95
+ end
96
+
97
+ delete = old.keys - new.keys
98
+ warn "//@#{sid}: delete #{delete*','}" if $VERBOSE and not delete.empty?
99
+ delete.each{|k| cur.delete k }
100
+
101
+ update = new.keys.select{|k| new[k] != old[k] }
102
+ warn "//@#{sid}: update #{update*','}" if $VERBOSE and not update.empty?
103
+ update.each{|k| cur[k] = new[k] }
104
+
105
+ cur
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,100 @@
1
+ # AUTHOR: blink <blinketje@gmail.com>; blink#ruby-lang@irc.freenode.net
2
+ # THANKS:
3
+ # apeiros, for session id generation, expiry setup, and threadiness
4
+ # sergio, threadiness and bugreps
5
+
6
+ require 'rack/session/abstract/id'
7
+ require 'thread'
8
+
9
+ module Rack
10
+ module Session
11
+ # Rack::Session::Pool provides simple cookie based session management.
12
+ # Session data is stored in a hash held by @pool.
13
+ # In the context of a multithreaded environment, sessions being
14
+ # committed to the pool is done in a merging manner.
15
+ #
16
+ # The :drop option is available in rack.session.options if you with to
17
+ # explicitly remove the session from the session cache.
18
+ #
19
+ # Example:
20
+ # myapp = MyRackApp.new
21
+ # sessioned = Rack::Session::Pool.new(myapp,
22
+ # :domain => 'foo.com',
23
+ # :expire_after => 2592000
24
+ # )
25
+ # Rack::Handler::WEBrick.run sessioned
26
+
27
+ class Pool < Abstract::ID
28
+ attr_reader :mutex, :pool
29
+ DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge :drop => false
30
+
31
+ def initialize(app, options={})
32
+ super
33
+ @pool = Hash.new
34
+ @mutex = Mutex.new
35
+ end
36
+
37
+ def generate_sid
38
+ loop do
39
+ sid = super
40
+ break sid unless @pool.key? sid
41
+ end
42
+ end
43
+
44
+ def get_session(env, sid)
45
+ session = @pool[sid] if sid
46
+ @mutex.lock if env['rack.multithread']
47
+ unless sid and session
48
+ env['rack.errors'].puts("Session '#{sid.inspect}' not found, initializing...") if $VERBOSE and not sid.nil?
49
+ session = {}
50
+ sid = generate_sid
51
+ @pool.store sid, session
52
+ end
53
+ session.instance_variable_set('@old', {}.merge(session))
54
+ return [sid, session]
55
+ ensure
56
+ @mutex.unlock if env['rack.multithread']
57
+ end
58
+
59
+ def set_session(env, session_id, new_session, options)
60
+ @mutex.lock if env['rack.multithread']
61
+ session = @pool[session_id]
62
+ if options[:renew] or options[:drop]
63
+ @pool.delete session_id
64
+ return false if options[:drop]
65
+ session_id = generate_sid
66
+ @pool.store session_id, 0
67
+ end
68
+ old_session = new_session.instance_variable_get('@old') || {}
69
+ session = merge_sessions session_id, old_session, new_session, session
70
+ @pool.store session_id, session
71
+ return session_id
72
+ rescue
73
+ warn "#{new_session.inspect} has been lost."
74
+ warn $!.inspect
75
+ ensure
76
+ @mutex.unlock if env['rack.multithread']
77
+ end
78
+
79
+ private
80
+
81
+ def merge_sessions sid, old, new, cur=nil
82
+ cur ||= {}
83
+ unless Hash === old and Hash === new
84
+ warn 'Bad old or new sessions provided.'
85
+ return cur
86
+ end
87
+
88
+ delete = old.keys - new.keys
89
+ warn "//@#{sid}: dropping #{delete*','}" if $DEBUG and not delete.empty?
90
+ delete.each{|k| cur.delete k }
91
+
92
+ update = new.keys.select{|k| new[k] != old[k] }
93
+ warn "//@#{sid}: updating #{update*','}" if $DEBUG and not update.empty?
94
+ update.each{|k| cur[k] = new[k] }
95
+
96
+ cur
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,349 @@
1
+ require 'ostruct'
2
+ require 'erb'
3
+ require 'rack/request'
4
+ require 'rack/utils'
5
+
6
+ module Rack
7
+ # Rack::ShowExceptions catches all exceptions raised from the app it
8
+ # wraps. It shows a useful backtrace with the sourcefile and
9
+ # clickable context, the whole Rack environment and the request
10
+ # data.
11
+ #
12
+ # Be careful when you use this on public-facing sites as it could
13
+ # reveal information helpful to attackers.
14
+
15
+ class ShowExceptions
16
+ CONTEXT = 7
17
+
18
+ def initialize(app)
19
+ @app = app
20
+ @template = ERB.new(TEMPLATE)
21
+ end
22
+
23
+ def call(env)
24
+ @app.call(env)
25
+ rescue StandardError, LoadError, SyntaxError => e
26
+ backtrace = pretty(env, e)
27
+ [500,
28
+ {"Content-Type" => "text/html",
29
+ "Content-Length" => backtrace.join.size.to_s},
30
+ backtrace]
31
+ end
32
+
33
+ def pretty(env, exception)
34
+ req = Rack::Request.new(env)
35
+ path = (req.script_name + req.path_info).squeeze("/")
36
+
37
+ frames = exception.backtrace.map { |line|
38
+ frame = OpenStruct.new
39
+ if line =~ /(.*?):(\d+)(:in `(.*)')?/
40
+ frame.filename = $1
41
+ frame.lineno = $2.to_i
42
+ frame.function = $4
43
+
44
+ begin
45
+ lineno = frame.lineno-1
46
+ lines = ::File.readlines(frame.filename)
47
+ frame.pre_context_lineno = [lineno-CONTEXT, 0].max
48
+ frame.pre_context = lines[frame.pre_context_lineno...lineno]
49
+ frame.context_line = lines[lineno].chomp
50
+ frame.post_context_lineno = [lineno+CONTEXT, lines.size].min
51
+ frame.post_context = lines[lineno+1..frame.post_context_lineno]
52
+ rescue
53
+ end
54
+
55
+ frame
56
+ else
57
+ nil
58
+ end
59
+ }.compact
60
+
61
+ env["rack.errors"].puts "#{exception.class}: #{exception.message}"
62
+ env["rack.errors"].puts exception.backtrace.map { |l| "\t" + l }
63
+ env["rack.errors"].flush
64
+
65
+ [@template.result(binding)]
66
+ end
67
+
68
+ def h(obj) # :nodoc:
69
+ case obj
70
+ when String
71
+ Utils.escape_html(obj)
72
+ else
73
+ Utils.escape_html(obj.inspect)
74
+ end
75
+ end
76
+
77
+ # :stopdoc:
78
+
79
+ # adapted from Django <djangoproject.com>
80
+ # Copyright (c) 2005, the Lawrence Journal-World
81
+ # Used under the modified BSD license:
82
+ # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
83
+ TEMPLATE = <<'HTML'
84
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
85
+ <html lang="en">
86
+ <head>
87
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
88
+ <meta name="robots" content="NONE,NOARCHIVE" />
89
+ <title><%=h exception.class %> at <%=h path %></title>
90
+ <style type="text/css">
91
+ html * { padding:0; margin:0; }
92
+ body * { padding:10px 20px; }
93
+ body * * { padding:0; }
94
+ body { font:small sans-serif; }
95
+ body>div { border-bottom:1px solid #ddd; }
96
+ h1 { font-weight:normal; }
97
+ h2 { margin-bottom:.8em; }
98
+ h2 span { font-size:80%; color:#666; font-weight:normal; }
99
+ h3 { margin:1em 0 .5em 0; }
100
+ h4 { margin:0 0 .5em 0; font-weight: normal; }
101
+ table {
102
+ border:1px solid #ccc; border-collapse: collapse; background:white; }
103
+ tbody td, tbody th { vertical-align:top; padding:2px 3px; }
104
+ thead th {
105
+ padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
106
+ font-weight:normal; font-size:11px; border:1px solid #ddd; }
107
+ tbody th { text-align:right; color:#666; padding-right:.5em; }
108
+ table.vars { margin:5px 0 2px 40px; }
109
+ table.vars td, table.req td { font-family:monospace; }
110
+ table td.code { width:100%;}
111
+ table td.code div { overflow:hidden; }
112
+ table.source th { color:#666; }
113
+ table.source td {
114
+ font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
115
+ ul.traceback { list-style-type:none; }
116
+ ul.traceback li.frame { margin-bottom:1em; }
117
+ div.context { margin: 10px 0; }
118
+ div.context ol {
119
+ padding-left:30px; margin:0 10px; list-style-position: inside; }
120
+ div.context ol li {
121
+ font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
122
+ div.context ol.context-line li { color:black; background-color:#ccc; }
123
+ div.context ol.context-line li span { float: right; }
124
+ div.commands { margin-left: 40px; }
125
+ div.commands a { color:black; text-decoration:none; }
126
+ #summary { background: #ffc; }
127
+ #summary h2 { font-weight: normal; color: #666; }
128
+ #summary ul#quicklinks { list-style-type: none; margin-bottom: 2em; }
129
+ #summary ul#quicklinks li { float: left; padding: 0 1em; }
130
+ #summary ul#quicklinks>li+li { border-left: 1px #666 solid; }
131
+ #explanation { background:#eee; }
132
+ #template, #template-not-exist { background:#f6f6f6; }
133
+ #template-not-exist ul { margin: 0 0 0 20px; }
134
+ #traceback { background:#eee; }
135
+ #requestinfo { background:#f6f6f6; padding-left:120px; }
136
+ #summary table { border:none; background:transparent; }
137
+ #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
138
+ #requestinfo h3 { margin-bottom:-1em; }
139
+ .error { background: #ffc; }
140
+ .specific { color:#cc3300; font-weight:bold; }
141
+ </style>
142
+ <script type="text/javascript">
143
+ //<!--
144
+ function getElementsByClassName(oElm, strTagName, strClassName){
145
+ // Written by Jonathan Snook, http://www.snook.ca/jon;
146
+ // Add-ons by Robert Nyman, http://www.robertnyman.com
147
+ var arrElements = (strTagName == "*" && document.all)? document.all :
148
+ oElm.getElementsByTagName(strTagName);
149
+ var arrReturnElements = new Array();
150
+ strClassName = strClassName.replace(/\-/g, "\\-");
151
+ var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
152
+ var oElement;
153
+ for(var i=0; i<arrElements.length; i++){
154
+ oElement = arrElements[i];
155
+ if(oRegExp.test(oElement.className)){
156
+ arrReturnElements.push(oElement);
157
+ }
158
+ }
159
+ return (arrReturnElements)
160
+ }
161
+ function hideAll(elems) {
162
+ for (var e = 0; e < elems.length; e++) {
163
+ elems[e].style.display = 'none';
164
+ }
165
+ }
166
+ window.onload = function() {
167
+ hideAll(getElementsByClassName(document, 'table', 'vars'));
168
+ hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
169
+ hideAll(getElementsByClassName(document, 'ol', 'post-context'));
170
+ }
171
+ function toggle() {
172
+ for (var i = 0; i < arguments.length; i++) {
173
+ var e = document.getElementById(arguments[i]);
174
+ if (e) {
175
+ e.style.display = e.style.display == 'none' ? 'block' : 'none';
176
+ }
177
+ }
178
+ return false;
179
+ }
180
+ function varToggle(link, id) {
181
+ toggle('v' + id);
182
+ var s = link.getElementsByTagName('span')[0];
183
+ var uarr = String.fromCharCode(0x25b6);
184
+ var darr = String.fromCharCode(0x25bc);
185
+ s.innerHTML = s.innerHTML == uarr ? darr : uarr;
186
+ return false;
187
+ }
188
+ //-->
189
+ </script>
190
+ </head>
191
+ <body>
192
+
193
+ <div id="summary">
194
+ <h1><%=h exception.class %> at <%=h path %></h1>
195
+ <h2><%=h exception.message %></h2>
196
+ <table><tr>
197
+ <th>Ruby</th>
198
+ <td><code><%=h frames.first.filename %></code>: in <code><%=h frames.first.function %></code>, line <%=h frames.first.lineno %></td>
199
+ </tr><tr>
200
+ <th>Web</th>
201
+ <td><code><%=h req.request_method %> <%=h(req.host + path)%></code></td>
202
+ </tr></table>
203
+
204
+ <h3>Jump to:</h3>
205
+ <ul id="quicklinks">
206
+ <li><a href="#get-info">GET</a></li>
207
+ <li><a href="#post-info">POST</a></li>
208
+ <li><a href="#cookie-info">Cookies</a></li>
209
+ <li><a href="#env-info">ENV</a></li>
210
+ </ul>
211
+ </div>
212
+
213
+ <div id="traceback">
214
+ <h2>Traceback <span>(innermost first)</span></h2>
215
+ <ul class="traceback">
216
+ <% frames.each { |frame| %>
217
+ <li class="frame">
218
+ <code><%=h frame.filename %></code>: in <code><%=h frame.function %></code>
219
+
220
+ <% if frame.context_line %>
221
+ <div class="context" id="c<%=h frame.object_id %>">
222
+ <% if frame.pre_context %>
223
+ <ol start="<%=h frame.pre_context_lineno+1 %>" class="pre-context" id="pre<%=h frame.object_id %>">
224
+ <% frame.pre_context.each { |line| %>
225
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
226
+ <% } %>
227
+ </ol>
228
+ <% end %>
229
+
230
+ <ol start="<%=h frame.lineno %>" class="context-line">
231
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h frame.context_line %><span>...</span></li></ol>
232
+
233
+ <% if frame.post_context %>
234
+ <ol start='<%=h frame.lineno+1 %>' class="post-context" id="post<%=h frame.object_id %>">
235
+ <% frame.post_context.each { |line| %>
236
+ <li onclick="toggle('pre<%=h frame.object_id %>', 'post<%=h frame.object_id %>')"><%=h line %></li>
237
+ <% } %>
238
+ </ol>
239
+ <% end %>
240
+ </div>
241
+ <% end %>
242
+ </li>
243
+ <% } %>
244
+ </ul>
245
+ </div>
246
+
247
+ <div id="requestinfo">
248
+ <h2>Request information</h2>
249
+
250
+ <h3 id="get-info">GET</h3>
251
+ <% unless req.GET.empty? %>
252
+ <table class="req">
253
+ <thead>
254
+ <tr>
255
+ <th>Variable</th>
256
+ <th>Value</th>
257
+ </tr>
258
+ </thead>
259
+ <tbody>
260
+ <% req.GET.sort_by { |k, v| k.to_s }.each { |key, val| %>
261
+ <tr>
262
+ <td><%=h key %></td>
263
+ <td class="code"><div><%=h val.inspect %></div></td>
264
+ </tr>
265
+ <% } %>
266
+ </tbody>
267
+ </table>
268
+ <% else %>
269
+ <p>No GET data.</p>
270
+ <% end %>
271
+
272
+ <h3 id="post-info">POST</h3>
273
+ <% unless req.POST.empty? %>
274
+ <table class="req">
275
+ <thead>
276
+ <tr>
277
+ <th>Variable</th>
278
+ <th>Value</th>
279
+ </tr>
280
+ </thead>
281
+ <tbody>
282
+ <% req.POST.sort_by { |k, v| k.to_s }.each { |key, val| %>
283
+ <tr>
284
+ <td><%=h key %></td>
285
+ <td class="code"><div><%=h val.inspect %></div></td>
286
+ </tr>
287
+ <% } %>
288
+ </tbody>
289
+ </table>
290
+ <% else %>
291
+ <p>No POST data.</p>
292
+ <% end %>
293
+
294
+
295
+ <h3 id="cookie-info">COOKIES</h3>
296
+ <% unless req.cookies.empty? %>
297
+ <table class="req">
298
+ <thead>
299
+ <tr>
300
+ <th>Variable</th>
301
+ <th>Value</th>
302
+ </tr>
303
+ </thead>
304
+ <tbody>
305
+ <% req.cookies.each { |key, val| %>
306
+ <tr>
307
+ <td><%=h key %></td>
308
+ <td class="code"><div><%=h val.inspect %></div></td>
309
+ </tr>
310
+ <% } %>
311
+ </tbody>
312
+ </table>
313
+ <% else %>
314
+ <p>No cookie data.</p>
315
+ <% end %>
316
+
317
+ <h3 id="env-info">Rack ENV</h3>
318
+ <table class="req">
319
+ <thead>
320
+ <tr>
321
+ <th>Variable</th>
322
+ <th>Value</th>
323
+ </tr>
324
+ </thead>
325
+ <tbody>
326
+ <% env.sort_by { |k, v| k.to_s }.each { |key, val| %>
327
+ <tr>
328
+ <td><%=h key %></td>
329
+ <td class="code"><div><%=h val %></div></td>
330
+ </tr>
331
+ <% } %>
332
+ </tbody>
333
+ </table>
334
+
335
+ </div>
336
+
337
+ <div id="explanation">
338
+ <p>
339
+ You're seeing this error because you use <code>Rack::ShowExceptions</code>.
340
+ </p>
341
+ </div>
342
+
343
+ </body>
344
+ </html>
345
+ HTML
346
+
347
+ # :startdoc:
348
+ end
349
+ end