actionpack 2.0.5 → 2.1.0

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 (210) hide show
  1. data/CHANGELOG +149 -7
  2. data/MIT-LICENSE +1 -1
  3. data/README +1 -1
  4. data/Rakefile +5 -6
  5. data/lib/action_controller.rb +2 -2
  6. data/lib/action_controller/assertions/model_assertions.rb +2 -1
  7. data/lib/action_controller/assertions/response_assertions.rb +4 -2
  8. data/lib/action_controller/assertions/routing_assertions.rb +3 -3
  9. data/lib/action_controller/assertions/selector_assertions.rb +30 -27
  10. data/lib/action_controller/assertions/tag_assertions.rb +3 -3
  11. data/lib/action_controller/base.rb +103 -129
  12. data/lib/action_controller/benchmarking.rb +3 -3
  13. data/lib/action_controller/caching.rb +41 -652
  14. data/lib/action_controller/caching/actions.rb +144 -0
  15. data/lib/action_controller/caching/fragments.rb +138 -0
  16. data/lib/action_controller/caching/pages.rb +154 -0
  17. data/lib/action_controller/caching/sql_cache.rb +18 -0
  18. data/lib/action_controller/caching/sweeping.rb +97 -0
  19. data/lib/action_controller/cgi_ext/cookie.rb +27 -23
  20. data/lib/action_controller/cgi_ext/stdinput.rb +1 -0
  21. data/lib/action_controller/cgi_process.rb +6 -4
  22. data/lib/action_controller/components.rb +7 -6
  23. data/lib/action_controller/cookies.rb +31 -19
  24. data/lib/action_controller/dispatcher.rb +51 -84
  25. data/lib/action_controller/filters.rb +295 -421
  26. data/lib/action_controller/flash.rb +1 -6
  27. data/lib/action_controller/headers.rb +31 -0
  28. data/lib/action_controller/helpers.rb +26 -9
  29. data/lib/action_controller/http_authentication.rb +1 -1
  30. data/lib/action_controller/integration.rb +65 -13
  31. data/lib/action_controller/layout.rb +24 -39
  32. data/lib/action_controller/mime_responds.rb +7 -3
  33. data/lib/action_controller/mime_type.rb +25 -9
  34. data/lib/action_controller/mime_types.rb +1 -1
  35. data/lib/action_controller/polymorphic_routes.rb +32 -17
  36. data/lib/action_controller/record_identifier.rb +10 -4
  37. data/lib/action_controller/request.rb +46 -30
  38. data/lib/action_controller/request_forgery_protection.rb +10 -9
  39. data/lib/action_controller/request_profiler.rb +29 -8
  40. data/lib/action_controller/rescue.rb +24 -24
  41. data/lib/action_controller/resources.rb +66 -23
  42. data/lib/action_controller/response.rb +2 -2
  43. data/lib/action_controller/routing.rb +113 -1229
  44. data/lib/action_controller/routing/builder.rb +204 -0
  45. data/lib/action_controller/{routing_optimisation.rb → routing/optimisations.rb} +13 -12
  46. data/lib/action_controller/routing/recognition_optimisation.rb +158 -0
  47. data/lib/action_controller/routing/route.rb +240 -0
  48. data/lib/action_controller/routing/route_set.rb +435 -0
  49. data/lib/action_controller/routing/routing_ext.rb +46 -0
  50. data/lib/action_controller/routing/segments.rb +283 -0
  51. data/lib/action_controller/session/active_record_store.rb +13 -8
  52. data/lib/action_controller/session/cookie_store.rb +20 -17
  53. data/lib/action_controller/session_management.rb +10 -3
  54. data/lib/action_controller/streaming.rb +45 -31
  55. data/lib/action_controller/test_case.rb +33 -23
  56. data/lib/action_controller/test_process.rb +39 -35
  57. data/lib/action_controller/url_rewriter.rb +18 -12
  58. data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -1
  59. data/lib/action_pack.rb +1 -1
  60. data/lib/action_pack/version.rb +2 -2
  61. data/lib/action_view.rb +11 -3
  62. data/lib/action_view/base.rb +73 -390
  63. data/lib/action_view/helpers/active_record_helper.rb +83 -62
  64. data/lib/action_view/helpers/asset_tag_helper.rb +101 -44
  65. data/lib/action_view/helpers/atom_feed_helper.rb +35 -7
  66. data/lib/action_view/helpers/benchmark_helper.rb +5 -3
  67. data/lib/action_view/helpers/cache_helper.rb +3 -2
  68. data/lib/action_view/helpers/capture_helper.rb +1 -2
  69. data/lib/action_view/helpers/date_helper.rb +104 -82
  70. data/lib/action_view/helpers/form_helper.rb +148 -75
  71. data/lib/action_view/helpers/form_options_helper.rb +44 -23
  72. data/lib/action_view/helpers/form_tag_helper.rb +22 -13
  73. data/lib/action_view/helpers/javascripts/controls.js +1 -1
  74. data/lib/action_view/helpers/javascripts/dragdrop.js +1 -1
  75. data/lib/action_view/helpers/javascripts/effects.js +1 -1
  76. data/lib/action_view/helpers/number_helper.rb +10 -3
  77. data/lib/action_view/helpers/prototype_helper.rb +61 -29
  78. data/lib/action_view/helpers/record_tag_helper.rb +3 -3
  79. data/lib/action_view/helpers/sanitize_helper.rb +23 -17
  80. data/lib/action_view/helpers/scriptaculous_helper.rb +86 -60
  81. data/lib/action_view/helpers/text_helper.rb +153 -125
  82. data/lib/action_view/helpers/url_helper.rb +83 -28
  83. data/lib/action_view/inline_template.rb +20 -0
  84. data/lib/action_view/partial_template.rb +70 -0
  85. data/lib/action_view/partials.rb +31 -73
  86. data/lib/action_view/template.rb +127 -0
  87. data/lib/action_view/template_error.rb +8 -7
  88. data/lib/action_view/template_finder.rb +177 -0
  89. data/lib/action_view/template_handler.rb +18 -1
  90. data/lib/action_view/template_handlers/builder.rb +10 -2
  91. data/lib/action_view/template_handlers/compilable.rb +128 -0
  92. data/lib/action_view/template_handlers/erb.rb +37 -2
  93. data/lib/action_view/template_handlers/rjs.rb +14 -1
  94. data/lib/action_view/test_case.rb +58 -0
  95. data/test/abstract_unit.rb +1 -1
  96. data/test/active_record_unit.rb +3 -6
  97. data/test/activerecord/active_record_store_test.rb +1 -2
  98. data/test/activerecord/render_partial_with_record_identification_test.rb +158 -41
  99. data/test/adv_attr_test.rb +20 -0
  100. data/test/controller/action_pack_assertions_test.rb +16 -19
  101. data/test/controller/addresses_render_test.rb +1 -1
  102. data/test/controller/assert_select_test.rb +13 -6
  103. data/test/controller/base_test.rb +48 -2
  104. data/test/controller/benchmark_test.rb +1 -2
  105. data/test/controller/caching_test.rb +282 -21
  106. data/test/controller/capture_test.rb +1 -1
  107. data/test/controller/cgi_test.rb +1 -1
  108. data/test/controller/components_test.rb +1 -1
  109. data/test/controller/content_type_test.rb +2 -2
  110. data/test/controller/cookie_test.rb +13 -2
  111. data/test/controller/custom_handler_test.rb +14 -10
  112. data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -1
  113. data/test/controller/dispatcher_test.rb +31 -49
  114. data/test/controller/fake_controllers.rb +17 -0
  115. data/test/controller/fake_models.rb +6 -0
  116. data/test/controller/filter_params_test.rb +14 -8
  117. data/test/controller/filters_test.rb +44 -16
  118. data/test/controller/flash_test.rb +2 -2
  119. data/test/controller/header_test.rb +14 -0
  120. data/test/controller/helper_test.rb +19 -15
  121. data/test/controller/html-scanner/document_test.rb +1 -2
  122. data/test/controller/html-scanner/node_test.rb +1 -2
  123. data/test/controller/html-scanner/sanitizer_test.rb +8 -5
  124. data/test/controller/html-scanner/tag_node_test.rb +1 -2
  125. data/test/controller/html-scanner/text_node_test.rb +2 -3
  126. data/test/controller/html-scanner/tokenizer_test.rb +8 -2
  127. data/test/controller/http_authentication_test.rb +1 -1
  128. data/test/controller/integration_test.rb +14 -16
  129. data/test/controller/integration_upload_test.rb +43 -0
  130. data/test/controller/layout_test.rb +26 -6
  131. data/test/controller/mime_responds_test.rb +39 -7
  132. data/test/controller/mime_type_test.rb +29 -5
  133. data/test/controller/new_render_test.rb +105 -34
  134. data/test/controller/polymorphic_routes_test.rb +32 -20
  135. data/test/controller/record_identifier_test.rb +38 -2
  136. data/test/controller/redirect_test.rb +21 -1
  137. data/test/controller/render_test.rb +59 -15
  138. data/test/controller/request_forgery_protection_test.rb +92 -5
  139. data/test/controller/request_test.rb +64 -6
  140. data/test/controller/rescue_test.rb +22 -6
  141. data/test/controller/resources_test.rb +102 -14
  142. data/test/controller/routing_test.rb +231 -19
  143. data/test/controller/selector_test.rb +2 -2
  144. data/test/controller/send_file_test.rb +14 -3
  145. data/test/controller/session/cookie_store_test.rb +16 -4
  146. data/test/controller/session/mem_cache_store_test.rb +3 -4
  147. data/test/controller/session_fixation_test.rb +1 -1
  148. data/test/controller/session_management_test.rb +23 -1
  149. data/test/controller/test_test.rb +39 -18
  150. data/test/controller/url_rewriter_test.rb +35 -1
  151. data/test/controller/verification_test.rb +1 -1
  152. data/test/controller/view_paths_test.rb +15 -12
  153. data/test/controller/webservice_test.rb +48 -3
  154. data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
  155. data/test/fixtures/company.rb +1 -0
  156. data/test/fixtures/customers/_customer.html.erb +1 -0
  157. data/test/fixtures/db_definitions/sqlite.sql +6 -0
  158. data/test/fixtures/functional_caching/_partial.erb +3 -0
  159. data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
  160. data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
  161. data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
  162. data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
  163. data/test/fixtures/mascot.rb +3 -0
  164. data/test/fixtures/mascots.yml +4 -0
  165. data/test/fixtures/mascots/_mascot.html.erb +1 -0
  166. data/test/fixtures/multipart/boundary_problem_file +10 -0
  167. data/test/fixtures/public/javascripts/application.js +1 -0
  168. data/test/fixtures/public/javascripts/controls.js +1 -0
  169. data/test/fixtures/public/javascripts/dragdrop.js +1 -0
  170. data/test/fixtures/public/javascripts/effects.js +1 -0
  171. data/test/fixtures/public/javascripts/prototype.js +1 -0
  172. data/test/fixtures/public/javascripts/version.1.0.js +1 -0
  173. data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
  174. data/test/fixtures/reply.rb +1 -0
  175. data/test/fixtures/shared.html.erb +1 -0
  176. data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
  177. data/test/fixtures/test/_customer_counter.erb +1 -0
  178. data/test/fixtures/test/_form.erb +1 -0
  179. data/test/fixtures/test/_labelling_form.erb +1 -0
  180. data/test/fixtures/test/_raise.html.erb +1 -0
  181. data/test/fixtures/test/greeting.js.rjs +1 -0
  182. data/test/fixtures/topics/_topic.html.erb +1 -0
  183. data/test/template/active_record_helper_test.rb +25 -8
  184. data/test/template/asset_tag_helper_test.rb +100 -17
  185. data/test/template/atom_feed_helper_test.rb +29 -1
  186. data/test/template/benchmark_helper_test.rb +10 -22
  187. data/test/template/date_helper_test.rb +455 -153
  188. data/test/template/erb_util_test.rb +10 -42
  189. data/test/template/form_helper_test.rb +192 -66
  190. data/test/template/form_options_helper_test.rb +19 -8
  191. data/test/template/form_tag_helper_test.rb +11 -8
  192. data/test/template/javascript_helper_test.rb +3 -9
  193. data/test/template/number_helper_test.rb +6 -3
  194. data/test/template/prototype_helper_test.rb +27 -40
  195. data/test/template/record_tag_helper_test.rb +54 -0
  196. data/test/template/sanitize_helper_test.rb +5 -6
  197. data/test/template/scriptaculous_helper_test.rb +7 -13
  198. data/test/template/tag_helper_test.rb +3 -6
  199. data/test/template/template_finder_test.rb +73 -0
  200. data/test/template/template_object_test.rb +95 -0
  201. data/test/template/test_test.rb +56 -0
  202. data/test/template/text_helper_test.rb +46 -33
  203. data/test/template/url_helper_test.rb +8 -10
  204. metadata +65 -12
  205. data/lib/action_view/compiled_templates.rb +0 -69
  206. data/test/action_view_test.rb +0 -44
  207. data/test/activerecord/fixtures_test.rb +0 -24
  208. data/test/controller/fragment_store_setting_test.rb +0 -47
  209. data/test/template/compiled_templates_test.rb +0 -197
  210. data/test/template/deprecate_ivars_test.rb +0 -51
