actionpack 2.2.2 → 2.2.3
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 +66 -6
- data/Rakefile +1 -1
- data/lib/action_controller/base.rb +3 -0
- data/lib/action_controller/dispatcher.rb +2 -1
- data/lib/action_controller/rack_process.rb +2 -8
- data/lib/action_controller/request.rb +9 -0
- data/lib/action_controller/resources.rb +1 -1
- data/lib/action_controller/routing/route_set.rb +6 -2
- data/lib/action_controller/routing/segments.rb +17 -21
- data/lib/action_controller/session/cookie_store.rb +33 -1
- data/lib/action_pack/version.rb +1 -1
- data/lib/action_view/base.rb +2 -0
- data/lib/action_view/helpers/asset_tag_helper.rb +153 -319
- data/lib/action_view/helpers/number_helper.rb +3 -4
- data/lib/action_view/helpers/tag_helper.rb +1 -1
- data/lib/action_view/locale/en.yml +1 -0
- data/lib/action_view/template_handler.rb +24 -4
- data/test/controller/cgi_test.rb +6 -0
- data/test/controller/dispatcher_test.rb +0 -5
- data/test/controller/request_test.rb +7 -0
- data/test/controller/resources_test.rb +10 -0
- data/test/controller/routing_test.rb +35 -0
- data/test/fixtures/test/hello_world.js +1 -0
- data/test/template/asset_tag_helper_test.rb +20 -2
- data/test/template/number_helper_i18n_test.rb +3 -0
- data/test/template/render_test.rb +15 -0
- metadata +8 -86
data/CHANGELOG
CHANGED
@@ -1,9 +1,72 @@
|
|
1
|
-
*2.2.
|
1
|
+
*2.2.3 (September 4th, 2009)*
|
2
2
|
|
3
|
-
*
|
3
|
+
* Sanitize multibyte strings before escaping them with escape_once. CVE-2009-3009
|
4
|
+
|
5
|
+
* Backwards Compatibility: bring back Request#relative_url_root but deprecate it.
|
6
|
+
|
7
|
+
* Rationalise the session options to one hash, prevents rack or integration tests from seeing in correct defaults. [Koz]
|
8
|
+
|
9
|
+
* I18n: translate number_to_human_size. Add storage_units: [Bytes, KB, MB, GB, TB] to your translations. #1448 [Yaroslav Markin]
|
10
|
+
|
11
|
+
* Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string #1299 [DHH]
|
12
|
+
|
13
|
+
* Make ActionController#render(string) work as a shortcut for render :file/:template/:action => string. [#1435] [Pratik Naik] Examples:
|
14
|
+
|
15
|
+
# Instead of render(:action => 'other_action')
|
16
|
+
render('other_action') # argument has no '/'
|
17
|
+
render(:other_action)
|
18
|
+
|
19
|
+
# Instead of render(:template => 'controller/action')
|
20
|
+
render('controller/action') # argument must not begin with a '/', but contain a '/'
|
21
|
+
|
22
|
+
# Instead of render(:file => '/Users/lifo/home.html.erb')
|
23
|
+
render('/Users/lifo/home.html.erb') # argument must begin with a '/'
|
24
|
+
|
25
|
+
* Add :prompt option to date/time select helpers. #561 [Sam Oliver]
|
26
|
+
|
27
|
+
* Fixed that send_file shouldn't set an etag #1578 [Hongli Lai]
|
28
|
+
|
29
|
+
* Allow users to opt out of the spoofing checks in Request#remote_ip. Useful for sites whose traffic regularly triggers false positives. [Darren Boyd]
|
30
|
+
|
31
|
+
* Deprecated formatted_polymorphic_url. [Jeremy Kemper]
|
32
|
+
|
33
|
+
* Added the option to declare an asset_host as an object that responds to call (see http://github.com/dhh/asset-hosting-with-minimum-ssl for an example) [David Heinemeier Hansson]
|
34
|
+
|
35
|
+
* Added support for multiple routes.rb files (useful for plugin engines). This also means that draw will no longer clear the route set, you have to do that by hand (shouldn't make a difference to you unless you're doing some funky stuff) [David Heinemeier Hansson]
|
4
36
|
|
37
|
+
* Dropped formatted_* routes in favor of just passing in :format as an option. This cuts resource routes generation in half #1359 [aaronbatalion]
|
5
38
|
|
6
|
-
*
|
39
|
+
* Remove support for old double-encoded cookies from the cookie store. These values haven't been generated since before 2.1.0, and any users who have visited the app in the intervening 6 months will have had their cookie upgraded. [Michael Koziarski]
|
40
|
+
|
41
|
+
* Allow helpers directory to be overridden via ActionController::Base.helpers_dir #1424 [Sam Pohlenz]
|
42
|
+
|
43
|
+
* Remove deprecated ActionController::Base#assign_default_content_type_and_charset
|
44
|
+
|
45
|
+
* Changed the default of ActionView#render to assume partials instead of files when not given an options hash [David Heinemeier Hansson]. Examples:
|
46
|
+
|
47
|
+
# Instead of <%= render :partial => "account" %>
|
48
|
+
<%= render "account" %>
|
49
|
+
|
50
|
+
# Instead of <%= render :partial => "account", :locals => { :account => @buyer } %>
|
51
|
+
<%= render "account", :account => @buyer %>
|
52
|
+
|
53
|
+
# @account is an Account instance, so it uses the RecordIdentifier to replace
|
54
|
+
# <%= render :partial => "accounts/account", :locals => { :account => @account } %>
|
55
|
+
<%= render(@account) %>
|
56
|
+
|
57
|
+
# @posts is an array of Post instances, so it uses the RecordIdentifier to replace
|
58
|
+
# <%= render :partial => "posts/post", :collection => @posts %>
|
59
|
+
<%= render(@posts) %>
|
60
|
+
|
61
|
+
* Remove deprecated render_component. Please use the plugin from http://github.com/rails/render_component/tree/master [Pratik Naik]
|
62
|
+
|
63
|
+
* Fixed RedCloth and BlueCloth shouldn't preload. Instead just assume that they're available if you want to use textilize and markdown and let autoload require them [David Heinemeier Hansson]
|
64
|
+
>>>>>>> 49a055d... Fixed the AssetTagHelper cache to use the computed asset host as part of the cache key instead of just assuming the its a string [#1299 state:committed]:actionpack/CHANGELOG
|
65
|
+
|
66
|
+
|
67
|
+
*2.2.2 (November 21st, 2008)*
|
68
|
+
|
69
|
+
* Deprecated the :file default for ActionView#render to prepare for 2.3's new :partial default [DHH]
|
7
70
|
|
8
71
|
* Restore backwards compatible functionality for setting relative_url_root. Include deprecation
|
9
72
|
|
@@ -33,9 +96,6 @@
|
|
33
96
|
|
34
97
|
* Fixed bug with asset timestamping when using relative_url_root #1265 [Joe Goldwasser]
|
35
98
|
|
36
|
-
|
37
|
-
*2.2.0 [RC1] (October 24th, 2008)*
|
38
|
-
|
39
99
|
* Fix incorrect closing CDATA delimiter and that HTML::Node.parse would blow up on unclosed CDATA sections [packagethief]
|
40
100
|
|
41
101
|
* Added stale? and fresh_when methods to provide a layer of abstraction above request.fresh? and friends [DHH]. Example:
|
data/Rakefile
CHANGED
@@ -80,7 +80,7 @@ spec = Gem::Specification.new do |s|
|
|
80
80
|
s.has_rdoc = true
|
81
81
|
s.requirements << 'none'
|
82
82
|
|
83
|
-
s.add_dependency('activesupport', '= 2.2.
|
83
|
+
s.add_dependency('activesupport', '= 2.2.3' + PKG_BUILD)
|
84
84
|
|
85
85
|
s.require_path = 'lib'
|
86
86
|
s.autorequire = 'action_controller'
|
@@ -1164,6 +1164,9 @@ module ActionController #:nodoc:
|
|
1164
1164
|
def reset_session #:doc:
|
1165
1165
|
request.reset_session
|
1166
1166
|
@_session = request.session
|
1167
|
+
#http://rails.lighthouseapp.com/projects/8994/tickets/1558-memory-problem-on-reset_session-in-around_filter#ticket-1558-1
|
1168
|
+
#MRI appears to have a GC related memory leak to do with the finalizer that is defined on CGI::Session
|
1169
|
+
ObjectSpace.undefine_finalizer(@_session)
|
1167
1170
|
response.session = @_session
|
1168
1171
|
end
|
1169
1172
|
|
@@ -10,6 +10,8 @@ module ActionController
|
|
10
10
|
# Development mode callbacks
|
11
11
|
before_dispatch :reload_application
|
12
12
|
after_dispatch :cleanup_application
|
13
|
+
|
14
|
+
ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
13
15
|
end
|
14
16
|
|
15
17
|
# Common callbacks
|
@@ -147,7 +149,6 @@ module ActionController
|
|
147
149
|
|
148
150
|
Routing::Routes.reload
|
149
151
|
ActionController::Base.view_paths.reload!
|
150
|
-
ActionView::Helpers::AssetTagHelper::AssetTag::Cache.clear
|
151
152
|
end
|
152
153
|
|
153
154
|
# Cleanup the application by clearing out loaded classes so they can
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'action_controller/cgi_ext'
|
2
2
|
require 'action_controller/session/cookie_store'
|
3
|
+
require 'action_controller/cgi_process'
|
3
4
|
|
4
5
|
module ActionController #:nodoc:
|
5
6
|
class RackRequest < AbstractRequest #:nodoc:
|
@@ -9,14 +10,7 @@ module ActionController #:nodoc:
|
|
9
10
|
class SessionFixationAttempt < StandardError #:nodoc:
|
10
11
|
end
|
11
12
|
|
12
|
-
DEFAULT_SESSION_OPTIONS =
|
13
|
-
:database_manager => CGI::Session::CookieStore, # store data in cookie
|
14
|
-
:prefix => "ruby_sess.", # prefix session file names
|
15
|
-
:session_path => "/", # available to all paths in app
|
16
|
-
:session_key => "_session_id",
|
17
|
-
:cookie_only => true,
|
18
|
-
:session_http_only=> true
|
19
|
-
}
|
13
|
+
DEFAULT_SESSION_OPTIONS = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS
|
20
14
|
|
21
15
|
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
|
22
16
|
@session_options = session_options
|
@@ -305,6 +305,15 @@ EOM
|
|
305
305
|
else 80
|
306
306
|
end
|
307
307
|
end
|
308
|
+
|
309
|
+
# Returns the value of ActionController::Base.relative_url_root. This method is
|
310
|
+
# deprecated as the value is an application wide setting, not something which
|
311
|
+
# changes per request.
|
312
|
+
def relative_url_root
|
313
|
+
ActiveSupport::Deprecation.warn(
|
314
|
+
"relative_url_root is now set application-wide, use ActionController::Base.relative_url_root instead.", caller)
|
315
|
+
ActionController::Base.relative_url_root
|
316
|
+
end
|
308
317
|
|
309
318
|
# Returns a \port suffix like ":8080" if the \port number of this request
|
310
319
|
# is not the default HTTP \port 80 or HTTPS \port 443.
|
@@ -535,9 +535,9 @@ module ActionController
|
|
535
535
|
|
536
536
|
with_options :controller => resource.controller do |map|
|
537
537
|
map_collection_actions(map, resource)
|
538
|
-
map_default_singleton_actions(map, resource)
|
539
538
|
map_new_actions(map, resource)
|
540
539
|
map_member_actions(map, resource)
|
540
|
+
map_default_singleton_actions(map, resource)
|
541
541
|
|
542
542
|
map_associations(resource, options)
|
543
543
|
|
@@ -136,9 +136,13 @@ module ActionController
|
|
136
136
|
end
|
137
137
|
end
|
138
138
|
|
139
|
+
def named_helper_module_eval(code, *args)
|
140
|
+
@module.module_eval(code, *args)
|
141
|
+
end
|
142
|
+
|
139
143
|
def define_hash_access(route, name, kind, options)
|
140
144
|
selector = hash_access_name(name, kind)
|
141
|
-
|
145
|
+
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
|
142
146
|
def #{selector}(options = nil)
|
143
147
|
options ? #{options.inspect}.merge(options) : #{options.inspect}
|
144
148
|
end
|
@@ -166,7 +170,7 @@ module ActionController
|
|
166
170
|
#
|
167
171
|
# foo_url(bar, baz, bang, :sort_by => 'baz')
|
168
172
|
#
|
169
|
-
|
173
|
+
named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks
|
170
174
|
def #{selector}(*args)
|
171
175
|
|
172
176
|
#{generate_optimisation_block(route, kind)}
|
@@ -3,7 +3,11 @@ module ActionController
|
|
3
3
|
class Segment #:nodoc:
|
4
4
|
RESERVED_PCHAR = ':@&=+$,;'
|
5
5
|
SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
|
6
|
-
|
6
|
+
if RUBY_VERSION >= '1.9'
|
7
|
+
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze
|
8
|
+
else
|
9
|
+
UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
|
10
|
+
end
|
7
11
|
|
8
12
|
# TODO: Convert :is_optional accessor to read only
|
9
13
|
attr_accessor :is_optional
|
@@ -191,23 +195,19 @@ module ActionController
|
|
191
195
|
end
|
192
196
|
|
193
197
|
def regexp_chunk
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
198
|
+
regexp ? regexp_string : default_regexp_chunk
|
199
|
+
end
|
200
|
+
|
201
|
+
def regexp_string
|
202
|
+
regexp_has_modifiers? ? "(#{regexp.to_s})" : "(#{regexp.source})"
|
203
|
+
end
|
204
|
+
|
205
|
+
def default_regexp_chunk
|
206
|
+
"([^#{Routing::SEPARATORS.join}]+)"
|
203
207
|
end
|
204
208
|
|
205
209
|
def number_of_captures
|
206
|
-
|
207
|
-
regexp.number_of_captures + 1
|
208
|
-
else
|
209
|
-
1
|
210
|
-
end
|
210
|
+
regexp ? regexp.number_of_captures + 1 : 1
|
211
211
|
end
|
212
212
|
|
213
213
|
def build_pattern(pattern)
|
@@ -244,10 +244,6 @@ module ActionController
|
|
244
244
|
"(?i-:(#{(regexp || Regexp.union(*possible_names)).source}))"
|
245
245
|
end
|
246
246
|
|
247
|
-
def number_of_captures
|
248
|
-
1
|
249
|
-
end
|
250
|
-
|
251
247
|
# Don't URI.escape the controller name since it may contain slashes.
|
252
248
|
def interpolation_chunk(value_code = local_name)
|
253
249
|
"\#{#{value_code}.to_s}"
|
@@ -289,8 +285,8 @@ module ActionController
|
|
289
285
|
"params[:#{key}] = PathSegment::Result.new_escaped((match[#{next_capture}]#{" || " + default.inspect if default}).split('/'))#{" if match[" + next_capture + "]" if !default}"
|
290
286
|
end
|
291
287
|
|
292
|
-
def
|
293
|
-
|
288
|
+
def default_regexp_chunk
|
289
|
+
"(.*)"
|
294
290
|
end
|
295
291
|
|
296
292
|
def number_of_captures
|
@@ -140,7 +140,7 @@ class CGI::Session::CookieStore
|
|
140
140
|
data, digest = cookie.split('--')
|
141
141
|
|
142
142
|
# Do two checks to transparently support old double-escaped data.
|
143
|
-
unless digest
|
143
|
+
unless secure_compare(digest, generate_digest(data)) || secure_compare(digest, generate_digest(data = CGI.unescape(data)))
|
144
144
|
delete
|
145
145
|
raise TamperedWithCookie
|
146
146
|
end
|
@@ -164,4 +164,36 @@ class CGI::Session::CookieStore
|
|
164
164
|
def clear_old_cookie_value
|
165
165
|
@session.cgi.cookies[@cookie_options['name']].clear
|
166
166
|
end
|
167
|
+
|
168
|
+
if "foo".respond_to?(:force_encoding)
|
169
|
+
# constant-time comparison algorithm to prevent timing attacks
|
170
|
+
def secure_compare(a, b)
|
171
|
+
a = a.dup.force_encoding(Encoding::BINARY)
|
172
|
+
b = b.dup.force_encoding(Encoding::BINARY)
|
173
|
+
|
174
|
+
if a.length == b.length
|
175
|
+
result = 0
|
176
|
+
for i in 0..(a.length - 1)
|
177
|
+
result |= a[i].ord ^ b[i].ord
|
178
|
+
end
|
179
|
+
result == 0
|
180
|
+
else
|
181
|
+
false
|
182
|
+
end
|
183
|
+
end
|
184
|
+
else
|
185
|
+
# For 1.8
|
186
|
+
def secure_compare(a, b)
|
187
|
+
if a.length == b.length
|
188
|
+
result = 0
|
189
|
+
for i in 0..(a.length - 1)
|
190
|
+
result |= a[i] ^ b[i]
|
191
|
+
end
|
192
|
+
result == 0
|
193
|
+
else
|
194
|
+
false
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
167
199
|
end
|
data/lib/action_pack/version.rb
CHANGED
data/lib/action_view/base.rb
CHANGED
@@ -320,6 +320,8 @@ module ActionView #:nodoc:
|
|
320
320
|
# OPTIMIZE: Checks to lookup template in view path
|
321
321
|
if template = self.view_paths["#{template_file_name}.#{template_format}"]
|
322
322
|
template
|
323
|
+
elsif template_file_extension && template = self.view_paths["#{template_file_name}.#{template_file_extension}"]
|
324
|
+
template
|
323
325
|
elsif template = self.view_paths[template_file_name]
|
324
326
|
template
|
325
327
|
elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) &&
|
@@ -151,7 +151,7 @@ module ActionView
|
|
151
151
|
# javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr.js
|
152
152
|
# javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js
|
153
153
|
def javascript_path(source)
|
154
|
-
|
154
|
+
compute_public_path(source, 'javascripts', 'js')
|
155
155
|
end
|
156
156
|
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
|
157
157
|
|
@@ -249,17 +249,15 @@ module ActionView
|
|
249
249
|
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
|
250
250
|
joined_javascript_path = File.join(JAVASCRIPTS_DIR, joined_javascript_name)
|
251
251
|
|
252
|
-
unless File.exists?(joined_javascript_path)
|
253
|
-
JavaScriptSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_javascript_path)
|
254
|
-
end
|
252
|
+
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) unless File.exists?(joined_javascript_path)
|
255
253
|
javascript_src_tag(joined_javascript_name, options)
|
256
254
|
else
|
257
|
-
|
258
|
-
javascript_src_tag(source, options)
|
259
|
-
}.join("\n")
|
255
|
+
expand_javascript_sources(sources, recursive).collect { |source| javascript_src_tag(source, options) }.join("\n")
|
260
256
|
end
|
261
257
|
end
|
262
258
|
|
259
|
+
@@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
|
260
|
+
|
263
261
|
# Register one or more javascript files to be included when <tt>symbol</tt>
|
264
262
|
# is passed to <tt>javascript_include_tag</tt>. This method is typically intended
|
265
263
|
# to be called from plugin initialization to register javascript files
|
@@ -272,9 +270,11 @@ module ActionView
|
|
272
270
|
# <script type="text/javascript" src="/javascripts/body.js"></script>
|
273
271
|
# <script type="text/javascript" src="/javascripts/tail.js"></script>
|
274
272
|
def self.register_javascript_expansion(expansions)
|
275
|
-
|
273
|
+
@@javascript_expansions.merge!(expansions)
|
276
274
|
end
|
277
275
|
|
276
|
+
@@stylesheet_expansions = {}
|
277
|
+
|
278
278
|
# Register one or more stylesheet files to be included when <tt>symbol</tt>
|
279
279
|
# is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
|
280
280
|
# to be called from plugin initialization to register stylesheet files
|
@@ -287,7 +287,7 @@ module ActionView
|
|
287
287
|
# <link href="/stylesheets/body.css" media="screen" rel="stylesheet" type="text/css" />
|
288
288
|
# <link href="/stylesheets/tail.css" media="screen" rel="stylesheet" type="text/css" />
|
289
289
|
def self.register_stylesheet_expansion(expansions)
|
290
|
-
|
290
|
+
@@stylesheet_expansions.merge!(expansions)
|
291
291
|
end
|
292
292
|
|
293
293
|
# Register one or more additional JavaScript files to be included when
|
@@ -295,11 +295,11 @@ module ActionView
|
|
295
295
|
# typically intended to be called from plugin initialization to register additional
|
296
296
|
# .js files that the plugin installed in <tt>public/javascripts</tt>.
|
297
297
|
def self.register_javascript_include_default(*sources)
|
298
|
-
|
298
|
+
@@javascript_expansions[:defaults].concat(sources)
|
299
299
|
end
|
300
300
|
|
301
301
|
def self.reset_javascript_include_default #:nodoc:
|
302
|
-
|
302
|
+
@@javascript_expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
|
303
303
|
end
|
304
304
|
|
305
305
|
# Computes the path to a stylesheet asset in the public stylesheets directory.
|
@@ -314,7 +314,7 @@ module ActionView
|
|
314
314
|
# stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style.css
|
315
315
|
# stylesheet_path "http://www.railsapplication.com/css/style.js" # => http://www.railsapplication.com/css/style.css
|
316
316
|
def stylesheet_path(source)
|
317
|
-
|
317
|
+
compute_public_path(source, 'stylesheets', 'css')
|
318
318
|
end
|
319
319
|
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
|
320
320
|
|
@@ -389,14 +389,10 @@ module ActionView
|
|
389
389
|
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
|
390
390
|
joined_stylesheet_path = File.join(STYLESHEETS_DIR, joined_stylesheet_name)
|
391
391
|
|
392
|
-
unless File.exists?(joined_stylesheet_path)
|
393
|
-
StylesheetSources.create(self, @controller, sources, recursive).write_asset_file_contents(joined_stylesheet_path)
|
394
|
-
end
|
392
|
+
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) unless File.exists?(joined_stylesheet_path)
|
395
393
|
stylesheet_tag(joined_stylesheet_name, options)
|
396
394
|
else
|
397
|
-
|
398
|
-
stylesheet_tag(source, options)
|
399
|
-
}.join("\n")
|
395
|
+
expand_stylesheet_sources(sources, recursive).collect { |source| stylesheet_tag(source, options) }.join("\n")
|
400
396
|
end
|
401
397
|
end
|
402
398
|
|
@@ -411,7 +407,7 @@ module ActionView
|
|
411
407
|
# image_path("/icons/edit.png") # => /icons/edit.png
|
412
408
|
# image_path("http://www.railsapplication.com/img/edit.png") # => http://www.railsapplication.com/img/edit.png
|
413
409
|
def image_path(source)
|
414
|
-
|
410
|
+
compute_public_path(source, 'images')
|
415
411
|
end
|
416
412
|
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
|
417
413
|
|
@@ -466,352 +462,190 @@ module ActionView
|
|
466
462
|
tag("img", options)
|
467
463
|
end
|
468
464
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
end
|
473
|
-
|
474
|
-
def stylesheet_tag(source, options)
|
475
|
-
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
|
476
|
-
end
|
477
|
-
|
478
|
-
module ImageAsset
|
479
|
-
DIRECTORY = 'images'.freeze
|
480
|
-
|
481
|
-
def directory
|
482
|
-
DIRECTORY
|
483
|
-
end
|
484
|
-
|
485
|
-
def extension
|
486
|
-
nil
|
487
|
-
end
|
488
|
-
end
|
489
|
-
|
490
|
-
module JavaScriptAsset
|
491
|
-
DIRECTORY = 'javascripts'.freeze
|
492
|
-
EXTENSION = 'js'.freeze
|
493
|
-
|
494
|
-
def public_directory
|
495
|
-
JAVASCRIPTS_DIR
|
496
|
-
end
|
497
|
-
|
498
|
-
def directory
|
499
|
-
DIRECTORY
|
500
|
-
end
|
465
|
+
def self.cache_asset_timestamps
|
466
|
+
@@cache_asset_timestamps
|
467
|
+
end
|
501
468
|
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
469
|
+
# You can enable or disable the asset tag timestamps cache.
|
470
|
+
# With the cache enabled, the asset tag helper methods will make fewer
|
471
|
+
# expense file system calls. However this prevents you from modifying
|
472
|
+
# any asset files while the server is running.
|
473
|
+
#
|
474
|
+
# ActionView::Helpers::AssetTagHelper.cache_asset_timestamps = false
|
475
|
+
def self.cache_asset_timestamps=(value)
|
476
|
+
@@cache_asset_timestamps = value
|
477
|
+
end
|
506
478
|
|
507
|
-
|
508
|
-
DIRECTORY = 'stylesheets'.freeze
|
509
|
-
EXTENSION = 'css'.freeze
|
479
|
+
@@cache_asset_timestamps = true
|
510
480
|
|
511
|
-
|
512
|
-
|
481
|
+
private
|
482
|
+
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
|
483
|
+
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
|
484
|
+
# roots. Rewrite the asset path for cache-busting asset ids. Include
|
485
|
+
# asset host, if configured, with the correct request protocol.
|
486
|
+
def compute_public_path(source, dir, ext = nil, include_host = true)
|
487
|
+
has_request = @controller.respond_to?(:request)
|
488
|
+
|
489
|
+
if ext && (File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")))
|
490
|
+
source += ".#{ext}"
|
513
491
|
end
|
514
492
|
|
515
|
-
|
516
|
-
|
517
|
-
end
|
493
|
+
unless source =~ %r{^[-a-z]+://}
|
494
|
+
source = "/#{dir}/#{source}" unless source[0] == ?/
|
518
495
|
|
519
|
-
|
520
|
-
EXTENSION
|
521
|
-
end
|
522
|
-
end
|
496
|
+
source = rewrite_asset_path(source)
|
523
497
|
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
CacheGuard = Mutex.new
|
529
|
-
|
530
|
-
ProtocolRegexp = %r{^[-a-z]+://}.freeze
|
531
|
-
|
532
|
-
def initialize(template, controller, source, include_host = true)
|
533
|
-
# NOTE: The template arg is temporarily needed for a legacy plugin
|
534
|
-
# hook that is expected to call rewrite_asset_path on the
|
535
|
-
# template. This should eventually be removed.
|
536
|
-
@template = template
|
537
|
-
@controller = controller
|
538
|
-
@source = source
|
539
|
-
@include_host = include_host
|
540
|
-
@cache_key = if controller.respond_to?(:request)
|
541
|
-
[self.class.name,controller.request.protocol,
|
542
|
-
ActionController::Base.asset_host,
|
543
|
-
ActionController::Base.relative_url_root,
|
544
|
-
source, include_host]
|
545
|
-
else
|
546
|
-
[self.class.name,ActionController::Base.asset_host, source, include_host]
|
498
|
+
if has_request && include_host
|
499
|
+
unless source =~ %r{^#{ActionController::Base.relative_url_root}/}
|
500
|
+
source = "#{ActionController::Base.relative_url_root}#{source}"
|
501
|
+
end
|
547
502
|
end
|
548
503
|
end
|
549
|
-
|
550
|
-
def public_path
|
551
|
-
compute_public_path(@source)
|
552
|
-
end
|
553
|
-
memoize :public_path
|
554
504
|
|
555
|
-
|
556
|
-
|
557
|
-
end
|
558
|
-
memoize :asset_file_path
|
559
|
-
|
560
|
-
def contents
|
561
|
-
File.read(asset_file_path)
|
562
|
-
end
|
563
|
-
|
564
|
-
def mtime
|
565
|
-
File.mtime(asset_file_path)
|
566
|
-
end
|
567
|
-
|
568
|
-
private
|
569
|
-
def request
|
570
|
-
@controller.request
|
571
|
-
end
|
505
|
+
if include_host && source !~ %r{^[-a-z]+://}
|
506
|
+
host = compute_asset_host(source)
|
572
507
|
|
573
|
-
|
574
|
-
@controller.
|
508
|
+
if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
|
509
|
+
host = "#{@controller.request.protocol}#{host}"
|
575
510
|
end
|
576
511
|
|
577
|
-
#
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
if source =~ ProtocolRegexp
|
583
|
-
source += ".#{extension}" if missing_extension?(source)
|
584
|
-
source = prepend_asset_host(source)
|
585
|
-
source
|
586
|
-
else
|
587
|
-
CacheGuard.synchronize do
|
588
|
-
Cache[@cache_key] ||= begin
|
589
|
-
source += ".#{extension}" if missing_extension?(source) || file_exists_with_extension?(source)
|
590
|
-
source = "/#{directory}/#{source}" unless source[0] == ?/
|
591
|
-
source = rewrite_asset_path(source)
|
592
|
-
source = prepend_relative_url_root(source)
|
593
|
-
source = prepend_asset_host(source)
|
594
|
-
source
|
595
|
-
end
|
596
|
-
end
|
597
|
-
end
|
598
|
-
end
|
599
|
-
|
600
|
-
def missing_extension?(source)
|
601
|
-
extension && File.extname(source).blank?
|
602
|
-
end
|
603
|
-
|
604
|
-
def file_exists_with_extension?(source)
|
605
|
-
extension && File.exist?(File.join(ASSETS_DIR, directory, "#{source}.#{extension}"))
|
606
|
-
end
|
607
|
-
|
608
|
-
def prepend_relative_url_root(source)
|
609
|
-
relative_url_root = ActionController::Base.relative_url_root
|
610
|
-
if request? && @include_host && source !~ %r{^#{relative_url_root}/}
|
611
|
-
"#{relative_url_root}#{source}"
|
612
|
-
else
|
613
|
-
source
|
614
|
-
end
|
615
|
-
end
|
512
|
+
"#{host}#{source}"
|
513
|
+
else
|
514
|
+
source
|
515
|
+
end
|
516
|
+
end
|
616
517
|
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
|
518
|
+
# Pick an asset host for this source. Returns +nil+ if no host is set,
|
519
|
+
# the host if no wildcard is set, the host interpolated with the
|
520
|
+
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
|
521
|
+
# or the value returned from invoking the proc if it's a proc or the value from
|
522
|
+
# invoking call if it's an object responding to call.
|
523
|
+
def compute_asset_host(source)
|
524
|
+
if host = ActionController::Base.asset_host
|
525
|
+
if host.is_a?(Proc) || host.respond_to?(:call)
|
526
|
+
case host.is_a?(Proc) ? host.arity : host.method(:call).arity
|
527
|
+
when 2
|
528
|
+
request = @controller.respond_to?(:request) && @controller.request
|
529
|
+
host.call(source, request)
|
624
530
|
else
|
625
|
-
source
|
626
|
-
end
|
627
|
-
end
|
628
|
-
|
629
|
-
# Pick an asset host for this source. Returns +nil+ if no host is set,
|
630
|
-
# the host if no wildcard is set, the host interpolated with the
|
631
|
-
# numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4),
|
632
|
-
# or the value returned from invoking the proc if it's a proc.
|
633
|
-
def compute_asset_host(source)
|
634
|
-
if host = ActionController::Base.asset_host
|
635
|
-
if host.is_a?(Proc)
|
636
|
-
case host.arity
|
637
|
-
when 2
|
638
|
-
host.call(source, request)
|
639
|
-
else
|
640
|
-
host.call(source)
|
641
|
-
end
|
642
|
-
else
|
643
|
-
(host =~ /%d/) ? host % (source.hash % 4) : host
|
644
|
-
end
|
531
|
+
host.call(source)
|
645
532
|
end
|
533
|
+
else
|
534
|
+
(host =~ /%d/) ? host % (source.hash % 4) : host
|
646
535
|
end
|
536
|
+
end
|
537
|
+
end
|
647
538
|
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
539
|
+
@@asset_timestamps_cache = {}
|
540
|
+
@@asset_timestamps_cache_guard = Mutex.new
|
541
|
+
|
542
|
+
# Use the RAILS_ASSET_ID environment variable or the source's
|
543
|
+
# modification time as its cache-busting asset id.
|
544
|
+
def rails_asset_id(source)
|
545
|
+
if asset_id = ENV["RAILS_ASSET_ID"]
|
546
|
+
asset_id
|
547
|
+
else
|
548
|
+
if @@cache_asset_timestamps && (asset_id = @@asset_timestamps_cache[source])
|
549
|
+
asset_id
|
550
|
+
else
|
551
|
+
path = File.join(ASSETS_DIR, source)
|
552
|
+
asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : ''
|
655
553
|
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
''
|
554
|
+
if @@cache_asset_timestamps
|
555
|
+
@@asset_timestamps_cache_guard.synchronize do
|
556
|
+
@@asset_timestamps_cache[source] = asset_id
|
660
557
|
end
|
661
558
|
end
|
662
|
-
end
|
663
559
|
|
664
|
-
|
665
|
-
# someplace other than the query string.
|
666
|
-
def rewrite_asset_path(source)
|
667
|
-
if @template.respond_to?(:rewrite_asset_path)
|
668
|
-
# DEPRECATE: This way to override rewrite_asset_path
|
669
|
-
@template.send(:rewrite_asset_path, source)
|
670
|
-
else
|
671
|
-
asset_id = rails_asset_id(source)
|
672
|
-
if asset_id.blank?
|
673
|
-
source
|
674
|
-
else
|
675
|
-
"#{source}?#{asset_id}"
|
676
|
-
end
|
677
|
-
end
|
560
|
+
asset_id
|
678
561
|
end
|
562
|
+
end
|
679
563
|
end
|
680
564
|
|
681
|
-
|
682
|
-
|
565
|
+
# Break out the asset path rewrite in case plugins wish to put the asset id
|
566
|
+
# someplace other than the query string.
|
567
|
+
def rewrite_asset_path(source)
|
568
|
+
asset_id = rails_asset_id(source)
|
569
|
+
if asset_id.blank?
|
570
|
+
source
|
571
|
+
else
|
572
|
+
source + "?#{asset_id}"
|
573
|
+
end
|
683
574
|
end
|
684
575
|
|
685
|
-
|
686
|
-
|
576
|
+
def javascript_src_tag(source, options)
|
577
|
+
content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
|
687
578
|
end
|
688
579
|
|
689
|
-
|
690
|
-
|
580
|
+
def stylesheet_tag(source, options)
|
581
|
+
tag("link", { "rel" => "stylesheet", "type" => Mime::CSS, "media" => "screen", "href" => html_escape(path_to_stylesheet(source)) }.merge(options), false, false)
|
691
582
|
end
|
692
583
|
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
Cache = {}
|
697
|
-
CacheGuard = Mutex.new
|
698
|
-
|
699
|
-
def self.create(template, controller, sources, recursive)
|
700
|
-
CacheGuard.synchronize do
|
701
|
-
key = [self, sources, recursive]
|
702
|
-
Cache[key] ||= new(template, controller, sources, recursive).freeze
|
703
|
-
end
|
704
|
-
end
|
584
|
+
def compute_javascript_paths(*args)
|
585
|
+
expand_javascript_sources(*args).collect { |source| compute_public_path(source, 'javascripts', 'js', false) }
|
586
|
+
end
|
705
587
|
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
@template = template
|
710
|
-
@controller = controller
|
711
|
-
@sources = sources
|
712
|
-
@recursive = recursive
|
713
|
-
end
|
588
|
+
def compute_stylesheet_paths(*args)
|
589
|
+
expand_stylesheet_sources(*args).collect { |source| compute_public_path(source, 'stylesheets', 'css', false) }
|
590
|
+
end
|
714
591
|
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
592
|
+
def expand_javascript_sources(sources, recursive = false)
|
593
|
+
if sources.include?(:all)
|
594
|
+
all_javascript_files = collect_asset_files(JAVASCRIPTS_DIR, ('**' if recursive), '*.js')
|
595
|
+
((determine_source(:defaults, @@javascript_expansions).dup & all_javascript_files) + all_javascript_files).uniq
|
596
|
+
else
|
597
|
+
expanded_sources = sources.collect do |source|
|
598
|
+
determine_source(source, @@javascript_expansions)
|
599
|
+
end.flatten
|
600
|
+
expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
|
601
|
+
expanded_sources
|
720
602
|
end
|
721
|
-
|
722
|
-
private
|
723
|
-
def determine_source(source, collection)
|
724
|
-
case source
|
725
|
-
when Symbol
|
726
|
-
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
|
727
|
-
else
|
728
|
-
source
|
729
|
-
end
|
730
|
-
end
|
731
|
-
|
732
|
-
def validate_sources!
|
733
|
-
@sources.collect { |source| determine_source(source, self.class.expansions) }.flatten
|
734
|
-
end
|
735
|
-
|
736
|
-
def all_asset_files
|
737
|
-
path = [public_directory, ('**' if @recursive), "*.#{extension}"].compact
|
738
|
-
Dir[File.join(*path)].collect { |file|
|
739
|
-
file[-(file.size - public_directory.size - 1)..-1].sub(/\.\w+$/, '')
|
740
|
-
}.sort
|
741
|
-
end
|
742
|
-
|
743
|
-
def tag_sources
|
744
|
-
expand_sources.collect { |source| tag_class.new(@template, @controller, source, false) }
|
745
|
-
end
|
746
|
-
|
747
|
-
def joined_contents
|
748
|
-
tag_sources.collect { |source| source.contents }.join("\n\n")
|
749
|
-
end
|
750
|
-
|
751
|
-
# Set mtime to the latest of the combined files to allow for
|
752
|
-
# consistent ETag without a shared filesystem.
|
753
|
-
def latest_mtime
|
754
|
-
tag_sources.map { |source| source.mtime }.max
|
755
|
-
end
|
756
603
|
end
|
757
604
|
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
605
|
+
def expand_stylesheet_sources(sources, recursive)
|
606
|
+
if sources.first == :all
|
607
|
+
collect_asset_files(STYLESHEETS_DIR, ('**' if recursive), '*.css')
|
608
|
+
else
|
609
|
+
sources.collect do |source|
|
610
|
+
determine_source(source, @@stylesheet_expansions)
|
611
|
+
end.flatten
|
765
612
|
end
|
613
|
+
end
|
766
614
|
|
767
|
-
|
768
|
-
|
769
|
-
|
770
|
-
|
771
|
-
|
772
|
-
|
773
|
-
((defaults.dup & assets) + assets).uniq!
|
774
|
-
else
|
775
|
-
expanded_sources = validate_sources!
|
776
|
-
expanded_sources << APPLICATION_JS if include_application?
|
777
|
-
expanded_sources
|
778
|
-
end
|
615
|
+
def determine_source(source, collection)
|
616
|
+
case source
|
617
|
+
when Symbol
|
618
|
+
collection[source] || raise(ArgumentError, "No expansion found for #{source.inspect}")
|
619
|
+
else
|
620
|
+
source
|
779
621
|
end
|
780
|
-
|
781
|
-
|
782
|
-
private
|
783
|
-
def tag_class
|
784
|
-
JavaScriptTag
|
785
|
-
end
|
786
|
-
|
787
|
-
def defaults
|
788
|
-
determine_source(:defaults, self.class.expansions)
|
789
|
-
end
|
622
|
+
end
|
790
623
|
|
791
|
-
|
792
|
-
|
793
|
-
end
|
624
|
+
def join_asset_file_contents(paths)
|
625
|
+
paths.collect { |path| File.read(asset_file_path(path)) }.join("\n\n")
|
794
626
|
end
|
795
627
|
|
796
|
-
|
797
|
-
|
628
|
+
def write_asset_file_contents(joined_asset_path, asset_paths)
|
629
|
+
FileUtils.mkdir_p(File.dirname(joined_asset_path))
|
630
|
+
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
|
798
631
|
|
799
|
-
|
632
|
+
# Set mtime to the latest of the combined files to allow for
|
633
|
+
# consistent ETag without a shared filesystem.
|
634
|
+
mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
|
635
|
+
File.utime(mt, mt, joined_asset_path)
|
636
|
+
end
|
800
637
|
|
801
|
-
|
802
|
-
|
803
|
-
|
638
|
+
def asset_file_path(path)
|
639
|
+
File.join(ASSETS_DIR, path.split('?').first)
|
640
|
+
end
|
804
641
|
|
805
|
-
|
806
|
-
|
807
|
-
end
|
808
|
-
memoize :expand_sources
|
642
|
+
def collect_asset_files(*path)
|
643
|
+
dir = path.first
|
809
644
|
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
end
|
645
|
+
Dir[File.join(*path.compact)].collect do |file|
|
646
|
+
file[-(file.size - dir.size - 1)..-1].sub(/\.\w+$/, '')
|
647
|
+
end.sort
|
814
648
|
end
|
815
649
|
end
|
816
650
|
end
|
817
|
-
end
|
651
|
+
end
|