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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +296 -0
  3. data/LICENSE +2 -2
  4. data/README.md +235 -262
  5. data/bin/sprockets +1 -0
  6. data/lib/rake/sprocketstask.rb +5 -4
  7. data/lib/sprockets/asset.rb +143 -212
  8. data/lib/sprockets/autoload/closure.rb +7 -0
  9. data/lib/sprockets/autoload/coffee_script.rb +7 -0
  10. data/lib/sprockets/autoload/eco.rb +7 -0
  11. data/lib/sprockets/autoload/ejs.rb +7 -0
  12. data/lib/sprockets/autoload/sass.rb +7 -0
  13. data/lib/sprockets/autoload/uglifier.rb +7 -0
  14. data/lib/sprockets/autoload/yui.rb +7 -0
  15. data/lib/sprockets/autoload.rb +11 -0
  16. data/lib/sprockets/base.rb +56 -393
  17. data/lib/sprockets/bower.rb +58 -0
  18. data/lib/sprockets/bundle.rb +69 -0
  19. data/lib/sprockets/cache/file_store.rb +168 -14
  20. data/lib/sprockets/cache/memory_store.rb +66 -0
  21. data/lib/sprockets/cache/null_store.rb +46 -0
  22. data/lib/sprockets/cache.rb +236 -0
  23. data/lib/sprockets/cached_environment.rb +69 -0
  24. data/lib/sprockets/closure_compressor.rb +35 -10
  25. data/lib/sprockets/coffee_script_processor.rb +25 -0
  26. data/lib/sprockets/coffee_script_template.rb +17 -0
  27. data/lib/sprockets/compressing.rb +44 -23
  28. data/lib/sprockets/configuration.rb +83 -0
  29. data/lib/sprockets/context.rb +86 -144
  30. data/lib/sprockets/dependencies.rb +73 -0
  31. data/lib/sprockets/deprecation.rb +90 -0
  32. data/lib/sprockets/digest_utils.rb +180 -0
  33. data/lib/sprockets/directive_processor.rb +207 -211
  34. data/lib/sprockets/eco_processor.rb +32 -0
  35. data/lib/sprockets/eco_template.rb +9 -30
  36. data/lib/sprockets/ejs_processor.rb +31 -0
  37. data/lib/sprockets/ejs_template.rb +9 -29
  38. data/lib/sprockets/encoding_utils.rb +261 -0
  39. data/lib/sprockets/engines.rb +53 -35
  40. data/lib/sprockets/environment.rb +17 -64
  41. data/lib/sprockets/erb_processor.rb +30 -0
  42. data/lib/sprockets/erb_template.rb +11 -0
  43. data/lib/sprockets/errors.rb +4 -13
  44. data/lib/sprockets/file_reader.rb +15 -0
  45. data/lib/sprockets/http_utils.rb +117 -0
  46. data/lib/sprockets/jst_processor.rb +35 -15
  47. data/lib/sprockets/legacy.rb +330 -0
  48. data/lib/sprockets/legacy_proc_processor.rb +35 -0
  49. data/lib/sprockets/legacy_tilt_processor.rb +29 -0
  50. data/lib/sprockets/loader.rb +325 -0
  51. data/lib/sprockets/manifest.rb +202 -127
  52. data/lib/sprockets/manifest_utils.rb +45 -0
  53. data/lib/sprockets/mime.rb +112 -31
  54. data/lib/sprockets/path_dependency_utils.rb +85 -0
  55. data/lib/sprockets/path_digest_utils.rb +47 -0
  56. data/lib/sprockets/path_utils.rb +287 -0
  57. data/lib/sprockets/paths.rb +42 -19
  58. data/lib/sprockets/processing.rb +178 -126
  59. data/lib/sprockets/processor_utils.rb +180 -0
  60. data/lib/sprockets/resolve.rb +211 -0
  61. data/lib/sprockets/sass_cache_store.rb +22 -17
  62. data/lib/sprockets/sass_compressor.rb +39 -15
  63. data/lib/sprockets/sass_functions.rb +2 -70
  64. data/lib/sprockets/sass_importer.rb +2 -30
  65. data/lib/sprockets/sass_processor.rb +292 -0
  66. data/lib/sprockets/sass_template.rb +12 -59
  67. data/lib/sprockets/server.rb +129 -84
  68. data/lib/sprockets/transformers.rb +145 -0
  69. data/lib/sprockets/uglifier_compressor.rb +39 -12
  70. data/lib/sprockets/unloaded_asset.rb +137 -0
  71. data/lib/sprockets/uri_tar.rb +98 -0
  72. data/lib/sprockets/uri_utils.rb +188 -0
  73. data/lib/sprockets/utils/gzip.rb +67 -0
  74. data/lib/sprockets/utils.rb +210 -44
  75. data/lib/sprockets/version.rb +1 -1
  76. data/lib/sprockets/yui_compressor.rb +39 -11
  77. data/lib/sprockets.rb +142 -81
  78. metadata +96 -90
  79. data/lib/sprockets/asset_attributes.rb +0 -137
  80. data/lib/sprockets/bundled_asset.rb +0 -78
  81. data/lib/sprockets/caching.rb +0 -96
  82. data/lib/sprockets/charset_normalizer.rb +0 -41
  83. data/lib/sprockets/index.rb +0 -100
  84. data/lib/sprockets/processed_asset.rb +0 -152
  85. data/lib/sprockets/processor.rb +0 -32
  86. data/lib/sprockets/safety_colons.rb +0 -28
  87. data/lib/sprockets/scss_template.rb +0 -13
  88. 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 'tilt'