@@ -0,0 +1,283 @@
1
+ module ActionController
2
+ module Routing
3
+ class Segment #:nodoc:
4
+ RESERVED_PCHAR = ':@&=+$,;'
5
+ UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
6
+
7
+ attr_accessor :is_optional
8
+ alias_method :optional?, :is_optional
9
+
10
+ def initialize
11
+ self.is_optional = false
12
+ end
13
+
14
+ def extraction_code
15
+ nil
16
+ end
17
+
18
+ # Continue generating string for the prior segments.
19
+ def continue_string_structure(prior_segments)
20
+ if prior_segments.empty?
21
+ interpolation_statement(prior_segments)
22
+ else
23
+ new_priors = prior_segments[0..-2]
24
+ prior_segments.last.string_structure(new_priors)
25
+ end
26
+ end
27
+
28
+ def interpolation_chunk
29
+ URI.escape(value, UNSAFE_PCHAR)
30
+ end
31
+
32
+ # Return a string interpolation statement for this segment and those before it.
33
+ def interpolation_statement(prior_segments)
34
+ chunks = prior_segments.collect { |s| s.interpolation_chunk }
35
+ chunks << interpolation_chunk
36
+ "\"#{chunks * ''}\"#{all_optionals_available_condition(prior_segments)}"
37
+ end
38
+
39
+ def string_structure(prior_segments)
40
+ optional? ? continue_string_structure(prior_segments) : interpolation_statement(prior_segments)
41
+ end
42
+
43
+ # Return an if condition that is true if all the prior segments can be generated.
44
+ # If there are no optional segments before this one, then nil is returned.
45
+ def all_optionals_available_condition(prior_segments)
46
+ optional_locals = prior_segments.collect { |s| s.local_name if s.optional? && s.respond_to?(:local_name) }.compact
47
+ optional_locals.empty? ? nil : " if #{optional_locals * ' && '}"
48
+ end
49
+
50
+ # Recognition
51
+
52
+ def match_extraction(next_capture)
53
+ nil
54
+ end
55
+
56
+ # Warning
57
+
58
+ # Returns true if this segment is optional? because of a default. If so, then
59
+ # no warning will be emitted regarding this segment.
60
+ def optionality_implied?
61
+ false
62
+ end
63
+ end
64
+
65
+ class StaticSegment < Segment #:nodoc:
66
+ attr_accessor :value, :raw
67
+ alias_method :raw?, :raw
68
+
69
+ def initialize(value = nil)
70
+ super()
71
+ self.value = value
72
+ end
73
+
74
+ def interpolation_chunk
75
+ raw? ? value : super
76
+ end
77
+
78
+ def regexp_chunk
79
+ chunk = Regexp.escape(value)
80
+ optional? ? Regexp.optionalize(chunk) : chunk
81
+ end
82
+
83
+ def build_pattern(pattern)
84
+ escaped = Regexp.escape(value)
85
+ if optional? && ! pattern.empty?
86
+ "(?:#{Regexp.optionalize escaped}\\Z|#{escaped}#{Regexp.unoptionalize pattern})"
87
+ elsif optional?
88
+ Regexp.optionalize escaped
89
+ else
90
+ escaped + pattern
91
+ end
92
+ end
93
+
94
+ def to_s
95
+ value
96
+ end
97
+ end
98
+
99
+ class DividerSegment < StaticSegment #:nodoc:
100
+ def initialize(value = nil)
101
+ super(value)
102
+ self.raw = true
103
+ self.is_optional = true
104
+ end
105
+
106
+ def optionality_implied?
107
+ true
108
+ end
109
+ end
110
+
111
+ class DynamicSegment < Segment #:nodoc:
112
+ attr_accessor :key, :default, :regexp
113
+
114
+ def initialize(key = nil, options = {})
115
+ super()
116
+ self.key = key
117
+ self.default = options[:default] if options.key? :default
118
+ self.is_optional = true if options[:optional] || options.key?(:default)
119
+ end
120
+
121
+ def to_s
122
+ ":#{key}"
123
+ end
124
+
125
+ # The local variable name that the value of this segment will be extracted to.
126
+ def local_name
127
+ "#{key}_value"
128
+ end
129
+
130
+ def extract_value
131
+ "#{local_name} = hash[:#{key}] && hash[:#{key}].to_param #{"|| #{default.inspect}" if default}"
132
+ end
133
+ def value_check
134
+ if default # Then we know it won't be nil
135
+ "#{value_regexp.inspect} =~ #{local_name}" if regexp
136
+ elsif optional?
137
+ # If we have a regexp check that the value is not given, or that it matches.
138
+ # If we have no regexp, return nil since we do not require a condition.
139
+ "#{local_name}.nil? || #{value_regexp.inspect} =~ #{local_name}" if regexp
140
+ else # Then it must be present, and if we have a regexp, it must match too.
141
+ "#{local_name} #{"&& #{value_regexp.inspect} =~ #{local_name}" if regexp}"
142
+ end
143
+ end
144
+ def expiry_statement
145
+ "expired, hash = true, options if !expired && expire_on[:#{key}]"
146
+ end
147
+
148
+ def extraction_code
149
+ s = extract_value
150
+ vc = value_check
151
+ s << "\nreturn [nil,nil] unless #{vc}" if vc
152
+ s << "\n#{expiry_statement}"
153
+ end
154
+
155
+ def interpolation_chunk(value_code = "#{local_name}")
156
+ "\#{URI.escape(#{value_code}.to_s, ActionController::Routing::Segment::UNSAFE_PCHAR)}"
157
+ end
158
+
159
+ def string_structure(prior_segments)
160
+ if optional? # We have a conditional to do...
161
+ # If we should not appear in the url, just write the code for the prior
162
+ # segments. This occurs if our value is the default value, or, if we are
163
+ # optional, if we have nil as our value.
164
+ "if #{local_name} == #{default.inspect}\n" +
165
+ continue_string_structure(prior_segments) +
166
+ "\nelse\n" + # Otherwise, write the code up to here
167
+ "#{interpolation_statement(prior_segments)}\nend"
168
+ else
169
+ interpolation_statement(prior_segments)
170
+ end
171
+ end
172
+
173
+ def value_regexp
174
+ Regexp.new "\\A#{regexp.to_s}\\Z" if regexp
175
+ end
176
+
177
+ def regexp_chunk
178
+ if regexp
179
+ if regexp_has_modifiers?
180
+ "(#{regexp.to_s})"
181
+ else
182
+ "(#{regexp.source})"
183
+ end
184
+ else
185
+ "([^#{Routing::SEPARATORS.join}]+)"
186
+ end
187
+ end
188
+
189
+ def build_pattern(pattern)
190
+ chunk = regexp_chunk
191
+ chunk = "(#{chunk})" if Regexp.new(chunk).number_of_captures == 0
192
+ pattern = "#{chunk}#{pattern}"
193
+ optional? ? Regexp.optionalize(pattern) : pattern
194
+ end
195
+
196
+ def match_extraction(next_capture)
197
+ # All non code-related keys (such as :id, :slug) are URI-unescaped as
198
+ # path parameters.
199
+ default_value = default ? default.inspect : nil
200
+ %[
201
+ value = if (m = match[#{next_capture}])
202
+ URI.unescape(m)
203
+ else
204
+ #{default_value}
205
+ end
206
+ params[:#{key}] = value if value
207
+ ]
208
+ end
209
+
210
+ def optionality_implied?
211
+ [:action, :id].include? key
212
+ end
213
+
214
+ def regexp_has_modifiers?
215
+ regexp.options & (Regexp::IGNORECASE | Regexp::EXTENDED) != 0
216
+ end
217
+
218
+ end
219
+
220
+ class ControllerSegment < DynamicSegment #:nodoc:
221
+ def regexp_chunk
222
+ possible_names = Routing.possible_controllers.collect { |name| Regexp.escape name }
223
+ "(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
224
+ end
225
+
226
+ # Don't URI.escape the controller name since it may contain slashes.
227
+ def interpolation_chunk(value_code = "#{local_name}")
228
+ "\#{#{value_code}.to_s}"
229
+ end
230
+
231
+ # Make sure controller names like Admin/Content are correctly normalized to
232
+ # admin/content
233
+ def extract_value
234
+ "#{local_name} = (hash[:#{key}] #{"|| #{default.inspect}" if default}).downcase"
235
+ end
236
+
237
+ def match_extraction(next_capture)
238
+ if default
239
+ "params[:#{key}] = match[#{next_capture}] ? match[#{next_capture}].downcase : '#{default}'"
240
+ else
241
+ "params[:#{key}] = match[#{next_capture}].downcase if match[#{next_capture}]"
242
+ end
243
+ end
244
+ end
245
+
246
+ class PathSegment < DynamicSegment #:nodoc:
247
+ def interpolation_chunk(value_code = "#{local_name}")
248
+ "\#{#{value_code}}"
249
+ end
250
+
251
+ def extract_value
252
+ "#{local_name} = hash[:#{key}] && hash[:#{key}].collect { |path_component| URI.escape(path_component.to_param, ActionController::Routing::Segment::UNSAFE_PCHAR) }.to_param #{"|| #{default.inspect}" if default}"
253
+ end
254
+
255
+ def default
256
+ ''
257
+ end
258
+
259
+ def default=(path)
260
+ raise RoutingError, "paths cannot have non-empty default values" unless path.blank?
261
+ end
262
+
263
+ def match_extraction(next_capture)
264
+ "params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
265
+ end
266
+
267
+ def regexp_chunk
268
+ regexp || "(.*)"
269
+ end
270
+
271
+ def optionality_implied?
272
+ true
273
+ end
274
+
275
+ class Result < ::Array #:nodoc:
276
+ def to_s() join '/' end
277
+ def self.new_escaped(strings)
278
+ new strings.collect {|str| URI.unescape str}
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
@@ -13,7 +13,7 @@ class CGI
13
13
 
14
14
 
15
15
  # A session store backed by an Active Record class. A default class is
16
- # provided, but any object duck-typing to an Active Record +Session+ class
16
+ # provided, but any object duck-typing to an Active Record Session class
17
17
  # with text +session_id+ and +data+ attributes is sufficient.
18
18
  #
19
19
  # The default assumes a +sessions+ tables with columns:
@@ -26,13 +26,13 @@ class CGI
26
26
  # ActionController::SessionOverflowError will be raised.
27
27
  #
28
28
  # You may configure the table name, primary key, and data column.
29
- # For example, at the end of config/environment.rb:
29
+ # For example, at the end of <tt>config/environment.rb</tt>:
30
30
  # CGI::Session::ActiveRecordStore::Session.table_name = 'legacy_session_table'
31
31
  # CGI::Session::ActiveRecordStore::Session.primary_key = 'session_id'
32
32
  # CGI::Session::ActiveRecordStore::Session.data_column_name = 'legacy_session_data'
33
- # Note that setting the primary key to the session_id frees you from
34
- # having a separate id column if you don't want it. However, you must
35
- # set session.model.id = session.session_id by hand! A before_filter
33
+ # Note that setting the primary key to the +session_id+ frees you from
34
+ # having a separate +id+ column if you don't want it. However, you must
35
+ # set <tt>session.model.id = session.session_id</tt> by hand! A before filter
36
36
  # on ApplicationController is a good place.
37
37
  #
38
38
  # Since the default class is a simple Active Record, you get timestamps
@@ -42,7 +42,7 @@ class CGI
42
42
  # You may provide your own session class implementation, whether a
43
43
  # feature-packed Active Record or a bare-metal high-performance SQL
44
44
  # store, by setting
45
- # +CGI::Session::ActiveRecordStore.session_class = MySessionClass+
45
+ # CGI::Session::ActiveRecordStore.session_class = MySessionClass
46
46
  # You must implement these methods:
47
47
  # self.find_by_session_id(session_id)
48
48
  # initialize(hash_of_session_id_and_data)
@@ -154,8 +154,13 @@ class CGI
154
154
  # The database connection, table name, and session id and data columns
155
155
  # are configurable class attributes. Marshaling and unmarshaling
156
156
  # are implemented as class methods that you may override. By default,
157
- # marshaling data is +ActiveSupport::Base64.encode64(Marshal.dump(data))+ and
158
- # unmarshaling data is +Marshal.load(ActiveSupport::Base64.decode64(data))+.
157
+ # marshaling data is
158
+ #
159
+ # ActiveSupport::Base64.encode64(Marshal.dump(data))
160
+ #
161
+ # and unmarshaling data is
162
+ #
163
+ # Marshal.load(ActiveSupport::Base64.decode64(data))
159
164
  #
160
165
  # This marshaling behavior is intended to store the widest range of
161
166
  # binary session data in a +text+ column. For higher performance,
@@ -14,27 +14,27 @@ require 'openssl' # to generate the HMAC message digest
14
14
  # TamperedWithCookie is raised if the data integrity check fails.
15
15
  #
16
16
  # A message digest is included with the cookie to ensure data integrity:
17
- # a user cannot alter his user_id without knowing the secret key included in
17
+ # a user cannot alter his +user_id+ without knowing the secret key included in
18
18
  # the hash. New apps are generated with a pregenerated secret in
19
19
  # config/environment.rb. Set your own for old apps you're upgrading.
20
20
  #
21
21
  # Session options:
22
- # :secret An application-wide key string or block returning a string
23
- # called per generated digest. The block is called with the
24
- # CGI::Session instance as an argument. It's important that the
25
- # secret is not vulnerable to a dictionary attack. Therefore,
26
- # you should choose a secret consisting of random numbers and
27
- # letters and more than 30 characters.
28
22
  #
29
- # Example: :secret => '449fe2e7daee471bffae2fd8dc02313d'
30
- # :secret => Proc.new { User.current_user.secret_key }
23
+ # * <tt>:secret</tt>: An application-wide key string or block returning a string
24
+ # called per generated digest. The block is called with the CGI::Session
25
+ # instance as an argument. It's important that the secret is not vulnerable to
26
+ # a dictionary attack. Therefore, you should choose a secret consisting of
27
+ # random numbers and letters and more than 30 characters. Examples:
31
28
  #
32
- # :digest The message digest algorithm used to verify session integrity
33
- # defaults to 'SHA1' but may be any digest provided by OpenSSL,
34
- # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
29
+ # :secret => '449fe2e7daee471bffae2fd8dc02313d'
30
+ # :secret => Proc.new { User.current_user.secret_key }
31
+ #
32
+ # * <tt>:digest</tt>: The message digest algorithm used to verify session
33
+ # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
34
+ # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
35
35
  #
36
36
  # To generate a secret key for an existing application, run
37
- # `rake secret` and set the key in config/environment.rb
37
+ # "rake secret" and set the key in config/environment.rb.
38
38
  #
39
39
  # Note that changing digest or secret invalidates all existing sessions!
40
40
  class CGI::Session::CookieStore
@@ -117,7 +117,7 @@ class CGI::Session::CookieStore
117
117
  def delete
118
118
  @data = nil
119
119
  clear_old_cookie_value
120
- write_cookie('value' => '', 'expires' => 1.year.ago)
120
+ write_cookie('value' => nil, 'expires' => 1.year.ago)
121
121
  end
122
122
 
123
123
  # Generate the HMAC keyed message digest. Uses SHA1 by default.
@@ -130,17 +130,20 @@ class CGI::Session::CookieStore
130
130
  # Marshal a session hash into safe cookie data. Include an integrity hash.
131
131
  def marshal(session)
132
132
  data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
133
- CGI.escape "#{data}--#{generate_digest(data)}"
133
+ "#{data}--#{generate_digest(data)}"
134
134
  end
135
135
 
136
136
  # Unmarshal cookie data to a hash and verify its integrity.
137
137
  def unmarshal(cookie)
138
138
  if cookie
139
- data, digest = CGI.unescape(cookie).split('--')
140
- unless digest == generate_digest(data)
139
+ data, digest = cookie.split('--')
140
+
141
+ # Do two checks to transparently support old double-escaped data.
142
+ unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data))
141
143
  delete
142
144
  raise TamperedWithCookie
143
145
  end
146
+
144
147
  Marshal.load(ActiveSupport::Base64.decode64(data))
145
148
  end
146
149
  end
@@ -16,9 +16,11 @@ module ActionController #:nodoc:
16
16
  end
17
17
 
18
18
  module ClassMethods
19
- # Set the session store to be used for keeping the session data between requests. By default, sessions are stored
20
- # in browser cookies (:cookie_store), but you can also specify one of the other included stores
21
- # (:active_record_store, :p_store, drb_store, :mem_cache_store, or :memory_store) or your own custom class.
19
+ # Set the session store to be used for keeping the session data between requests.
20
+ # By default, sessions are stored in browser cookies (<tt>:cookie_store</tt>),
21
+ # but you can also specify one of the other included stores (<tt>:active_record_store</tt>,
22
+ # <tt>:p_store</tt>, <tt>:drb_store</tt>, <tt>:mem_cache_store</tt>, or
23
+ # <tt>:memory_store</tt>) or your own custom class.
22
24
  def session_store=(store)
23
25
  ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager] =
