roda 3.1.0 → 3.2.0

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
2
  SHA1:
3
- metadata.gz: d0f2116bac0848dc9f1f4563daab14474e62a79b
4
- data.tar.gz: 5f564ae0dc012ae84af1a9d5d64c1b4bb214b7b0
3
+ metadata.gz: e4935b8951819226ca81b82723628a6c555e505e
4
+ data.tar.gz: c3fd3714c47c7b5e014ed34a2f817a249b257fa6
5
5
  SHA512:
6
- metadata.gz: 31621a2f3576447f261cad0d53ec12b24d407b3f929fbe51be8c102ca279492cc3f19bf80ded152e4b4018fc4f02836b70a5800cbbe39f2fc196e001307e4f6d
7
- data.tar.gz: b9ede292a9c37c68532b0068fcd79a5d942da3f9b31e751f9ba360df8bcc4959eb35908bcbb06a6389fd93f593b60a80f48537a00e92d76938241799e5e1426a
6
+ metadata.gz: 7bf5572895450e439a6559a79815603a98fd5dd1b2a1b11a1b13218dd15a35cbdbc67db60021752878c22fc1dceadc8f4926c79f90d65705ca78187a68fb8c55
7
+ data.tar.gz: 4a6e9c478b28b58da51dedad966cd77b72ff5bca90a21d4adcba47c4560252549af8d9f75df2123fc30f46c600b7da06e7b6f5d8bb692b36d5f7052a0a364148
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ = 3.2.0 (2017-11-16)
2
+
3
+ * Use microseconds in assets plugin :timestamp_paths timestamps (jeremyevans)
4
+
5
+ * Add timestamp_public plugin for serving static files with paths that change based on modify timestamp (jeremyevans)
6
+
1
7
  = 3.1.0 (2017-10-13)
2
8
 
3
9
  * Make set_layout_locals and set_view_locals in branch_locals plugin work when the other is not called (jeremyevans)
@@ -0,0 +1,22 @@
1
+ = New Features
2
+
3
+ * A timestamp_public plugin has been added for serving static files
4
+ with paths that change based on the modification timestamp of the
5
+ file. By using a new path, cached versions of the file will not
6
+ be used, fixing staleness issues. Example:
7
+
8
+ plugin :timestamp_public
9
+
10
+ route do |r|
11
+ # serves requests for /static/\d+/.*
12
+ r.timestamp_public
13
+
14
+ # /static/1234567890/path/to/file
15
+ timestamp_path("path/to/file")
16
+ end
17
+
18
+ = Other Improvements
19
+
20
+ * When using the assets plugin :timestamp_paths option, the
21
+ timestamps now include microseconds, to make cache poisoning more
22
+ difficult.
@@ -620,7 +620,13 @@ class Roda
620
620
  dirs.each{|f| asset_dir = asset_dir[f]}
621
621
  prefix = "#{dirs.join('/')}/" if o[:group_subdirs]
622
622
  end