1
+ require 'sprockets/sass_processor'
2
2
 
3
3
  module Sprockets
4
- # This custom Tilt handler replaces the one built into Tilt. The
5
- # main difference is that it uses a custom importer that plays nice
6
- # with sprocket's caching system.
7
- #
8
- # See `SassImporter` for more infomation.
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
- def prepare
30
- end
31
-
32
- def syntax
33
- :sass
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
@@ -1,9 +1,9 @@
1
1
  require 'time'
2
- require 'uri'
2
+ require 'rack/utils'
3
3
 
4
4
  module Sprockets
5
5
  # `Server` is a concern mixed into `Environment` and
6
- # `Index` that provides a Rack compatible `call`
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
- msg = "Served asset #{env['PATH_INFO']} -"
26
+ if !['GET', 'HEAD'].include?(env['REQUEST_METHOD'])
27
+ return method_not_allowed_response
28
+ end
27
29
 
28
- # Mark session as "skipped" so no `Set-Cookie` header is set
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
- asset = find_asset(path, :bundle => !body_only?(env))
46
+ options = {}
47
+ options[:pipeline] = :self if body_only?(env)
48
48
 
49
- # `find_asset` returns nil if the asset doesn't exist
50
- if asset.nil?
51
- logger.info "#{msg} 404 Not Found (#{time_elapsed.call}ms)"
49
+ asset = find_asset(path, options)
52
50
 
53
- # Return a 404 Not Found
54
- not_found_response
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
- # Check request headers `HTTP_IF_NONE_MATCH` against the asset digest
57
- elsif etag_match?(asset, env)
58
- logger.info "#{msg} 304 Not Modified (#{time_elapsed.call}ms)"
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
- # Return a 304 Not Modified
61
- not_modified_response(asset, env)
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
- logger.info "#{msg} 200 OK (#{time_elapsed.call}ms)"
77
+ status = :ok
78
+ end
65
79
 
66
- # Return a 200 with the asset contents
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 content_type_of(path)
74
- when "application/javascript"
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 "text/css"
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?("..") || Pathname.new(path).absolute? || 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
- [ 403, { "Content-Type" => "text/plain", "Content-Length" => "9" }, [ "Forbidden" ] ]
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
- [ 404, { "Content-Type" => "text/plain", "Content-Length" => "9", "X-Cascade" => "pass" }, [ "Not found" ] ]
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" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
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" => Rack::Utils.bytesize(body).to_s }, [ body ] ]
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
- # Returns a 304 Not Modified response tuple
187
- def not_modified_response(asset, env)
188
- [ 304, {}, [] ]
189
- end
244
+ def cache_headers(env, etag)
245
+ headers = {}
190
246
 
191
- # Returns a 200 OK response tuple
192
- def ok_response(asset, env)
193
- [ 200, headers(env, asset, asset.length), asset ]
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
- Hash.new.tap do |headers|
198
- # Set content type and length headers
199
- headers["Content-Type"] = asset.content_type
200
- headers["Content-Length"] = length.to_s
201
-
202
- # Set caching headers
203
- headers["Cache-Control"] = "public"
204
- headers["Last-Modified"] = asset.mtime.httpdate
205
- headers["ETag"] = etag(asset)
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 digest fingerprint.
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,40})\.[^.]+\z/, 1]
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