roda 3.18.0 → 3.19.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.
@@ -0,0 +1,79 @@
1
+ require_relative "../spec_helper"
2
+
3
+ describe "match hook plugin" do
4
+ it "matches verbs" do
5
+ matches = []
6
+ app(:bare) do
7
+ plugin :match_hook
8
+ match_hook do
9
+ matches << [request.matched_path, request.remaining_path]
10
+ end
11
+ route do |r|
12
+ r.on "foo" do
13
+ r.on "bar" do
14
+ r.get "baz" do
15
+ "fbb"
16
+ end
17
+ "fb"
18
+ end
19
+ "f"
20
+ end
21
+
22
+ r.get "bar" do
23
+ "b"
24
+ end
25
+
26
+ r.root do
27
+ "r"
28
+ end
29
+
30
+ "n"
31
+ end
32
+ end
33
+
34
+ body("/foo").must_equal 'f'
35
+ matches.must_equal [["/foo", ""]]
36
+
37
+ matches.clear
38
+ body("/foo/bar").must_equal 'fb'
39
+ matches.must_equal [["/foo", "/bar"], ["/foo/bar", ""]]
40
+
41
+ matches.clear
42
+ body("/foo/bar/baz").must_equal 'fbb'
43
+ matches.must_equal [["/foo", "/bar/baz"], ["/foo/bar", "/baz"], ["/foo/bar/baz", ""]]
44
+
45
+ matches.clear
46
+ body("/bar").must_equal 'b'
47
+ matches.must_equal [["/bar", ""]]
48
+
49
+ matches.clear
50
+ body.must_equal 'r'
51
+ matches.must_equal [["", "/"]]
52
+
53
+ matches.clear
54
+ body('/x').must_equal 'n'
55
+ matches.must_be_empty
56
+
57
+ matches.clear
58
+ body("/foo/baz").must_equal 'f'
59
+ matches.must_equal [["/foo", "/baz"]]
60
+
61
+ matches.clear
62
+ body("/foo/bar/bar").must_equal 'fb'
63
+ matches.must_equal [["/foo", "/bar/bar"], ["/foo/bar", "/bar"]]
64
+
65
+ app.match_hook{matches << :x }
66
+
67
+ matches.clear
68
+ body("/foo/bar/baz").must_equal 'fbb'
69
+ matches.must_equal [["/foo", "/bar/baz"], :x, ["/foo/bar", "/baz"], :x, ["/foo/bar/baz", ""], :x]
70
+
71
+ app.freeze
72
+
73
+ matches.clear
74
+ body("/foo/bar/baz").must_equal 'fbb'
75
+ matches.must_equal [["/foo", "/bar/baz"], :x, ["/foo/bar", "/baz"], :x, ["/foo/bar/baz", ""], :x]
76
+
77
+ app.opts[:match_hooks].must_be :frozen?
78
+ end
79
+ end
@@ -91,6 +91,7 @@ describe "middleware plugin" do
91
91
  use a
92
92
  route{}
93
93
  end
94
+ body.must_equal 'a'
94
95
  a.opts[:a] = 'b'
95
96
  body.must_equal 'a'
96
97
  end
@@ -1,396 +1,439 @@
1
1
  require_relative "../spec_helper"
2
2
 
3
3
  if RUBY_VERSION >= '2'
4
- describe "sessions plugin" do
5
- include CookieJar
4
+ [true, false].each do |per_cookie_cipher_secret|
5
+ describe "sessions plugin with per_cookie_cipher_secret: #{per_cookie_cipher_secret}" do
6
+ include CookieJar
6
7
 
7
- def req(path, opts={})
8
- @errors ||= (errors = []; def errors.puts(s) self << s; end; errors)
9
- super(path, opts.merge('rack.errors'=>@errors))
10
- end
8
+ def req(path, opts={})
9
+ @errors ||= (errors = []; def errors.puts(s) self << s; end; errors)
10
+ super(path, opts.merge('rack.errors'=>@errors))
11
+ end
11
12
 
12
- def errors
13
- e = @errors.dup
14
- @errors.clear
15
- e
16
- end
13
+ def errors
14
+ e = @errors.dup
15
+ @errors.clear
16
+ e
17
+ end
17
18
 
18
- before do
19
- app(:bare) do
20
- plugin :sessions, :secret=>'1'*64
21
- route do |r|
22
- if r.GET['sut']
23
- session
24
- env['roda.session.updated_at'] -= r.GET['sut'].to_i if r.GET['sut']
19
+ before do
20
+ app(:bare) do
21
+ plugin :sessions, :secret=>'1'*64, :per_cookie_cipher_secret=>per_cookie_cipher_secret
22
+ route do |r|
23
+ if r.GET['sut']
24
+ session
25
+ env['roda.session.updated_at'] -= r.GET['sut'].to_i if r.GET['sut']
26
+ end
27
+ r.get('s', String, String){|k, v| session[k] = v}
28
+ r.get('g', String){|k| session[k].to_s}
29
+ r.get('sct'){|i| session; env['roda.session.created_at'].to_s}
30
+ r.get('ssct', Integer){|i| session; (env['roda.session.created_at'] -= i).to_s}
31
+ r.get('sc'){session.clear; 'c'}
32
+ r.get('cs', String, String){|k, v| clear_session; session[k] = v}
33
+ ''
25
34
  end
