roda 3.85.0 → 3.87.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
  SHA256:
3
- metadata.gz: a862ed0414bf90081e744ed7c50ac19806f141ebeae8c699d5f96b9a4e718e10
4
- data.tar.gz: b1fcd622255d24044ba7f26c2c3f35e5fc8ea7f30b2ba577bec5fe306ca75c76
3
+ metadata.gz: bee7ae126ad0e6e8081bba94be0ad60e2a5f7523c483b2441571549b1bd29274
4
+ data.tar.gz: f5441e96617877d1347f4a5b6e54e5d48713ffc91a8ad3b9282781c0c26ca108
5
5
  SHA512:
6
- metadata.gz: '086d21f564d66b14fafabbb99c6d670559c12fed91105c1acb9e5e9147623bf15de0730d35f2b5bd098a6e26aba0d6a73497a914ee23d97d8cb58dbbf0f6dd5b'
7
- data.tar.gz: 713dee3419ce7bf52742d7da6be4243c393b393aeb5762dcd0dc61a58d691e586226636b5df687e56e0dd280a89930a18b1e32f3c9b499516afc461cf92fcf4c
6
+ metadata.gz: 141408b4d85e7467c0b753c05180201b1a63a3096f713241b94d379bad8d65224d96f1d60978ac336a7d8b9fb3093f35c1d2887d422276cf255cd1c64c557719
7
+ data.tar.gz: a64207e6d0a46d22c59bc0acc9d56ed43f4dbc75a9d22737908097564a2e4e801f3c19fd3bf95a28722dbf4a828d9930146dd088e421847709948d3a8f2cf24b
@@ -68,7 +68,7 @@ class Roda
68
68
 
69
69
  # Eagerly load all hash branches when freezing the application.
70
70
  def freeze
71
- opts.delete(:autoload_hash_branch_files).each{|file| require file}
71
+ opts.delete(:autoload_hash_branch_files).each{|file| require file} unless opts.frozen?
72
72
  super
73
73
  end
74
74
  end
@@ -54,7 +54,7 @@ class Roda
54
54
 
55
55
  # Eagerly load all autoloaded named routes when freezing the application.
56
56
  def freeze
57
- opts.delete(:autoload_named_route_files).each{|file| require file}
57
+ opts.delete(:autoload_named_route_files).each{|file| require file} unless opts.frozen?
58
58
  super
59
59
  end
60
60
  end
@@ -0,0 +1,67 @@
1
+ # frozen-string-literal: true
2
+
3
+ class Roda
4
+ module RodaPlugins
5
+ # The conditional_sessions plugin loads the sessions plugin. However,
6
+ # it only allows sessions if the block passed to the plugin returns
7
+ # truthy. The block is evaluated in request context. This is designed for
8
+ # use in applications that want to use sessions for some requests,
9
+ # and want to be sure that sessions are not used for other requests.
10
+ # For example, if you want to make sure that sessions are not used for
11
+ # requests with paths starting with /static, you could do:
12
+ #
13
+ # plugin :conditional_sessions, secret: ENV["SECRET"] do
14
+ # !path_info.start_with?('/static')
15
+ # end
16
+ #
17
+ # The the request session, session_created_at, and session_updated_at methods
18
+ # raise a RodaError exception when sessions are not allowed. The request
19
+ # persist_session and route scope clear_session methods do nothing when
20
+ # sessions are not allowed.
21
+ module ConditionalSessions
22
+ # Pass all options to the sessions block, and use the block to define
23
+ # a request method for whether sessions are allowed.
24
+ def self.load_dependencies(app, opts=OPTS, &block)
25
+ app.plugin :sessions, opts
26
+ app::RodaRequest.class_eval do
27
+ define_method(:use_sessions?, &block)
28
+ alias use_sessions? use_sessions?
29
+ end
30
+ end
31
+
32
+ module InstanceMethods
33
+ # Do nothing if not using sessions.
34
+ def clear_session
35
+ super if @_request.use_sessions?
36
+ end
37
+ end
38
+
39
+ module RequestMethods
40
+ # Raise RodaError if not using sessions.
41
+ def session
42
+ raise RodaError, "session called on request not using sessions" unless use_sessions?
43
+ super
44
+ end
45
+
46
+ # Raise RodaError if not using sessions.
47
+ def session_created_at
48
+ raise RodaError, "session_created_at called on request not using sessions" unless use_sessions?
49
+ super
50
+ end
51
+
52
+ # Raise RodaError if not using sessions.
53
+ def session_updated_at
54
+ raise RodaError, "session_updated_at called on request not using sessions" unless use_sessions?
55
+ super
56
+ end
57
+
58
+ # Do nothing if not using sessions.
59
+ def persist_session(headers, session)
60
+ super if use_sessions?
61
+ end
62
+ end
63
+ end
64
+
65
+ register_plugin(:conditional_sessions, ConditionalSessions)
66
+ end
67
+ end
@@ -92,7 +92,10 @@ class Roda
92
92
  # content_security_policy.get_script_src