24
26
  store.is_a?(Symbol) ? CGI::Session.const_get(store == :drb_store ? "DRbStore" : store.to_s.camelize) : store
@@ -67,11 +69,16 @@ module ActionController #:nodoc:
67
69
  # session :off,
68
70
  # :if => Proc.new { |req| !(req.format.html? || req.format.js?) }
69
71
  #
72
+ # # turn the session back on, useful when it was turned off in the
73
+ # # application controller, and you need it on in another controller
74
+ # session :on
75
+ #
70
76
  # All session options described for ActionController::Base.process_cgi
71
77
  # are valid arguments.
72
78
  def session(*args)
73
79
  options = args.extract_options!
74
80
 
81
+ options[:disabled] = false if args.delete(:on)
75
82
  options[:disabled] = true if !args.empty?
76
83
  options[:only] = [*options[:only]].map { |o| o.to_s } if options[:only]
77
84
  options[:except] = [*options[:except]].map { |o| o.to_s } if options[:except]
@@ -4,34 +4,37 @@ module ActionController #:nodoc:
4
4
  DEFAULT_SEND_FILE_OPTIONS = {
5
5
  :type => 'application/octet-stream'.freeze,
6
6
  :disposition => 'attachment'.freeze,
7
- :stream => true,
8
- :buffer_size => 4096
7
+ :stream => true,
8
+ :buffer_size => 4096,
9
+ :x_sendfile => false
9
10
  }.freeze
