sprockets 2.12.5 → 3.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +296 -0
- data/LICENSE +2 -2
- data/README.md +235 -262
- data/bin/sprockets +1 -0
- data/lib/rake/sprocketstask.rb +5 -4
- data/lib/sprockets/asset.rb +143 -212
- data/lib/sprockets/autoload/closure.rb +7 -0
- data/lib/sprockets/autoload/coffee_script.rb +7 -0
- data/lib/sprockets/autoload/eco.rb +7 -0
- data/lib/sprockets/autoload/ejs.rb +7 -0
- data/lib/sprockets/autoload/sass.rb +7 -0
- data/lib/sprockets/autoload/uglifier.rb +7 -0
- data/lib/sprockets/autoload/yui.rb +7 -0
- data/lib/sprockets/autoload.rb +11 -0
- data/lib/sprockets/base.rb +56 -393
- data/lib/sprockets/bower.rb +58 -0
- data/lib/sprockets/bundle.rb +69 -0
- data/lib/sprockets/cache/file_store.rb +168 -14
- data/lib/sprockets/cache/memory_store.rb +66 -0
- data/lib/sprockets/cache/null_store.rb +46 -0
- data/lib/sprockets/cache.rb +236 -0
- data/lib/sprockets/cached_environment.rb +69 -0
- data/lib/sprockets/closure_compressor.rb +35 -10
- data/lib/sprockets/coffee_script_processor.rb +25 -0
- data/lib/sprockets/coffee_script_template.rb +17 -0
- data/lib/sprockets/compressing.rb +44 -23
- data/lib/sprockets/configuration.rb +83 -0
- data/lib/sprockets/context.rb +86 -144
- data/lib/sprockets/dependencies.rb +73 -0
- data/lib/sprockets/deprecation.rb +90 -0
- data/lib/sprockets/digest_utils.rb +180 -0
- data/lib/sprockets/directive_processor.rb +207 -211
- data/lib/sprockets/eco_processor.rb +32 -0
- data/lib/sprockets/eco_template.rb +9 -30
- data/lib/sprockets/ejs_processor.rb +31 -0
- data/lib/sprockets/ejs_template.rb +9 -29
- data/lib/sprockets/encoding_utils.rb +261 -0
- data/lib/sprockets/engines.rb +53 -35
- data/lib/sprockets/environment.rb +17 -64
- data/lib/sprockets/erb_processor.rb +30 -0
- data/lib/sprockets/erb_template.rb +11 -0
- data/lib/sprockets/errors.rb +4 -13
- data/lib/sprockets/file_reader.rb +15 -0
- data/lib/sprockets/http_utils.rb +117 -0
- data/lib/sprockets/jst_processor.rb +35 -15
- data/lib/sprockets/legacy.rb +330 -0
- data/lib/sprockets/legacy_proc_processor.rb +35 -0
- data/lib/sprockets/legacy_tilt_processor.rb +29 -0
- data/lib/sprockets/loader.rb +325 -0
- data/lib/sprockets/manifest.rb +202 -127
- data/lib/sprockets/manifest_utils.rb +45 -0
- data/lib/sprockets/mime.rb +112 -31
- data/lib/sprockets/path_dependency_utils.rb +85 -0
- data/lib/sprockets/path_digest_utils.rb +47 -0
- data/lib/sprockets/path_utils.rb +287 -0
- data/lib/sprockets/paths.rb +42 -19
- data/lib/sprockets/processing.rb +178 -126
- data/lib/sprockets/processor_utils.rb +180 -0
- data/lib/sprockets/resolve.rb +211 -0
- data/lib/sprockets/sass_cache_store.rb +22 -17
- data/lib/sprockets/sass_compressor.rb +39 -15
- data/lib/sprockets/sass_functions.rb +2 -70
- data/lib/sprockets/sass_importer.rb +2 -30
- data/lib/sprockets/sass_processor.rb +292 -0
- data/lib/sprockets/sass_template.rb +12 -59
- data/lib/sprockets/server.rb +129 -84
- data/lib/sprockets/transformers.rb +145 -0
- data/lib/sprockets/uglifier_compressor.rb +39 -12
- data/lib/sprockets/unloaded_asset.rb +137 -0
- data/lib/sprockets/uri_tar.rb +98 -0
- data/lib/sprockets/uri_utils.rb +188 -0
- data/lib/sprockets/utils/gzip.rb +67 -0
- data/lib/sprockets/utils.rb +210 -44
- data/lib/sprockets/version.rb +1 -1
- data/lib/sprockets/yui_compressor.rb +39 -11
- data/lib/sprockets.rb +142 -81
- metadata +96 -90
- data/lib/sprockets/asset_attributes.rb +0 -137
- data/lib/sprockets/bundled_asset.rb +0 -78
- data/lib/sprockets/caching.rb +0 -96
- data/lib/sprockets/charset_normalizer.rb +0 -41
- data/lib/sprockets/index.rb +0 -100
- data/lib/sprockets/processed_asset.rb +0 -152
- data/lib/sprockets/processor.rb +0 -32
- data/lib/sprockets/safety_colons.rb +0 -28
- data/lib/sprockets/scss_template.rb +0 -13
- data/lib/sprockets/static_asset.rb +0 -60
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
require 'rack/utils'
|
|
2
|
+
require 'sprockets/autoload'
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
module Sprockets
|
|
6
|
+
# Processor engine class for the SASS/SCSS compiler. Depends on the `sass` gem.
|
|
7
|
+
#
|
|
8
|
+
# For more infomation see:
|
|
9
|
+
#
|
|
10
|
+
# https://github.com/sass/sass
|
|
11
|
+
# https://github.com/rails/sass-rails
|
|
12
|
+
#
|
|
13
|
+
class SassProcessor
|
|
14
|
+
autoload :CacheStore, 'sprockets/sass_cache_store'
|
|
15
|
+
|
|
16
|
+
# Internal: Defines default sass syntax to use. Exposed so the ScssProcessor
|
|
17
|
+
# may override it.
|
|
18
|
+
def self.syntax
|
|
19
|
+
:sass
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Public: Return singleton instance with default options.
|
|
23
|
+
#
|
|
24
|
+
# Returns SassProcessor object.
|
|
25
|
+
def self.instance
|
|
26
|
+
@instance ||= new
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.call(input)
|
|
30
|
+
instance.call(input)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.cache_key
|
|
34
|
+
instance.cache_key
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
attr_reader :cache_key
|
|
38
|
+
|
|
39
|
+
# Public: Initialize template with custom options.
|
|
40
|
+
#
|
|
41
|
+
# options - Hash
|
|
42
|
+
# cache_version - String custom cache version. Used to force a cache
|
|
43
|
+
# change after code changes are made to Sass Functions.
|
|
44
|
+
#
|
|
45
|
+
def initialize(options = {}, &block)
|
|
46
|
+
@cache_version = options[:cache_version]
|
|
47
|
+
@cache_key = "#{self.class.name}:#{VERSION}:#{Autoload::Sass::VERSION}:#{@cache_version}".freeze
|
|
48
|
+
|
|
49
|
+
@functions = Module.new do
|
|
50
|
+
include Functions
|
|
51
|
+
include options[:functions] if options[:functions]
|
|
52
|
+
class_eval(&block) if block_given?
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def call(input)
|
|
57
|
+
context = input[:environment].context_class.new(input)
|
|
58
|
+
|
|
59
|
+
options = {
|
|
60
|
+
filename: input[:filename],
|
|
61
|
+
syntax: self.class.syntax,
|
|
62
|
+
cache_store: build_cache_store(input, @cache_version),
|
|
63
|
+
load_paths: input[:environment].paths,
|
|
64
|
+
sprockets: {
|
|
65
|
+
context: context,
|
|
66
|
+
environment: input[:environment],
|
|
67
|
+
dependencies: context.metadata[:dependencies]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
engine = Autoload::Sass::Engine.new(input[:data], options)
|
|
72
|
+
|
|
73
|
+
css = Utils.module_include(Autoload::Sass::Script::Functions, @functions) do
|
|
74
|
+
engine.render
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Track all imported files
|
|
78
|
+
sass_dependencies = Set.new([input[:filename]])
|
|
79
|
+
engine.dependencies.map do |dependency|
|
|
80
|
+
sass_dependencies << dependency.options[:filename]
|
|
81
|
+
context.metadata[:dependencies] << URIUtils.build_file_digest_uri(dependency.options[:filename])
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
context.metadata.merge(data: css, sass_dependencies: sass_dependencies)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Public: Build the cache store to be used by the Sass engine.
|
|
88
|
+
#
|
|
89
|
+
# input - the input hash.
|
|
90
|
+
# version - the cache version.
|
|
91
|
+
#
|
|
92
|
+
# Override this method if you need to use a different cache than the
|
|
93
|
+
# Sprockets cache.
|
|
94
|
+
def build_cache_store(input, version)
|
|
95
|
+
CacheStore.new(input[:cache], version)
|
|
96
|
+
end
|
|
97
|
+
private :build_cache_store
|
|
98
|
+
|
|
99
|
+
# Public: Functions injected into Sass context during Sprockets evaluation.
|
|
100
|
+
#
|
|
101
|
+
# This module may be extended to add global functionality to all Sprockets
|
|
102
|
+
# Sass environments. Though, scoping your functions to just your environment
|
|
103
|
+
# is preferred.
|
|
104
|
+
#
|
|
105
|
+
# module Sprockets::SassProcessor::Functions
|
|
106
|
+
# def asset_path(path, options = {})
|
|
107
|
+
# end
|
|
108
|
+
# end
|
|
109
|
+
#
|
|
110
|
+
module Functions
|
|
111
|
+
# Public: Generate a url for asset path.
|
|
112
|
+
#
|
|
113
|
+
# Default implementation is deprecated. Currently defaults to
|
|
114
|
+
# Context#asset_path.
|
|
115
|
+
#
|
|
116
|
+
# Will raise NotImplementedError in the future. Users should provide their
|
|
117
|
+
# own base implementation.
|
|
118
|
+
#
|
|
119
|
+
# Returns a Sass::Script::String.
|
|
120
|
+
def asset_path(path, options = {})
|
|
121
|
+
path = path.value
|
|
122
|
+
|
|
123
|
+
path, _, query, fragment = URI.split(path)[5..8]
|
|
124
|
+
path = sprockets_context.asset_path(path, options)
|
|
125
|
+
query = "?#{query}" if query
|
|
126
|
+
fragment = "##{fragment}" if fragment
|
|
127
|
+
|
|
128
|
+
Autoload::Sass::Script::String.new("#{path}#{query}#{fragment}", :string)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Public: Generate a asset url() link.
|
|
132
|
+
#
|
|
133
|
+
# path - Sass::Script::String URL path
|
|
134
|
+
#
|
|
135
|
+
# Returns a Sass::Script::String.
|
|
136
|
+
def asset_url(path, options = {})
|
|
137
|
+
Autoload::Sass::Script::String.new("url(#{asset_path(path, options).value})")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Public: Generate url for image path.
|
|
141
|
+
#
|
|
142
|
+
# path - Sass::Script::String URL path
|
|
143
|
+
#
|
|
144
|
+
# Returns a Sass::Script::String.
|
|
145
|
+
def image_path(path)
|
|
146
|
+
asset_path(path, type: :image)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Public: Generate a image url() link.
|
|
150
|
+
#
|
|
151
|
+
# path - Sass::Script::String URL path
|
|
152
|
+
#
|
|
153
|
+
# Returns a Sass::Script::String.
|
|
154
|
+
def image_url(path)
|
|
155
|
+
asset_url(path, type: :image)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Public: Generate url for video path.
|
|
159
|
+
#
|
|
160
|
+
# path - Sass::Script::String URL path
|
|
161
|
+
#
|
|
162
|
+
# Returns a Sass::Script::String.
|
|
163
|
+
def video_path(path)
|
|
164
|
+
asset_path(path, type: :video)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Public: Generate a video url() link.
|
|
168
|
+
#
|
|
169
|
+
# path - Sass::Script::String URL path
|
|
170
|
+
#
|
|
171
|
+
# Returns a Sass::Script::String.
|
|
172
|
+
def video_url(path)
|
|
173
|
+
asset_url(path, type: :video)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Public: Generate url for audio path.
|
|
177
|
+
#
|
|
178
|
+
# path - Sass::Script::String URL path
|
|
179
|
+
#
|
|
180
|
+
# Returns a Sass::Script::String.
|
|
181
|
+
def audio_path(path)
|
|
182
|
+
asset_path(path, type: :audio)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Public: Generate a audio url() link.
|
|
186
|
+
#
|
|
187
|
+
# path - Sass::Script::String URL path
|
|
188
|
+
#
|
|
189
|
+
# Returns a Sass::Script::String.
|
|
190
|
+
def audio_url(path)
|
|
191
|
+
asset_url(path, type: :audio)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Public: Generate url for font path.
|
|
195
|
+
#
|
|
196
|
+
# path - Sass::Script::String URL path
|
|
197
|
+
#
|
|
198
|
+
# Returns a Sass::Script::String.
|
|
199
|
+
def font_path(path)
|
|
200
|
+
asset_path(path, type: :font)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Public: Generate a font url() link.
|
|
204
|
+
#
|
|
205
|
+
# path - Sass::Script::String URL path
|
|
206
|
+
#
|
|
207
|
+
# Returns a Sass::Script::String.
|
|
208
|
+
def font_url(path)
|
|
209
|
+
asset_url(path, type: :font)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Public: Generate url for javascript path.
|
|
213
|
+
#
|
|
214
|
+
# path - Sass::Script::String URL path
|
|
215
|
+
#
|
|
216
|
+
# Returns a Sass::Script::String.
|
|
217
|
+
def javascript_path(path)
|
|
218
|
+
asset_path(path, type: :javascript)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Public: Generate a javascript url() link.
|
|
222
|
+
#
|
|
223
|
+
# path - Sass::Script::String URL path
|
|
224
|
+
#
|
|
225
|
+
# Returns a Sass::Script::String.
|
|
226
|
+
def javascript_url(path)
|
|
227
|
+
asset_url(path, type: :javascript)
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Public: Generate url for stylesheet path.
|
|
231
|
+
#
|
|
232
|
+
# path - Sass::Script::String URL path
|
|
233
|
+
#
|
|
234
|
+
# Returns a Sass::Script::String.
|
|
235
|
+
def stylesheet_path(path)
|
|
236
|
+
asset_path(path, type: :stylesheet)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Public: Generate a stylesheet url() link.
|
|
240
|
+
#
|
|
241
|
+
# path - Sass::Script::String URL path
|
|
242
|
+
#
|
|
243
|
+
# Returns a Sass::Script::String.
|
|
244
|
+
def stylesheet_url(path)
|
|
245
|
+
asset_url(path, type: :stylesheet)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Public: Generate a data URI for asset path.
|
|
249
|
+
#
|
|
250
|
+
# path - Sass::Script::String logical asset path
|
|
251
|
+
#
|
|
252
|
+
# Returns a Sass::Script::String.
|
|
253
|
+
def asset_data_url(path)
|
|
254
|
+
url = sprockets_context.asset_data_uri(path.value)
|
|
255
|
+
Autoload::Sass::Script::String.new("url(" + url + ")")
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
protected
|
|
259
|
+
# Public: The Environment.
|
|
260
|
+
#
|
|
261
|
+
# Returns Sprockets::Environment.
|
|
262
|
+
def sprockets_environment
|
|
263
|
+
options[:sprockets][:environment]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Public: Mutatable set of dependencies.
|
|
267
|
+
#
|
|
268
|
+
# Returns a Set.
|
|
269
|
+
def sprockets_dependencies
|
|
270
|
+
options[:sprockets][:dependencies]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# Deprecated: Get the Context instance. Use APIs on
|
|
274
|
+
# sprockets_environment or sprockets_dependencies directly.
|
|
275
|
+
#
|
|
276
|
+
# Returns a Context instance.
|
|
277
|
+
def sprockets_context
|
|
278
|
+
options[:sprockets][:context]
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
class ScssProcessor < SassProcessor
|
|
285
|
+
def self.syntax
|
|
286
|
+
:scss
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Deprecated: Use Sprockets::SassProcessor::Functions instead.
|
|
291
|
+
SassFunctions = SassProcessor::Functions
|
|
292
|
+
end
|
|
@@ -1,66 +1,19 @@
|
|
|
1
|
-
require '
|
|
1
|
+
require 'sprockets/sass_processor'
|
|
2
2
|
|
|
3
3
|
module Sprockets
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class SassTemplate < Tilt::Template
|
|
10
|
-
self.default_mime_type = 'text/css'
|
|
11
|
-
|
|
12
|
-
def self.engine_initialized?
|
|
13
|
-
defined?(::Sass::Engine) && defined?(::Sass::Script::Functions) &&
|
|
14
|
-
::Sass::Script::Functions < Sprockets::SassFunctions
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def initialize_engine
|
|
18
|
-
# Double check constant to avoid tilt warning
|
|
19
|
-
unless defined? ::Sass
|
|
20
|
-
require_template_library 'sass'
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# Install custom functions. It'd be great if this didn't need to
|
|
24
|
-
# be installed globally, but could be passed into Engine as an
|
|
25
|
-
# option.
|
|
26
|
-
::Sass::Script::Functions.send :include, Sprockets::SassFunctions
|
|
4
|
+
# Deprecated
|
|
5
|
+
class SassTemplate < SassProcessor
|
|
6
|
+
def self.call(*args)
|
|
7
|
+
Deprecation.new.warn "SassTemplate is deprecated please use SassProcessor instead"
|
|
8
|
+
super
|
|
27
9
|
end
|
|
10
|
+
end
|
|
28
11
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def evaluate(context, locals, &block)
|
|
37
|
-
# Use custom importer that knows about Sprockets Caching
|
|
38
|
-
cache_store = SassCacheStore.new(context.environment)
|
|
39
|
-
|
|
40
|
-
options = {
|
|
41
|
-
:filename => eval_file,
|
|
42
|
-
:line => line,
|
|
43
|
-
:syntax => syntax,
|
|
44
|
-
:cache_store => cache_store,
|
|
45
|
-
:importer => SassImporter.new(context.pathname.to_s),
|
|
46
|
-
:load_paths => context.environment.paths.map { |path| SassImporter.new(path.to_s) },
|
|
47
|
-
:sprockets => {
|
|
48
|
-
:context => context,
|
|
49
|
-
:environment => context.environment
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
result = ::Sass::Engine.new(data, options).render
|
|
54
|
-
|
|
55
|
-
# Track all imported files
|
|
56
|
-
filenames = ([options[:importer].imported_filenames] + options[:load_paths].map(&:imported_filenames)).flatten.uniq
|
|
57
|
-
filenames.each { |filename| context.depend_on(filename) }
|
|
58
|
-
|
|
59
|
-
result
|
|
60
|
-
rescue ::Sass::SyntaxError => e
|
|
61
|
-
# Annotates exception message with parse line number
|
|
62
|
-
context.__LINE__ = e.sass_backtrace.first[:line]
|
|
63
|
-
raise e
|
|
12
|
+
# Deprecated
|
|
13
|
+
class ScssTemplate < ScssProcessor
|
|
14
|
+
def self.call(*args)
|
|
15
|
+
Deprecation.new.warn "ScssTemplate is deprecated please use ScssProcessor instead"
|
|
16
|
+
super
|
|
64
17
|
end
|
|
65
18
|
end
|
|
66
19
|
end
|
data/lib/sprockets/server.rb
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
require 'time'
|
|
2
|
-
require '
|
|
2
|
+
require 'rack/utils'
|
|
3
3
|
|
|
4
4
|
module Sprockets
|
|
5
5
|
# `Server` is a concern mixed into `Environment` and
|
|
6
|
-
# `
|
|
6
|
+
# `CachedEnvironment` that provides a Rack compatible `call`
|
|
7
7
|
# interface and url generation helpers.
|
|
8
8
|
module Server
|
|
9
9
|
# `call` implements the Rack 1.x specification which accepts an
|
|
@@ -23,15 +23,14 @@ module Sprockets
|
|
|
23
23
|
start_time = Time.now.to_f
|
|
24
24
|
time_elapsed = lambda { ((Time.now.to_f - start_time) * 1000).to_i }
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
if !['GET', 'HEAD'].include?(env['REQUEST_METHOD'])
|
|
27
|
+
return method_not_allowed_response
|
|
28
|
+
end
|
|
27
29
|
|
|
28
|
-
|
|
29
|
-
env['rack.session.options'] ||= {}
|
|
30
|
-
env['rack.session.options'][:defer] = true
|
|
31
|
-
env['rack.session.options'][:skip] = true
|
|
30
|
+
msg = "Served asset #{env['PATH_INFO']} -"
|
|
32
31
|
|
|
33
32
|
# Extract the path from everything after the leading slash
|
|
34
|
-
path = unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
|
|
33
|
+
path = Rack::Utils.unescape(env['PATH_INFO'].to_s.sub(/^\//, ''))
|
|
35
34
|
|
|
36
35
|
# Strip fingerprint
|
|
37
36
|
if fingerprint = path_fingerprint(path)
|
|
@@ -40,42 +39,68 @@ module Sprockets
|
|
|
40
39
|
|
|
41
40
|
# URLs containing a `".."` are rejected for security reasons.
|
|
42
41
|
if forbidden_request?(path)
|
|
43
|
-
return forbidden_response
|
|
42
|
+
return forbidden_response(env)
|
|
44
43
|
end
|
|
45
44
|
|
|
46
45
|
# Look up the asset.
|
|
47
|
-
|
|
46
|
+
options = {}
|
|
47
|
+
options[:pipeline] = :self if body_only?(env)
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
if asset.nil?
|
|
51
|
-
logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
|
|
49
|
+
asset = find_asset(path, options)
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
# 2.x/3.x compatibility hack. Just ignore fingerprints on ?body=1 requests.
|
|
52
|
+
# 3.x/4.x prefers strong validation of fingerprint to body contents, but
|
|
53
|
+
# 2.x just ignored it.
|
|
54
|
+
if asset && parse_asset_uri(asset.uri)[1][:pipeline] == "self"
|
|
55
|
+
fingerprint = nil
|
|
56
|
+
end
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
if fingerprint
|
|
59
|
+
if_match = fingerprint
|
|
60
|
+
elsif env['HTTP_IF_MATCH']
|
|
61
|
+
if_match = env['HTTP_IF_MATCH'][/^"(\w+)"$/, 1]
|
|
62
|
+
end
|
|
59
63
|
|
|
60
|
-
|
|
61
|
-
|
|
64
|
+
if env['HTTP_IF_NONE_MATCH']
|
|
65
|
+
if_none_match = env['HTTP_IF_NONE_MATCH'][/^"(\w+)"$/, 1]
|
|
66
|
+
end
|
|
62
67
|
|
|
68
|
+
if asset.nil?
|
|
69
|
+
status = :not_found
|
|
70
|
+
elsif fingerprint && asset.etag != fingerprint
|
|
71
|
+
status = :not_found
|
|
72
|
+
elsif if_match && asset.etag != if_match
|
|
73
|
+
status = :precondition_failed
|
|
74
|
+
elsif if_none_match && asset.etag == if_none_match
|
|
75
|
+
status = :not_modified
|
|
63
76
|
else
|
|
64
|
-
|
|
77
|
+
status = :ok
|
|
78
|
+
end
|
|
65
79
|
|
|
66
|
-
|
|
80
|
+
case status
|
|
81
|
+
when :ok
|
|
82
|
+
logger.info "#{msg} 200 OK (#{time_elapsed.call}ms)"
|
|
67
83
|
ok_response(asset, env)
|
|
84
|
+
when :not_modified
|
|
85
|
+
logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)"
|
|
86
|
+
not_modified_response(env, if_none_match)
|
|
87
|
+
when :not_found
|
|
88
|
+
logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
|
|
89
|
+
not_found_response(env)
|
|
90
|
+
when :precondition_failed
|
|
91
|
+
logger.info "#{msg} 412 Precondition Failed (#{time_elapsed.call}ms)"
|
|
92
|
+
precondition_failed_response(env)
|
|
68
93
|
end
|
|
69
94
|
rescue Exception => e
|
|
70
95
|
logger.error "Error compiling asset #{path}:"
|
|
71
96
|
logger.error "#{e.class.name}: #{e.message}"
|
|
72
97
|
|
|
73
|
-
case
|
|
74
|
-
when "
|
|
98
|
+
case File.extname(path)
|
|
99
|
+
when ".js"
|
|
75
100
|
# Re-throw JavaScript asset exceptions to the browser
|
|
76
101
|
logger.info "#{msg} 500 Internal Server Error\n\n"
|
|
77
102
|
return javascript_exception_response(e)
|
|
78
|
-
when "
|
|
103
|
+
when ".css"
|
|
79
104
|
# Display CSS asset exceptions in the browser
|
|
80
105
|
logger.info "#{msg} 500 Internal Server Error\n\n"
|
|
81
106
|
return css_exception_response(e)
|
|
@@ -90,25 +115,63 @@ module Sprockets
|
|
|
90
115
|
#
|
|
91
116
|
# http://example.org/assets/../../../etc/passwd
|
|
92
117
|
#
|
|
93
|
-
path.include?("..") ||
|
|
118
|
+
path.include?("..") || absolute_path?(path) || path.include?("://")
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def head_request?(env)
|
|
122
|
+
env['REQUEST_METHOD'] == 'HEAD'
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Returns a 200 OK response tuple
|
|
126
|
+
def ok_response(asset, env)
|
|
127
|
+
if head_request?(env)
|
|
128
|
+
[ 200, headers(env, asset, 0), [] ]
|
|
129
|
+
else
|
|
130
|
+
[ 200, headers(env, asset, asset.length), asset ]
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Returns a 304 Not Modified response tuple
|
|
135
|
+
def not_modified_response(env, etag)
|
|
136
|
+
[ 304, cache_headers(env, etag), [] ]
|
|
94
137
|
end
|
|
95
138
|
|
|
96
139
|
# Returns a 403 Forbidden response tuple
|
|
97
|
-
def forbidden_response
|
|
98
|
-
|
|
140
|
+
def forbidden_response(env)
|
|
141
|
+
if head_request?(env)
|
|
142
|
+
[ 403, { "Content-Type" => "text/plain", "Content-Length" => "0" }, [] ]
|
|
143
|
+
else
|
|
144
|
+
[ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
|
|
145
|
+
end
|
|
99
146
|
end
|
|
100
147
|
|
|
101
148
|
# Returns a 404 Not Found response tuple
|
|
102
|
-
def not_found_response
|
|
103
|
-
|
|
149
|
+
def not_found_response(env)
|
|
150
|
+
if head_request?(env)
|
|
151
|
+
[ 404, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
|
|
152
|
+
else
|
|
153
|
+
[ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def method_not_allowed_response
|
|
158
|
+
[ 405, { "Content-Type" => "text/plain", "Content-Length" => "18" }, [ "Method Not Allowed" ] ]
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def precondition_failed_response(env)
|
|
162
|
+
if head_request?(env)
|
|
163
|
+
[ 412, { "Content-Type" => "text/plain", "Content-Length" => "0", "X-Cascade" => "pass" }, [] ]
|
|
164
|
+
else
|
|
165
|
+
[ 412, { "Content-Type" => "text/plain", "Content-Length" => "19", "X-Cascade" => "pass" }, [ "Precondition Failed" ] ]
|
|
166
|
+
end
|
|
104
167
|
end
|
|
105
168
|
|
|
106
169
|
# Returns a JavaScript response that re-throws a Ruby exception
|
|
107
170
|
# in the browser
|
|
108
171
|
def javascript_exception_response(exception)
|
|
109
|
-
err = "#{exception.class.name}: #{exception.message}"
|
|
172
|
+
err = "#{exception.class.name}: #{exception.message}\n (in #{exception.backtrace[0]})"
|
|
110
173
|
body = "throw Error(#{err.inspect})"
|
|
111
|
-
[ 200, { "Content-Type" => "application/javascript", "Content-Length" =>
|
|
174
|
+
[ 200, { "Content-Type" => "application/javascript", "Content-Length" => body.bytesize.to_s }, [ body ] ]
|
|
112
175
|
end
|
|
113
176
|
|
|
114
177
|
# Returns a CSS response that hides all elements on the page and
|
|
@@ -161,7 +224,7 @@ module Sprockets
|
|
|
161
224
|
}
|
|
162
225
|
CSS
|
|
163
226
|
|
|
164
|
-
[ 200, { "Content-Type" => "text/css;charset=utf-8", "Content-Length" =>
|
|
227
|
+
[ 200, { "Content-Type" => "text/css; charset=utf-8", "Content-Length" => body.bytesize.to_s }, [ body ] ]
|
|
165
228
|
end
|
|
166
229
|
|
|
167
230
|
# Escape special characters for use inside a CSS content("...") string
|
|
@@ -173,75 +236,57 @@ module Sprockets
|
|
|
173
236
|
gsub('/', '\\\\002f ')
|
|
174
237
|
end
|
|
175
238
|
|
|
176
|
-
# Compare the requests `HTTP_IF_NONE_MATCH` against the assets digest
|
|
177
|
-
def etag_match?(asset, env)
|
|
178
|
-
env["HTTP_IF_NONE_MATCH"] == etag(asset)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
239
|
# Test if `?body=1` or `body=true` query param is set
|
|
182
240
|
def body_only?(env)
|
|
183
241
|
env["QUERY_STRING"].to_s =~ /body=(1|t)/
|
|
184
242
|
end
|
|
185
243
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
[ 304, {}, [] ]
|
|
189
|
-
end
|
|
244
|
+
def cache_headers(env, etag)
|
|
245
|
+
headers = {}
|
|
190
246
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
[
|
|
247
|
+
# Set caching headers
|
|
248
|
+
headers["Cache-Control"] = "public"
|
|
249
|
+
headers["ETag"] = %("#{etag}")
|
|
250
|
+
|
|
251
|
+
# If the request url contains a fingerprint, set a long
|
|
252
|
+
# expires on the response
|
|
253
|
+
if path_fingerprint(env["PATH_INFO"])
|
|
254
|
+
headers["Cache-Control"] << ", max-age=31536000"
|
|
255
|
+
|
|
256
|
+
# Otherwise set `must-revalidate` since the asset could be modified.
|
|
257
|
+
else
|
|
258
|
+
headers["Cache-Control"] << ", must-revalidate"
|
|
259
|
+
headers["Vary"] = "Accept-Encoding"
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
headers
|
|
194
263
|
end
|
|
195
264
|
|
|
196
265
|
def headers(env, asset, length)
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
# If the request url contains a fingerprint, set a long
|
|
208
|
-
# expires on the response
|
|
209
|
-
if path_fingerprint(env["PATH_INFO"])
|
|
210
|
-
headers["Cache-Control"] << ", max-age=31536000"
|
|
211
|
-
|
|
212
|
-
# Otherwise set `must-revalidate` since the asset could be modified.
|
|
213
|
-
else
|
|
214
|
-
headers["Cache-Control"] << ", must-revalidate"
|
|
266
|
+
headers = {}
|
|
267
|
+
|
|
268
|
+
# Set content length header
|
|
269
|
+
headers["Content-Length"] = length.to_s
|
|
270
|
+
|
|
271
|
+
# Set content type header
|
|
272
|
+
if type = asset.content_type
|
|
273
|
+
# Set charset param for text/* mime types
|
|
274
|
+
if type.start_with?("text/") && asset.charset
|
|
275
|
+
type += "; charset=#{asset.charset}"
|
|
215
276
|
end
|
|
277
|
+
headers["Content-Type"] = type
|
|
216
278
|
end
|
|
279
|
+
|
|
280
|
+
headers.merge(cache_headers(env, asset.etag))
|
|
217
281
|
end
|
|
218
282
|
|
|
219
|
-
# Gets
|
|
283
|
+
# Gets ETag fingerprint.
|
|
220
284
|
#
|
|
221
285
|
# "foo-0aa2105d29558f3eb790d411d7d8fb66.js"
|
|
222
286
|
# # => "0aa2105d29558f3eb790d411d7d8fb66"
|
|
223
287
|
#
|
|
224
288
|
def path_fingerprint(path)
|
|
225
|
-
path[/-([0-9a-f]{7,
|
|
226
|
-
end
|
|
227
|
-
|
|
228
|
-
# URI.unescape is deprecated on 1.9. We need to use URI::Parser
|
|
229
|
-
# if its available.
|
|
230
|
-
if defined? URI::DEFAULT_PARSER
|
|
231
|
-
def unescape(str)
|
|
232
|
-
str = URI::DEFAULT_PARSER.unescape(str)
|
|
233
|
-
str.force_encoding(Encoding.default_internal) if Encoding.default_internal
|
|
234
|
-
str
|
|
235
|
-
end
|
|
236
|
-
else
|
|
237
|
-
def unescape(str)
|
|
238
|
-
URI.unescape(str)
|
|
239
|
-
end
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
# Helper to quote the assets digest for use as an ETag.
|
|
243
|
-
def etag(asset)
|
|
244
|
-
%("#{asset.digest}")
|
|
289
|
+
path[/-([0-9a-f]{7,128})\.[^.]+\z/, 1]
|
|
245
290
|
end
|
|
246
291
|
end
|
|
247
292
|
end
|