actionpack 2.3.8 → 2.3.9.pre
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 +5 -0
- data/Rakefile +1 -1
- data/lib/action_controller/caching/fragments.rb +1 -1
- data/lib/action_controller/integration.rb +16 -5
- data/lib/action_controller/request.rb +2 -2
- data/lib/action_controller/rescue.rb +2 -2
- data/lib/action_controller/resources.rb +1 -1
- data/lib/action_controller/routing.rb +1 -1
- data/lib/action_controller/routing/route.rb +3 -3
- data/lib/action_controller/session/abstract_store.rb +152 -56
- data/lib/action_controller/session/cookie_store.rb +51 -16
- data/lib/action_controller/session/mem_cache_store.rb +9 -0
- data/lib/action_controller/test_process.rb +1 -1
- data/lib/action_controller/url_rewriter.rb +9 -1
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_view/helpers/form_helper.rb +3 -3
- data/lib/action_view/helpers/form_options_helper.rb +1 -1
- data/lib/action_view/helpers/form_tag_helper.rb +1 -1
- data/lib/action_view/helpers/prototype_helper.rb +3 -3
- data/lib/action_view/helpers/text_helper.rb +27 -17
- data/lib/action_view/locale/en.yml +14 -14
- data/lib/action_view/template.rb +2 -2
- data/test/abstract_unit.rb +17 -0
- data/test/activerecord/active_record_store_test.rb +65 -18
- data/test/controller/integration_test.rb +26 -0
- data/test/controller/request/multipart_params_parsing_test.rb +15 -0
- data/test/controller/rescue_test.rb +5 -6
- data/test/controller/session/cookie_store_test.rb +80 -2
- data/test/controller/session/mem_cache_store_test.rb +65 -2
- data/test/controller/url_rewriter_test.rb +9 -3
- data/test/fixtures/session_autoload_test/session_autoload_test/foo.rb +10 -0
- data/test/template/form_helper_test.rb +17 -0
- data/test/template/form_options_helper_test.rb +4 -0
- data/test/template/text_helper_test.rb +47 -18
- metadata +14 -9
@@ -36,6 +36,8 @@ module ActionController
|
|
36
36
|
#
|
37
37
|
# Note that changing digest or secret invalidates all existing sessions!
|
38
38
|
class CookieStore
|
39
|
+
include AbstractStore::SessionUtils
|
40
|
+
|
39
41
|
# Cookies can typically store 4096 bytes.
|
40
42
|
MAX = 4096
|
41
43
|
SECRET_MIN_LENGTH = 30 # characters
|
@@ -93,20 +95,20 @@ module ActionController
|
|
93
95
|
end
|
94
96
|
|
95
97
|
def call(env)
|
96
|
-
|
97
|
-
|
98
|
-
|
98
|
+
prepare!(env)
|
99
|
+
|
99
100
|
status, headers, body = @app.call(env)
|
100
101
|
|
101
102
|
session_data = env[ENV_SESSION_KEY]
|
102
103
|
options = env[ENV_SESSION_OPTIONS_KEY]
|
103
104
|
|
104
|
-
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.
|
105
|
-
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.
|
105
|
+
if !session_data.is_a?(AbstractStore::SessionHash) || session_data.loaded? || options[:expire_after]
|
106
|
+
session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.loaded?
|
107
|
+
|
108
|
+
persistent_session_id!(session_data)
|
106
109
|
session_data = marshal(session_data.to_hash)
|
107
110
|
|
108
111
|
raise CookieOverflow if session_data.size > MAX
|
109
|
-
|
110
112
|
cookie = Hash.new
|
111
113
|
cookie[:value] = session_data
|
112
114
|
unless options[:expire_after].nil?
|
@@ -114,17 +116,20 @@ module ActionController
|
|
114
116
|
end
|
115
117
|
|
116
118
|
cookie = build_cookie(@key, cookie.merge(options))
|
117
|
-
|
118
|
-
|
119
|
-
else
|
120
|
-
headers[HTTP_SET_COOKIE] = cookie
|
121
|
-
end
|
119
|
+
headers[HTTP_SET_COOKIE] = [] if headers[HTTP_SET_COOKIE].blank?
|
120
|
+
headers[HTTP_SET_COOKIE] << cookie
|
122
121
|
end
|
123
122
|
|
124
123
|
[status, headers, body]
|
125
124
|
end
|
126
125
|
|
127
126
|
private
|
127
|
+
|
128
|
+
def prepare!(env)
|
129
|
+
env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env)
|
130
|
+
env[ENV_SESSION_OPTIONS_KEY] = AbstractStore::OptionsHash.new(self, env, @default_options)
|
131
|
+
end
|
132
|
+
|
128
133
|
# Should be in Rack::Utils soon
|
129
134
|
def build_cookie(key, value)
|
130
135
|
case value
|
@@ -146,20 +151,46 @@ module ActionController
|
|
146
151
|
end
|
147
152
|
|
148
153
|
def load_session(env)
|
149
|
-
|
150
|
-
|
151
|
-
data = unmarshal(session_data) || persistent_session_id!({})
|
154
|
+
data = unpacked_cookie_data(env)
|
155
|
+
data = persistent_session_id!(data)
|
152
156
|
[data[:session_id], data]
|
153
157
|
end
|
158
|
+
|
159
|
+
def extract_session_id(env)
|
160
|
+
if data = unpacked_cookie_data(env)
|
161
|
+
persistent_session_id!(data) unless data.empty?
|
162
|
+
data[:session_id]
|
163
|
+
else
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def current_session_id(env)
|
169
|
+
env[ENV_SESSION_OPTIONS_KEY][:id]
|
170
|
+
end
|
171
|
+
|
172
|
+
def exists?(env)
|
173
|
+
current_session_id(env).present?
|
174
|
+
end
|
175
|
+
|
176
|
+
def unpacked_cookie_data(env)
|
177
|
+
env["action_dispatch.request.unsigned_session_cookie"] ||= begin
|
178
|
+
stale_session_check! do
|
179
|
+
request = Rack::Request.new(env)
|
180
|
+
session_data = request.cookies[@key]
|
181
|
+
unmarshal(session_data) || {}
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
154
185
|
|
155
186
|
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
156
187
|
def marshal(session)
|
157
|
-
@verifier.generate(
|
188
|
+
@verifier.generate(session)
|
158
189
|
end
|
159
190
|
|
160
191
|
# Unmarshal cookie data to a hash and verify its integrity.
|
161
192
|
def unmarshal(cookie)
|
162
|
-
|
193
|
+
@verifier.verify(cookie) if cookie
|
163
194
|
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
164
195
|
nil
|
165
196
|
end
|
@@ -207,6 +238,10 @@ module ActionController
|
|
207
238
|
ActiveSupport::SecureRandom.hex(16)
|
208
239
|
end
|
209
240
|
|
241
|
+
def destroy(env)
|
242
|
+
# session data is stored on client; nothing to do here
|
243
|
+
end
|
244
|
+
|
210
245
|
def persistent_session_id!(data)
|
211
246
|
(data ||= {}).merge!(inject_persistent_session_id(data))
|
212
247
|
end
|
@@ -43,6 +43,15 @@ begin
|
|
43
43
|
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
44
44
|
return false
|
45
45
|
end
|
46
|
+
|
47
|
+
def destroy(env)
|
48
|
+
if sid = current_session_id(env)
|
49
|
+
@pool.delete(sid)
|
50
|
+
end
|
51
|
+
rescue MemCache::MemCacheError, Errno::ECONNREFUSED
|
52
|
+
false
|
53
|
+
end
|
54
|
+
|
46
55
|
end
|
47
56
|
end
|
48
57
|
end
|
@@ -450,7 +450,7 @@ module ActionController #:nodoc:
|
|
450
450
|
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
|
451
451
|
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
|
452
452
|
@request.env['HTTP_ACCEPT'] = [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
|
453
|
-
|
453
|
+
__send__(request_method, action, parameters, session, flash).tap do
|
454
454
|
@request.env.delete 'HTTP_X_REQUESTED_WITH'
|
455
455
|
@request.env.delete 'HTTP_ACCEPT'
|
456
456
|
end
|
@@ -92,6 +92,14 @@ module ActionController
|
|
92
92
|
# end
|
93
93
|
# end
|
94
94
|
module UrlWriter
|
95
|
+
RESERVED_PCHAR = ':@&=+$,;%'
|
96
|
+
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
97
|
+
if RUBY_VERSION >= '1.9'
|
98
|
+
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
|
99
|
+
else
|
100
|
+
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
101
|
+
end
|
102
|
+
|
95
103
|
def self.included(base) #:nodoc:
|
96
104
|
ActionController::Routing::Routes.install_helpers(base)
|
97
105
|
base.mattr_accessor :default_url_options
|
@@ -142,7 +150,7 @@ module ActionController
|
|
142
150
|
end
|
143
151
|
trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash)
|
144
152
|
url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root]
|
145
|
-
anchor = "##{
|
153
|
+
anchor = "##{URI.escape(options.delete(:anchor).to_param.to_s, UNSAFE_PCHAR)}" if options[:anchor]
|
146
154
|
generated = Routing::Routes.generate(options, {})
|
147
155
|
url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated)
|
148
156
|
url << anchor if anchor
|
data/lib/action_pack/version.rb
CHANGED
@@ -877,9 +877,9 @@ module ActionView
|
|
877
877
|
|
878
878
|
def value_before_type_cast(object, method_name)
|
879
879
|
unless object.nil?
|
880
|
-
object.respond_to?(method_name
|
881
|
-
object.send(method_name
|
882
|
-
object.send(method_name)
|
880
|
+
object.respond_to?(method_name) ?
|
881
|
+
object.send(method_name) :
|
882
|
+
object.send(method_name + "_before_type_cast")
|
883
883
|
end
|
884
884
|
end
|
885
885
|
|
@@ -440,7 +440,7 @@ module ActionView
|
|
440
440
|
|
441
441
|
private
|
442
442
|
def html_options_for_form(url_for_options, options, *parameters_for_url)
|
443
|
-
|
443
|
+
options.stringify_keys.tap do |html_options|
|
444
444
|
html_options["enctype"] = "multipart/form-data" if html_options.delete("multipart")
|
445
445
|
html_options["action"] = url_for(url_for_options, *parameters_for_url)
|
446
446
|
end
|
@@ -653,7 +653,7 @@ module ActionView
|
|
653
653
|
# <script> tag.
|
654
654
|
module GeneratorMethods
|
655
655
|
def to_s #:nodoc:
|
656
|
-
|
656
|
+
(@lines * $/).tap do |javascript|
|
657
657
|
if ActionView::Base.debug_rjs
|
658
658
|
source = javascript.dup
|
659
659
|
javascript.replace "try {\n#{source}\n} catch (e) "
|
@@ -981,8 +981,8 @@ module ActionView
|
|
981
981
|
end
|
982
982
|
|
983
983
|
def record(line)
|
984
|
-
|
985
|
-
self <<
|
984
|
+
"#{line.to_s.chomp.gsub(/\;\z/, '')};".tap do |_line|
|
985
|
+
self << _line
|
986
986
|
end
|
987
987
|
end
|
988
988
|
|
@@ -532,9 +532,14 @@ module ActionView
|
|
532
532
|
end
|
533
533
|
|
534
534
|
AUTO_LINK_RE = %r{
|
535
|
-
(
|
535
|
+
(?: ([\w+.:-]+:)// | www\. )
|
536
536
|
[^\s<]+
|
537
|
-
}x
|
537
|
+
}x
|
538
|
+
|
539
|
+
# regexps for determining context, used high-volume
|
540
|
+
AUTO_LINK_CRE = [/<[^>]+$/, /^[^>]*>/, /<a\b.*?>/i, /<\/a>/i]
|
541
|
+
|
542
|
+
AUTO_EMAIL_RE = /[\w.!#\$%+-]+@[\w-]+(?:\.[\w-]+)+/
|
538
543
|
|
539
544
|
BRACKETS = { ']' => '[', ')' => '(', '}' => '{' }
|
540
545
|
|
@@ -543,26 +548,26 @@ module ActionView
|
|
543
548
|
def auto_link_urls(text, html_options = {})
|
544
549
|
link_attributes = html_options.stringify_keys
|
545
550
|
text.gsub(AUTO_LINK_RE) do
|
546
|
-
href = $&
|
547
|
-
punctuation =
|
548
|
-
|
549
|
-
|
550
|
-
if left =~ /<[^>]+$/ && right =~ /^[^>]*>/
|
551
|
+
scheme, href = $1, $&
|
552
|
+
punctuation = []
|
553
|
+
|
554
|
+
if auto_linked?($`, $')
|
551
555
|
# do not change string; URL is already linked
|
552
556
|
href
|
553
557
|
else
|
554
558
|
# don't include trailing punctuation character as part of the URL
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
+
while href.sub!(/[^\w\/-]$/, '')
|
560
|
+
punctuation.push $&
|
561
|
+
if opening = BRACKETS[punctuation.last] and href.scan(opening).size > href.scan(punctuation.last).size
|
562
|
+
href << punctuation.pop
|
563
|
+
break
|
559
564
|
end
|
560
565
|
end
|
561
566
|
|
562
567
|
link_text = block_given?? yield(href) : href
|
563
|
-
href = 'http://' + href unless
|
568
|
+
href = 'http://' + href unless scheme
|
564
569
|
|
565
|
-
content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation
|
570
|
+
content_tag(:a, h(link_text), link_attributes.merge('href' => href)) + punctuation.reverse.join('')
|
566
571
|
end
|
567
572
|
end
|
568
573
|
end
|
@@ -570,11 +575,10 @@ module ActionView
|
|
570
575
|
# Turns all email addresses into clickable links. If a block is given,
|
571
576
|
# each email is yielded and the result is used as the link text.
|
572
577
|
def auto_link_email_addresses(text, html_options = {})
|
573
|
-
|
574
|
-
|
575
|
-
text = $1
|
578
|
+
text.gsub(AUTO_EMAIL_RE) do
|
579
|
+
text = $&
|
576
580
|
|
577
|
-
if
|
581
|
+
if auto_linked?($`, $')
|
578
582
|
text
|
579
583
|
else
|
580
584
|
display_text = (block_given?) ? yield(text) : text
|
@@ -582,6 +586,12 @@ module ActionView
|
|
582
586
|
end
|
583
587
|
end
|
584
588
|
end
|
589
|
+
|
590
|
+
# Detects already linked context or position in the middle of a tag
|
591
|
+
def auto_linked?(left, right)
|
592
|
+
(left =~ AUTO_LINK_CRE[0] and right =~ AUTO_LINK_CRE[1]) or
|
593
|
+
(left.rindex(AUTO_LINK_CRE[2]) and $' !~ AUTO_LINK_CRE[3])
|
594
|
+
end
|
585
595
|
end
|
586
596
|
end
|
587
597
|
end
|
@@ -63,37 +63,37 @@
|
|
63
63
|
half_a_minute: "half a minute"
|
64
64
|
less_than_x_seconds:
|
65
65
|
one: "less than 1 second"
|
66
|
-
other: "less than {
|
66
|
+
other: "less than %{count} seconds"
|
67
67
|
x_seconds:
|
68
68
|
one: "1 second"
|
69
|
-
other: "{
|
69
|
+
other: "%{count} seconds"
|
70
70
|
less_than_x_minutes:
|
71
71
|
one: "less than a minute"
|
72
|
-
other: "less than {
|
72
|
+
other: "less than %{count} minutes"
|
73
73
|
x_minutes:
|
74
74
|
one: "1 minute"
|
75
|
-
other: "{
|
75
|
+
other: "%{count} minutes"
|
76
76
|
about_x_hours:
|
77
77
|
one: "about 1 hour"
|
78
|
-
other: "about {
|
78
|
+
other: "about %{count} hours"
|
79
79
|
x_days:
|
80
80
|
one: "1 day"
|
81
|
-
other: "{
|
81
|
+
other: "%{count} days"
|
82
82
|
about_x_months:
|
83
83
|
one: "about 1 month"
|
84
|
-
other: "about {
|
84
|
+
other: "about %{count} months"
|
85
85
|
x_months:
|
86
86
|
one: "1 month"
|
87
|
-
other: "{
|
87
|
+
other: "%{count} months"
|
88
88
|
about_x_years:
|
89
89
|
one: "about 1 year"
|
90
|
-
other: "about {
|
90
|
+
other: "about %{count} years"
|
91
91
|
over_x_years:
|
92
92
|
one: "over 1 year"
|
93
|
-
other: "over {
|
93
|
+
other: "over %{count} years"
|
94
94
|
almost_x_years:
|
95
95
|
one: "almost 1 year"
|
96
|
-
other: "almost {
|
96
|
+
other: "almost %{count} years"
|
97
97
|
prompts:
|
98
98
|
year: "Year"
|
99
99
|
month: "Month"
|
@@ -106,12 +106,12 @@
|
|
106
106
|
errors:
|
107
107
|
template:
|
108
108
|
header:
|
109
|
-
one: "1 error prohibited this {
|
110
|
-
other: "{
|
109
|
+
one: "1 error prohibited this %{model} from being saved"
|
110
|
+
other: "%{count} errors prohibited this %{model} from being saved"
|
111
111
|
# The variable :count is also available
|
112
112
|
body: "There were problems with the following fields:"
|
113
113
|
|
114
114
|
support:
|
115
115
|
select:
|
116
116
|
# default value for :prompt => true in FormOptionsHelper
|
117
|
-
prompt: "Please select"
|
117
|
+
prompt: "Please select"
|
data/lib/action_view/template.rb
CHANGED
data/test/abstract_unit.rb
CHANGED
@@ -58,4 +58,21 @@ class DummyMutex
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
+
class ActionController::IntegrationTest < ActiveSupport::TestCase
|
62
|
+
def with_autoload_path(path)
|
63
|
+
path = File.join(File.dirname(__FILE__), "fixtures", path)
|
64
|
+
if ActiveSupport::Dependencies.autoload_paths.include?(path)
|
65
|
+
yield
|
66
|
+
else
|
67
|
+
begin
|
68
|
+
ActiveSupport::Dependencies.autoload_paths << path
|
69
|
+
yield
|
70
|
+
ensure
|
71
|
+
ActiveSupport::Dependencies.autoload_paths.reject! {|p| p == path}
|
72
|
+
ActiveSupport::Dependencies.clear
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
61
78
|
ActionController::Reloader.default_lock = DummyMutex.new
|
@@ -22,7 +22,6 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def get_session_id
|
25
|
-
session[:foo]
|
26
25
|
render :text => "#{request.session_options[:id]}"
|
27
26
|
end
|
28
27
|
|
@@ -45,23 +44,27 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
|
45
44
|
ActiveRecord::SessionStore.session_class.drop_table!
|
46
45
|
end
|
47
46
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
47
|
+
%w{ session sql_bypass }.each do |class_name|
|
48
|
+
define_method("test_setting_and_getting_session_value_with_#{class_name}_store") do
|
49
|
+
with_store class_name do
|
50
|
+
with_test_route_set do
|
51
|
+
get '/set_session_value'
|
52
|
+
assert_response :success
|
53
|
+
assert cookies['_session_id']
|
54
|
+
|
55
|
+
get '/get_session_value'
|
56
|
+
assert_response :success
|
57
|
+
assert_equal 'foo: "bar"', response.body
|
58
|
+
|
59
|
+
get '/set_session_value', :foo => "baz"
|
60
|
+
assert_response :success
|
61
|
+
assert cookies['_session_id']
|
62
|
+
|
63
|
+
get '/get_session_value'
|
64
|
+
assert_response :success
|
65
|
+
assert_equal 'foo: "baz"', response.body
|
66
|
+
end
|
67
|
+
end
|
65
68
|
end
|
66
69
|
end
|
67
70
|
|
@@ -107,6 +110,38 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
|
107
110
|
end
|
108
111
|
end
|
109
112
|
|
113
|
+
def test_getting_session_value
|
114
|
+
with_test_route_set do
|
115
|
+
get '/set_session_value'
|
116
|
+
assert_response :success
|
117
|
+
assert cookies['_session_id']
|
118
|
+
|
119
|
+
get '/get_session_value'
|
120
|
+
assert_response :success
|
121
|
+
assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
|
122
|
+
session_id = cookies["_session_id"]
|
123
|
+
|
124
|
+
get '/call_reset_session'
|
125
|
+
assert_response :success
|
126
|
+
assert_not_equal [], headers['Set-Cookie']
|
127
|
+
|
128
|
+
cookies["_session_id"] = session_id # replace our new session_id with our old, pre-reset session_id
|
129
|
+
|
130
|
+
get '/get_session_value'
|
131
|
+
assert_response :success
|
132
|
+
assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from the database"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_getting_from_nonexistent_session
|
137
|
+
with_test_route_set do
|
138
|
+
get '/get_session_value'
|
139
|
+
assert_response :success
|
140
|
+
assert_equal 'foo: nil', response.body
|
141
|
+
assert_nil cookies['_session_id'], "should only create session on write, not read"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
110
145
|
def test_prevents_session_fixation
|
111
146
|
with_test_route_set do
|
112
147
|
get '/set_session_value'
|
@@ -171,4 +206,16 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest
|
|
171
206
|
yield
|
172
207
|
end
|
173
208
|
end
|
209
|
+
|
210
|
+
def with_store(class_name)
|
211
|
+
begin
|
212
|
+
session_class = ActiveRecord::SessionStore.session_class
|
213
|
+
ActiveRecord::SessionStore.session_class = "ActiveRecord::SessionStore::#{class_name.camelize}".constantize
|
214
|
+
yield
|
215
|
+
rescue
|
216
|
+
ActiveRecord::SessionStore.session_class = session_class
|
217
|
+
raise
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
174
221
|
end
|