26
- r.get('s', String, String){|k, v| session[k] = v}
27
- r.get('g', String){|k| session[k].to_s}
28
- r.get('sct'){|i| session; env['roda.session.created_at'].to_s}
29
- r.get('ssct', Integer){|i| session; (env['roda.session.created_at'] -= i).to_s}
30
- r.get('sc'){session.clear; 'c'}
31
- r.get('cs', String, String){|k, v| clear_session; session[k] = v}
32
- ''
33
35
  end
34
36
  end
35
- end
36
37
 
37
- it "requires appropriate :secret option" do
38
- proc{app(:bare){plugin :sessions}}.must_raise Roda::RodaError
39
- proc{app(:bare){plugin :sessions, :secret=>Object.new}}.must_raise Roda::RodaError
40
- proc{app(:bare){plugin :sessions, :secret=>'1'*63}}.must_raise Roda::RodaError
41
- end
38
+ it "requires appropriate :secret option" do
39
+ proc{app(:bare){plugin :sessions}}.must_raise Roda::RodaError
40
+ proc{app(:bare){plugin :sessions, :secret=>Object.new}}.must_raise Roda::RodaError
41
+ proc{app(:bare){plugin :sessions, :secret=>'1'*63}}.must_raise Roda::RodaError
42
+ end
42
43
 
43
- it "has session store data between requests" do
44
- req('/').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"0"}, [""]]
45
- body('/s/foo/bar').must_equal 'bar'
46
- body('/g/foo').must_equal 'bar'
44
+ it "has session store data between requests" do
45
+ req('/').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"0"}, [""]]
46
+ body('/s/foo/bar').must_equal 'bar'
47
+ body('/g/foo').must_equal 'bar'
47
48
 
48
- body('/s/foo/baz').must_equal 'baz'
49
- body('/g/foo').must_equal 'baz'
49
+ body('/s/foo/baz').must_equal 'baz'
50
+ body('/g/foo').must_equal 'baz'
50
51
 
51
- body("/s/foo/\u1234").must_equal "\u1234"
52
- body("/g/foo").must_equal "\u1234"
52
+ body("/s/foo/\u1234").must_equal "\u1234"
53
+ body("/g/foo").must_equal "\u1234"
53
54
 
54
- errors.must_equal []
55
- end
55
+ errors.must_equal []
56
+ end
56
57
 
57
- it "does not add Set-Cookie header if session does not change, unless outside :skip_within seconds" do
58
- req('/').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"0"}, [""]]
59
- _, h, b = req('/s/foo/bar')
60
- h['Set-Cookie'].must_match(/\Aroda.session/)
61
- b.must_equal ["bar"]
62
- req('/g/foo').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["bar"]]
63
- req('/s/foo/bar').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["bar"]]
64
-
65
- _, h, b = req('/s/foo/baz')
66
- h['Set-Cookie'].must_match(/\Aroda.session/)
67
- b.must_equal ["baz"]
68
- req('/g/foo').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["baz"]]
69
-
70
- req('/g/foo', 'QUERY_STRING'=>'sut=3500').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["baz"]]
71
- _, h, b = req('/g/foo', 'QUERY_STRING'=>'sut=3700')
72
- h['Set-Cookie'].must_match(/\Aroda.session/)
73
- b.must_equal ["baz"]
74
-
75
- @app.plugin(:sessions, :skip_within=>3800)
76
- req('/g/foo', 'QUERY_STRING'=>'sut=3700').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["baz"]]
77
- _, h, b = req('/g/foo', 'QUERY_STRING'=>'sut=3900')
78
- h['Set-Cookie'].must_match(/\Aroda.session/)
79
- b.must_equal ["baz"]
80
-
81
- errors.must_equal []
82
- end
58
+ it "supports loading sessions created when per_cookie_cipher_secret: #{!per_cookie_cipher_secret} " do
59
+ req('/').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"0"}, [""]]
60
+ body('/s/foo/bar').must_equal 'bar'
61
+ body('/g/foo').must_equal 'bar'
83
62
 
84
- it "removes session cookie when session is submitted but empty after request" do
85
- body('/s/foo/bar').must_equal 'bar'
86
- sct = body('/sct').to_i
87
- body('/g/foo').must_equal 'bar'
63
+ app.plugin :sessions, :per_cookie_cipher_secret=>!per_cookie_cipher_secret
88
64
 
89
- _, h, b = req('/sc')
65
+ body('/s/foo/baz').must_equal 'baz'
66
+ body('/g/foo').must_equal 'baz'
90
67
 
91
- # Parameters can come in any order, and only the final parameter may omit the ;
92
- ['roda.session=', 'max-age=0', 'path=/'].each do |param|
93
- h['Set-Cookie'].must_match /#{Regexp.escape(param)}(;|\z)/
68
+ errors.must_equal []
94
69
  end
95
- h['Set-Cookie'].must_match /expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/
96
70
 
97
- b.must_equal ['c']
71
+ it "does not add Set-Cookie header if session does not change, unless outside :skip_within seconds" do
72
+ req('/').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"0"}, [""]]
73
+ _, h, b = req('/s/foo/bar')
74
+ h['Set-Cookie'].must_match(/\Aroda.session/)
75
+ b.must_equal ["bar"]
76
+ req('/g/foo').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["bar"]]
77
+ req('/s/foo/bar').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["bar"]]
78
+
79
+ _, h, b = req('/s/foo/baz')
80
+ h['Set-Cookie'].must_match(/\Aroda.session/)
81
+ b.must_equal ["baz"]
82
+ req('/g/foo').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["baz"]]
83
+
84
+ req('/g/foo', 'QUERY_STRING'=>'sut=3500').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["baz"]]
85
+ _, h, b = req('/g/foo', 'QUERY_STRING'=>'sut=3700')
86
+ h['Set-Cookie'].must_match(/\Aroda.session/)
87
+ b.must_equal ["baz"]
88
+
89
+ @app.plugin(:sessions, :skip_within=>3800)
90
+ req('/g/foo', 'QUERY_STRING'=>'sut=3700').must_equal [200, {"Content-Type"=>"text/html", "Content-Length"=>"3"}, ["baz"]]
91
+ _, h, b = req('/g/foo', 'QUERY_STRING'=>'sut=3900')
92
+ h['Set-Cookie'].must_match(/\Aroda.session/)
93
+ b.must_equal ["baz"]
94
+
95
+ errors.must_equal []
96
+ end
98
97
 
