rack 2.0.0.alpha → 2.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of rack might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cc63040ab20caf8e900b85fe57f9be6eca73d858
4
- data.tar.gz: c8422fc04a12b1dadaeeb49956ab23a583589351
3
+ metadata.gz: 54be55d492853c85486a9d94bfa3122be91a375c
4
+ data.tar.gz: b1f919ccb928bf607b66efb2dc494d350c4e67f8
5
5
  SHA512:
6
- metadata.gz: 224fc13672ae42e6d296ddb1b70fd8dd0f1ba29dc5780c97780593217e9790444bd2f40a30f952fd1620289ebe98d932cc25ffd833a258aa24be77e70d7ed8ce
7
- data.tar.gz: 102bccf930b6d7fa4b495f7f73973281770c2e69dc76bde3a1cc9f926c6d11495d136a51c46a3001793a1cc115bc9f04c74139bcdc653ffa56252d15a759b414
6
+ metadata.gz: 969ed8dd45442d71e9448dcc7951d997429cbe3a8857694c76cd0c8137ea22ba831f54dc1c24d65f2f2b35ce44c4a1d43bba182b98d93d86fc5917a671cc1486
7
+ data.tar.gz: cf0ede4ad49a3e2bb23f0136532c32d6fb799b5cb050bffa909727478bf1cca6c6d7704f574cb9a9c9e657e7b5f6430e5e1e8485ac1c747d5d6a14a8d4b81a2d
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007-2015 Christian Neukirchen <purl.org/net/chneukirchen>
1
+ Copyright (c) 2007-2016 Christian Neukirchen <purl.org/net/chneukirchen>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
data/HISTORY.md CHANGED
@@ -1,9 +1,18 @@
1
1
  Sun Dec 4 18:48:03 2015 Jeremy Daer <jeremydaer@gmail.com>
2
2
 
3
- * "First-Party" cookies. Browsers omit First-Party cookies from
4
- third-party requests, closing the door on many common CSRF attacks.
5
- Pass `first_party: true` to enable:
6
- response.set_cookie 'foo', value: 'bar', first_party: true
3
+ * First-party "SameSite" cookies. Browsers omit SameSite cookies
4
+ from third-party requests, closing the door on many CSRF attacks.
5
+
6
+ Pass `same_site: true` (or `:strict`) to enable:
7
+ response.set_cookie 'foo', value: 'bar', same_site: true
8
+ or `same_site: :lax` to use Lax enforcement:
9
+ response.set_cookie 'foo', value: 'bar', same_site: :lax
10
+
11
+ Based on version 7 of the Same-site Cookies internet draft:
12
+ https://tools.ietf.org/html/draft-west-first-party-cookies-07
13
+
14
+ Thanks to Ben Toews (@mastahyeti) and Bob Long (@bobjflong) for
15
+ updating to drafts 5 and 7.
7
16
 
8
17
  Tue Nov 3 16:17:26 2015 Aaron Patterson <tenderlove@ruby-lang.org>
9
18
 
@@ -4,7 +4,7 @@ require 'rack/lobster'
4
4
  lobster = Rack::Lobster.new
5
5
 
6
6
  protected_lobster = Rack::Auth::Basic.new(lobster) do |username, password|
7
- 'secret' == password
7
+ Rack::Utils.secure_compare('secret', password)
8
8
  end
9
9
 
10
10
  protected_lobster.realm = 'Lobster 2.0'
@@ -2,7 +2,7 @@ require 'rack/lobster'
2
2
 
3
3
  use Rack::ShowExceptions
4
4
  use Rack::Auth::Basic, "Lobster 2.0" do |username, password|
5
- 'secret' == password
5
+ Rack::Utils.secure_compare('secret', password)
6
6
  end
7
7
 
8
8
  run Rack::Lobster.new
@@ -18,7 +18,7 @@ module Rack
18
18
  VERSION.join(".")
19
19
  end
20
20
 
21
- RELEASE = "2.0.0.alpha"
21
+ RELEASE = "2.0.0.rc1"
22
22
 
23
23
  # Return the Rack release as a dotted string.
24
24
  def self.release
@@ -17,7 +17,7 @@ module Rack
17
17
  end
18
18
 
19
19
  def self.split_header_value(str)
20
- str.scan( /(\w+\=(?:"[^\"]+"|[^,]+))/n ).collect{ |v| v[0] }
20
+ str.scan(/\w+\=(?:"[^\"]+"|[^,]+)/n)
21
21
  end
22
22
 
23
23
  def initialize
