roda 3.27.0 → 3.28.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: 1aaf8be3e1a6fa8d822436561e48f5a5978e94e3430c0287b32be3a0a292d351
4
- data.tar.gz: 84ed6cd43e59574cc1264889578296d30c96c91d4b53a45bb2b839619f2d1786
3
+ metadata.gz: 2de9e0d72db9d2f1d1143c71738067798dbcc3f2786611f8f0a5f85c32f26167
4
+ data.tar.gz: 92645c4e9792b1217cfae3d8b430741f64212a30003cf81b393c37636dc531c0
5
5
  SHA512:
6
- metadata.gz: 7e339e3062178e6e47bf5b7fb59b32076f5e48131ce14480e3c685f5eb4d0d59903fa635ac790410ef88a2d5cdc46a5e3792d98c511c21acee7931ec5d15d16a
7
- data.tar.gz: 38f9c91a1b9356d07854c364a10a901c2a126162e42e25e112cdca59b9b065e587f17c2019f4c785ed6610fc0b98011f0ab7adb8cfab0da51acb13e1a0477c34
6
+ metadata.gz: 35a615a610ca645130cbd15482f6864a50a3a8727e6e4cf7bad8d63a00e40118f7a0443f88931ec24a5f416ae822a9bd8ebc101f930efff178831660d5125849
7
+ data.tar.gz: a6b9f48cfe249297ce8abfd3a856c65634364ea0490910b956f25fe4cd54c4b77096bd980a59276e6ed0fb4db537e99fa8b4e1e0e35441afaaf942ca7e92bb02
data/CHANGELOG CHANGED
@@ -1,3 +1,11 @@
1
+ = 3.28.0 (2020-01-15)
2
+
3
+ * Add session_created_at and session_updated_at methods to the sessions plugin (jeremyevans)
4
+
5
+ * Make upgrading from rack session cookie in sessions plugin work with rack 2.0.8 (jeremyevans)
6
+
7
+ * Make json_parser parse request body as json even if request body has already been read (jeremyevans)
8
+
1
9
  = 3.27.0 (2019-12-13)
2
10
 