99
- errors.must_equal []
100
- end
98
+ it "removes session cookie when session is submitted but empty after request" do
99
+ body('/s/foo/bar').must_equal 'bar'
100
+ sct = body('/sct').to_i
101
+ body('/g/foo').must_equal 'bar'
101
102
 
102
- it "removes session cookie even when max-age and expires are in cookie options" do
103
- app.plugin :sessions, :cookie_options=>{:max_age=>'1000', :expires=>Time.now+1000}
104
- body('/s/foo/bar').must_equal 'bar'
105
- sct = body('/sct').to_i
106
- body('/g/foo').must_equal 'bar'
103
+ _, h, b = req('/sc')
107
104
 
108
- _, h, b = req('/sc')
105
+ # Parameters can come in any order, and only the final parameter may omit the ;
106
+ ['roda.session=', 'max-age=0', 'path=/'].each do |param|
107
+ h['Set-Cookie'].must_match /#{Regexp.escape(param)}(;|\z)/
108
+ end
109
+ h['Set-Cookie'].must_match /expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/
110
+
111
+ b.must_equal ['c']
109
112
 
110
- # Parameters can come in any order, and only the final parameter may omit the ;
111
- ['roda.session=', 'max-age=0', 'path=/'].each do |param|
112
- h['Set-Cookie'].must_match /#{Regexp.escape(param)}(;|\z)/
113
+ errors.must_equal []
113
114
  end
114
- h['Set-Cookie'].must_match /expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/
115
115
 
116
- b.must_equal ['c']
116
+ it "removes session cookie even when max-age and expires are in cookie options" do
117
+ app.plugin :sessions, :cookie_options=>{:max_age=>'1000', :expires=>Time.now+1000}
118
+ body('/s/foo/bar').must_equal 'bar'
119
+ sct = body('/sct').to_i
120
+ body('/g/foo').must_equal 'bar'
117
121
 
118
- errors.must_equal []
119
- end
122
+ _, h, b = req('/sc')
120
123
 
121
- it "sets new session create time when clear_session is called even when session is not empty when serializing" do
122
- body('/s/foo/bar').must_equal 'bar'
123
- sct = body('/sct').to_i
124
- body('/g/foo').must_equal 'bar'
125
- body('/sct').to_i.must_equal sct
126
- body('/ssct/10').to_i.must_equal(sct - 10)
124
+ # Parameters can come in any order, and only the final parameter may omit the ;
125
+ ['roda.session=', 'max-age=0', 'path=/'].each do |param|
126
+ h['Set-Cookie'].must_match /#{Regexp.escape(param)}(;|\z)/
127
+ end
128
+ h['Set-Cookie'].must_match /expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/
127
129
 
128
- body('/cs/foo/baz').must_equal 'baz'
129
- body('/sct').to_i.must_be :>=, sct
130
+ b.must_equal ['c']
130
131
 
131
- errors.must_equal []
132
- end
132
+ errors.must_equal []
133
+ end
133
134
 
134
- it "should include HttpOnly and secure cookie options appropriately" do
135
- h = header('Set-Cookie', '/s/foo/bar')
136
- h.must_include('; HttpOnly')
137
- h.wont_include('; secure')
135
+ it "sets new session create time when clear_session is called even when session is not empty when serializing" do
136
+ body('/s/foo/bar').must_equal 'bar'
137
+ sct = body('/sct').to_i
138
+ body('/g/foo').must_equal 'bar'
139
+ body('/sct').to_i.must_equal sct
140
+ body('/ssct/10').to_i.must_equal(sct - 10)
138
141
 
139
- h = header('Set-Cookie', '/s/foo/baz', 'HTTPS'=>'on')
140
- h.must_include('; HttpOnly')
141
- h.must_include('; secure')
142
+ body('/cs/foo/baz').must_equal 'baz'
143
+ body('/sct').to_i.must_be :>=, sct
142
144
 
143
- @app.plugin(:sessions, :cookie_options=>{})
144
- h = header('Set-Cookie', '/s/foo/bar')
145
- h.must_include('; HttpOnly')
146
- h.wont_include('; secure')
147
- end
145
+ errors.must_equal []
146
+ end
148
147
 
149
- it "should merge :cookie_options options into the default cookie options" do
150
- @app.plugin(:sessions, :cookie_options=>{:secure=>true})
151
- h = header('Set-Cookie', '/s/foo/bar')
152
- h.must_include('; HttpOnly')
153
- h.must_include('; path=/')
154
- h.must_include('; secure')
155
- end
148
+ it "should include HttpOnly and secure cookie options appropriately" do
149
+ h = header('Set-Cookie', '/s/foo/bar')
150
+ h.must_include('; HttpOnly')
151
+ h.wont_include('; secure')
152
+
153
+ h = header('Set-Cookie', '/s/foo/baz', 'HTTPS'=>'on')
154
+ h.must_include('; HttpOnly')
155
+ h.must_include('; secure')
156
156
 