93
93
  # # => [:self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']]
94
94
  #
95
- # The clear method can be used to remove all settings from the policy.
95
+ # The clear method can be used to remove all settings from the policy. Empty policies
96
+ # do not set any headers. You can use +response.skip_content_security_policy!+ to skip
97
+ # setting a policy. This is faster than calling +content_security_policy.clear+, since
98
+ # it does not duplicate the default policy.
96
99
  #
97
100
  # The following methods to set boolean settings are also defined:
98
101
  #
@@ -304,12 +307,19 @@ class Roda
304
307
  @content_security_policy ||= roda_class.opts[:content_security_policy].dup
305
308
  end
306
309
 
310
+ # Do not set a content security policy header for this response.
311
+ def skip_content_security_policy!
312
+ @skip_content_security_policy = true
313
+ end
314
+
307
315
  private
308
316
 
309
317
  # Set the appropriate content security policy header.
310
318
  def set_default_headers
311
319
  super
312
- (@content_security_policy || roda_class.opts[:content_security_policy]).set_header(headers)
320
+ unless @skip_content_security_policy
321
+ (@content_security_policy || roda_class.opts[:content_security_policy]).set_header(headers)
322
+ end
313
323
  end
314
324
  end
315
325
  end
@@ -28,7 +28,16 @@ class Roda
28
28
  #
29
29
  # Note that custom block result handling only occurs if the types
30
30
  # are not handled by Roda itself. You cannot use this to modify
31
- # the handling of nil, false, or string results.
31
+ # the handling of nil, false, or string results. Additionally,
32
+ # if the response body has already been written to before the the
33
+ # route block exits, then the result of the block is ignored,
34
+ # and the related +handle_block_result+ block will not be called
35
+ # (this is standard Roda behavior).
36
+ #
37
+ # The return value of the +handle_block_result+ block is written
38
+ # to the body if the block return value is a String, similar to
39
+ # standard Roda handling of block results. Non-String return
40
+ # values are ignored.
32
41
  module CustomBlockResults
33
42
  def self.configure(app)
34
43
  app.opts[:custom_block_results] ||= {}
@@ -55,7 +64,15 @@ class Roda
55
64
  # to get the block result.
56
65
  def unsupported_block_result(result)
57
66
  roda_class.opts[:custom_block_results].each do |klass, meth|
58
- return scope.send(meth, result) if klass === result
67
+ if klass === result
68
+ result = scope.send(meth, result)
69
+
70
+ if String === result
71
+ return result
72
+ else
73
+ return
74
+ end
75
+ end
59
76
  end
60
77
 
61
78
  super
@@ -4,8 +4,7 @@
4
4
  class Roda
5
5
  module RodaPlugins
6
6
  # The early_hints plugin allows sending 103 Early Hints responses
7
- # using the rack.early_hints environment variable. Currently, this
8
- # is only supported by puma 3.11+, and on other servers this is a no-op.
7
+ # using the rack.early_hints environment variable.
9
8
  # Early hints allow clients to preload necessary files before receiving
10
9
  # the response.
11
10
  module EarlyHints
@@ -51,7 +51,8 @@ class Roda
51
51
 
52
52
  # Match if the given uppercase key is present inside the environment.
53
53
  def match_header(key)
54
- key = key.upcase.tr("-","_")
54
+ key = key.upcase
55
+ key.tr!("-","_")
55
56
  unless key == "CONTENT_TYPE" || key == "CONTENT_LENGTH"
56
57
  key = "HTTP_#{key}"
57
58
  end
@@ -75,8 +76,8 @@ class Roda
75
76
  # Match the submitted user agent to the given pattern, capturing any
76
77
  # regexp match groups.
77
78
  def match_user_agent(pattern)
78
- if (user_agent = @env["HTTP_USER_AGENT"]) && user_agent.to_s =~ pattern
79
- @captures.concat($~[1..-1])
79
+ if (user_agent = @env["HTTP_USER_AGENT"]) && (match = pattern.match(user_agent))
80
+ @captures.concat(match.captures)
80
81
  end
81
82
  end
82
83
  end
