roda 3.27.0 → 3.28.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: 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