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 CHANGED
@@ -1,9 +1,72 @@
1
- *2.2.2 [2.2 Final]*
1
+ *2.2.3 (September 4th, 2009)*
2
2
 
3
- * Deprecated the :file default for ActionView#render to prepare for 2.3's new :partial default [DHH]
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
- *2.2.1 [RC2] (November 14th, 2008)*
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.2' + PKG_BUILD)
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
- @module.module_eval <<-end_eval # We use module_eval to avoid leaks
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
- @module.module_eval <<-end_eval # We use module_eval to avoid leaks
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
- UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
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
- if regexp
195
- if regexp_has_modifiers?
196
- "(#{regexp.to_s})"
197
- else
198
- "(#{regexp.source})"
199
- end
200
- else
201
- "([^#{Routing::SEPARATORS.join}]+)"
202
- end
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
- if regexp
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 regexp_chunk
293
- regexp || "(.*)"
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 == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data))
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
@@ -2,7 +2,7 @@ module ActionPack #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 2
4
4
  MINOR = 2
5
- TINY = 2
5
+ TINY = 3
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
@@ -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
- JavaScriptTag.new(self, @controller, source).public_path
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
- JavaScriptSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
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
- JavaScriptSources.expansions.merge!(expansions)
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
- StylesheetSources.expansions.merge!(expansions)
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
- JavaScriptSources.expansions[:defaults].concat(sources)
298
+ @@javascript_expansions[:defaults].concat(sources)
299
299
  end
300
300
 
301
301
  def self.reset_javascript_include_default #:nodoc:
302
- JavaScriptSources.expansions[:defaults] = JAVASCRIPT_DEFAULT_SOURCES.dup
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
- StylesheetTag.new(self, @controller, source).public_path
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
- StylesheetSources.create(self, @controller, sources, recursive).expand_sources.collect { |source|
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
- ImageTag.new(self, @controller, source).public_path
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
- private
470
- def javascript_src_tag(source, options)
471
- content_tag("script", "", { "type" => Mime::JS, "src" => path_to_javascript(source) }.merge(options))
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
- def extension
503
- EXTENSION
504
- end
505
- end
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
- module StylesheetAsset
508
- DIRECTORY = 'stylesheets'.freeze
509
- EXTENSION = 'css'.freeze
479
+ @@cache_asset_timestamps = true
510
480
 
511
- def public_directory
512
- STYLESHEETS_DIR
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
- def directory
516
- DIRECTORY
517
- end
493
+ unless source =~ %r{^[-a-z]+://}
494
+ source = "/#{dir}/#{source}" unless source[0] == ?/
518
495
 
519
- def extension
520
- EXTENSION
521
- end
522
- end
496
+ source = rewrite_asset_path(source)
523
497
 
524
- class AssetTag
525
- extend ActiveSupport::Memoizable
526
-
527
- Cache = {}
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
- def asset_file_path
556
- File.join(ASSETS_DIR, public_path.split('?').first)
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
- def request?
574
- @controller.respond_to?(:request)
508
+ if has_request && !host.blank? && host !~ %r{^[-a-z]+://}
509
+ host = "#{@controller.request.protocol}#{host}"
575
510
  end
576
511
 
577
- # Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
578
- # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
579
- # roots. Rewrite the asset path for cache-busting asset ids. Include
580
- # asset host, if configured, with the correct request protocol.
581
- def compute_public_path(source)
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
- def prepend_asset_host(source)
618
- if @include_host && source !~ ProtocolRegexp
619
- host = compute_asset_host(source)
620
- if request? && !host.blank? && host !~ ProtocolRegexp
621
- host = "#{request.protocol}#{host}"
622
- end
623
- "#{host}#{source}"
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
- # Use the RAILS_ASSET_ID environment variable or the source's
649
- # modification time as its cache-busting asset id.
650
- def rails_asset_id(source)
651
- if asset_id = ENV["RAILS_ASSET_ID"]
652
- asset_id
653
- else
654
- path = File.join(ASSETS_DIR, source)
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
- if File.exist?(path)
657
- File.mtime(path).to_i.to_s
658
- else
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
- # Break out the asset path rewrite in case plugins wish to put the asset id
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
- class ImageTag < AssetTag
682
- include ImageAsset
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
- class JavaScriptTag < AssetTag
686
- include JavaScriptAsset
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
- class StylesheetTag < AssetTag
690
- include StylesheetAsset
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
- class AssetCollection
694
- extend ActiveSupport::Memoizable
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
- def initialize(template, controller, sources, recursive)
707
- # NOTE: The template arg is temporarily needed for a legacy plugin
708
- # hook. See NOTE under AssetTag#initialize for more details
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
- def write_asset_file_contents(joined_asset_path)
716
- FileUtils.mkdir_p(File.dirname(joined_asset_path))
717
- File.open(joined_asset_path, "w+") { |cache| cache.write(joined_contents) }
718
- mt = latest_mtime
719
- File.utime(mt, mt, joined_asset_path)
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
- class JavaScriptSources < AssetCollection
759
- include JavaScriptAsset
760
-
761
- EXPANSIONS = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
762
-
763
- def self.expansions
764
- EXPANSIONS
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
- APPLICATION_JS = "application".freeze
768
- APPLICATION_FILE = "application.js".freeze
769
-
770
- def expand_sources
771
- if @sources.include?(:all)
772
- assets = all_asset_files
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
- memoize :expand_sources
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
- def include_application?
792
- @sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, APPLICATION_FILE))
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
- class StylesheetSources < AssetCollection
797
- include StylesheetAsset
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
- EXPANSIONS = {}
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
- def self.expansions
802
- EXPANSIONS
803
- end
638
+ def asset_file_path(path)
639
+ File.join(ASSETS_DIR, path.split('?').first)
640
+ end
804
641
 
805
- def expand_sources
806
- @sources.first == :all ? all_asset_files : validate_sources!
807
- end
808
- memoize :expand_sources
642
+ def collect_asset_files(*path)
643
+ dir = path.first
809
644
 
810
- private
811
- def tag_class
812
- StylesheetTag
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