10
11
 
12
+ X_SENDFILE_HEADER = 'X-Sendfile'.freeze
13
+
11
14
  protected
12
15
  # Sends the file by streaming it 4096 bytes at a time. This way the
13
16
  # whole file doesn't need to be read into memory at once. This makes
14
17
  # it feasible to send even large files.
15
18
  #
16
19
  # Be careful to sanitize the path parameter if it coming from a web
17
- # page. send_file(params[:path]) allows a malicious user to
20
+ # page. <tt>send_file(params[:path])</tt> allows a malicious user to
18
21
  # download any file on your server.
19
22
  #
20
23
  # Options:
21
24
  # * <tt>:filename</tt> - suggests a filename for the browser to use.
22
- # Defaults to File.basename(path).
25
+ # Defaults to <tt>File.basename(path)</tt>.
23
26
  # * <tt>:type</tt> - specifies an HTTP content type.
24
27
  # Defaults to 'application/octet-stream'.
25
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
28
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
26
29
  # Valid values are 'inline' and 'attachment' (default).
27
- # * <tt>:stream</tt> - whether to send the file to the user agent as it is read (true)
28
- # or to read the entire file before sending (false). Defaults to true.
30
+ # * <tt>:stream</tt> - whether to send the file to the user agent as it is read (+true+)
31
+ # or to read the entire file before sending (+false+). Defaults to +true+.
29
32
  # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
