sprockets 2.12.5 → 3.7.2

Sign up to get free protection for your applications and to get access to all the features.
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