@@ -0,0 +1,190 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The host_routing plugin adds support for more routing requests based on
7
+ # the requested host. It also adds predicate methods for checking
8
+ # whether a request was requested with the given host.
9
+ #
10
+ # When loading the plugin, you pass a block, which is used for configuring
11
+ # the plugin. For example, if you want to treat requests to api.example.com
12
+ # or api2.example.com as api requests, and treat other requests as www
13
+ # requests, you could use:
14
+ #
15
+ # plugin :host_routing do |hosts|
16
+ # hosts.to :api, "api.example.com", "api2.example.com"
17
+ # hosts.default :www
18
+ # end
19
+ #
20
+ # With this configuration, in your routing tree, you can call the +r.api+ and
21
+ # +r.www+ methods for dispatching to routing blocks only for those types of
22
+ # requests:
23
+ #
24
+ # route do |r|
25
+ # r.api do
26
+ # # requests to api.example.com or api2.example.com
27
+ # end
28
+ #
29
+ # r.www do
30
+ # # requests to other domains
31
+ # end
32
+ # end
33
+ #
34
+ # In addition to the routing methods, predicate methods are also added to the
35
+ # request object:
36
+ #
37
+ # route do |r|
38
+ # "#{r.api?}-#{r.www?}"
39
+ # end
40
+ # # Requests to api.example.com or api2.example.com return "true-false"
41
+ # # Other requests return "false-true"
42
+ #
43
+ # If the +:scope_predicates+ plugin option is given, predicate methods are also
44
+ # created in route block scope:
45
+ #
46
+ # plugin :host_routing, scope_predicates: true do |hosts|
47
+ # hosts.to :api, "api.example.com"
48
+ # hosts.default :www
49
+ # end
50
+ #
51
+ # route do |r|
52
+ # "#{api?}-#{www?}"
53
+ # end
54
+ #
55
+ # To handle hosts that match a certain format (such as all subdomains),
56
+ # where the specific host names are not known up front, you can provide a block
57
+ # when calling +hosts.default+. This block is passed the host name, or an empty
58
+ # string if no host name is provided, and is evaluated in route block scope.
59
+ # When using this support, you should also call +hosts.register+
60
+ # to register host types that could be returned by the block. For example, to
61
+ # handle api subdomains differently:
62
+ #
63
+ # plugin :host_routing do |hosts|
64
+ # hosts.to :api, "api.example.com"
65
+ # hosts.register :api_sub
66
+ # hosts.default :www do |host|
67
+ # :api_sub if host.end_with?(".api.example.com")
68
+ # end
69
+ # end
70
+ #
71
+ # This plugin uses the host method on the request to get the hostname (this method
72
+ # is defined by Rack).
73
+ module HostRouting
74
+ # Setup the host routing support. The block yields an object used to
75
+ # configure the plugin. Options:
76
+ #
77
+ # :scope_predicates :: Setup predicate methods in route block scope
78
+ # in addition to request scope.
79
+ def self.configure(app, opts=OPTS, &block)
80
+ hosts, host_hash, default_block, default_host = DSL.new.process(&block)
81
+ app.opts[:host_routing_hash] = host_hash
82
+ app.opts[:host_routing_default_host] = default_host
83
+
84
+ app.send(:define_method, :_host_routing_default, &default_block) if default_block
85
+
86
+ app::RodaRequest.class_exec do
87
+ hosts.each do |host|
88
+ host_sym = host.to_sym
89
+ define_method(host_sym){|&blk| always(&blk) if _host_routing_host == host}
90
+ alias_method host_sym, host_sym
91
+
92
+ meth = :"#{host}?"
93
+ define_method(meth){_host_routing_host == host}
94
+ alias_method meth, meth
95
+ end
96
+ end
97
+
98
+ if opts[:scope_predicates]
99
+ app.class_exec do
100
+ hosts.each do |host|
101
+ meth = :"#{host}?"
102
+ define_method(meth){@_request.send(meth)}
103
+ alias_method meth, meth
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ class DSL
110
+ def initialize
111
+ @hosts = []
112
+ @host_hash = {}
113
+ end
114
+
115
+ # Run the DSL for the given block.
116
+ def process(&block)
117
+ instance_exec(self, &block)
118
+
119
+ if !@default_host
120
+ raise RodaError, "must call default method inside host_routing plugin block to set default host"
121
+ end
122
+
123
+ @hosts.concat(@host_hash.values)
124
+ @hosts << @default_host
125
+ @hosts.uniq!
126
+ [@hosts.freeze, @host_hash.freeze, @default_block, @default_host].freeze
127
+ end
128
+
129
+ # Register hosts that can be returned. This is only needed if
130
+ # calling register with a block, where the block can return
131
+ # a value that doesn't match a host given to +to+ or +default+.
132
+ def register(*hosts)
133
+ @hosts = hosts
134
+ end
135
+
136
+ # Treat all given hostnames as routing to the give host.
137
+ def to(host, *hostnames)
138
+ hostnames.each do |hostname|
139
+ @host_hash[hostname] = host
140
+ end
141
+ end
142
+
143
+ # Register the default hostname. If a block is provided, it is
144
+ # called with the host if there is no match for one of the hostnames
145
+ # provided to +to+. If the block returns nil/false, the hostname
146
+ # given to this method is used.
147
+ def default(hostname, &block)
148
+ @default_host = hostname
149
+ @default_block = block
150
+ end
151
+ end
152
+ private_constant :DSL
153
+
154
+ module InstanceMethods
155
+ # Handle case where plugin is used without providing a block to
156
+ # +hosts.default+. This returns nil, ensuring that the hostname
157
+ # provided to +hosts.default+ will be used.
158
+ def _host_routing_default(_)
159
+ nil
160
+ end
161
+ end
162
+
163
+ module RequestMethods
164
+ private
165
+
166
+ # Cache the host to use in the host routing support, so the processing
167
+ # is only done once per request.
168
+ def _host_routing_host
169
+ @_host_routing_host ||= _get_host_routing_host
170
+ end
171
+
172
+ # Determine the host to use for the host routing support. Tries the
173
+ # following, in order:
174
+ #
175
+ # * An exact match for a hostname given in +hosts.to+
176
+ # * The return value of the +hosts.default+ block, if given
177
+ # * The default value provided in the +hosts.default+ call
178
+ def _get_host_routing_host
179
+ host = self.host || ""
180
+
181
+ roda_class.opts[:host_routing_hash][host] ||
182
+ scope._host_routing_default(host) ||
183
+ roda_class.opts[:host_routing_default_host]
184
+ end
185
+ end
186
+ end
187
+
188
+ register_plugin(:host_routing, HostRouting)
189
+ end
190
+ end
@@ -99,7 +99,10 @@ class Roda
99
99
  # permissions_policy.get_fullscreen