623
- Array(asset_dir).map{|f| "#{url_prefix}/#{o[:"#{stype}_prefix"]}#{"#{asset_last_modified(File.join(o[:"#{stype}_path"], *[prefix, f].compact)).to_i}/" if o[:timestamp_paths]}#{prefix}#{f}#{o[:"#{stype}_suffix"]}"}
623
+ Array(asset_dir).map do |f|
624
+ if o[:timestamp_paths]
625
+ mtime = asset_last_modified(File.join(o[:"#{stype}_path"], *[prefix, f].compact))
626
+ mtime = "#{sprintf("%i%06i", mtime.to_i, mtime.usec)}/"
627
+ end
628
+ "#{url_prefix}/#{o[:"#{stype}_prefix"]}#{mtime}#{prefix}#{f}#{o[:"#{stype}_suffix"]}"
629
+ end
624
630
  end
625
631
  end
626
632
 
@@ -45,8 +45,12 @@ class Roda
45
45
  # :headers :: A hash of headers to use for statically served files
46
46
  # :root :: Use this option for the root of the public directory (default: "public")
47
47
  def self.configure(app, opts={})
48
- root = app.expand_path(opts[:root]||"public")
49
- app.opts[:public_server] = ::Rack::File.new(root, opts[:headers]||{}, opts[:default_mime] || 'text/plain')
48
+ if opts[:root]
49
+ app.opts[:public_root] = app.expand_path(opts[:root])
50
+ elsif !app.opts[:public_root]
51
+ app.opts[:public_root] = app.expand_path("public")
52
+ end
53
+ app.opts[:public_server] = ::Rack::File.new(app.opts[:public_root], opts[:headers]||{}, opts[:default_mime] || 'text/plain')
50
54
  app.opts[:public_gzip] = opts[:gzip]
51
55
  end
52
56
 
@@ -53,7 +53,7 @@ class Roda
53
53
  # and halts the request. It takes an optional body:
54
54
  #
55
55
  # error # 500 response, empty boby
56
- # error 501 # 501 reponse, empty body
56
+ # error 501 # 501 response, empty body
57
57
  # error 'b' # 500 response, 'b' body
58
58
  # error 501, 'b' # 501 response, 'b' body
59
59
  #
@@ -0,0 +1,75 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The timestamp_public plugin adds a +timestamp_path+ method for constructing
7
+ # timestamp paths, and a +r.timestamp_public+ routing method to serve static files
8
+ # from a directory (using the public plugin). This plugin is useful when you want
9
+ # to modify the path to static files when the modify timestamp on the file changes,
10
+ # ensuring that requests for the static file will not be cached.
11
+ #
12
+ # Note that while this plugin will not serve files outside of the public directory,
13
+ # for performance reasons it does not check the path of the file is inside the public
14
+ # directory when getting the modify timestamp. If the +timestamp_path+ method is
15
+ # called with untrusted input, it is possible for an attacker to get the modify
16
+ # timestamp for any file on the file system.
17
+ #
18
+ # Examples:
19
+ #
20
+ # # Use public folder as location of files, and static as the path prefix
21
+ # plugin :timestamp_public
22
+ #
23
+ # # Use /path/to/app/static as location of files, and public as the path prefix
24
+ # opts[:root] = '/path/to/app'
25
+ # plugin :public, root: 'static', prefix: 'public'
26
+ #
27
+ # # Assuming public is the location of files, and static is the path prefix
28
+ # r.route do
29
+ # # Make GET /static/1238099123/images/foo.png look for public/images/foo.png
30
+ # r.timestamp_public
31
+ #
32
+ # r.get "example" do
33
+ # # "/static/1238099123/images/foo.png"
34
+ # timestamp_path("images/foo.png")
35
+ # end
36
+ # end
37
+ module TimestampPublic
38
+ # Use options given to setup timestamped file serving. The following option is
39
+ # recognized by the plugin:
40
+ #
41
+ # :prefix :: The prefix for paths, before the timestamp segment
42
+ #
43
+ # The options given are also passed to the public plugin.
44
+ def self.configure(app, opts={})
45
+ app.plugin :public, opts
46
+ app.opts[:timestamp_public_prefix] = (opts[:prefix] || app.opts[:timestamp_public_prefix] || "static").dup.freeze
47
+ end
48
+
49
+ module InstanceMethods
50
+ # Return a path to the static file that could be served by r.timestamp_public.
51
+ # This does not check the file is inside the directory for performance reasons,
52
+ # so this should not be called with untrusted input.
53
+ def timestamp_path(file)
54
+ mtime = File.mtime(File.join(opts[:public_root], file))
55
+ "/#{opts[:timestamp_public_prefix]}/#{sprintf("%i%06i", mtime.to_i, mtime.usec)}/#{file}"
56
+ end
57
+ end
58
+
59
+ module RequestMethods
60
+ # Serve files from the public directory if the file exists,
61
+ # it includes the timestamp_public prefix segment followed by
62
+ # a integer segment for the timestamp, and this is a GET request.
63
+ def timestamp_public
64
+ if is_get?
65
+ on roda_class.opts[:timestamp_public_prefix], Integer do |_|
66
+ public
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ register_plugin(:timestamp_public, TimestampPublic)
74
+ end
75
+ end
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 1
7
+ RodaMinorVersion = 2
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -0,0 +1,85 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe "timestamp_public plugin" do
4
+ it "adds r.timestamp_public for serving static files from timestamp_public folder" do
5
+ app(:bare) do
6
+ plugin :timestamp_public, :root=>'spec/views'
7
+
8
+ route do |r|
9
+ r.timestamp_public
10
+ end
11
+ end
12
+
13
+ status("/about/_test.erb\0").must_equal 404
14
+ status("/about/_test.erb").must_equal 404
15
+ status("/static/a/about/_test.erb").must_equal 404
16
+ status("/static/1/about/_test.erb\0").must_equal 404
17
+ body("/static/1/about/_test.erb").must_equal File.read('spec/views/about/_test.erb')
18
+ end
19
+
20
+ it "adds r.timestamp_public for serving static files from timestamp_public folder" do
21
+ app(:bare) do
22
+ plugin :timestamp_public, :root=>'spec/views', :prefix=>'foo'
23
+
24
+ route do |r|
25
+ r.timestamp_public
26
+ end
27
+ end
28
+
29
+ body("/foo/1/about/_test.erb").must_equal File.read('spec/views/about/_test.erb')
30
+ end
31
+
32
+ it "adds r.timestamp_public for serving static files from timestamp_public folder" do
33
+ app(:bare) do
34
+ plugin :timestamp_public, :root=>'spec/plugin'
35
+
36
+ route do |r|
37
+ r.timestamp_public
38
+ timestamp_path('../views/about/_test.erb')
39
+ end
40
+ end
41
+
42
+ mtime = File.mtime('spec/views/about/_test.erb')
43
+ body.must_equal "/static/#{sprintf("%i%06i", mtime.to_i, mtime.usec)}/../views/about/_test.erb"
44
+ status("/static/1/../views/about/_test.erb").must_equal 404
45
+ end
46
+
47
+ it "respects the application's :root option" do
48
+ app(:bare) do
49
+ opts[:root] = File.expand_path('../../', __FILE__)
50
+ plugin :timestamp_public, :root=>'views'
51
+
52
+ route do |r|
53
+ r.timestamp_public
54
+ end
55
+ end
56
+
57
+ body('/static/1/about/_test.erb').must_equal File.read('spec/views/about/_test.erb')
58
+ end
59
+
60
+ it "handles serving gzip files in gzip mode if client supports gzip" do
61
+ app(:bare) do
62
+ plugin :timestamp_public, :root=>'spec/views', :gzip=>true
63
+
64
+ route do |r|
65
+ r.timestamp_public
66
+ end
67
+ end
68
+
69
+ body('/static/1/about/_test.erb').must_equal File.read('spec/views/about/_test.erb')
70
+ header('Content-Encoding', '/about/_test.erb').must_be_nil
71
+
72
+ body('/static/1/about.erb').must_equal File.read('spec/views/about.erb')
73
+ header('Content-Encoding', '/about.erb').must_be_nil
74
+
75
+ body('/static/1/about/_test.erb', 'HTTP_ACCEPT_ENCODING'=>'deflate, gzip').must_equal File.binread('spec/views/about/_test.erb.gz')
76
+ h = req('/static/1/about/_test.erb', 'HTTP_ACCEPT_ENCODING'=>'deflate, gzip')[1]
77
+ h['Content-Encoding'].must_equal 'gzip'
78
+ h['Content-Type'].must_equal 'text/plain'
79
+
80
+ body('/static/1/about/_test.css', 'HTTP_ACCEPT_ENCODING'=>'deflate, gzip').must_equal File.binread('spec/views/about/_test.css.gz')
81
+ h = req('/static/1/about/_test.css', 'HTTP_ACCEPT_ENCODING'=>'deflate, gzip')[1]
82
+ h['Content-Encoding'].must_equal 'gzip'
83
+ h['Content-Type'].must_equal 'text/css'
84
+ end
85
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-13 00:00:00.000000000 Z
11
+ date: 2017-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -197,6 +197,7 @@ extra_rdoc_files:
197
197
  - doc/release_notes/2.29.0.txt
198
198
  - doc/release_notes/3.0.0.txt
199
199
  - doc/release_notes/3.1.0.txt
200
+ - doc/release_notes/3.2.0.txt
200
201
  files:
201
202
  - CHANGELOG
202
203
  - MIT-LICENSE
@@ -240,6 +241,7 @@ files:
240
241
  - doc/release_notes/2.9.0.txt
241
242
  - doc/release_notes/3.0.0.txt
242
243
  - doc/release_notes/3.1.0.txt
244
+ - doc/release_notes/3.2.0.txt
243
245
  - lib/roda.rb
244
246
  - lib/roda/plugins/_symbol_regexp_matchers.rb
245
247
  - lib/roda/plugins/all_verbs.rb
@@ -318,6 +320,7 @@ files:
318
320
  - lib/roda/plugins/symbol_matchers.rb
319
321
  - lib/roda/plugins/symbol_status.rb
320
322
  - lib/roda/plugins/symbol_views.rb
323
+ - lib/roda/plugins/timestamp_public.rb
321
324
  - lib/roda/plugins/type_routing.rb
322
325
  - lib/roda/plugins/unescape_path.rb
323
326
  - lib/roda/plugins/view_options.rb
@@ -409,6 +412,7 @@ files:
409
412
  - spec/plugin/symbol_matchers_spec.rb
410
413
  - spec/plugin/symbol_status_spec.rb
411
414
  - spec/plugin/symbol_views_spec.rb
415
+ - spec/plugin/timestamp_public_spec.rb
412
416
  - spec/plugin/type_routing_spec.rb
413
417
  - spec/plugin/unescape_path_spec.rb
414
418
  - spec/plugin/view_options_spec.rb