157
- it "handles secret rotation using :old_secret option" do
158
- body('/s/foo/bar').must_equal 'bar'
159
- body('/g/foo').must_equal 'bar'
157
+ @app.plugin(:sessions, :cookie_options=>{})
158
+ h = header('Set-Cookie', '/s/foo/bar')
159
+ h.must_include('; HttpOnly')
160
+ h.wont_include('; secure')
161
+ end
162
+
163
+ it "should merge :cookie_options options into the default cookie options" do
164
+ @app.plugin(:sessions, :cookie_options=>{:secure=>true})
165
+ h = header('Set-Cookie', '/s/foo/bar')
166
+ h.must_include('; HttpOnly')
167
+ h.must_include('; path=/')
168
+ h.must_include('; secure')
169
+ end
160
170
 
161
- old_cookie = @cookie
162
- @app.plugin(:sessions, :secret=>'2'*64, :old_secret=>'1'*64)
163
- body('/g/foo', 'QUERY_STRING'=>'sut=3700').must_equal 'bar'
171
+ it "handles secret rotation using :old_secret option" do
172
+ body('/s/foo/bar').must_equal 'bar'
173
+ body('/g/foo').must_equal 'bar'
164
174
 
165
- @app.plugin(:sessions, :secret=>'2'*64, :old_secret=>nil)
166
- body('/g/foo', 'QUERY_STRING'=>'sut=3700').must_equal 'bar'
175
+ old_cookie = @cookie
176
+ @app.plugin(:sessions, :secret=>'2'*64, :old_secret=>'1'*64)
177
+ body('/g/foo', 'QUERY_STRING'=>'sut=3700').must_equal 'bar'
167
178
 
168
- @cookie = old_cookie
169
- body('/g/foo').must_equal ''
170
- errors.must_equal ["Not decoding session: HMAC invalid"]
179
+ @app.plugin(:sessions, :secret=>'2'*64, :old_secret=>nil)
180
+ body('/g/foo', 'QUERY_STRING'=>'sut=3700').must_equal 'bar'
171
181
 
172
- proc{app(:bare){plugin :sessions, :old_secret=>'1'*63}}.must_raise Roda::RodaError
173
- proc{app(:bare){plugin :sessions, :old_secret=>Object.new}}.must_raise Roda::RodaError
174
- end
182
+ @cookie = old_cookie
183
+ body('/g/foo').must_equal ''
184
+ errors.must_equal ["Not decoding session: HMAC invalid"]
175
185
 
176
- it "pads data by default to make it more difficult to guess session contents based on size" do
177
- long = "bar"*35
178
-
179
- _, h1, b = req('/s/foo/bar')
180
- b.must_equal ['bar']
181
- _, h2, b = req('/s/foo/bar', 'QUERY_STRING'=>'sut=3700')
182
- b.must_equal ['bar']
183
- _, h3, b = req('/s/foo/bar2')
184
- b.must_equal ['bar2']
185
- _, h4, b = req("/s/foo/#{long}")
186
- b.must_equal [long]
187
- h1['Set-Cookie'].length.must_equal h2['Set-Cookie'].length
188
- h1['Set-Cookie'].wont_equal h2['Set-Cookie']
189
- h1['Set-Cookie'].length.must_equal h3['Set-Cookie'].length
190
- h1['Set-Cookie'].wont_equal h3['Set-Cookie']
191
- h1['Set-Cookie'].length.wont_equal h4['Set-Cookie'].length
192
-
193
- @app.plugin(:sessions, :pad_size=>256)
194
-
195
- _, h1, b = req('/s/foo/bar')
196
- b.must_equal ['bar']
197
- _, h2, b = req('/s/foo/bar', 'QUERY_STRING'=>'sut=3700')
198
- b.must_equal ['bar']
199
- _, h3, b = req('/s/foo/bar2')
200
- b.must_equal ['bar2']
201
- _, h4, b = req("/s/foo/#{long}")
202
- b.must_equal [long]
203
- h1['Set-Cookie'].length.must_equal h2['Set-Cookie'].length
204
- h1['Set-Cookie'].wont_equal h2['Set-Cookie']
205
- h1['Set-Cookie'].length.must_equal h3['Set-Cookie'].length
206
- h1['Set-Cookie'].wont_equal h3['Set-Cookie']
207
- h1['Set-Cookie'].length.must_equal h4['Set-Cookie'].length
208
- h1['Set-Cookie'].wont_equal h3['Set-Cookie']
209
-
210
- @app.plugin(:sessions, :pad_size=>nil)
211
-
212
- _, h1, b = req('/s/foo/bar')
213
- b.must_equal ['bar']
214
- _, h2, b = req('/s/foo/bar', 'QUERY_STRING'=>'sut=3700')
215
- b.must_equal ['bar']
216
- _, h3, b = req('/s/foo/bar2')
217
- b.must_equal ['bar2']
218
- h1['Set-Cookie'].length.must_equal h2['Set-Cookie'].length
219
- h1['Set-Cookie'].wont_equal h2['Set-Cookie']
220
- if !defined?(JRUBY_VERSION) || JRUBY_VERSION >= '9.2'
221
- h1['Set-Cookie'].length.wont_equal h3['Set-Cookie'].length
186
+ proc{app(:bare){plugin :sessions, :old_secret=>'1'*63}}.must_raise Roda::RodaError
187
+ proc{app(:bare){plugin :sessions, :old_secret=>Object.new}}.must_raise Roda::RodaError
222
188
  end