3
11
  * Allow json_parser return correct result for invalid JSON if the params_capturing plugin is used (jeremyevans) (#180)
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014-2019 Jeremy Evans
1
+ Copyright (c) 2014-2020 Jeremy Evans
2
2
  Copyright (c) 2010-2014 Michel Martens, Damian Janowski and Cyril David
3
3
  Copyright (c) 2008-2009 Christian Neukirchen
4
4
 
data/Rakefile CHANGED
@@ -77,6 +77,11 @@ task "spec_cov" do
77
77
  spec.call('COVERAGE'=>'1')
78
78
  end
79
79
 
80
+ desc "Run specs with branch coverage"
81
+ task "spec_branch_cov" do
82
+ spec.call('COVERAGE'=>'1', 'BRANCH_COVERAGE'=>'1')
83
+ end
84
+
80
85
  desc "Run specs with -w, some warnings filtered"
81
86
  task "spec_w" do
82
87
  rubyopt = ENV['RUBYOPT']
@@ -0,0 +1,13 @@
1
+ = New Features
2
+
3
+ * The sessions plugin now supports RodaRequest#session_created_at
4
+ and RodaRequest#session_updated_at for the times of session
5
+ creation and last update.
6
+
7
+ = Other Improvements
8
+
9
+ * The json_parser plugin now correctly parses the request body even
10
+ if the request body has already been read.
11
+
12
+ * The sessions plugin now correctly handles upgrading rack cookie
13
+ sessions when using rack 2.0.8+.
@@ -125,10 +125,12 @@ class Roda
125
125
  # Complexity of handling keyword arguments using define_method is too high,
126
126
  # Fallback to instance_exec in this case.
127
127
  b = block
128
- if RUBY_VERSION >= '2.7'
129
- block = eval('lambda{|*a, **kw| instance_exec(*a, **kw, &b)}', nil, __FILE__, __LINE__) # Keyword arguments fallback
128
+ block = if RUBY_VERSION >= '2.7'
129
+ eval('lambda{|*a, **kw| instance_exec(*a, **kw, &b)}', nil, __FILE__, __LINE__) # Keyword arguments fallback
130
130
  else
131
- block = lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback
131
+ # :nocov:
132
+ lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback
133
+ # :nocov:
132
134
  end
133
135
  else
134
136
  arity_meth = meth
@@ -297,7 +297,7 @@ class Roda
297
297
  # can use nil to disable subresource integrity.
298
298
  # :timestamp_paths :: Include the timestamp of assets in asset paths in non-compiled mode. Doing this can
299
299
  # slow down development requests due to additional requests to get last modified times,
300
- # put it will make sure the paths change in development when there are modifications,
300
+ # but it will make sure the paths change in development when there are modifications,
301
301
  # which can fix issues when using a caching proxy in non-compiled mode. This can also
302
302
  # be specified as a string to use that string to separate the timestamp from the asset.
303
303
  # By default, <tt>/</tt> is used as the separator if timestamp paths are enabled.
@@ -17,7 +17,7 @@ class Roda
17
17
  module DefaultStatus
18
18
  def self.configure(app, &block)
19
19
  raise RodaError, "default_status plugin requires a block" unless block
20
- if check_arity = app.opts.fetch(:check_arity, true)
20
+ if check_arity = app.opts.fetch(:check_arity, true)
21
21
  unless block.arity == 0
22
22
  if check_arity == :warn
23
23
  RodaPlugins.warn "Arity mismatch in block passed to plugin :default_status. Expected Arity 0, but arguments required for #{block.inspect}"
@@ -52,6 +52,7 @@ class Roda
52
52
  if post_params = (env["roda.json_params"] || env["rack.request.form_hash"])
53
53
  post_params
54
54
  elsif (input = env["rack.input"]) && content_type =~ /json/
55
+ input.rewind
55
56
  str = input.read
56
57
  input.rewind
57
58
  return super if str.empty?
@@ -332,7 +332,7 @@ class Roda
332
332
  string_meth = nil
333
333
  regexp_meth = nil
334
334
  addresses.each do |address|
335
- key = case address
335
+ case address
336
336
  when String
337
337
  unless string_meth
338
338
  string_meth = define_roda_method("mail_processor_string_route_#{address}", 1, &convert_route_block(block))
@@ -143,9 +143,11 @@ class Roda
143
143
  template.send(:compiled_method, locals_keys, scope_class)
144
144
  end
145
145
  else
146
+ # :nocov:
146
147
  def self.tilt_template_compiled_method(template, locals_keys, scope_class)
147
148
  template.send(:compiled_method, locals_keys)
148
149
  end
150
+ # :nocov:
149
151
  end
150
152
 
151
153
  # Setup default rendering options. See Render for details.
@@ -172,7 +172,6 @@ class Roda
172
172
 
173
173
  # Configure the plugin, see Sessions for details on options.
174
174
  def self.configure(app, opts=OPTS)
175
- plugin_opts = opts
176
175
  opts = (app.opts[:sessions] || DEFAULT_OPTIONS).merge(opts)
177
176
  co = opts[:cookie_options] = DEFAULT_COOKIE_OPTIONS.merge(opts[:cookie_options] || OPTS).freeze
178
177
  opts[:remove_cookie_options] = co.merge(:max_age=>'0', :expires=>Time.at(0))
@@ -239,6 +238,18 @@ class Roda
239
238
  @env['rack.session'] ||= _load_session
240
239
  end
241
240
 
241
+ # The time the session was originally created. nil if there is no active session.
242
+ def session_created_at
243
+ session
244
+ Time.at(@env[SESSION_CREATED_AT]) if @env[SESSION_SERIALIZED]
245
+ end
246
+
247
+ # The time the session was last updated. nil if there is no active session.
248
+ def session_updated_at
249
+ session
250
+ Time.at(@env[SESSION_UPDATED_AT]) if @env[SESSION_SERIALIZED]
251
+ end
252
+
242
253
  # Persist the session data as a cookie. If transparently upgrading from
243
254
  # Rack::Session::Cookie, mark the related cookie for expiration so it isn't
244
255
  # sent in the future.
@@ -296,8 +307,6 @@ class Roda
296
307
  # hmac and coder.
297
308
  def _deserialize_rack_session(data)
298
309
  opts = roda_class.opts[:sessions]
299
- key = opts[:upgrade_from_rack_session_cookie_key]
300
- secret = opts[:upgrade_from_rack_session_cookie_secret]
301
310
  data, digest = data.split("--", 2)
302
311
  unless digest
303
312
  return _session_serialization_error("Not decoding Rack::Session::Cookie session: invalid format")
@@ -315,6 +324,11 @@ class Roda
315
324
  # Mark rack session cookie for deletion on success
316
325
  env[SESSION_DELETE_RACK_COOKIE] = true
317
326
 
327
+ # Delete the session id before serializing it. Starting in rack 2.0.8,
328
+ # this is an object and not just a string, and calling to_s on it raises
329
+ # a RuntimeError.
330
+ session.delete("session_id")
331
+
318
332
  # Convert the rack session by roundtripping it through
319
333
  # the parser and serializer, so that you would get the
320
334
  # same result as you would if the session was handled
@@ -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 = 27
7
+ RodaMinorVersion = 28
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -8,12 +8,12 @@ describe "Roda.define_roda_method" do
8
8
  it "should define methods using block" do
9
9
  m0 = app.define_roda_method("x", 0){1}
10
10
  m0.must_be_kind_of Symbol
11
- m0.must_match /\A_roda_x_\d+\z/
11
+ m0.must_match(/\A_roda_x_\d+\z/)
12
12
  @scope.send(m0).must_equal 1
13
13
 
14
14
  m1 = app.define_roda_method("x", 1){|x| [x, 2]}
15
15
  m1.must_be_kind_of Symbol
16
- m1.must_match /\A_roda_x_\d+\z/
16
+ m1.must_match(/\A_roda_x_\d+\z/)
17
17
  @scope.send(m1, 3).must_equal [3, 2]
18
18
  end
19
19
 
@@ -14,26 +14,26 @@ describe "common_logger plugin" do
14
14
 
15
15
  body.must_equal '/'
16
16
  @logger.rewind
17
- @logger.read.must_match /\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 200 1 0.\d\d\d\d\n\z/
17
+ @logger.read.must_match(/\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 200 1 0.\d\d\d\d\n\z/)
18
18
 
19
19
  @logger.rewind
20
20
  @logger.truncate(0)
21
21
  body('', 'HTTP_X_FORWARDED_FOR'=>'1.1.1.1', 'REMOTE_USER'=>'je', 'REQUEST_METHOD'=>'POST', 'QUERY_STRING'=>'', "HTTP_VERSION"=>'HTTP/1.1').must_equal ''
22
22
  @logger.rewind
23
- @logger.read.must_match /\A1\.1\.1\.1 - je \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "POST HTTP\/1.1" 200 - 0.\d\d\d\d\n\z/
23
+ @logger.read.must_match(/\A1\.1\.1\.1 - je \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "POST HTTP\/1.1" 200 - 0.\d\d\d\d\n\z/)
24
24
 
25
25
  @logger.rewind
26
26
  @logger.truncate(0)
27
27
  body('/b', 'REMOTE_ADDR'=>'1.1.1.2', 'QUERY_STRING'=>'foo=bar', "HTTP_VERSION"=>'HTTP/1.0').must_equal '/b'
28
28
  @logger.rewind
29
- @logger.read.must_match /\A1\.1\.1\.2 - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/b\?foo=bar HTTP\/1.0" 200 2 0.\d\d\d\d\n\z/
29
+ @logger.read.must_match(/\A1\.1\.1\.2 - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/b\?foo=bar HTTP\/1.0" 200 2 0.\d\d\d\d\n\z/)
30
30
 
31
31
  @app.plugin :common_logger, Logger.new(@logger)
32
32
  @logger.rewind
33
33
  @logger.truncate(0)
34
34
  body.must_equal '/'
35
35
  @logger.rewind
36
- @logger.read.must_match /\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 200 1 0.\d\d\d\d\n\z/
36
+ @logger.read.must_match(/\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 200 1 0.\d\d\d\d\n\z/)
37
37
  end
38
38
 
39
39
  it 'skips timer information if not available' do
@@ -44,7 +44,7 @@ describe "common_logger plugin" do
44
44
 
45
45
  body.must_equal '/'
46
46
  @logger.rewind
47
- @logger.read.must_match /\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 200 1 -\n\z/
47
+ @logger.read.must_match(/\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 200 1 -\n\z/)
48
48
  end
49
49
 
50
50
  it 'skips length information if not available' do
@@ -54,7 +54,7 @@ describe "common_logger plugin" do
54
54
 
55
55
  body.must_equal ''
56
56
  @logger.rewind
57
- @logger.read.must_match /\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 500 - 0.\d\d\d\d\n\z/
57
+ @logger.read.must_match(/\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 500 - 0.\d\d\d\d\n\z/)
58
58
  end
59
59
 
60
60
  it 'does not log if an error is raised' do
@@ -80,6 +80,6 @@ describe "common_logger plugin" do
80
80
 
81
81
  body.must_equal 'bad'
82
82
  @logger.rewind
83
- @logger.read.must_match /\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 500 3 0.\d\d\d\d\n\z/
83
+ @logger.read.must_match(/\A- - - \[\d\d\/[A-Z][a-z]{2}\/\d\d\d\d:\d\d:\d\d:\d\d [-+]\d\d\d\d\] "GET \/ " 500 3 0.\d\d\d\d\n\z/)
84
84
  end
85
85
  end
@@ -15,6 +15,14 @@ describe "json_parser plugin" do
15
15
  body('rack.input'=>StringIO.new('a[b]=1'), 'REQUEST_METHOD'=>'POST').must_equal '1'
16
16
  end
17
17
 
18
+ it "parses incoming json if content type specifies json and body is already read" do
19
+ @app.route do |r|
20
+ r.body.read
21
+ r.params['a']['b'].to_s
22
+ end
23
+ body('rack.input'=>StringIO.new('{"a":{"b":1}}'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal '1'
24
+ end
25
+
18
26
  it "returns 400 for invalid json" do
19
27
  req('rack.input'=>StringIO.new('{"a":{"b":1}'), 'CONTENT_TYPE'=>'text/json', 'REQUEST_METHOD'=>'POST').must_equal [400, {}, []]
20
28
  end
@@ -28,8 +28,11 @@ if RUBY_VERSION >= '2'
28
28
  r.get('g', String){|k| session[k].to_s}
29
29
  r.get('sct'){|i| session; env['roda.session.created_at'].to_s}
30
30
  r.get('ssct', Integer){|i| session; (env['roda.session.created_at'] -= i).to_s}
31
+ r.get('ssct2', Integer, String, String){|i, k, v| session[k] = v; (env['roda.session.created_at'] -= i).to_s}
31
32
  r.get('sc'){session.clear; 'c'}
32
33
  r.get('cs', String, String){|k, v| clear_session; session[k] = v}
34
+ r.get('cat'){t = r.session_created_at; t.strftime("%F") if t}
35
+ r.get('uat'){t = r.session_updated_at; t.strftime("%F") if t}
33
36
  ''
34
37
  end
35
38
  end
@@ -68,6 +71,16 @@ if RUBY_VERSION >= '2'
68
71
  errors.must_equal []
69
72
  end
70
73
 
74
+ it "allows accessing session creation and last update times" do
75
+ status('/cat').must_equal 404
76
+ status('/uat').must_equal 404
77
+ status('/s/foo/bar').must_equal 200
78
+ body('/cat').must_equal Date.today.strftime("%F")
79
+ body('/uat').must_equal Date.today.strftime("%F")
80
+ status('/ssct2/172800/bar/baz').must_equal 200
81
+ body('/cat').must_equal((Date.today - 2).strftime("%F"))
82
+ end
83
+
71
84
  it "does not add Set-Cookie header if session does not change, unless outside :skip_within seconds" do
72
85
  req('/').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"0"}, [""]]
73
86
  _, h, b = req('/s/foo/bar')
@@ -97,16 +110,16 @@ if RUBY_VERSION >= '2'
97
110
 
98
111
  it "removes session cookie when session is submitted but empty after request" do
99
112
  body('/s/foo/bar').must_equal 'bar'
100
- sct = body('/sct').to_i
113
+ body('/sct').to_i
101
114
  body('/g/foo').must_equal 'bar'
102
115
 
103
116
  _, h, b = req('/sc')
104
117
 
105
118
  # Parameters can come in any order, and only the final parameter may omit the ;
106
119
  ['roda.session=', 'max-age=0', 'path=/'].each do |param|
107
- h['Set-Cookie'].must_match /#{Regexp.escape(param)}(;|\z)/
120
+ h['Set-Cookie'].must_match(/#{Regexp.escape(param)}(;|\z)/)
108
121
  end
109
- h['Set-Cookie'].must_match /expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/
122
+ h['Set-Cookie'].must_match(/expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/)
110
123
 
111
124
  b.must_equal ['c']
112
125
 
@@ -116,16 +129,16 @@ if RUBY_VERSION >= '2'
116
129
  it "removes session cookie even when max-age and expires are in cookie options" do
117
130
  app.plugin :sessions, :cookie_options=>{:max_age=>'1000', :expires=>Time.now+1000}
118
131
  body('/s/foo/bar').must_equal 'bar'
119
- sct = body('/sct').to_i
132
+ body('/sct').to_i
120
133
  body('/g/foo').must_equal 'bar'
121
134
 
122
135
  _, h, b = req('/sc')
123
136
 
124
137
  # Parameters can come in any order, and only the final parameter may omit the ;
125
138
  ['roda.session=', 'max-age=0', 'path=/'].each do |param|
126
- h['Set-Cookie'].must_match /#{Regexp.escape(param)}(;|\z)/
139
+ h['Set-Cookie'].must_match(/#{Regexp.escape(param)}(;|\z)/)
127
140
  end
128
- h['Set-Cookie'].must_match /expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/
141
+ h['Set-Cookie'].must_match(/expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/)
129
142
 
130
143
  b.must_equal ['c']
131
144
 
@@ -216,7 +216,7 @@ describe "streaming plugin" do
216
216
  end
217
217
  end
218
218
 
219
- *, b = req
219
+ req
220
220
  q.deq
221
221
  a.must_equal %w'a b c d e f g h i j'
222
222
  end
@@ -238,7 +238,7 @@ describe "streaming plugin" do
238
238
  end
239
239
  end
240
240
 
241
- *, b = req
241
+ req
242
242
  q.deq
243
243
  a.must_equal %w'a b c d e'
244
244
  end
@@ -27,11 +27,11 @@ describe "session handling" do
27
27
  end
28
28
  end
29
29
 
30
- _, h, b = req
30
+ _, _, b = req
31
31
  b.join.must_equal 'ab'
32
- _, h, b = req
32
+ _, _, b = req
33
33
  b.join.must_equal 'abb'
34
- _, h, b = req
34
+ _, _, b = req
35
35
  b.join.must_equal 'abbb'
36
36
  end
37
37
  end
@@ -11,6 +11,7 @@ if ENV['COVERAGE']
11
11
 
12
12
  def SimpleCov.roda_coverage(opts = {})
13
13
  start do
14
+ enable_coverage :branch if ENV['BRANCH_COVERAGE']
14
15
  add_filter "/spec/"
15
16
  add_group('Missing'){|src| src.covered_percent < 100}
16
17
  add_group('Covered'){|src| src.covered_percent == 100}
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.27.0
4
+ version: 3.28.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: 2019-12-13 00:00:00.000000000 Z
11
+ date: 2020-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -238,6 +238,7 @@ extra_rdoc_files:
238
238
  - doc/release_notes/3.25.0.txt
239
239
  - doc/release_notes/3.26.0.txt
240
240
  - doc/release_notes/3.27.0.txt
241
+ - doc/release_notes/3.28.0.txt
241
242
  files:
242
243
  - CHANGELOG
243
244
  - MIT-LICENSE
@@ -301,6 +302,7 @@ files:
301
302
  - doc/release_notes/3.25.0.txt
302
303
  - doc/release_notes/3.26.0.txt
303
304
  - doc/release_notes/3.27.0.txt
305
+ - doc/release_notes/3.28.0.txt
304
306
  - doc/release_notes/3.3.0.txt
305
307
  - doc/release_notes/3.4.0.txt
306
308
  - doc/release_notes/3.5.0.txt
@@ -578,7 +580,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
578
580
  - !ruby/object:Gem::Version
579
581
  version: '0'
580
582
  requirements: []
581
- rubygems_version: 3.0.3
583
+ rubygems_version: 3.1.2
582
584
  signing_key:
583
585
  specification_version: 4
584
586
  summary: Routing tree web toolkit