@@ -38,7 +38,7 @@ module Rack
38
38
 
39
39
  def to_s
40
40
  map do |k, v|
41
- "#{k}=" + (UNQUOTED.include?(k) ? v.to_s : quote(v))
41
+ "#{k}=" << (UNQUOTED.include?(k) ? v.to_s : quote(v))
42
42
  end.join(', ')
43
43
  end
44
44
 
@@ -50,4 +50,3 @@ module Rack
50
50
  end
51
51
  end
52
52
  end
53
-
@@ -48,7 +48,7 @@ module Rack
48
48
  now.strftime("%d/%b/%Y:%H:%M:%S %z"),
49
49
  env[REQUEST_METHOD],
50
50
  env[PATH_INFO],
51
- env[QUERY_STRING].empty? ? "" : "?"+env[QUERY_STRING],
51
+ env[QUERY_STRING].empty? ? "" : "?#{env[QUERY_STRING]}",
52
52
  env[HTTP_VERSION],
53
53
  status.to_s[0..3],
54
54
  length,
@@ -59,13 +59,21 @@ table { width:100%%; }
59
59
  def initialize(root, app=nil)
60
60
  @root = ::File.expand_path(root)
61
61
  @app = app || Rack::File.new(@root)
62
+ @head = Rack::Head.new(lambda { |env| get env })
62
63
  end
63
64
 
64
65
  def call(env)
66
+ # strip body if this is a HEAD call
67
+ @head.call env
68
+ end
69
+
70
+ def get(env)
65
71
  script_name = env[SCRIPT_NAME]
66
72
  path_info = Utils.unescape_path(env[PATH_INFO])
67
73
 
68
- if forbidden = check_forbidden(path_info)
74
+ if bad_request = check_bad_request(path_info)
75
+ bad_request
76
+ elsif forbidden = check_forbidden(path_info)
69
77
  forbidden
70
78
  else
71
79
  path = ::File.join(@root, path_info)
@@ -73,6 +81,16 @@ table { width:100%%; }
73
81
  end
74
82
  end
75
83
 
84
+ def check_bad_request(path_info)
85
+ return if Utils.valid_path?(path_info)
86
+
87
+ body = "Bad Request\n"
88
+ size = body.bytesize
89
+ return [400, {CONTENT_TYPE => "text/plain",
90
+ CONTENT_LENGTH => size.to_s,
91
+ "X-Cascade" => "pass"}, [body]]
92
+ end
93
+
76
94
  def check_forbidden(path_info)
77
95
  return unless path_info.include? ".."
78
96
 
@@ -155,7 +173,7 @@ table { width:100%%; }
155
173
  return format % (int.to_f / size) if int >= size
156
174
  end
157
175
 
158
- int.to_s + 'B'
176
+ "#{int}B"
159
177
  end
160
178
  end
161
179
  end
@@ -65,10 +65,10 @@ module Rack
65
65
 
66
66
  body.each do |part|
67
67
  parts << part
68
- (digest ||= Digest::MD5.new) << part unless part.empty?
68
+ (digest ||= Digest::SHA256.new) << part unless part.empty?
69
69
  end
70
70
 
71
- [digest && digest.hexdigest, parts]
71
+ [digest && digest.hexdigest.byteslice(0, 32), parts]
72
72
  end
73
73
  end
74
74
  end
@@ -2,6 +2,7 @@ require 'time'
2
2
  require 'rack/utils'
3
3
  require 'rack/mime'
4
4
  require 'rack/request'
5
+ require 'rack/head'
5
6
 
6
7
  module Rack
7
8
  # Rack::File serves files below the +root+ directory given, according to the
@@ -22,17 +23,24 @@ module Rack
22
23
  @root = root
23
24
  @headers = headers
24
25
  @default_mime = default_mime
26
+ @head = Rack::Head.new(lambda { |env| get env })
25
27
  end
26
28
 
27
29
  def call(env)
30
+ # HEAD requests drop the response body, including 4xx error messages.
31
+ @head.call env
32
+ end
33
+
34
+ def get(env)
28
35
  request = Rack::Request.new env
29
36
  unless ALLOWED_VERBS.include? request.request_method
30
37
  return fail(405, "Method Not Allowed", {'Allow' => ALLOW_HEADER})
31
38
  end
32
39
 
33
40
  path_info = Utils.unescape_path request.path_info
34
- clean_path_info = Utils.clean_path_info(path_info)
41
+ return fail(400, "Bad Request") unless Utils.valid_path?(path_info)
35
42
 