223
189
 
224
- proc{@app.plugin(:sessions, :pad_size=>0)}.must_raise Roda::RodaError
225
- proc{@app.plugin(:sessions, :pad_size=>1)}.must_raise Roda::RodaError
226
- proc{@app.plugin(:sessions, :pad_size=>Object.new)}.must_raise Roda::RodaError
190
+ it "handles secret rotation using :old_secret option when also changing :per_cookie_cipher_secret option" do
191
+ body('/s/foo/bar').must_equal 'bar'
192
+ body('/g/foo').must_equal 'bar'
227
193
 
228
- errors.must_equal []
229
- end
194
+ old_cookie = @cookie
195
+ @app.plugin(:sessions, :secret=>'2'*64, :old_secret=>'1'*64, :per_cookie_cipher_secret=>!per_cookie_cipher_secret)
230
196
 
231
- it "compresses data over a certain size by default" do
232
- long = 'b'*8192
233
- proc{body("/s/foo/#{long}")}.must_raise Roda::RodaPlugins::Sessions::CookieTooLarge
197
+ body('/g/foo', 'QUERY_STRING'=>'sut=3700').must_equal 'bar'
234
198
 
235
- @app.plugin(:sessions, :gzip_over=>8000)
236
- body("/s/foo/#{long}").must_equal long
237
- body("/g/foo", 'QUERY_STRING'=>'sut=3700').must_equal long
199
+ @app.plugin(:sessions, :secret=>'2'*64, :old_secret=>nil)
200
+ body('/g/foo', 'QUERY_STRING'=>'sut=3700').must_equal 'bar'
238
201
 
239
- @app.plugin(:sessions, :gzip_over=>15000)
240
- proc{body("/g/foo", 'QUERY_STRING'=>'sut=3700')}.must_raise Roda::RodaPlugins::Sessions::CookieTooLarge
202
+ @cookie = old_cookie
203
+ body('/g/foo').must_equal ''
204
+ errors.must_equal ["Not decoding session: HMAC invalid"]
241
205
 
242
- errors.must_equal []
243
- end
206
+ proc{app(:bare){plugin :sessions, :old_secret=>'1'*63}}.must_raise Roda::RodaError
207
+ proc{app(:bare){plugin :sessions, :old_secret=>Object.new}}.must_raise Roda::RodaError
208
+ end
244
209
 
245
- it "raises CookieTooLarge if cookie is too large" do
246
- proc{req('/s/foo/'+Base64.urlsafe_encode64(SecureRandom.random_bytes(8192)))}.must_raise Roda::RodaPlugins::Sessions::CookieTooLarge
247
- end
210
+ it "pads data by default to make it more difficult to guess session contents based on size" do
211
+ long = "bar"*35
212
+
213
+ _, h1, b = req('/s/foo/bar')
214
+ b.must_equal ['bar']
215
+ _, h2, b = req('/s/foo/bar', 'QUERY_STRING'=>'sut=3700')
216
+ b.must_equal ['bar']
217
+ _, h3, b = req('/s/foo/bar2')
218
+ b.must_equal ['bar2']
219
+ _, h4, b = req("/s/foo/#{long}")
220
+ b.must_equal [long]
221
+ h1['Set-Cookie'].length.must_equal h2['Set-Cookie'].length
222
+ h1['Set-Cookie'].wont_equal h2['Set-Cookie']
223
+ h1['Set-Cookie'].length.must_equal h3['Set-Cookie'].length
224
+ h1['Set-Cookie'].wont_equal h3['Set-Cookie']
225
+ h1['Set-Cookie'].length.wont_equal h4['Set-Cookie'].length
226
+
227
+ @app.plugin(:sessions, :pad_size=>256)
228
+
229
+ _, h1, b = req('/s/foo/bar')
230
+ b.must_equal ['bar']
231
+ _, h2, b = req('/s/foo/bar', 'QUERY_STRING'=>'sut=3700')
232
+ b.must_equal ['bar']
233
+ _, h3, b = req('/s/foo/bar2')
234
+ b.must_equal ['bar2']
235
+ _, h4, b = req("/s/foo/#{long}")
236
+ b.must_equal [long]
237
+ h1['Set-Cookie'].length.must_equal h2['Set-Cookie'].length
238
+ h1['Set-Cookie'].wont_equal h2['Set-Cookie']
239
+ h1['Set-Cookie'].length.must_equal h3['Set-Cookie'].length
240
+ h1['Set-Cookie'].wont_equal h3['Set-Cookie']
241
+ h1['Set-Cookie'].length.must_equal h4['Set-Cookie'].length
242
+ h1['Set-Cookie'].wont_equal h3['Set-Cookie']
243
+
244
+ @app.plugin(:sessions, :pad_size=>nil)
245
+
246
+ _, h1, b = req('/s/foo/bar')
247
+ b.must_equal ['bar']
248
+ _, h2, b = req('/s/foo/bar', 'QUERY_STRING'=>'sut=3700')
249
+ b.must_equal ['bar']
250
+ _, h3, b = req('/s/foo/bar2')
251
+ b.must_equal ['bar2']
252
+ h1['Set-Cookie'].length.must_equal h2['Set-Cookie'].length
253
+ h1['Set-Cookie'].wont_equal h2['Set-Cookie']
254
+ if !defined?(JRUBY_VERSION) || JRUBY_VERSION >= '9.2'
255
+ h1['Set-Cookie'].length.wont_equal h3['Set-Cookie'].length
256
+ end
248
257
 
