sprockets-rails 2.3.2 → 3.2.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 480e5ad0e2f9fa910adadff1df528c2fdbdcd7ba
4
- data.tar.gz: 13ffd578bddc43427ba93d2ee3d1cff0e120fcee
2
+ SHA256:
3
+ metadata.gz: 801a6cbc13340fb4c74ecf9b7aa2d5c6c563605e3462512e0073f268e269fe8d
4
+ data.tar.gz: facde95b73f355f987059128c7f6d852b5b8845d26f2eca5214b4fc37ded168d
5
5
  SHA512:
6
- metadata.gz: a4fadc3c38141890f8f6f6c8d9cb8053eb8444893e8657d280f90c1c222eee8948553760b7e564137bf08caa684df89fab9f1b7f391291c55ab4eea54dd3ad4b
7
- data.tar.gz: 05ce457e78078a598d208915a24577a67d298b0ca3dd188be9efe9a9e72451110f5f9c3660820b97440501d6dc38ece225eca30a6fdf1fcbfef331db6d051770
6
+ metadata.gz: 7f6c2f179ba5b3294ad0b49357c4a7a50c207f221d6cbb5d9cad406b1143ee5ff9d5a71897f3f6a2bd9709eda8d5e5bad5380e4defa1e135700cc3edd9669749
7
+ data.tar.gz: cbd34277b1e2e89c5ddded1b616ce179a269e384f1781b7032972c966737803e725440f5a23951383adf6b4a8705b50a307eb94a2bd6055ab42dcfc505aa468c
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 Joshua Peek
1
+ Copyright (c) 2014-2016 Joshua Peek
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -27,7 +27,7 @@ Only removes old assets (keeps the most recent 3 copies) from `public/assets`. U
27
27
 
28
28
  **`rake assets:clobber`**
29
29
 
30
- Nuke `public/assets` and clear the Sprockets file system cache.
30
+ Nuke `public/assets`.
31
31
 
32
32
  #### Customize
33
33
 
@@ -37,11 +37,6 @@ You can also redefine the task with the built in task generator.
37
37
 
38
38
  ``` ruby
39
39
  require 'sprockets/rails/task'
40
- # clean the old tasks
41
- Rake::Task["assets:environment"].clear
42
- Rake::Task["assets:precompile"].clear
43
- Rake::Task["assets:clean"].clear
44
- Rake::Task["assets:clobber"].clear
45
40
  Sprockets::Rails::Task.new(Rails.application) do |t|
46
41
  t.environment = lambda { Rails.application.assets }
47
42
  t.assets = %w( application.js application.css )
@@ -51,23 +46,25 @@ end
51
46
 
52
47
  Each asset task will invoke `assets:environment` first. By default this loads the Rails environment. You can override this task to add or remove dependencies for your specific compilation environment.
53
48
 
54
- Also see [Sprockets::Rails::Task](https://github.com/rails/sprockets-rails/blob/master/lib/sprockets/rails/task.rb) and [Rake::SprocketsTask](https://github.com/sstephenson/sprockets/blob/master/lib/rake/sprocketstask.rb).
55
-
49
+ Also see [Sprockets::Rails::Task](https://github.com/rails/sprockets-rails/blob/master/lib/sprockets/rails/task.rb) and [Rake::SprocketsTask](https://github.com/rails/sprockets/blob/master/lib/rake/sprocketstask.rb).
56
50
 
57
51
  ### Initializer options
58
52
 
59
- **`config.assets.precompile`**
53
+ **`config.assets.unknown_asset_fallback`**
60
54
 
61
- Add additional assets to compile on deploy. Defaults to `application.js`, `application.css` and any other non-js/css file under `app/assets`.
55
+ When set to a truthy value, a result will be returned even if the requested asset is not found in the asset pipeline. When set to a falsey value it will raise an error when no asset is found in the pipeline. Defaults to `true`.
62
56
 
63
- **`config.assets.raise_runtime_errors`**
57
+ **`config.assets.precompile`**
64
58
 
65
- Set to `true` to enable additional runtime error checking. Recommended in the `development` environment to minimize unexpected behavior when deploying to `production`.
59
+ Add additional assets to compile on deploy. Defaults to `application.js`, `application.css` and any other non-js/css file under `app/assets`.
66
60
 
67
61
  **`config.assets.paths`**
68
62
 
69
63
  Add additional load paths to this Array. Rails includes `app/assets`, `lib/assets` and `vendor/assets` for you already. Plugins might want to add their custom paths to this.
70
64
 
65
+ **`config.assets.quiet`**
66
+
67
+ Suppresses logger output for asset requests. Uses the `config.assets.prefix` path to match asset requests. Defaults to `false`.
71
68
 
72
69
  **`config.assets.version`**
73
70
 
@@ -83,13 +80,9 @@ config.assets.version = 'v2'
83
80
 
84
81
  Defaults to `/assets`. Changes the directory to compile assets to.
85
82
 
86
- **`config.assets.manifest`**
87
-
88
- Defines the full path to be used for the asset precompiler's manifest file. Defaults to a randomly-generated filename in the `config.assets.prefix` directory within the public folder.
89
-
90
83
  **`config.assets.digest`**
91
84
 
92
- Link to undigest asset filenames. This option will eventually go away. Unless when `compile` is disabled.
85
+ When enabled, fingerprints will be added to asset filenames.
93
86
 
94
87
  **`config.assets.debug`**
95
88
 
@@ -97,7 +90,7 @@ Enable expanded asset debugging mode. Individual files will be served to make re
97
90
 
98
91
  **`config.assets.compile`**
99
92
 
100
- Enables Sprockets compile environment. If disabled, `Rails.application.assets` will be unavailable to any ActionView helpers. View helpers will depend on assets being precompiled to `public/assets` in order to link to them. You can still access the environment by directly calling `Rails.application.assets`.
93
+ Enables Sprockets compile environment. If disabled, `Rails.application.assets` will be `nil` to prevent inadvertent compilation calls. View helpers will depend on assets being precompiled to `public/assets` in order to link to them. Initializers expecting `Rails.application.assets` during boot should be accessing the environment in a `config.assets.configure` block. See below.
101
94
 
102
95
  **`config.assets.configure`**
103
96
 
@@ -105,18 +98,38 @@ Invokes block with environment when the environment is initialized. Allows direc
105
98
 
106
99
  ``` ruby