30
33
  # Defaults to 4096.
31
34
  # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
32
- # * <tt>:url_based_filename</tt> - set to true if you want the browser guess the filename from
33
- # the URL, which is necessary for i18n filenames on certain browsers
34
- # (setting :filename overrides this option).
35
+ # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from
36
+ # the URL, which is necessary for i18n filenames on certain browsers
37
+ # (setting <tt>:filename</tt> overrides this option).
35
38
  #
36
39
  # The default Content-Type and Content-Disposition headers are
37
40
  # set to download arbitrary binary files in as many browsers as
@@ -39,17 +42,20 @@ module ActionController #:nodoc:
39
42
  # a variety of quirks (especially when downloading over SSL).
40
43
  #
41
44
  # Simple download:
45
+ #
42
46
  # send_file '/path/to.zip'
43
47
  #
44
48
  # Show a JPEG in the browser:
49
+ #
45
50
  # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
46
51
  #
47
52
  # Show a 404 page in the browser:
53
+ #
48
54
  # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
49
55
  #
50
56
  # Read about the other Content-* HTTP headers if you'd like to
51
- # provide the user with more information (such as Content-Description).
52
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
57
+ # provide the user with more information (such as Content-Description) in
58
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11.
53
59
  #
54
60
  # Also be aware that the document may be cached by proxies and browsers.
