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.
- data/CHANGELOG +149 -7
- data/MIT-LICENSE +1 -1
- data/README +1 -1
- data/Rakefile +5 -6
- data/lib/action_controller.rb +2 -2
- data/lib/action_controller/assertions/model_assertions.rb +2 -1
- data/lib/action_controller/assertions/response_assertions.rb +4 -2
- data/lib/action_controller/assertions/routing_assertions.rb +3 -3
- data/lib/action_controller/assertions/selector_assertions.rb +30 -27
- data/lib/action_controller/assertions/tag_assertions.rb +3 -3
- data/lib/action_controller/base.rb +103 -129
- data/lib/action_controller/benchmarking.rb +3 -3
- data/lib/action_controller/caching.rb +41 -652
- data/lib/action_controller/caching/actions.rb +144 -0
- data/lib/action_controller/caching/fragments.rb +138 -0
- data/lib/action_controller/caching/pages.rb +154 -0
- data/lib/action_controller/caching/sql_cache.rb +18 -0
- data/lib/action_controller/caching/sweeping.rb +97 -0
- data/lib/action_controller/cgi_ext/cookie.rb +27 -23
- data/lib/action_controller/cgi_ext/stdinput.rb +1 -0
- data/lib/action_controller/cgi_process.rb +6 -4
- data/lib/action_controller/components.rb +7 -6
- data/lib/action_controller/cookies.rb +31 -19
- data/lib/action_controller/dispatcher.rb +51 -84
- data/lib/action_controller/filters.rb +295 -421
- data/lib/action_controller/flash.rb +1 -6
- data/lib/action_controller/headers.rb +31 -0
- data/lib/action_controller/helpers.rb +26 -9
- data/lib/action_controller/http_authentication.rb +1 -1
- data/lib/action_controller/integration.rb +65 -13
- data/lib/action_controller/layout.rb +24 -39
- data/lib/action_controller/mime_responds.rb +7 -3
- data/lib/action_controller/mime_type.rb +25 -9
- data/lib/action_controller/mime_types.rb +1 -1
- data/lib/action_controller/polymorphic_routes.rb +32 -17
- data/lib/action_controller/record_identifier.rb +10 -4
- data/lib/action_controller/request.rb +46 -30
- data/lib/action_controller/request_forgery_protection.rb +10 -9
- data/lib/action_controller/request_profiler.rb +29 -8
- data/lib/action_controller/rescue.rb +24 -24
- data/lib/action_controller/resources.rb +66 -23
- data/lib/action_controller/response.rb +2 -2
- data/lib/action_controller/routing.rb +113 -1229
- data/lib/action_controller/routing/builder.rb +204 -0
- data/lib/action_controller/{routing_optimisation.rb → routing/optimisations.rb} +13 -12
- data/lib/action_controller/routing/recognition_optimisation.rb +158 -0
- data/lib/action_controller/routing/route.rb +240 -0
- data/lib/action_controller/routing/route_set.rb +435 -0
- data/lib/action_controller/routing/routing_ext.rb +46 -0
- data/lib/action_controller/routing/segments.rb +283 -0
- data/lib/action_controller/session/active_record_store.rb +13 -8
- data/lib/action_controller/session/cookie_store.rb +20 -17
- data/lib/action_controller/session_management.rb +10 -3
- data/lib/action_controller/streaming.rb +45 -31
- data/lib/action_controller/test_case.rb +33 -23
- data/lib/action_controller/test_process.rb +39 -35
- data/lib/action_controller/url_rewriter.rb +18 -12
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +1 -1
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/version.rb +2 -2
- data/lib/action_view.rb +11 -3
- data/lib/action_view/base.rb +73 -390
- data/lib/action_view/helpers/active_record_helper.rb +83 -62
- data/lib/action_view/helpers/asset_tag_helper.rb +101 -44
- data/lib/action_view/helpers/atom_feed_helper.rb +35 -7
- data/lib/action_view/helpers/benchmark_helper.rb +5 -3
- data/lib/action_view/helpers/cache_helper.rb +3 -2
- data/lib/action_view/helpers/capture_helper.rb +1 -2
- data/lib/action_view/helpers/date_helper.rb +104 -82
- data/lib/action_view/helpers/form_helper.rb +148 -75
- data/lib/action_view/helpers/form_options_helper.rb +44 -23
- data/lib/action_view/helpers/form_tag_helper.rb +22 -13
- data/lib/action_view/helpers/javascripts/controls.js +1 -1
- data/lib/action_view/helpers/javascripts/dragdrop.js +1 -1
- data/lib/action_view/helpers/javascripts/effects.js +1 -1
- data/lib/action_view/helpers/number_helper.rb +10 -3
- data/lib/action_view/helpers/prototype_helper.rb +61 -29
- data/lib/action_view/helpers/record_tag_helper.rb +3 -3
- data/lib/action_view/helpers/sanitize_helper.rb +23 -17
- data/lib/action_view/helpers/scriptaculous_helper.rb +86 -60
- data/lib/action_view/helpers/text_helper.rb +153 -125
- data/lib/action_view/helpers/url_helper.rb +83 -28
- data/lib/action_view/inline_template.rb +20 -0
- data/lib/action_view/partial_template.rb +70 -0
- data/lib/action_view/partials.rb +31 -73
- data/lib/action_view/template.rb +127 -0
- data/lib/action_view/template_error.rb +8 -7
- data/lib/action_view/template_finder.rb +177 -0
- data/lib/action_view/template_handler.rb +18 -1
- data/lib/action_view/template_handlers/builder.rb +10 -2
- data/lib/action_view/template_handlers/compilable.rb +128 -0
- data/lib/action_view/template_handlers/erb.rb +37 -2
- data/lib/action_view/template_handlers/rjs.rb +14 -1
- data/lib/action_view/test_case.rb +58 -0
- data/test/abstract_unit.rb +1 -1
- data/test/active_record_unit.rb +3 -6
- data/test/activerecord/active_record_store_test.rb +1 -2
- data/test/activerecord/render_partial_with_record_identification_test.rb +158 -41
- data/test/adv_attr_test.rb +20 -0
- data/test/controller/action_pack_assertions_test.rb +16 -19
- data/test/controller/addresses_render_test.rb +1 -1
- data/test/controller/assert_select_test.rb +13 -6
- data/test/controller/base_test.rb +48 -2
- data/test/controller/benchmark_test.rb +1 -2
- data/test/controller/caching_test.rb +282 -21
- data/test/controller/capture_test.rb +1 -1
- data/test/controller/cgi_test.rb +1 -1
- data/test/controller/components_test.rb +1 -1
- data/test/controller/content_type_test.rb +2 -2
- data/test/controller/cookie_test.rb +13 -2
- data/test/controller/custom_handler_test.rb +14 -10
- data/test/controller/deprecation/deprecated_base_methods_test.rb +1 -1
- data/test/controller/dispatcher_test.rb +31 -49
- data/test/controller/fake_controllers.rb +17 -0
- data/test/controller/fake_models.rb +6 -0
- data/test/controller/filter_params_test.rb +14 -8
- data/test/controller/filters_test.rb +44 -16
- data/test/controller/flash_test.rb +2 -2
- data/test/controller/header_test.rb +14 -0
- data/test/controller/helper_test.rb +19 -15
- data/test/controller/html-scanner/document_test.rb +1 -2
- data/test/controller/html-scanner/node_test.rb +1 -2
- data/test/controller/html-scanner/sanitizer_test.rb +8 -5
- data/test/controller/html-scanner/tag_node_test.rb +1 -2
- data/test/controller/html-scanner/text_node_test.rb +2 -3
- data/test/controller/html-scanner/tokenizer_test.rb +8 -2
- data/test/controller/http_authentication_test.rb +1 -1
- data/test/controller/integration_test.rb +14 -16
- data/test/controller/integration_upload_test.rb +43 -0
- data/test/controller/layout_test.rb +26 -6
- data/test/controller/mime_responds_test.rb +39 -7
- data/test/controller/mime_type_test.rb +29 -5
- data/test/controller/new_render_test.rb +105 -34
- data/test/controller/polymorphic_routes_test.rb +32 -20
- data/test/controller/record_identifier_test.rb +38 -2
- data/test/controller/redirect_test.rb +21 -1
- data/test/controller/render_test.rb +59 -15
- data/test/controller/request_forgery_protection_test.rb +92 -5
- data/test/controller/request_test.rb +64 -6
- data/test/controller/rescue_test.rb +22 -6
- data/test/controller/resources_test.rb +102 -14
- data/test/controller/routing_test.rb +231 -19
- data/test/controller/selector_test.rb +2 -2
- data/test/controller/send_file_test.rb +14 -3
- data/test/controller/session/cookie_store_test.rb +16 -4
- data/test/controller/session/mem_cache_store_test.rb +3 -4
- data/test/controller/session_fixation_test.rb +1 -1
- data/test/controller/session_management_test.rb +23 -1
- data/test/controller/test_test.rb +39 -18
- data/test/controller/url_rewriter_test.rb +35 -1
- data/test/controller/verification_test.rb +1 -1
- data/test/controller/view_paths_test.rb +15 -12
- data/test/controller/webservice_test.rb +48 -3
- data/test/fixtures/bad_customers/_bad_customer.html.erb +1 -0
- data/test/fixtures/company.rb +1 -0
- data/test/fixtures/customers/_customer.html.erb +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +6 -0
- data/test/fixtures/functional_caching/_partial.erb +3 -0
- data/test/fixtures/functional_caching/fragment_cached.html.erb +2 -0
- data/test/fixtures/functional_caching/html_fragment_cached_with_partial.html.erb +1 -0
- data/test/fixtures/functional_caching/js_fragment_cached_with_partial.js.rjs +1 -0
- data/test/fixtures/good_customers/_good_customer.html.erb +1 -0
- data/test/fixtures/mascot.rb +3 -0
- data/test/fixtures/mascots.yml +4 -0
- data/test/fixtures/mascots/_mascot.html.erb +1 -0
- data/test/fixtures/multipart/boundary_problem_file +10 -0
- data/test/fixtures/public/javascripts/application.js +1 -0
- data/test/fixtures/public/javascripts/controls.js +1 -0
- data/test/fixtures/public/javascripts/dragdrop.js +1 -0
- data/test/fixtures/public/javascripts/effects.js +1 -0
- data/test/fixtures/public/javascripts/prototype.js +1 -0
- data/test/fixtures/public/javascripts/version.1.0.js +1 -0
- data/test/fixtures/public/stylesheets/version.1.0.css +1 -0
- data/test/fixtures/reply.rb +1 -0
- data/test/fixtures/shared.html.erb +1 -0
- data/test/fixtures/symlink_parent/symlinked_layout.erb +5 -0
- data/test/fixtures/test/_customer_counter.erb +1 -0
- data/test/fixtures/test/_form.erb +1 -0
- data/test/fixtures/test/_labelling_form.erb +1 -0
- data/test/fixtures/test/_raise.html.erb +1 -0
- data/test/fixtures/test/greeting.js.rjs +1 -0
- data/test/fixtures/topics/_topic.html.erb +1 -0
- data/test/template/active_record_helper_test.rb +25 -8
- data/test/template/asset_tag_helper_test.rb +100 -17
- data/test/template/atom_feed_helper_test.rb +29 -1
- data/test/template/benchmark_helper_test.rb +10 -22
- data/test/template/date_helper_test.rb +455 -153
- data/test/template/erb_util_test.rb +10 -42
- data/test/template/form_helper_test.rb +192 -66
- data/test/template/form_options_helper_test.rb +19 -8
- data/test/template/form_tag_helper_test.rb +11 -8
- data/test/template/javascript_helper_test.rb +3 -9
- data/test/template/number_helper_test.rb +6 -3
- data/test/template/prototype_helper_test.rb +27 -40
- data/test/template/record_tag_helper_test.rb +54 -0
- data/test/template/sanitize_helper_test.rb +5 -6
- data/test/template/scriptaculous_helper_test.rb +7 -13
- data/test/template/tag_helper_test.rb +3 -6
- data/test/template/template_finder_test.rb +73 -0
- data/test/template/template_object_test.rb +95 -0
- data/test/template/test_test.rb +56 -0
- data/test/template/text_helper_test.rb +46 -33
- data/test/template/url_helper_test.rb +8 -10
- metadata +65 -12
- data/lib/action_view/compiled_templates.rb +0 -69
- data/test/action_view_test.rb +0 -44
- data/test/activerecord/fixtures_test.rb +0 -24
- data/test/controller/fragment_store_setting_test.rb +0 -47
- data/test/template/compiled_templates_test.rb +0 -197
- 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
|
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
|
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
|
-
#
|
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
|
158
|
-
#
|
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
|
-
#
|
30
|
-
#
|
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
|
-
#
|
33
|
-
#
|
34
|
-
#
|
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
|
-
#
|
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' =>
|
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
|
-
|
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 =
|
140
|
-
|
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.
|
20
|
-
# in browser cookies (
|
21
|
-
#
|
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.
|
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
|
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[:
|
71
|
-
|
72
|
-
|
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
|
-
|
82
|
-
|
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> -
|
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]}"
|
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
|