roda 3.102.0 → 3.103.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
  SHA256:
3
- metadata.gz: f36503d930c18c02148a0da9c30d8f7166790402e04a4220e5a792dc2502779a
4
- data.tar.gz: 533b40ec81263d73cbcf60d3a9807434ba0ca1a0d92ee1ca3fc59226edc80c96
3
+ metadata.gz: d9809241399d00f1fc975608188f61e99c76c5818d7775a4fe8f0b7eeb9715e3
4
+ data.tar.gz: e04d8e54e041a4f96e998ebdd0431aada5e6d201fb1b6acc4b8c2a3fab50ea79
5
5
  SHA512:
6
- metadata.gz: 0afa286317e707bd64e903fe6d5225d17a499606fdc71253769795818505793a0a12d42f8999c5eb8e678481da09d82aacb495b7a228ab47a6437b721807f57b
7
- data.tar.gz: 14e94a735b1f88e45ff4c2c21b4c3acb7d96cf75f3435695b0c13a0f22471f6c619d3b0915b2794dff745ea88a333c0fd5e629c9566326f5e15d8b4cedd2be32
6
+ metadata.gz: 19235fd38195015a2b7392ebbf97d34d2f7bda2977c6d45c42fd3b2fd86b1cdee5eff2b65bccf485157955ad474994567ae1b99e93c9459a3a9601df65800f6c
7
+ data.tar.gz: 628a775ff0bbb503e18d3cc1989ddfb8dcafec407b890b96adf30684674f5ca749cc6ae6fdc9c4ff9ec59e7779f878f55ae37e76c0e52c26da4e8127a5a0f0f9
@@ -0,0 +1,105 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'digest/sha2'
4
+
5
+ class Roda
6
+ module RodaPlugins
7
+ # The hash_public plugin adds a +hash_path+ method for constructing
8
+ # content-hash-based paths, and a +r.hash_public+ routing method to serve
9
+ # static files from a directory (using the public plugin). This plugin is
10
+ # useful when you want to modify the path to static files when the content
11
+ # of the file changes, ensuring that requests for the static file will not
12
+ # be cached.
13
+ #
14
+ # Unlike the timestamp_public plugin, which uses file modification times,
15
+ # hash_public uses a SHA256 digest of the file content. This makes paths
16
+ # stable across different build environments (e.g. Docker images built in
17
+ # CI/CD pipelines), where file modification times may vary even when the
18
+ # file content has not changed.
19
+ #
20
+ # Note that while this plugin will not serve files outside of the public
21
+ # directory, for performance reasons it does not check the path of the file
22
+ # is inside the public directory when computing the content hash. If the
23
+ # +hash_path+ method is called with untrusted input, it is possible for an
24
+ # attacker to read the content hash of any file on the file system.
25
+ #
26
+ # This plugin caches the digest of file content on first read. That means
27
+ # if you change the file after that, it will continue to show the old hash.
28
+ # This can cause problems in development mode if you are modifying the
29
+ # content of files served by the plugin.
30
+ #
31
+ # Examples:
32
+ #
33
+ # # Use public folder as location of files, and static as the path prefix
34
+ # plugin :hash_public
35
+ #
36
+ # # Use /path/to/app/static as location of files, and public as the path prefix
37
+ # opts[:root] = '/path/to/app'
38
+ # plugin :hash_public, root: 'static', prefix: 'public'
39
+ #
40
+ # # Assuming public is the location of files, and static as the path prefix
41
+ # route do
42
+ # # Make GET /static/any-string/images/foo.png look for public/images/foo.png
43
+ # r.hash_public
44
+ #
45
+ # r.get "example" do
46
+ # # "/static/sha256-url-safe-base64-encoded-file-digest-/images/foo.png"
47
+ # hash_path("images/foo.png")
48
+ # end
49
+ # end
50
+ module HashPublic
51
+ # Use options given to setup content-hash-based file serving. The
52
+ # following options are recognized by the plugin:
53
+ #
54
+ # :prefix :: The prefix for paths, before the hash segment
55
+ # :length :: The number of characters of the digest to use in paths
56
+ # (default: full 43-character SHA256 URL safe base64 digest)
57
+ #
58
+ # The options given are also passed to the public plugin.
59
+ def self.configure(app, opts = {})
60
+ app.plugin :public, opts
61
+ app.opts[:hash_public_prefix] = (opts[:prefix] || app.opts[:hash_public_prefix] || 'static').dup.freeze
62
+ app.opts[:hash_public_length] = opts[:length] || app.opts[:hash_public_length]
63
+ app.opts[:hash_public_mutex] ||= Mutex.new
64
+ app.opts[:hash_public_cache] ||= {}
65
+ end
66
+
67
+ module InstanceMethods
68
+ # Return a path to the static file that could be served by r.hash_public.
69
+ # This does not check the file is inside the directory for performance
70
+ # reasons, so this should not be called with untrusted input.
71
+ def hash_path(file)
72
+ opts = self.opts
73
+ cache = opts[:hash_public_cache]
74
+ mutex = opts[:hash_public_mutex]
75
+ unless digest = mutex.synchronize{cache[file]}
76
+ digest = ::Digest::SHA256.file(File.join(opts[:public_root], file)).base64digest
77
+ digest.chomp!("=")
78
+ digest.tr!("+/", "-_")
79
+ if length = opts[:hash_public_length]
80
+ digest = digest[0, length]
81
+ end
82
+ digest.freeze
83
+ mutex.synchronize{cache[file] = digest}
84
+ end
85
+ "/#{opts[:hash_public_prefix]}/#{digest}/#{file}"
86
+ end
87
+ end
88
+
89
+ module RequestMethods
90
+ # Serve files from the public directory if the file exists,
91
+ # it includes the hash_public prefix segment followed by
92
+ # a string segment for the content hash, and this is a GET request.
93
+ def hash_public
94
+ if is_get?
95
+ on roda_class.opts[:hash_public_prefix], String do |_|
96
+ public
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ register_plugin(:hash_public, HashPublic)
104
+ end
105
+ end
@@ -0,0 +1,34 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The ip_from_header plugin allows for overriding +request.ip+ to return
7
+ # the value contained in a specific header. This is useful when the
8
+ # application is behind a proxy that sets a specific header, especially
9
+ # when the proxy does not use a fixed IP address range. Example showing
10
+ # usage with Cloudflare:
11
+ #
12
+ # plugin :ip_from_header, "CF-Connecting-IP"
13
+ #
14
+ # This plugin assumes that if the header is set, it contains a valid IP
15
+ # address, it does not check the format of the header value, just as
16
+ # <tt>Rack::Request#ip</tt> does not check the IP address it returns is
17
+ # actually valid.
18
+ module IPFromHeader
19
+ def self.configure(app, header)
20
+ app.opts[:ip_from_header_env_key] = "HTTP_#{header.upcase.tr('-', '_')}".freeze
21
+ end
22
+
23
+ module RequestMethods
24
+ # Return the IP address continained in the configured header, if present.
25
+ # Fallback to the default behavior if not present.
26
+ def ip
27
+ @env[roda_class.opts[:ip_from_header_env_key]] || super
28
+ end
29
+ end
30
+ end
31
+
32
+ register_plugin(:ip_from_header, IPFromHeader)
33
+ end
34
+ end
data/lib/roda/version.rb CHANGED
@@ -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 = 102
7
+ RodaMinorVersion = 103
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: roda
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.102.0
4
+ version: 3.103.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -218,6 +218,7 @@ files:
218
218
  - lib/roda/plugins/hash_branches.rb
219
219
  - lib/roda/plugins/hash_matcher.rb
220
220
  - lib/roda/plugins/hash_paths.rb
221
+ - lib/roda/plugins/hash_public.rb
221
222
  - lib/roda/plugins/hash_routes.rb
222
223
  - lib/roda/plugins/head.rb
223
224
  - lib/roda/plugins/header_matchers.rb
@@ -230,6 +231,7 @@ files:
230
231
  - lib/roda/plugins/indifferent_params.rb
231
232
  - lib/roda/plugins/inject_erb.rb
232
233
  - lib/roda/plugins/invalid_request_body.rb
234
+ - lib/roda/plugins/ip_from_header.rb
233
235
  - lib/roda/plugins/json.rb
234
236
  - lib/roda/plugins/json_parser.rb
235
237
  - lib/roda/plugins/link_to.rb