100
100
  # # => [:self, "https://example.com", "https://*.example.com"]
101
101
  #
102
- # The clear method can be used to remove all settings from the policy.
102
+ # The clear method can be used to remove all settings from the policy. Empty policies
103
+ # do not set any headers. You can use +response.skip_permissions_policy!+ to skip
104
+ # setting a policy. This is faster than calling +permissions_policy.clear+, since
105
+ # it does not duplicate the default policy.
103
106
  module PermissionsPolicy
104
107
  SUPPORTED_SETTINGS = %w'
105
108
  accelerometer
@@ -311,12 +314,19 @@ class Roda
311
314
  @permissions_policy ||= roda_class.opts[:permissions_policy].dup
312
315
  end
313
316
 
317
+ # Do not set a permissions policy header for this response.
318
+ def skip_permissions_policy!
319
+ @skip_permissions_policy = true
320
+ end
321
+
314
322
  private
315
323
 
316
324
  # Set the appropriate permissions policy header.
317
325
  def set_default_headers
318
326
  super
319
- (@permissions_policy || roda_class.opts[:permissions_policy]).set_header(headers)
327
+ unless @skip_permissions_policy
328
+ (@permissions_policy || roda_class.opts[:permissions_policy]).set_header(headers)
329
+ end
320
330
  end
321
331
  end
322
332
  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 = 85
7
+ RodaMinorVersion = 87
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
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.85.0
4
+ version: 3.87.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: 2024-10-11 00:00:00.000000000 Z
11
+ date: 2024-12-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -186,6 +186,7 @@ files:
186
186
  - lib/roda/plugins/class_level_routing.rb
187
187
  - lib/roda/plugins/class_matchers.rb
188
188
  - lib/roda/plugins/common_logger.rb
189
+ - lib/roda/plugins/conditional_sessions.rb
189
190
  - lib/roda/plugins/content_for.rb
190
191
  - lib/roda/plugins/content_security_policy.rb
191
192
  - lib/roda/plugins/cookie_flags.rb
@@ -224,6 +225,7 @@ files:
224
225
  - lib/roda/plugins/hmac_paths.rb
225
226
  - lib/roda/plugins/hooks.rb
226
227
  - lib/roda/plugins/host_authorization.rb
228
+ - lib/roda/plugins/host_routing.rb
227
229
  - lib/roda/plugins/hsts.rb
228
230
  - lib/roda/plugins/indifferent_params.rb
229
231
  - lib/roda/plugins/inject_erb.rb
@@ -326,7 +328,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
326
328
  - !ruby/object:Gem::Version
327
329
  version: '0'
328
330
  requirements: []
329
- rubygems_version: 3.5.16
331
+ rubygems_version: 3.5.22
330
332
  signing_key:
331
333
  specification_version: 4
332
334
  summary: Routing tree web toolkit