107
100
  config.assets.configure do |env|
108
- env.js_compressor = :uglify # or :closure, :yui
101
+ env.js_compressor = :uglifier # or :closure, :yui
109
102
  env.css_compressor = :sass # or :yui
110
103
 
111
104
  require 'my_processor'
112
105
  env.register_preprocessor 'application/javascript', MyProcessor
113
106
 
114
107
  env.logger = Rails.logger
115
-
116
- env.cache = ActiveSupport::Cache::FileStore.new("tmp/cache/assets")
117
108
  end
118
109
  ```
119
110
 
111
+ **`config.assets.resolve_with`**
112
+
113
+ A list of `:environment` and `:manifest` symbols that defines the order that
114
+ we try to find assets: manifest first, environment second? Manifest only?
115
+
116
+ By default, we check the manifest first if asset digests are enabled and debug
117
+ is not enabled, then we check the environment if compiling is enabled:
118
+ ```
119
+ # Dev where debug is true, or digests are disabled
120
+ %i[ environment ]
121
+
122
+ # Dev default, or production with compile enabled.
123
+ %i[ manifest environment ]
124
+
125
+ # Production default.
126
+ %i[ manifest ]
127
+ ```
128
+ If the resolver list is empty (e.g. if debug is true and compile is false), the standard rails public path resolution will be used.
129
+
130
+ **`config.assets.check_precompiled_asset`**
131
+
132
+ When enabled, an exception is raised for missing assets. This option is enabled by default.
120
133
 
121
134
  ## Complementary plugins
122
135
 
@@ -125,36 +138,51 @@ The following plugins provide some extras for the Sprockets Asset Pipeline.
125
138
  * [coffee-rails](https://github.com/rails/coffee-rails)
126
139
  * [sass-rails](https://github.com/rails/sass-rails)
127
140
 
128
- **NOTE** That these plugins are optional. The core coffee-script, sass, less, uglify, (any many more) features are built into Sprockets itself. Many of these plugins only provide generators and extra helpers. You can probably get by without them.
141
+ **NOTE** That these plugins are optional. The core coffee-script, sass, less, uglify, (and many more) features are built into Sprockets itself. Many of these plugins only provide generators and extra helpers. You can probably get by without them.
129
142
 
130
143
 
131
144
  ## Changes from Rails 3.x
132
145
 
133
146
  * Only compiles digest filenames. Static non-digest assets should simply live in public/.
134
147
  * Unmanaged asset paths and urls fallback to linking to public/. This should make it easier to work with both compiled assets and simple static assets. As a side effect, there will never be any "asset not precompiled errors" when linking to missing assets. They will just link to a public file which may or may not exist.
135
- * JS and CSS compressors must be explicitly set. Magic detection has been removed to avoid loading compressors in environments where you want to avoid loading any of the asset libraries. Assign `config.assets.js_compressor = :uglify` or `config.assets.css_compressor = :sass` for the standard compressors.
148
+ * JS and CSS compressors must be explicitly set. Magic detection has been removed to avoid loading compressors in environments where you want to avoid loading any of the asset libraries. Assign `config.assets.js_compressor = :uglifier` or `config.assets.css_compressor = :sass` for the standard compressors.
136
149
  * The manifest file is now in a JSON format. Since it lives in public/ by default, the initial filename is also randomized to obfuscate public access to the resource.
137
150
  * `config.assets.manifest` (if used) must now include the manifest filename, e.g. `Rails.root.join('config/manifest.json')`. It cannot be a directory.
138
- * Two cleanup tasks. `rake assets:clean` is now a safe cleanup that only removes older assets that are no longer used. While `rake assets:clobber` nukes the entire `public/assets` directory and clears your filesystem cache. The clean task allows for rolling deploys that may still be linking to an old asset while the new assets are being built.
151
+ * Two cleanup tasks: `rake assets:clean` is now a safe cleanup that only removes older assets that are no longer used, while `rake assets:clobber` nukes the entire `public/assets` directory. The clean task allows for rolling deploys that may still be linking to an old asset while the new assets are being built.
152
+
153
+ ### But what if I want sprockets to generate non-digest assets?
154
+
155
+ You have several options:
156
+
157
+ * Use the [non-digest-assets gem](https://github.com/mvz/non-digest-assets).
158
+ * Use the [sprockets-redirect gem](https://github.com/sikachu/sprockets-redirect).
159
+ * Use the [smart_assets gem](https://github.com/zarqman/smart_assets).
160
+ * Create [a rake task](https://github.com/rails/sprockets-rails/issues/49#issuecomment-20535134) to pre-generate a non-digest version in `public/`.
139
161
 
162
+ ## Experimental
140
163
 
141
- ## Contributing
164
+ ### [SRI](http://www.w3.org/TR/SRI/) support
142
165
 
143
- Usual bundler workflow.
166
+ Sprockets 3.x adds experimental support for subresource integrity checks. The spec is still evolving and the API may change in backwards incompatible ways.
144
167
 
145
- ``` shell
146
- $ git clone https://github.com/rails/sprockets-rails.git
147
- $ cd sprockets-rails/
148
- $ bundle install
149
- $ bundle exec rake test
168
+ ``` ruby
169
+ javascript_include_tag :application, integrity: true
170
+ # => "<script src="/assets/application.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="></script>"
150
171
  ```