55
61
  # The Pragma and Cache-Control headers declare how the file may be cached
@@ -67,19 +73,24 @@ module ActionController #:nodoc:
67
73
 
68
74
  @performed_render = false
69
75
 
70
- if options[:stream]
71
- render :status => options[:status], :text => Proc.new { |response, output|
72
- logger.info "Streaming file #{path}" unless logger.nil?
73
- len = options[:buffer_size] || 4096
74
- File.open(path, 'rb') do |file|
75
- while buf = file.read(len)
76
- output.write(buf)
77
- end
78
- end
79
- }
76
+ if options[:x_sendfile]
77
+ logger.info "Sending #{X_SENDFILE_HEADER} header #{path}" if logger
78
+ head options[:status], X_SENDFILE_HEADER => path
80
79
  else
81
- logger.info "Sending file #{path}" unless logger.nil?
82
- File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
80
+ if options[:stream]
81
+ render :status => options[:status], :text => Proc.new { |response, output|
82
+ logger.info "Streaming file #{path}" unless logger.nil?
83
+ len = options[:buffer_size] || 4096
84
+ File.open(path, 'rb') do |file|
85
+ while buf = file.read(len)
86
+ output.write(buf)
87
+ end
88
+ end
89
+ }
90
+ else
91
+ logger.info "Sending file #{path}" unless logger.nil?
92
+ File.open(path, 'rb') { |file| render :status => options[:status], :text => file.read }
93
+ end
83
94
  end
