roda 3.1.0 → 3.2.0

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 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