roda 3.18.0 → 3.19.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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