249
- it "ignores session cookies if session exceeds max time since create" do
250
- body("/s/foo/bar").must_equal 'bar'
251
- body("/g/foo").must_equal 'bar'
258
+ proc{@app.plugin(:sessions, :pad_size=>0)}.must_raise Roda::RodaError
259
+ proc{@app.plugin(:sessions, :pad_size=>1)}.must_raise Roda::RodaError
260
+ proc{@app.plugin(:sessions, :pad_size=>Object.new)}.must_raise Roda::RodaError
252
261
 
253
- @app.plugin(:sessions, :max_seconds=>-1)
254
- body("/g/foo").must_equal ''
255
- errors.must_equal ["Not returning session: maximum session time expired"]
262
+ errors.must_equal []
263
+ end
256
264
 
257
- @app.plugin(:sessions, :max_seconds=>10)
258
- body("/s/foo/bar").must_equal 'bar'
259
- body("/g/foo").must_equal 'bar'
265
+ it "compresses data over a certain size by default" do
266
+ long = 'b'*8192
267
+ proc{body("/s/foo/#{long}")}.must_raise Roda::RodaPlugins::Sessions::CookieTooLarge
260
268
 
261
- errors.must_equal []
262
- end
269
+ @app.plugin(:sessions, :gzip_over=>8000)
270
+ body("/s/foo/#{long}").must_equal long
271
+ body("/g/foo", 'QUERY_STRING'=>'sut=3700').must_equal long
263
272
 
264
- it "ignores session cookies if session exceeds max idle time since update" do
265
- body("/s/foo/bar").must_equal 'bar'
266
- body("/g/foo").must_equal 'bar'
273
+ @app.plugin(:sessions, :gzip_over=>15000)
274
+ proc{body("/g/foo", 'QUERY_STRING'=>'sut=3700')}.must_raise Roda::RodaPlugins::Sessions::CookieTooLarge
267
275
 
268
- @app.plugin(:sessions, :max_idle_seconds=>-1)
269
- body("/g/foo").must_equal ''
270
- errors.must_equal ["Not returning session: maximum session idle time expired"]
276
+ errors.must_equal []
277
+ end
271
278
 
272
- @app.plugin(:sessions, :max_idle_seconds=>10)
273
- body("/s/foo/bar").must_equal 'bar'
274
- body("/g/foo").must_equal 'bar'
279
+ it "raises CookieTooLarge if cookie is too large" do
280
+ proc{req('/s/foo/'+Base64.urlsafe_encode64(SecureRandom.random_bytes(8192)))}.must_raise Roda::RodaPlugins::Sessions::CookieTooLarge
281
+ end
275
282
 
276
- errors.must_equal []
277
- end
283
+ it "ignores session cookies if session exceeds max time since create" do
284
+ body("/s/foo/bar").must_equal 'bar'
285
+ body("/g/foo").must_equal 'bar'
278
286
 
279
- it "supports :serializer and :parser options to override serializer/deserializer" do
280
- body('/s/foo/bar').must_equal 'bar'
287
+ @app.plugin(:sessions, :max_seconds=>-1)
288
+ body("/g/foo").must_equal ''
289
+ errors.must_equal ["Not returning session: maximum session time expired"]
281
290
 
282
- @app.plugin(:sessions, :parser=>proc{|s| JSON.parse("{#{s[1...-1].reverse}}")})
283
- body('/g/rab').must_equal 'oof'
291
+ @app.plugin(:sessions, :max_seconds=>10)
292
+ body("/s/foo/bar").must_equal 'bar'
293
+ body("/g/foo").must_equal 'bar'
284
294
 
285
- @app.plugin(:sessions, :serializer=>proc{|s| s.to_json.upcase})
295
+ errors.must_equal []
296
+ end
286
297
 
287
- body('/s/foo/baz').must_equal 'baz'
288
- body('/g/ZAB').must_equal 'OOF'
298
+ it "ignores session cookies if session exceeds max idle time since update" do
299
+ body("/s/foo/bar").must_equal 'bar'
300
+ body("/g/foo").must_equal 'bar'
289
301
 
290
- errors.must_equal []
291
- end
302
+ @app.plugin(:sessions, :max_idle_seconds=>-1)
303
+ body("/g/foo").must_equal ''
304
+ errors.must_equal ["Not returning session: maximum session idle time expired"]
292
305
 
293
- it "logs session decoding errors to rack.errors" do
294
- body('/s/foo/bar').must_equal 'bar'
295
- c = @cookie.dup
296
- k = c.split('=', 2)[0] + '='
306
+ @app.plugin(:sessions, :max_idle_seconds=>10)
307
+ body("/s/foo/bar").must_equal 'bar'
308
+ body("/g/foo").must_equal 'bar'
297
309
 
298
- @cookie[20] = '!'
299
- body('/g/foo').must_equal ''
300
- errors.must_equal ["Unable to decode session: invalid base64"]
310
+ errors.must_equal []
311
+ end
301
312
 
302
- @cookie = k+Base64.urlsafe_encode64('1'*60)
303
- body('/g/foo').must_equal ''
304
- errors.must_equal ["Unable to decode session: data too short"]
313
+ it "supports :serializer and :parser options to override serializer/deserializer" do
314
+ body('/s/foo/bar').must_equal 'bar'
305
315
 
306
- @cookie = k+Base64.urlsafe_encode64('1'*75)
307
- body('/g/foo').must_equal ''
308
- errors.must_equal ["Unable to decode session: version marker unsupported"]
316
+ @app.plugin(:sessions, :parser=>proc{|s| JSON.parse("{#{s[1...-1].reverse}}")})
317
+ body('/g/rab').must_equal 'oof'
309
318
 