151
172
 
152
- [![Build Status](https://secure.travis-ci.org/rails/sprockets-rails.png)](http://travis-ci.org/rails/sprockets-rails)
173
+ Note that sprockets-rails only adds integrity hashes to assets when served in a secure context (over an HTTPS connection or localhost).
174
+
175
+
176
+ ## Contributing to Sprockets Rails
153
177
 
178
+ Sprockets Rails is work of many contributors. You're encouraged to submit pull requests, propose
179
+ features and discuss issues.
180
+
181
+ See [CONTRIBUTING](CONTRIBUTING.md).
154
182
 
155
183
  ## Releases
156
184
 
157
- sprockets-rails 2.x will primarily target sprockets 2.x with future compatibility for 3.x. Consider upgrading to sprockets-rails 3.x to take full advantage of 3.x features.
185
+ sprockets-rails 3.x will primarily target sprockets 3.x. And future versions will target the corresponding sprockets release line.
158
186
 
159
187
  The minor and patch version will be updated according to [semver](http://semver.org/).
160
188
 
@@ -162,9 +190,11 @@ The minor and patch version will be updated according to [semver](http://semver.
162
190
  * Any time the sprockets dependency is bumped, there will be a new minor release
163
191
  * Simple bug fixes will be patch releases
164
192
 
165
-
166
193
  ## License
167
194
 
168
- Copyright &copy; 2014 Joshua Peek.
195
+ Sprockets Rails is released under the [MIT License](MIT-LICENSE).
196
+
197
+ ## Code Status
169
198
 
170
- Released under the MIT license. See `LICENSE` for details.
199
+ * [![Travis CI](https://travis-ci.org/rails/sprockets-rails.svg?branch=master)](http://travis-ci.org/rails/sprockets-rails)
200
+ * [![Gem Version](https://badge.fury.io/rb/sprockets-rails.svg)](http://badge.fury.io/rb/sprockets-rails)
@@ -0,0 +1,48 @@
1
+ require 'action_view/helpers'
2
+ require 'sprockets'
3
+
4
+ module Sprockets
5
+ module Rails
6
+ module Context
7
+ include ActionView::Helpers::AssetUrlHelper
8
+ include ActionView::Helpers::AssetTagHelper
9
+
10
+ def self.included(klass)
11
+ klass.class_eval do
12
+ class_attribute :config, :assets_prefix, :digest_assets
13
+ end
14
+ end
15
+
16
+ def compute_asset_path(path, options = {})
17
+ @dependencies << 'actioncontroller-asset-url-config'
18
+
19
+ begin
20
+ asset_uri = resolve(path)
21
+ rescue FileNotFound
22
+ # TODO: eh, we should be able to use a form of locate that returns
23
+ # nil instead of raising an exception.
24
+ end
25
+
26
+ if asset_uri
27
+ asset = link_asset(path)
28
+ digest_path = asset.digest_path
29
+ path = digest_path if digest_assets
30
+ File.join(assets_prefix || "/", path)
31
+ else
32
+ super
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ register_dependency_resolver 'actioncontroller-asset-url-config' do |env|
39
+ config = env.context_class.config
40
+ [config.relative_url_root,
41
+ (config.asset_host unless config.asset_host.respond_to?(:call))]
42
+ end
43
+
44
+ # fallback to the default pipeline when using Sprockets 3.x
45
+ unless config[:pipelines].include? :debug
46
+ register_pipeline :debug, config[:pipelines][:default]
47
+ end
48
+ end
@@ -1,128 +1,132 @@
1
1
  require 'action_view'
2
2
  require 'sprockets'
3
3
  require 'active_support/core_ext/class/attribute'
4
+ require 'sprockets/rails/utils'
4
5
 
5
6
  module Sprockets
6
7
  module Rails
7
8
  module Helper
8
- class << self
9
- attr_accessor :precompile, :assets, :raise_runtime_errors
10
- end
11
-
12
- def precompile
13
- Sprockets::Rails::Helper.precompile
14
- end
15
-
16
- def assets
17
- Sprockets::Rails::Helper.assets
18
- end
19
-
20
- def raise_runtime_errors
21
- Sprockets::Rails::Helper.raise_runtime_errors
22
- end
9
+ class AssetNotFound < StandardError; end
10
+ class AssetNotPrecompiled < StandardError; end
23
11
 
24
- class AssetFilteredError < StandardError
12
+ class AssetNotPrecompiledError < AssetNotPrecompiled
13
+ include Sprockets::Rails::Utils
25
14
  def initialize(source)
26
- msg = "Asset filtered out and will not be served: " <<
27
- "add `Rails.application.config.assets.precompile += %w( #{source} )` " <<
28
- "to `config/initializers/assets.rb` and restart your server"
29
- super(msg)
30
- end
31
- end
32
-
33
- class AbsoluteAssetPathError < ArgumentError
34
- def initialize(bad_path, good_path, prefix)
35
- msg = "Asset names passed to helpers should not include the #{prefix.inspect} prefix. " <<
36
- "Instead of #{bad_path.inspect}, use #{good_path.inspect}"
15
+ msg =
16
+ if using_sprockets4?
17
+ "Asset `#{ source }` was not declared to be precompiled in production.\n" +
18
+ "Declare links to your assets in `app/assets/config/manifest.js`.\n\n" +
19
+ " //= link #{ source }\n\n" +
20
+ "and restart your server"
21
+ else
22
+ "Asset was not declared to be precompiled in production.\n" +
23
+ "Add `Rails.application.config.assets.precompile += " +
24
+ "%w( #{source} )` to `config/initializers/assets.rb` and " +
25
+ "restart your server"
26
+ end
37
27
  super(msg)
38
28
  end
39
29
  end
40
30
 
41
- if defined? ActionView::Helpers::AssetUrlHelper
42
- include ActionView::Helpers::AssetUrlHelper
43
- include ActionView::Helpers::AssetTagHelper
44
- else
45
- require 'sprockets/rails/legacy_asset_tag_helper'
46
- require 'sprockets/rails/legacy_asset_url_helper'
47
- include LegacyAssetTagHelper
48
- include LegacyAssetUrlHelper
49
- end
31
+ include ActionView::Helpers::AssetUrlHelper
32
+ include ActionView::Helpers::AssetTagHelper
33
+ include Sprockets::Rails::Utils
50
34
 
51
- VIEW_ACCESSORS = [:assets_environment, :assets_manifest,
52
- :assets_prefix, :digest_assets, :debug_assets]
35
+ VIEW_ACCESSORS = [
36
+ :assets_environment, :assets_manifest,
37
+ :assets_precompile, :precompiled_asset_checker,
38
+ :assets_prefix, :digest_assets, :debug_assets,
39
+ :resolve_assets_with, :check_precompiled_asset,
40
+ :unknown_asset_fallback
41
+ ]
53
42
 
54
43
  def self.included(klass)
55
- if klass < Sprockets::Context
56
- klass.class_eval do
57
- alias_method :assets_environment, :environment
58
- def assets_manifest; end
59
- class_attribute :config, :assets_prefix, :digest_assets, :debug_assets
44
+ klass.class_attribute(*VIEW_ACCESSORS)
45
+
46
+ klass.class_eval do
47
+ remove_method :assets_environment
48
+ def assets_environment
49
+ if instance_variable_defined?(:@assets_environment)
50
+ @assets_environment = @assets_environment.cached
51
+ elsif env = self.class.assets_environment
52
+ @assets_environment = env.cached
53
+ else
54
+ nil
55
+ end
60
56
  end
61
- else
62
- klass.class_attribute(*VIEW_ACCESSORS)
63
57
  end
64
58
  end
65
59
 
66
60
  def self.extended(obj)
67
- obj.class_eval do
61
+ obj.singleton_class.class_eval do
68
62
  attr_accessor(*VIEW_ACCESSORS)
63
+
64
+ remove_method :assets_environment
65
+ def assets_environment
66
+ if env = @assets_environment
67
+ @assets_environment = env.cached
68
+ else
69
+ nil
70
+ end
71
+ end
69
72
  end
70
73
  end
71
74
 
75
+ # Writes over the built in ActionView::Helpers::AssetUrlHelper#compute_asset_path
76
+ # to use the asset pipeline.
72
77
  def compute_asset_path(path, options = {})
73
- # Check if we are inside Sprockets context before calling check_dependencies!.
74
- check_dependencies!(path) if defined?(depend_on)
78
+ debug = options[:debug]
75
79
 
76
- if digest_path = asset_digest_path(path)
77
- path = digest_path if digest_assets
78
- path += "?body=1" if options[:debug]
79
- File.join(assets_prefix || "/", path)
80
+ if asset_path = resolve_asset_path(path, debug)
81
+ File.join(assets_prefix || "/", legacy_debug_path(asset_path, debug))
80
82
  else
83
+ message = "The asset #{ path.inspect } is not present in the asset pipeline.\n"
84
+ raise AssetNotFound, message unless unknown_asset_fallback
85
+
86
+ if respond_to?(:public_compute_asset_path)
87
+ message << "Falling back to an asset that may be in the public folder.\n"
88
+ message << "This behavior is deprecated and will be removed.\n"
89
+ message << "To bypass the asset pipeline and preserve this behavior,\n"
90
+ message << "use the `skip_pipeline: true` option.\n"
91
+
92
+ call_stack = Kernel.respond_to?(:caller_locations) && ::Rails::VERSION::MAJOR >= 5 ? caller_locations : caller
93
+ ActiveSupport::Deprecation.warn(message, call_stack)
94
+ end
81
95
  super
82
96
  end
83
97
  end
84
98
 
85
- # Computes the full URL to a asset in the public directory. This
86
- # method checks for errors before returning path.
87
- def asset_path(source, options = {})
88
- unless options[:debug]
89
- check_errors_for(source, options)
99
+ # Resolve the asset path against the Sprockets manifest or environment.
100
+ # Returns nil if it's an asset we don't know about.
101
+ def resolve_asset_path(path, allow_non_precompiled = false) #:nodoc:
102
+ resolve_asset do |resolver|
103
+ resolver.asset_path path, digest_assets, allow_non_precompiled
90
104
  end
91
- super(source, options)
92
105
  end
93
- alias :path_to_asset :asset_path
94
106
 
95
- # Get digest for asset path.
107
+ # Expand asset path to digested form.
96
108
  #
97
109
  # path - String path
98
110
  # options - Hash options
99
111
  #
100
- # Returns String Hex digest or nil if digests are disabled.
101
- def asset_digest(path, options = {})
102
- return unless digest_assets
103
-
104
- if digest_path = asset_digest_path(path, options)
105
- digest_path[/-(.+)\./, 1]
112
+ # Returns String path or nil if no asset was found.
113
+ def asset_digest_path(path, options = {})
114
+ resolve_asset do |resolver|
115
+ resolver.digest_path path, options[:debug]
106
116
  end
107
117
  end
108
118
 
109
- # Expand asset path to digested form.
119
+ # Experimental: Get integrity for asset path.
110
120
  #
111
121
  # path - String path
112
122
  # options - Hash options
113
123
  #
114
- # Returns String path or nil if no asset was found.
115
- def asset_digest_path(path, options = {})
116
- if manifest = assets_manifest
117
- if digest_path = manifest.assets[path]
118
- return digest_path
119
- end
120
- end
124
+ # Returns String integrity attribute or nil if no asset was found.
125
+ def asset_integrity(path, options = {})
126
+ path = path_with_extname(path, options)
121
127
 
122
- if environment = assets_environment
123
- if asset = environment[path]
124
- return asset.digest_path
125
- end
128
+ resolve_asset do |resolver|
129
+ resolver.integrity path
126
130
  end
127
131
  end
128
132
 
@@ -131,21 +135,27 @@ module Sprockets
131
135
  # Eventually will be deprecated and replaced by source maps.
132
136
  def javascript_include_tag(*sources)
133
137
  options = sources.extract_options!.stringify_keys
138
+ integrity = compute_integrity?(options)
134
139
 
135
140
  if options["debug"] != false && request_debug_assets?
136
141
  sources.map { |source|
137
- check_errors_for(source, :type => :javascript)
138
- if asset = lookup_asset_for_path(source, :type => :javascript)
139
- asset.to_a.map do |a|
140
- super(path_to_javascript(a.logical_path, :debug => true), options)
142
+ if asset = lookup_debug_asset(source, type: :javascript)
143
+ if asset.respond_to?(:to_a)
144
+ asset.to_a.map do |a|
145
+ super(path_to_javascript(a.logical_path, debug: true), options)
146
+ end
147
+ else
148
+ super(path_to_javascript(asset.logical_path, debug: true), options)
141
149
  end
142
150
  else
143
151
  super(source, options)
144
152
  end
145
153
  }.flatten.uniq.join("\n").html_safe
146
154
  else
147
- sources.push(options)
148
- super(*sources)
155
+ sources.map { |source|
156
+ options = options.merge('integrity' => asset_integrity(source, type: :javascript)) if integrity
157
+ super source, options
158
+ }.join("\n").html_safe
149
159
  end
150
160
  end
151
161
 
@@ -154,81 +164,214 @@ module Sprockets
154
164
  # Eventually will be deprecated and replaced by source maps.
155
165
  def stylesheet_link_tag(*sources)
156
166
  options = sources.extract_options!.stringify_keys
167
+ integrity = compute_integrity?(options)
168
+
157
169
  if options["debug"] != false && request_debug_assets?
158
170
  sources.map { |source|
159
- check_errors_for(source, :type => :stylesheet)
160
- if asset = lookup_asset_for_path(source, :type => :stylesheet)
161
- asset.to_a.map do |a|
162
- super(path_to_stylesheet(a.logical_path, :debug => true), options)
171
+ if asset = lookup_debug_asset(source, type: :stylesheet)
172
+ if asset.respond_to?(:to_a)
173
+ asset.to_a.map do |a|
174
+ super(path_to_stylesheet(a.logical_path, debug: true), options)
175
+ end
176
+ else
177
+ super(path_to_stylesheet(asset.logical_path, debug: true), options)
163
178
  end
164
179
  else
165
180
  super(source, options)
166
181
  end
167
182
  }.flatten.uniq.join("\n").html_safe
168
183
  else
169
- sources.push(options)
170
- super(*sources)
184
+ sources.map { |source|
185
+ options = options.merge('integrity' => asset_integrity(source, type: :stylesheet)) if integrity
186
+ super source, options
187
+ }.join("\n").html_safe
171
188
  end
172
189
  end
173
190
 
174
191
  protected
175
- # Ensures the asset is included in the dependencies list.
176
- def check_dependencies!(dep)
177
- depend_on(dep)
178
- depend_on_asset(dep)
179
- rescue Sprockets::FileNotFound
192
+ # This is awkward: `integrity` is a boolean option indicating whether
193
+ # we want to include or omit the subresource integrity hash, but the
194
+ # options hash is also passed through as literal tag attributes.
195
+ # That means we have to delete the shortcut boolean option so it
196
+ # doesn't bleed into the tag attributes, but also check its value if
197
+ # it's boolean-ish.
198
+ def compute_integrity?(options)
199
+ if secure_subresource_integrity_context?
200
+ case options['integrity']
201
+ when nil, false, true
202
+ options.delete('integrity') == true
203
+ end
204
+ else
205
+ options.delete 'integrity'
206
+ false
207
+ end
180
208
  end
181
209
 
182
- # Raise errors when source is not in the precompiled list, or
183
- # incorrectly contains the assets_prefix.
184
- def check_errors_for(source, options)
185
- return unless self.raise_runtime_errors
210
+ # Only serve integrity metadata for HTTPS requests:
211
+ # http://www.w3.org/TR/SRI/#non-secure-contexts-remain-non-secure
212
+ def secure_subresource_integrity_context?
213
+ respond_to?(:request) && self.request && (self.request.local? || self.request.ssl?)
214
+ end
186
215
 
187
- source = source.to_s
188
- return if source.blank? || source =~ URI_REGEXP
216
+ # Enable split asset debugging. Eventually will be deprecated
217
+ # and replaced by source maps in Sprockets 3.x.
218
+ def request_debug_assets?
219
+ debug_assets || (defined?(controller) && controller && params[:debug_assets])
220
+ rescue # FIXME: what exactly are we rescuing?
221
+ false
222
+ end
189
223
 
190
- asset = lookup_asset_for_path(source, options)
224
+ # Internal method to support multifile debugging. Will
225
+ # eventually be removed w/ Sprockets 3.x.
226
+ def lookup_debug_asset(path, options = {})
227
+ path = path_with_extname(path, options)
191
228
 
192
- if asset && asset_needs_precompile?(asset.logical_path, asset.pathname.to_s)
193
- raise AssetFilteredError.new(asset.logical_path)
229
+ resolve_asset do |resolver|
230
+ resolver.find_debug_asset path
194
231
  end
232
+ end
195
233
 
196
- full_prefix = File.join(self.assets_prefix || "/", '')
197
- if !asset && source.start_with?(full_prefix)
198
- short_path = source[full_prefix.size, source.size]
199
- if lookup_asset_for_path(short_path, options)
200
- raise AbsoluteAssetPathError.new(source, short_path, full_prefix)
234
+ # compute_asset_extname is in AV::Helpers::AssetUrlHelper
235
+ def path_with_extname(path, options)
236
+ path = path.to_s
237
+ "#{path}#{compute_asset_extname(path, options)}"
238
+ end
239
+
240
+ # Try each asset resolver and return the first non-nil result.
241
+ def resolve_asset
242
+ asset_resolver_strategies.detect do |resolver|
243
+ if result = yield(resolver)
244
+ break result
201
245
  end
202
246
  end
203
247
  end
204
248
 
205
- # Returns true when an asset will not be available after precompile is run
206
- def asset_needs_precompile?(source, filename)
207
- if assets_environment && assets_environment.send(:matches_filter, precompile || [], source, filename)
208
- false
249
+ # List of resolvers in `config.assets.resolve_with` order.
250
+ def asset_resolver_strategies
251
+ @asset_resolver_strategies ||=
252
+ Array(resolve_assets_with).map do |name|
253
+ HelperAssetResolvers[name].new(self)
254
+ end
255
+ end
256
+
257
+ # Append ?body=1 if debug is on and we're on old Sprockets.
258
+ def legacy_debug_path(path, debug)
259
+ if debug && !using_sprockets4?
260
+ "#{path}?body=1"
209
261
  else
210
- true
262
+ path
211
263
  end
212
264
  end
265
+ end
213
266
 
214
- # Enable split asset debugging. Eventually will be deprecated
215
- # and replaced by source maps in Sprockets 3.x.
216
- def request_debug_assets?
217
- debug_assets || (defined?(controller) && controller && params[:debug_assets])
218
- rescue
219
- return false
267
+ # Use a separate module since Helper is mixed in and we needn't pollute
268
+ # the class namespace with our internals.
269
+ module HelperAssetResolvers #:nodoc:
270
+ def self.[](name)
271
+ case name
272
+ when :manifest
273
+ Manifest
274
+ when :environment
275
+ Environment
276
+ else
277
+ raise ArgumentError, "Unrecognized asset resolver: #{name.inspect}. Expected :manifest or :environment"
220
278
  end
279
+ end
221
280
 
222
- # Internal method to support multifile debugging. Will
223
- # eventually be removed w/ Sprockets 3.x.
224
- def lookup_asset_for_path(path, options = {})
225
- return unless env = assets_environment
226
- path = path.to_s
227
- if extname = compute_asset_extname(path, options)
228
- path = "#{path}#{extname}"
281
+ class Manifest #:nodoc:
282
+ def initialize(view)
283
+ @manifest = view.assets_manifest
284
+ raise ArgumentError, 'config.assets.resolve_with includes :manifest, but app.assets_manifest is nil' unless @manifest
285
+ end
286
+
287
+ def asset_path(path, digest, allow_non_precompiled = false)
288
+ if digest
289
+ digest_path path, allow_non_precompiled
290
+ end
291
+ end
292
+
293
+ def digest_path(path, allow_non_precompiled = false)
294
+ @manifest.assets[path]
295
+ end
296
+
297
+ def integrity(path)
298
+ if meta = metadata(path)
299
+ meta["integrity"]
300
+ end
301
+ end
302
+
303
+ def find_debug_asset(path)
304
+ nil
305
+ end
306
+
307
+ private
308
+ def metadata(path)
309
+ if digest_path = digest_path(path)
310
+ @manifest.files[digest_path]
311
+ end
312
+ end
313
+ end
314
+
315
+ class Environment #:nodoc:
316
+ def initialize(view)
317
+ raise ArgumentError, 'config.assets.resolve_with includes :environment, but app.assets is nil' unless view.assets_environment
318
+ @env = view.assets_environment
319
+ @precompiled_asset_checker = view.precompiled_asset_checker
320
+ @check_precompiled_asset = view.check_precompiled_asset
321
+ end
322
+
323
+ def asset_path(path, digest, allow_non_precompiled = false)
324
+ # Digests enabled? Do the work to calculate the full asset path.
325
+ if digest
326
+ digest_path path, allow_non_precompiled
327
+
328
+ # Otherwise, ask the Sprockets environment whether the asset exists
329
+ # and check whether it's also precompiled for production deploys.
330
+ elsif asset = find_asset(path)
331
+ raise_unless_precompiled_asset asset.logical_path unless allow_non_precompiled
332
+ path
333
+ end
334
+ end
335
+
336
+ def digest_path(path, allow_non_precompiled = false)
337
+ if asset = find_asset(path)
338
+ raise_unless_precompiled_asset asset.logical_path unless allow_non_precompiled
339
+ asset.digest_path
340
+ end
341
+ end
342
+
343
+ def integrity(path)
344
+ find_asset(path).try :integrity
345
+ end
346
+
347
+ def find_debug_asset(path)
348
+ if asset = find_asset(path, pipeline: :debug)
349
+ raise_unless_precompiled_asset asset.logical_path.sub('.debug', '')
350
+ asset
229
351
  end
230
- env[path]
231
352
  end
353
+
354
+ private
355
+ if RUBY_VERSION >= "2.7"
356
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
357
+ def find_asset(path, options = {})
358
+ @env[path, **options]
359
+ end
360
+ RUBY
361
+ else
362
+ def find_asset(path, options = {})
363
+ @env[path, options]
364
+ end
365
+ end
366
+
367
+ def precompiled?(path)
368
+ @precompiled_asset_checker.call path
369
+ end
370
+
371
+ def raise_unless_precompiled_asset(path)
372
+ raise Helper::AssetNotPrecompiled.new(path) if @check_precompiled_asset && !precompiled?(path)
373
+ end
374
+ end
232
375
  end
233
376
  end
234
377
  end