1
1
module Propshaft
2
+ # Helper module that provides asset path resolution and integrity support for Rails applications.
3
+ #
4
+ # This module extends Rails' built-in asset helpers with additional functionality:
5
+ # - Subresource Integrity (SRI) support for enhanced security
6
+ # - Bulk stylesheet inclusion with :all and :app options
7
+ # - Asset path resolution with proper error handling
8
+ #
9
+ # == Subresource Integrity (SRI) Support
10
+ #
11
+ # SRI helps protect against malicious modifications of assets by ensuring that
12
+ # resources fetched from CDNs or other sources haven't been tampered with.
13
+ #
14
+ # SRI is automatically enabled in secure contexts (HTTPS or local development)
15
+ # when the 'integrity' option is set to true:
16
+ #
17
+ # <%= stylesheet_link_tag "application", integrity: true %>
18
+ # <%= javascript_include_tag "application", integrity: true %>
19
+ #
20
+ # This will generate integrity hashes and include them in the HTML:
21
+ #
22
+ # <link rel="stylesheet" href="/assets/application-abc123.css"
23
+ # integrity="sha256-xyz789...">
24
+ # <script src="/assets/application-def456.js"
25
+ # integrity="sha256-uvw012..."></script>
26
+ #
27
+ # == Bulk Stylesheet Inclusion
28
+ #
29
+ # The stylesheet_link_tag helper supports special symbols for bulk inclusion:
30
+ # - :all - includes all CSS files found in the load path
31
+ # - :app - includes only CSS files from app/assets/**/*.css
32
+ #
33
+ # <%= stylesheet_link_tag :all %> # All stylesheets
34
+ # <%= stylesheet_link_tag :app %> # Only app stylesheets
2
35
module Helper
36
+ # Computes the Subresource Integrity (SRI) hash for the given asset path.
37
+ #
38
+ # This method generates a cryptographic hash of the asset content that can be used
39
+ # to verify the integrity of the resource when it's loaded by the browser.
40
+ #
41
+ # asset_integrity("application.css")
42
+ # # => "sha256-xyz789abcdef..."
43
+ def asset_integrity ( path , options = { } )
44
+ path = _path_with_extname ( path , options )
45
+ Rails . application . assets . resolver . integrity ( path )
46
+ end
47
+
48
+ # Resolves the full path for an asset, raising an error if not found.
3
49
def compute_asset_path ( path , options = { } )
4
50
Rails . application . assets . resolver . resolve ( path ) || raise ( MissingAssetError . new ( path ) )
5
51
end
6
52
7
- # Add an option to call `stylesheet_link_tag` with `:all` to include every css file found on the load path
8
- # or `:app` to include css files found in `Rails.root("app/assets/**/*.css")`, which will exclude lib/ and plugins.
53
+ # Enhanced +stylesheet_link_tag+ with integrity support and bulk inclusion options.
54
+ #
55
+ # In addition to the standard Rails functionality, this method supports:
56
+ # * Automatic SRI (Subresource Integrity) hash generation in secure contexts
57
+ # * Add an option to call +stylesheet_link_tag+ with +:all+ to include every css
58
+ # file found on the load path or +:app+ to include css files found in
59
+ # <tt>Rails.root("app/assets/**/*.css")</tt>, which will exclude lib/ and plugins.
60
+ #
61
+ # ==== Options
62
+ #
63
+ # * <tt>:integrity</tt> - Enable SRI hash generation
64
+ #
65
+ # ==== Examples
66
+ #
67
+ # stylesheet_link_tag "application", integrity: true
68
+ # # => <link rel="stylesheet" href="/assets/application-abc123.css"
69
+ # # integrity="sha256-xyz789...">
70
+ #
71
+ # stylesheet_link_tag :all # All stylesheets in load path
72
+ # stylesheet_link_tag :app # Only app/assets stylesheets
9
73
def stylesheet_link_tag ( *sources , **options )
10
74
case sources . first
11
75
when :all
12
- super ( * all_stylesheets_paths , ** options )
76
+ sources = all_stylesheets_paths
13
77
when :app
14
- super ( *app_stylesheets_paths , **options )
15
- else
16
- super
78
+ sources = app_stylesheets_paths
17
79
end
80
+
81
+ _build_asset_tags ( sources , options , :stylesheet ) { |source , opts | super ( source , opts ) }
82
+ end
83
+
84
+ # Enhanced +javascript_include_tag+ with automatic SRI (Subresource Integrity) support.
85
+ #
86
+ # This method extends Rails' built-in +javascript_include_tag+ to automatically
87
+ # generate and include integrity hashes when running in secure contexts.
88
+ #
89
+ # ==== Options
90
+ #
91
+ # * <tt>:integrity</tt> - Enable SRI hash generation
92
+ #
93
+ # ==== Examples
94
+ #
95
+ # javascript_include_tag "application", integrity: true
96
+ # # => <script src="/assets/application-abc123.js"
97
+ # # integrity="sha256-xyz789..."></script>
98
+ def javascript_include_tag ( *sources , **options )
99
+ _build_asset_tags ( sources , options , :javascript ) { |source , opts | super ( source , opts ) }
18
100
end
19
101
20
102
# Returns a sorted and unique array of logical paths for all stylesheets in the load path.
@@ -26,5 +108,50 @@ def all_stylesheets_paths
26
108
def app_stylesheets_paths
27
109
Rails . application . assets . load_path . asset_paths_by_glob ( "#{ Rails . root . join ( "app/assets" ) } /**/*.css" )
28
110
end
111
+
112
+ private
113
+ # Core method that builds asset tags with optional integrity support.
114
+ #
115
+ # This method handles the common logic for both +stylesheet_link_tag+ and
116
+ # +javascript_include_tag+, including SRI hash generation and HTML tag creation.
117
+ def _build_asset_tags ( sources , options , asset_type )
118
+ options = options . stringify_keys
119
+ integrity = _compute_integrity? ( options )
120
+
121
+ sources . map { |source |
122
+ opts = integrity ? options . merge! ( 'integrity' => asset_integrity ( source , type : asset_type ) ) : options
123
+ yield ( source , opts )
124
+ } . join ( "\n " ) . html_safe
125
+ end
126
+
127
+ # Determines whether integrity hashes should be computed for assets.
128
+ #
129
+ # Integrity is only computed in secure contexts (HTTPS or local development)
130
+ # and when explicitly requested via the +integrity+ option.
131
+ def _compute_integrity? ( options )
132
+ if _secure_subresource_integrity_context?
133
+ case options [ 'integrity' ]
134
+ when nil , false , true
135
+ options . delete ( 'integrity' ) == true
136
+ end
137
+ else
138
+ options . delete 'integrity'
139
+ false
140
+ end
141
+ end
142
+
143
+ # Checks if the current context is secure enough for Subresource Integrity.
144
+ #
145
+ # SRI is only beneficial in secure contexts. Returns true when:
146
+ # * The request is made over HTTPS (SSL), OR
147
+ # * The request is local (development environment)
148
+ def _secure_subresource_integrity_context?
149
+ respond_to? ( :request ) && self . request && ( self . request . local? || self . request . ssl? )
150
+ end
151
+
152
+ # Ensures the asset path includes the appropriate file extension.
153
+ def _path_with_extname ( path , options )
154
+ "#{ path } #{ compute_asset_extname ( path , options ) } "
155
+ end
29
156
end
30
157
end
0 commit comments