84
95
  end
85
96
 
@@ -87,25 +98,28 @@ module ActionController #:nodoc:
87
98
  # and specify whether to show data inline or download as an attachment.
88
99
  #
89
100
  # Options:
90
- # * <tt>:filename</tt> - Suggests a filename for the browser to use.
101
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
91
102
  # * <tt>:type</tt> - specifies an HTTP content type.
92
103
  # Defaults to 'application/octet-stream'.
93
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
104
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
94
105
  # Valid values are 'inline' and 'attachment' (default).
95
106
  # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'.
96
107
  #
97
108
  # Generic data download:
109
+ #
98
110
  # send_data buffer
99
111
  #
100
112
  # Download a dynamically-generated tarball:
113
+ #
101
114
  # send_data generate_tgz('dir'), :filename => 'dir.tgz'
102
115
  #
103
116
  # Display an image Active Record in the browser:
117
+ #
104
118
  # send_data image.data, :type => image.content_type, :disposition => 'inline'
105
119
  #
106
120
  # See +send_file+ for more information on HTTP Content-* headers and caching.
107
121
  def send_data(data, options = {}) #:doc:
108
- logger.info "Sending data #{options[:filename]}" unless logger.nil?
122
+ logger.info "Sending data #{options[:filename]}" if logger
109
123
  send_file_headers! options.merge(:length => data.size)
110
124
  @performed_render = false
111
125
  render :status => options[:status], :text => data
@@ -130,10 +144,10 @@ module ActionController #:nodoc:
130
144
  )
131
145
 
132
146
  # Fix a problem with IE 6.0 on opening downloaded files:
133
- # If Cache-Control: no-cache is set (which Rails does by default),
134
- # IE removes the file it just downloaded from its cache immediately
135
- # after it displays the "open/save" dialog, which means that if you
136
- # hit "open" the file isn't there anymore when the application that
147
+ # If Cache-Control: no-cache is set (which Rails does by default),
148
+ # IE removes the file it just downloaded from its cache immediately
149
+ # after it displays the "open/save" dialog, which means that if you
150
+ # hit "open" the file isn't there anymore when the application that
137
151
  # is called for handling the download is run, so let's workaround that
138
152
  headers['Cache-Control'] = 'private' if headers['Cache-Control'] == 'no-cache'
139
153
  end