43
+ clean_path_info = Utils.clean_path_info(path_info)
36
44
  path = ::File.join(@root, clean_path_info)
37
45
 
38
46
  available = begin
@@ -131,6 +139,7 @@ module Rack
131
139
 
132
140
  def fail(status, body, headers = {})
133
141
  body += "\n"
142
+
134
143
  [
135
144
  status,
136
145
  {
@@ -52,7 +52,7 @@ module Rack
52
52
  elsif ENV.include?("RACK_HANDLER")
53
53
  self.get(ENV["RACK_HANDLER"])
54
54
  else
55
- pick ['thin', 'puma', 'webrick']
55
+ pick ['puma', 'thin', 'webrick']
56
56
  end
57
57
  end
58
58
 
@@ -128,7 +128,7 @@ module Rack
128
128
  end
129
129
  end
130
130
 
131
- empty_str = ''.force_encoding(Encoding::ASCII_8BIT)
131
+ empty_str = String.new.force_encoding(Encoding::ASCII_8BIT)
132
132
  opts[:input] ||= empty_str
133
133
  if String === opts[:input]
134
134
  rack_input = StringIO.new(opts[:input])
@@ -22,7 +22,7 @@ module Rack
22
22
  else
23
23
  content_for_other(file, name)
24
24
  end
25
- end.join + "--#{MULTIPART_BOUNDARY}--\r"
25
+ end.join << "--#{MULTIPART_BOUNDARY}--\r"
26
26
  end
27
27
 
28
28
  private
@@ -26,7 +26,7 @@ module Rack
26
26
  str = if left < size
27
27
  @io.read left
28
28
  else
29
- @io.read size
29
+ @io.read size
30
30
  end
31
31
 
32
32
  if str
@@ -100,8 +100,6 @@ module Rack
100
100
  # Generic multipart cases, not coming from a form
101
101
  data = {:type => content_type,
102
102
  :name => name, :tempfile => body, :head => head}
103
- elsif !filename && data.empty?
104
- return
105
103
  end
106
104
 
107
105
  yield data
@@ -347,6 +345,7 @@ module Rack
347
345
  k,v = param.split('=', 2)
348
346
  k.strip!
349
347
  v.strip!
348
+ v = v[1..-2] if v[0] == '"' && v[-1] == '"'
350
349
  encoding = Encoding.find v if k == CHARSET
351
350
  end
352
351
  end
@@ -79,16 +79,22 @@ module Rack
79
79
  raise RangeError if depth <= 0
80
80
 
81
81
  name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
82
- k = $1 || ''
83
- after = $' || ''
82
+ k = $1 || ''.freeze
83
+ after = $' || ''.freeze
84
84
 
85
- return if k.empty?
85
+ if k.empty?
86
+ if !v.nil? && name == "[]".freeze
87
+ return Array(v)
88
+ else
89
+ return
90
+ end
91
+ end
86
92
 
87
- if after == ""
93
+ if after == ''.freeze
88
94
  params[k] = v
89
- elsif after == "["
95
+ elsif after == "[".freeze
90
96
  params[name] = v
91
- elsif after == "[]"
97
+ elsif after == "[]".freeze
92
98
  params[k] ||= []
93
99
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
94
100
  params[k] << v
@@ -96,7 +102,8 @@ module Rack
96
102
  child_key = $1
97
103
  params[k] ||= []
98
104
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
99
- if params_hash_type?(params[k].last) && !params[k].last.key?(child_key)
105
+ first_key = child_key.gsub(/[\[\]]/, ' ').split.first
106
+ if params_hash_type?(params[k].last) && !params[k].last.key?(first_key)
100
107
  normalize_params(params[k].last, child_key, v, depth - 1)
101
108
  else
102
109
  params[k] << normalize_params(make_params, child_key, v, depth - 1)
@@ -107,7 +114,7 @@ module Rack
107
114
  params[k] = normalize_params(params[k], after, v, depth - 1)
108
115
  end
109
116
 
110
- return params
117
+ params
111
118
  end
112
119
 
113
120
  def make_params
@@ -26,6 +26,7 @@ module Rack
26
26
  @last = (Time.now - cooldown)
27
27
  @cache = {}
28
28
  @mtimes = {}
29
+ @reload_mutex = Mutex.new
29
30
 
30
31
  extend backend
31
32
  end
@@ -33,7 +34,7 @@ module Rack
33
34
  def call(env)
34
35
  if @cooldown and Time.now > @last + @cooldown
35
36
  if Thread.list.size > 1
36
- Thread.exclusive{ reload! }
37
+ @reload_mutex.synchronize{ reload! }
37
38
  else
38
39
  reload!
39
40
  end
@@ -16,23 +16,6 @@ module Rack
16
16
  super(env)
17
17
  end
18
18
 
19
- # shortcut for <tt>request.params[key]</tt>
20
- def [](key)
21
- params[key.to_s]
22
- end
23
-
24
- # shortcut for <tt>request.params[key] = value</tt>
25
- #
26
- # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
27
- def []=(key, value)
28
- params[key.to_s] = value
29
- end
30
-
31
- # like Hash#values_at
32
- def values_at(*keys)
33
- keys.map{|key| params[key] }
34
- end
35
-
36
19
  def params
37
20
  @params ||= super
38
21
  end
@@ -160,7 +143,7 @@ module Rack
160
143
 
161
144
  def session
162
145
  fetch_header(RACK_SESSION) do |k|
163
- set_header RACK_SESSION, {}
146
+ set_header RACK_SESSION, default_session
164
147
  end
165
148
  end
166
149
 
@@ -437,8 +420,35 @@ module Rack
437
420
  ip =~ /\A127\.0\.0\.1\Z|\A(10|172\.(1[6-9]|2[0-9]|30|31)|192\.168)\.|\A::1\Z|\Afd[0-9a-f]{2}:.+|\Alocalhost\Z|\Aunix\Z|\Aunix:/i
438
421
  end
439
422
 
423
+ # shortcut for <tt>request.params[key]</tt>
424
+ def [](key)
425
+ if $verbose
426
+ warn("Request#[] is deprecated and will be removed in a future version of Rack. Please use request.params[] instead")
427
+ end
428
+
429
+ params[key.to_s]
430
+ end
431
+
432
+ # shortcut for <tt>request.params[key] = value</tt>
433
+ #
434
+ # Note that modifications will not be persisted in the env. Use update_param or delete_param if you want to destructively modify params.
435
+ def []=(key, value)
436
+ if $verbose
437
+ warn("Request#[]= is deprecated and will be removed in a future version of Rack. Please use request.params[]= instead")
438
+ end
439
+
440
+ params[key.to_s] = value
441
+ end
442
+
443
+ # like Hash#values_at
444
+ def values_at(*keys)
445
+ keys.map { |key| params[key] }
446
+ end
447
+
440
448
  private
441
449
 
450
+ def default_session; {}; end
451
+
442
452
  def parse_http_accept_header(header)
443
453
  header.to_s.split(/\s*,\s*/).map do |part|
444
454
  attribute, parameters = part.split(/\s*;\s*/, 2)
@@ -124,10 +124,12 @@ module Rack
124
124
  end
125
125
 
126
126
  def keys
127
+ load_for_read!
127
128
  @data.keys
128
129
  end
129
130
 
130
131
  def values
132
+ load_for_read!
131
133
  @data.values
132
134
  end
133
135
 
@@ -93,7 +93,7 @@ module Rack
93
93
  # HTTP Headers
94
94
  @header_rules = options[:header_rules] || []
95
95
  # Allow for legacy :cache_control option while prioritizing global header_rules setting
96
- @header_rules.insert(0, [:all, {CACHE_CONTROL => options[:cache_control]}]) if options[:cache_control]
96
+ @header_rules.unshift([:all, {CACHE_CONTROL => options[:cache_control]}]) if options[:cache_control]
97
97
 
98
98
  @file_server = Rack::File.new(root)
99
99
  end
@@ -47,11 +47,13 @@ module Rack
47
47
  server_name = env[SERVER_NAME]
48
48
  server_port = env[SERVER_PORT]
49
49
 
50
+ is_same_server = casecmp?(http_host, server_name) ||
51
+ casecmp?(http_host, "#{server_name}:#{server_port}")
52
+
50
53
  @mapping.each do |host, location, match, app|
51
54
  unless casecmp?(http_host, host) \
52
55
  || casecmp?(server_name, host) \
53
- || (!host && (casecmp?(http_host, server_name) ||
54
- casecmp?(http_host, "#{server_name}:#{server_port}")))
56
+ || (!host && is_same_server)
55
57
  next
56
58
  end
57
59
 
@@ -248,13 +248,23 @@ module Rack
248
248
  rfc2822(value[:expires].clone.gmtime) if value[:expires]
249
249
  secure = "; secure" if value[:secure]
250
250
  httponly = "; HttpOnly" if (value.key?(:httponly) ? value[:httponly] : value[:http_only])
251
- first_party = "; First-Party" if value[:first_party]
251
+ same_site =
252
+ case value[:same_site]
253
+ when false, nil
254
+ nil
255
+ when :lax, 'Lax', :Lax
256
+ '; SameSite=Lax'.freeze
257
+ when true, :strict, 'Strict', :Strict
258
+ '; SameSite=Strict'.freeze
259
+ else
260
+ raise ArgumentError, "Invalid SameSite value: #{value[:same_site].inspect}"
261
+ end
252
262
  value = value[:value]
253
263
  end
254
264
  value = [value] unless Array === value
255
265
 
256
266
  cookie = "#{escape(key)}=#{value.map { |v| escape v }.join('&')}#{domain}" \
257
- "#{path}#{max_age}#{expires}#{secure}#{httponly}#{first_party}"
267
+ "#{path}#{max_age}#{expires}#{secure}#{httponly}#{same_site}"
258
268
 
259
269
  case header
260
270
  when nil, ''
@@ -551,6 +561,7 @@ module Rack
551
561
  428 => 'Precondition Required',
552
562
  429 => 'Too Many Requests',
553
563
  431 => 'Request Header Fields Too Large',
564
+ 451 => 'Unavailable for Legal Reasons',
554
565
  500 => 'Internal Server Error',
555
566
  501 => 'Not Implemented',
556
567
  502 => 'Bad Gateway',
@@ -598,5 +609,12 @@ module Rack
598
609
  end
599
610
  module_function :clean_path_info
600
611
 
612
+ NULL_BYTE = "\0".freeze
613
+
614
+ def valid_path?(path)
615
+ path.valid_encoding? && !path.include?(NULL_BYTE)
616
+ end
617
+ module_function :valid_path?
618
+
601
619
  end
602
620
  end
@@ -0,0 +1,8 @@
1
+ 2016-05-04 12:00:25: (log.c.164) server started
2
+ 2016-05-04 12:00:35: (server.c.1558) server stopped by UID = 502 PID = 80374
3
+ 2016-05-04 12:13:42: (log.c.164) server started
4
+ 2016-05-04 12:13:51: (server.c.1558) server stopped by UID = 502 PID = 82209
5
+ 2016-05-04 12:16:30: (log.c.164) server started
6
+ 2016-05-04 12:16:39: (server.c.1558) server stopped by UID = 502 PID = 82523
7
+ 2016-05-04 12:18:17: (log.c.164) server started
8
+ 2016-05-04 12:18:26: (server.c.1558) server stopped by UID = 502 PID = 82745
@@ -2,7 +2,10 @@ require 'minitest/autorun'
2
2
 
3
3
  module Rack
4
4
  class TestCase < Minitest::Test
5
- if `which lighttpd` && !$?.success?
5
+ # Check for Lighttpd and launch it for tests if available.
6
+ `which lighttpd`
7
+
8
+ if $?.success?
6
9
  begin
7
10
  # Keep this first.
8
11
  LIGHTTPD_PID = fork {
@@ -0,0 +1,11 @@
1
+ --AaB03x
2
+ Content-Type: text/plain; charset="utf-8"
3
+ Content-disposition: form-data; name="user_sid"
4
+
5
+ bbf14f82-d2aa-4c07-9fb8-ca6714a7ea97
6
+ --AaB03x
7
+ Content-Type: image/png; charset=UTF-8
8
+ Content-disposition: form-data; name="file";
9
+ filename="b67879ed-bfed-4491-a8cc-f99cca769f94.png"
10
+
11
+ --AaB03x
@@ -63,6 +63,13 @@ describe Rack::Directory do
63
63
  assert_match(res, /passed!/)
64
64
  end
65
65
 
66
+ it "serve uri with URL encoded null byte (%00) in filenames" do
67
+ res = Rack::MockRequest.new(Rack::Lint.new(app))
68
+ .get("/cgi/test%00")
69
+
70
+ res.must_be :bad_request?
71
+ end
72
+
66
73
  it "not allow directory traversal" do
67
74
  res = Rack::MockRequest.new(Rack::Lint.new(app)).
68
75
  get("/cgi/../test")
@@ -130,4 +137,12 @@ describe Rack::Directory do
130
137
  res = mr.get("/script-path/cgi/test+directory/test+file")
131
138
  res.must_be :ok?
132
139
  end
140
+
141
+ it "return error when file not found for head request" do
142
+ res = Rack::MockRequest.new(Rack::Lint.new(app)).
143
+ head("/cgi/missing")
144
+
145
+ res.must_be :not_found?
146
+ res.body.must_be :empty?
147
+ end
133
148
  end
@@ -22,13 +22,13 @@ describe Rack::ETag do
22
22
  it "set ETag if none is set if status is 200" do
23
23
  app = lambda { |env| [200, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
24
24
  response = etag(app).call(request)
25
- response[1]['ETag'].must_equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\""
25
+ response[1]['ETag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\""
26
26
  end
27
27
 
28
28
  it "set ETag if none is set if status is 201" do
29
29
  app = lambda { |env| [201, {'Content-Type' => 'text/plain'}, ["Hello, World!"]] }
30
30
  response = etag(app).call(request)
31
- response[1]['ETag'].must_equal "W/\"65a8e27d8879283831b664bd8b7f0ad4\""
31
+ response[1]['ETag'].must_equal "W/\"dffd6021bb2bd5b0af676290809ec3a5\""
32
32
  end
33
33
 
34
34
  it "set Cache-Control to 'max-age=0, private, must-revalidate' (default) if none is set" do
@@ -68,6 +68,11 @@ describe Rack::File do
68
68
  assert_match(res, /ruby/)
69
69
  end
70
70
 
71
+ it "serve uri with URL encoded null byte (%00) in filenames" do
72
+ res = Rack::MockRequest.new(file(DOCROOT)).get("/cgi/test%00")
73
+ res.must_be :bad_request?
74
+ end
75
+
71
76
  it "allow safe directory traversal" do
72
77
  req = Rack::MockRequest.new(file(DOCROOT))
73
78
 
@@ -237,4 +242,10 @@ describe Rack::File do
237
242
  res['Content-Type'].must_equal nil
238
243
  end
239
244
 
245
+ it "return error when file not found for head request" do
246
+ res = Rack::MockRequest.new(file(DOCROOT)).head("/cgi/missing")
247
+ res.must_be :not_found?
248
+ res.body.must_be :empty?
249
+ end
250
+
240
251
  end
@@ -72,6 +72,13 @@ describe Rack::Multipart do
72
72
  end
73
73
  end
74
74
 
75
+ it "handles quoted encodings" do
76
+ # See #905
77
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:unity3d_wwwform))
78
+ params = Rack::Multipart.parse_multipart(env)
79
+ params['user_sid'].encoding.must_equal Encoding::UTF_8
80
+ end
81
+
75
82
  it "raise RangeError if the key space is exhausted" do
76
83
  env = Rack::MockRequest.env_for("/", multipart_fixture(:content_type_and_no_filename))
77
84
 
@@ -88,6 +95,7 @@ describe Rack::Multipart do
88
95
  env['CONTENT_TYPE'] = "multipart/form-data; boundary=----WebKitFormBoundaryWLHCs9qmcJJoyjKR"
89
96
  params = Rack::Multipart.parse_multipart(env)
90
97
  params['profile']['bio'].must_include 'hello'
98
+ params['profile'].keys.must_include 'public_email'
91
99
  end
92
100
 
93
101
  it "reject insanely long boundaries" do
@@ -1305,6 +1305,11 @@ EOF
1305
1305
  req.trusted_proxy?("2001:470:1f0b:18f8::1").must_equal nil
1306
1306
  end
1307
1307
 
1308
+ it "sets the default session to an empty hash" do
1309
+ req = make_request(Rack::MockRequest.env_for("http://example.com:8080/"))
1310
+ assert_equal Hash.new, req.session
1311
+ end
1312
+
1308
1313
  class MyRequest < Rack::Request
1309
1314
  def params
1310
1315
  {:foo => "bar"}
@@ -115,16 +115,66 @@ describe Rack::Response do
115
115
  response["Set-Cookie"].must_equal "foo=bar"
116
116
  end
117
117
 
118
- it "can set First-Party cookies" do
118
+ it "can set SameSite cookies with symbol value :lax" do
119
119
  response = Rack::Response.new
120
- response.set_cookie "foo", {:value => "bar", :first_party => true}
121
- response["Set-Cookie"].must_equal "foo=bar; First-Party"
120
+ response.set_cookie "foo", {:value => "bar", :same_site => :lax}
121
+ response["Set-Cookie"].must_equal "foo=bar; SameSite=Lax"
122
+ end
123
+
124
+ it "can set SameSite cookies with symbol value :Lax" do
125
+ response = Rack::Response.new
126
+ response.set_cookie "foo", {:value => "bar", :same_site => :lax}
127
+ response["Set-Cookie"].must_equal "foo=bar; SameSite=Lax"
128
+ end
129
+
130
+ it "can set SameSite cookies with string value 'Lax'" do
131
+ response = Rack::Response.new
132
+ response.set_cookie "foo", {:value => "bar", :same_site => "Lax"}
133
+ response["Set-Cookie"].must_equal "foo=bar; SameSite=Lax"
134
+ end
135
+
136
+ it "can set SameSite cookies with boolean value true" do
137
+ response = Rack::Response.new
138
+ response.set_cookie "foo", {:value => "bar", :same_site => true}
139
+ response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
140
+ end
141
+
142
+ it "can set SameSite cookies with symbol value :strict" do
143
+ response = Rack::Response.new
144
+ response.set_cookie "foo", {:value => "bar", :same_site => :strict}
145
+ response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
146
+ end
147
+
148
+ it "can set SameSite cookies with symbol value :Strict" do
149
+ response = Rack::Response.new
150
+ response.set_cookie "foo", {:value => "bar", :same_site => :Strict}
151
+ response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
152
+ end
153
+
154
+ it "can set SameSite cookies with string value 'Strict'" do
155
+ response = Rack::Response.new
156
+ response.set_cookie "foo", {:value => "bar", :same_site => "Strict"}
157
+ response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
158
+ end
159
+
160
+ it "validates the SameSite option value" do
161
+ response = Rack::Response.new
162
+ lambda {
163
+ response.set_cookie "foo", {:value => "bar", :same_site => "Foo"}
164
+ }.must_raise(ArgumentError).
165
+ message.must_match(/Invalid SameSite value: "Foo"/)
166
+ end
167
+
168
+ it "can set SameSite cookies with symbol value" do
169
+ response = Rack::Response.new
170
+ response.set_cookie "foo", {:value => "bar", :same_site => :Strict}
171
+ response["Set-Cookie"].must_equal "foo=bar; SameSite=Strict"
122
172
  end
123
173
 
124
174
  [ nil, false ].each do |non_truthy|
125
- it "omits First-Party attribute given a #{non_truthy.inspect} value" do
175
+ it "omits SameSite attribute given a #{non_truthy.inspect} value" do
126
176
  response = Rack::Response.new
127
- response.set_cookie "foo", {:value => "bar", :first_party => non_truthy}
177
+ response.set_cookie "foo", {:value => "bar", :same_site => non_truthy}
128
178
  response["Set-Cookie"].must_equal "foo=bar"
129
179
  end
130
180
  end
@@ -0,0 +1,28 @@
1
+ require 'minitest/autorun'
2
+ require 'rack/session/abstract/id'
3
+
4
+ describe Rack::Session::Abstract::SessionHash do
5
+ attr_reader :hash
6
+
7
+ def setup
8
+ super
9
+ store = Class.new do
10
+ def load_session(req)
11
+ ["id", {foo: :bar, baz: :qux}]
12
+ end
13
+ def session_exists?(req)
14
+ true
15
+ end
16
+ end
17
+ @hash = Rack::Session::Abstract::SessionHash.new(store.new, nil)
18
+ end
19
+
20
+ it "returns keys" do
21
+ assert_equal ["foo", "baz"], hash.keys
22
+ end
23
+
24
+ it "returns values" do
25
+ assert_equal [:bar, :qux], hash.values
26
+ end
27
+
28
+ end
@@ -206,6 +206,14 @@ describe Rack::Utils do
206
206
  Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
207
207
  must_equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
208
208
 
209
+ Rack::Utils.parse_nested_query("x[][y]=1&x[][z][w]=a&x[][y]=2&x[][z][w]=b").
210
+ must_equal "x" => [{"y" => "1", "z" => {"w" => "a"}}, {"y" => "2", "z" => {"w" => "b"}}]
211
+ Rack::Utils.parse_nested_query("x[][z][w]=a&x[][y]=1&x[][z][w]=b&x[][y]=2").
212
+ must_equal "x" => [{"y" => "1", "z" => {"w" => "a"}}, {"y" => "2", "z" => {"w" => "b"}}]
213
+
214
+ Rack::Utils.parse_nested_query("data[books][][data][page]=1&data[books][][data][page]=2").
215
+ must_equal "data" => { "books" => [{ "data" => { "page" => "1"}}, { "data" => { "page" => "2"}}] }
216
+
209
217
  lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
210
218
  must_raise(Rack::Utils::ParameterTypeError).
211
219
  message.must_equal "expected Hash (got String) for param `y'"
@@ -300,13 +308,15 @@ describe Rack::Utils do
300
308
  must_equal 'x[y][][z]=1&x[y][][z]=2'
301
309
  Rack::Utils.build_nested_query('x' => { 'y' => [{ 'z' => '1', 'w' => 'a' }, { 'z' => '2', 'w' => '3' }] }).
302
310
  must_equal 'x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3'
311
+ Rack::Utils.build_nested_query({"foo" => ["1", ["2"]]}).
312
+ must_equal 'foo[]=1&foo[][]=2'
303
313
 
304
314
  lambda { Rack::Utils.build_nested_query("foo=bar") }.
305
315
  must_raise(ArgumentError).
306
316
  message.must_equal "value must be a Hash"
307
317
  end
308
318
 
309
- should 'perform the inverse function of #parse_nested_query' do
319
+ it 'performs the inverse function of #parse_nested_query' do
310
320
  [{"foo" => nil, "bar" => ""},
311
321
  {"foo" => "bar", "baz" => ""},
312
322
  {"foo" => ["1", "2"]},
@@ -323,7 +333,8 @@ describe Rack::Utils do
323
333
  {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
324
334
  {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
325
335
  {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
326
- {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
336
+ {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}},
337
+ {"foo" => ["1", ["2"]]},
327
338
  ].each { |params|
328
339
  qs = Rack::Utils.build_nested_query(params)
329
340
  Rack::Utils.parse_nested_query(qs).must_equal params
@@ -17,6 +17,19 @@ describe Rack::Handler::WEBrick do
17
17
  Rack::Lint.new(TestRequest.new)
18
18
  @thread = Thread.new { @server.start }
19
19
  trap(:INT) { @server.shutdown }
20
+ @status_thread = Thread.new do
21
+ seconds = 10
22
+ wait_time = 0.1
23
+ until is_running? || seconds <= 0
24
+ seconds -= wait_time
25
+ sleep wait_time
26
+ end
27
+ raise "Server never reached status 'Running'" unless is_running?
28
+ end
29
+ end
30
+
31
+ def is_running?
32
+ @server.status == :Running
20
33
  end
21
34
 
22
35
  it "respond" do
@@ -188,6 +201,7 @@ describe Rack::Handler::WEBrick do
188
201
  end
189
202
 
190
203
  after do
204
+ @status_thread.join
191
205
  @server.shutdown
192
206
  @thread.join
193
207
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.alpha
4
+ version: 2.0.0.rc1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Christian Neukirchen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-17 00:00:00.000000000 Z
11
+ date: 2016-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -185,6 +185,7 @@ files:
185
185
  - test/cgi/assets/javascripts/app.js
186
186
  - test/cgi/assets/stylesheets/app.css
187
187
  - test/cgi/lighttpd.conf
188
+ - test/cgi/lighttpd.errors
188
189
  - test/cgi/rackup_stub.rb
189
190
  - test/cgi/sample_rackup.ru
190
191
  - test/cgi/test
@@ -221,6 +222,7 @@ files:
221
222
  - test/multipart/semicolon
222
223
  - test/multipart/text
223
224
  - test/multipart/three_files_three_fields
225
+ - test/multipart/unity3d_wwwform
224
226
  - test/multipart/webkit
225
227
  - test/rackup/config.ru
226
228
  - test/registering_handler/rack/handler/registering_myself.rb
@@ -262,6 +264,7 @@ files:
262
264
  - test/spec_sendfile.rb
263
265
  - test/spec_server.rb
264
266
  - test/spec_session_abstract_id.rb
267
+ - test/spec_session_abstract_session_hash.rb
265
268
  - test/spec_session_cookie.rb
266
269
  - test/spec_session_memcache.rb
267
270
  - test/spec_session_pool.rb
@@ -300,7 +303,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
300
303
  version: 1.3.1
301
304
  requirements: []
302
305
  rubyforge_project:
303
- rubygems_version: 2.5.1
306
+ rubygems_version: 2.6.4
304
307
  signing_key:
305
308
  specification_version: 4
306
309
  summary: a modular Ruby webserver interface
@@ -343,6 +346,7 @@ test_files:
343
346
  - test/spec_sendfile.rb
344
347
  - test/spec_server.rb
345
348
  - test/spec_session_abstract_id.rb
349
+ - test/spec_session_abstract_session_hash.rb
346
350
  - test/spec_session_cookie.rb
347
351
  - test/spec_session_memcache.rb
348
352
  - test/spec_session_pool.rb