310
- @cookie = k+Base64.urlsafe_encode64("\0"*75)
311
- body('/g/foo').must_equal ''
312
- errors.must_equal ["Not decoding session: HMAC invalid"]
313
- end
314
- end
319
+ @app.plugin(:sessions, :serializer=>proc{|s| s.to_json.upcase})
315
320
 
316
- describe "sessions plugin" do
317
- include CookieJar
321
+ body('/s/foo/baz').must_equal 'baz'
322
+ body('/g/ZAB').must_equal 'OOF'
318
323
 
319
- def req(path, opts={})
320
- @errors ||= (errors = []; def errors.puts(s) self << s; end; errors)
321
- super(path, opts.merge('rack.errors'=>@errors))
322
- end
324
+ errors.must_equal []
325
+ end
326
+
327
+ it "logs session decoding errors to rack.errors" do
328
+ body('/s/foo/bar').must_equal 'bar'
329
+ c = @cookie.dup
330
+ k = c.split('=', 2)[0] + '='
323
331
 
324
- def errors
325
- e = @errors.dup
326
- @errors.clear
327
- e
332
+ @cookie[20] = '!'
333
+ body('/g/foo').must_equal ''
334
+ errors.must_equal ["Unable to decode session: invalid base64"]
335
+
336
+ @cookie = k+Base64.urlsafe_encode64('')
337
+ body('/g/foo').must_equal ''
338
+ errors.must_equal ["Unable to decode session: no data"]
339
+
340
+ @cookie = k+Base64.urlsafe_encode64("\0" * 60)
341
+ body('/g/foo').must_equal ''
342
+ errors.must_equal ["Unable to decode session: data too short"]
343
+
344
+ @cookie = k+Base64.urlsafe_encode64("\1" * 92)
345
+ body('/g/foo').must_equal ''
346
+ errors.must_equal ["Unable to decode session: data too short"]
347
+
348
+ @cookie = k+Base64.urlsafe_encode64('1'*75)
349
+ body('/g/foo').must_equal ''
350
+ errors.must_equal ["Unable to decode session: version marker unsupported"]
351
+
352
+ @cookie = k+Base64.urlsafe_encode64("\0"*75)
353
+ body('/g/foo').must_equal ''
354
+ errors.must_equal ["Not decoding session: HMAC invalid"]
355
+ end
328
356
  end
329
357
 
330
- it "supports transparent upgrade from Rack::Session::Cookie with default HMAC and coder" do
331
- app(:bare) do
332
- use Rack::Session::Cookie, :secret=>'1'
333
- plugin :middleware_stack
334
- route do |r|
335
- r.get('s', String, String){|k, v| session[k] = {:a=>v}; v}
336
- r.get('g', String){|k| session[k].inspect}
337
- ''
338
- end
358
+ describe "sessions plugin" do
359
+ include CookieJar
360
+
361
+ def req(path, opts={})
362
+ @errors ||= (errors = []; def errors.puts(s) self << s; end; errors)
363
+ super(path, opts.merge('rack.errors'=>@errors))
364
+ end
365
+
366
+ def errors
367
+ e = @errors.dup
368
+ @errors.clear
369
+ e
339
370
  end
340
371
 
341
- _, h, b = req('/s/foo/bar')
342
- (h['Set-Cookie'] =~ /\A(rack\.session=.*); path=\/; HttpOnly\z/).must_equal 0
343
- c = $1
344
- b.must_equal ['bar']
345
- _, h, b = req('/g/foo')
346
- h['Set-Cookie'].must_be_nil
347
- b.must_equal ['{:a=>"bar"}']
348
-
349
- @app.plugin :sessions, :secret=>'1'*64,
350
- :upgrade_from_rack_session_cookie_secret=>'1'
351
- @app.middleware_stack.remove{|m, *| m == Rack::Session::Cookie}
352
-
353
- @cookie = c.dup
354
- @cookie.slice!(15)
355
- body('/g/foo').must_equal 'nil'
356
- errors.must_equal ["Not decoding Rack::Session::Cookie session: HMAC invalid"]
357
-
358
- @cookie = c.split('--', 2)[0]
359
- body('/g/foo').must_equal 'nil'
360
- errors.must_equal ["Not decoding Rack::Session::Cookie session: invalid format"]
361
-
362
- @cookie = c.split('--', 2)[0][13..-1]
363
- @cookie = Rack::Utils.unescape(@cookie).unpack('m')[0]
364
- @cookie[2] = "^"
365
- @cookie = [@cookie].pack('m')
366
- cookie = String.new
367
- cookie << 'rack.session=' << @cookie << '--' << OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, '1', @cookie)
368
- @cookie = cookie
369
- body('/g/foo').must_equal 'nil'
370
- errors.must_equal ["Error decoding Rack::Session::Cookie session: not base64 encoded marshal dump"]
371
-
372
- @cookie = c
373
- _, h, b = req('/g/foo')
374
- h['Set-Cookie'].must_match(/\Aroda\.session=(.*); path=\/; HttpOnly(; SameSite=Lax)?\nrack\.session=; path=\/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00/m)
375
- b.must_equal ['{"a"=>"bar"}']
376
-
377
- @app.plugin :sessions, :cookie_options=>{:path=>'/foo'}, :upgrade_from_rack_session_cookie_options=>{}
378
- @cookie = c
379
- _, h, b = req('/g/foo')
380
- h['Set-Cookie'].must_match(/\Aroda\.session=(.*); path=\/foo; HttpOnly(; SameSite=Lax)?\nrack\.session=; path=\/foo; max-age=0; expires=Thu, 01 Jan 1970 00:00:00/m)
381
- b.must_equal ['{"a"=>"bar"}']
382
-
383
- @app.plugin :sessions, :upgrade_from_rack_session_cookie_options=>{:path=>'/baz'}
384
- @cookie = c
385
- _, h, b = req('/g/foo')
386
- h['Set-Cookie'].must_match(/\Aroda\.session=(.*); path=\/foo; HttpOnly(; SameSite=Lax)?\nrack\.session=; path=\/baz; max-age=0; expires=Thu, 01 Jan 1970 00:00:00/m)
387
- b.must_equal ['{"a"=>"bar"}']
388
-
389
- @app.plugin :sessions, :upgrade_from_rack_session_cookie_key=>'quux.session'
390
- @cookie = c.sub(/\Arack/, 'quux')
391
- _, h, b = req('/g/foo')
392
- h['Set-Cookie'].must_match(/\Aroda\.session=(.*); path=\/foo; HttpOnly(; SameSite=Lax)?\nquux\.session=; path=\/baz; max-age=0; expires=Thu, 01 Jan 1970 00:00:00/m)
393
- b.must_equal ['{"a"=>"bar"}']
372
+ it "supports transparent upgrade from Rack::Session::Cookie with default HMAC and coder" do
373
+ app(:bare) do
374
+ use Rack::Session::Cookie, :secret=>'1'
375
+ plugin :middleware_stack
376
+ route do |r|
377
+ r.get('s', String, String){|k, v| session[k] = {:a=>v}; v}
378
+ r.get('g', String){|k| session[k].inspect}
379
+ ''
380
+ end
381
+ end
382
+
383
+ _, h, b = req('/s/foo/bar')
384
+ (h['Set-Cookie'] =~ /\A(rack\.session=.*); path=\/; HttpOnly\z/).must_equal 0
385
+ c = $1
386
+ b.must_equal ['bar']
387
+ _, h, b = req('/g/foo')
388
+ h['Set-Cookie'].must_be_nil
389
+ b.must_equal ['{:a=>"bar"}']
390
+
391
+ @app.plugin :sessions, :secret=>'1'*64,
392
+ :upgrade_from_rack_session_cookie_secret=>'1'
393
+ @app.middleware_stack.remove{|m, *| m == Rack::Session::Cookie}
394
+
395
+ @cookie = c.dup
396
+ @cookie.slice!(15)
397
+ body('/g/foo').must_equal 'nil'
398
+ errors.must_equal ["Not decoding Rack::Session::Cookie session: HMAC invalid"]
399
+
400
+ @cookie = c.split('--', 2)[0]
401
+ body('/g/foo').must_equal 'nil'
402
+ errors.must_equal ["Not decoding Rack::Session::Cookie session: invalid format"]
403
+
404
+ @cookie = c.split('--', 2)[0][13..-1]
405
+ @cookie = Rack::Utils.unescape(@cookie).unpack('m')[0]
406
+ @cookie[2] = "^"
407
+ @cookie = [@cookie].pack('m')
408
+ cookie = String.new
409
+ cookie << 'rack.session=' << @cookie << '--' << OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, '1', @cookie)
410
+ @cookie = cookie
411
+ body('/g/foo').must_equal 'nil'
412
+ errors.must_equal ["Error decoding Rack::Session::Cookie session: not base64 encoded marshal dump"]
413
+
414
+ @cookie = c
415
+ _, h, b = req('/g/foo')
416
+ h['Set-Cookie'].must_match(/\Aroda\.session=(.*); path=\/; HttpOnly(; SameSite=Lax)?\nrack\.session=; path=\/; max-age=0; expires=Thu, 01 Jan 1970 00:00:00/m)
417
+ b.must_equal ['{"a"=>"bar"}']
418
+
419
+ @app.plugin :sessions, :cookie_options=>{:path=>'/foo'}, :upgrade_from_rack_session_cookie_options=>{}
420
+ @cookie = c
421
+ _, h, b = req('/g/foo')
422
+ h['Set-Cookie'].must_match(/\Aroda\.session=(.*); path=\/foo; HttpOnly(; SameSite=Lax)?\nrack\.session=; path=\/foo; max-age=0; expires=Thu, 01 Jan 1970 00:00:00/m)
423
+ b.must_equal ['{"a"=>"bar"}']
424
+
425
+ @app.plugin :sessions, :upgrade_from_rack_session_cookie_options=>{:path=>'/baz'}
426
+ @cookie = c
427
+ _, h, b = req('/g/foo')
428
+ h['Set-Cookie'].must_match(/\Aroda\.session=(.*); path=\/foo; HttpOnly(; SameSite=Lax)?\nrack\.session=; path=\/baz; max-age=0; expires=Thu, 01 Jan 1970 00:00:00/m)
429
+ b.must_equal ['{"a"=>"bar"}']
430
+
431
+ @app.plugin :sessions, :upgrade_from_rack_session_cookie_key=>'quux.session'
432
+ @cookie = c.sub(/\Arack/, 'quux')
433
+ _, h, b = req('/g/foo')
434
+ h['Set-Cookie'].must_match(/\Aroda\.session=(.*); path=\/foo; HttpOnly(; SameSite=Lax)?\nquux\.session=; path=\/baz; max-age=0; expires=Thu, 01 Jan 1970 00:00:00/m)
435
+ b.must_equal ['{"a"=>"bar"}']
436
+ end
394
437
  end
395
438
  end
396
439
  end