rack 2.2.9 → 3.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +330 -88
  3. data/CONTRIBUTING.md +63 -55
  4. data/MIT-LICENSE +1 -1
  5. data/README.md +328 -0
  6. data/SPEC.rdoc +204 -131
  7. data/lib/rack/auth/abstract/handler.rb +3 -1
  8. data/lib/rack/auth/abstract/request.rb +3 -1
  9. data/lib/rack/auth/basic.rb +1 -4
  10. data/lib/rack/bad_request.rb +8 -0
  11. data/lib/rack/body_proxy.rb +21 -3
  12. data/lib/rack/builder.rb +102 -69
  13. data/lib/rack/cascade.rb +2 -3
  14. data/lib/rack/common_logger.rb +23 -18
  15. data/lib/rack/conditional_get.rb +18 -15
  16. data/lib/rack/constants.rb +67 -0
  17. data/lib/rack/content_length.rb +12 -16
  18. data/lib/rack/content_type.rb +8 -5
  19. data/lib/rack/deflater.rb +40 -26
  20. data/lib/rack/directory.rb +9 -3
  21. data/lib/rack/etag.rb +14 -23
  22. data/lib/rack/events.rb +4 -0
  23. data/lib/rack/files.rb +15 -17
  24. data/lib/rack/head.rb +9 -8
  25. data/lib/rack/headers.rb +238 -0
  26. data/lib/rack/lint.rb +840 -644
  27. data/lib/rack/lock.rb +2 -5
  28. data/lib/rack/logger.rb +3 -0
  29. data/lib/rack/method_override.rb +5 -1
  30. data/lib/rack/mime.rb +14 -5
  31. data/lib/rack/mock.rb +1 -271
  32. data/lib/rack/mock_request.rb +161 -0
  33. data/lib/rack/mock_response.rb +124 -0
  34. data/lib/rack/multipart/generator.rb +7 -5
  35. data/lib/rack/multipart/parser.rb +213 -95
  36. data/lib/rack/multipart/uploaded_file.rb +4 -0
  37. data/lib/rack/multipart.rb +53 -40
  38. data/lib/rack/null_logger.rb +9 -0
  39. data/lib/rack/query_parser.rb +81 -102
  40. data/lib/rack/recursive.rb +2 -0
  41. data/lib/rack/reloader.rb +0 -2
  42. data/lib/rack/request.rb +260 -123
  43. data/lib/rack/response.rb +151 -66
  44. data/lib/rack/rewindable_input.rb +24 -5
  45. data/lib/rack/runtime.rb +7 -6
  46. data/lib/rack/sendfile.rb +30 -25
  47. data/lib/rack/show_exceptions.rb +21 -4
  48. data/lib/rack/show_status.rb +17 -7
  49. data/lib/rack/static.rb +8 -8
  50. data/lib/rack/tempfile_reaper.rb +15 -4
  51. data/lib/rack/urlmap.rb +3 -1
  52. data/lib/rack/utils.rb +232 -233
  53. data/lib/rack/version.rb +1 -9
  54. data/lib/rack.rb +13 -89
  55. metadata +15 -41
  56. data/README.rdoc +0 -320
  57. data/Rakefile +0 -130
  58. data/bin/rackup +0 -5
  59. data/contrib/rack.png +0 -0
  60. data/contrib/rack.svg +0 -150
  61. data/contrib/rack_logo.svg +0 -164
  62. data/contrib/rdoc.css +0 -412
  63. data/example/lobster.ru +0 -6
  64. data/example/protectedlobster.rb +0 -16
  65. data/example/protectedlobster.ru +0 -10
  66. data/lib/rack/auth/digest/md5.rb +0 -131
  67. data/lib/rack/auth/digest/nonce.rb +0 -54
  68. data/lib/rack/auth/digest/params.rb +0 -54
  69. data/lib/rack/auth/digest/request.rb +0 -43
  70. data/lib/rack/chunked.rb +0 -117
  71. data/lib/rack/core_ext/regexp.rb +0 -14
  72. data/lib/rack/file.rb +0 -7
  73. data/lib/rack/handler/cgi.rb +0 -59
  74. data/lib/rack/handler/fastcgi.rb +0 -100
  75. data/lib/rack/handler/lsws.rb +0 -61
  76. data/lib/rack/handler/scgi.rb +0 -71
  77. data/lib/rack/handler/thin.rb +0 -36
  78. data/lib/rack/handler/webrick.rb +0 -129
  79. data/lib/rack/handler.rb +0 -104
  80. data/lib/rack/lobster.rb +0 -70
  81. data/lib/rack/server.rb +0 -466
  82. data/lib/rack/session/abstract/id.rb +0 -523
  83. data/lib/rack/session/cookie.rb +0 -204
  84. data/lib/rack/session/memcache.rb +0 -10
  85. data/lib/rack/session/pool.rb +0 -85
  86. data/rack.gemspec +0 -46
@@ -1,64 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+ require_relative 'utils'
5
+
3
6
  require_relative 'multipart/parser'
7
+ require_relative 'multipart/generator'
8
+
9
+ require_relative 'bad_request'
4
10
 
5
11
  module Rack
6
12
  # A multipart form data parser, adapted from IOWA.
7
13
  #
8
14
  # Usually, Rack::Request#POST takes care of calling this.
9
15
  module Multipart
10
- autoload :UploadedFile, 'rack/multipart/uploaded_file'
11
- autoload :Generator, 'rack/multipart/generator'
12
-
13
- EOL = "\r\n"
14
16
  MULTIPART_BOUNDARY = "AaB03x"
15
- MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
16
- TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
17
- CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
18
- VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
19
- BROKEN = /^#{CONDISP}.*;\s*filename=(#{VALUE})/i
20
- MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
21
- MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:[^:]*;\s*name=(#{VALUE})/ni
22
- MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
23
- # Updated definitions from RFC 2231
24
- ATTRIBUTE_CHAR = %r{[^ \x00-\x1f\x7f)(><@,;:\\"/\[\]?='*%]}
25
- ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
26
- SECTION = /\*[0-9]+/
27
- REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
28
- REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
29
- EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9][0-9]*\*/
30
- EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
31
- EXTENDED_OTHER_PARAMETER = /(#{EXTENDED_OTHER_NAME})=(#{EXTENDED_OTHER_VALUE}*)/
32
- EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
33
- EXTENDED_INITIAL_VALUE = /[a-zA-Z0-9\-]*'[a-zA-Z0-9\-]*'#{EXTENDED_OTHER_VALUE}*/
34
- EXTENDED_INITIAL_PARAMETER = /(#{EXTENDED_INITIAL_NAME})=(#{EXTENDED_INITIAL_VALUE})/
35
- EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
36
- DISPPARM = /;\s*(?:#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER})\s*/
37
- RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
17
+
18
+ class MissingInputError < StandardError
19
+ include BadRequest
20
+ end
21
+
22
+ # Accumulator for multipart form data, conforming to the QueryParser API.
23
+ # In future, the Parser could return the pair list directly, but that would
24
+ # change its API.
25
+ class ParamList # :nodoc:
26
+ def self.make_params
27
+ new
28
+ end
29
+
30
+ def self.normalize_params(params, key, value)
31
+ params << [key, value]
32
+ end
33
+
34
+ def initialize
35
+ @pairs = []
36
+ end
37
+
38
+ def <<(pair)
39
+ @pairs << pair
40
+ end
41
+
42
+ def to_params_hash
43
+ @pairs
44
+ end
45
+ end
38
46
 
39
47
  class << self
40
48
  def parse_multipart(env, params = Rack::Utils.default_query_parser)
41
- extract_multipart Rack::Request.new(env), params
42
- end
49
+ unless io = env[RACK_INPUT]
50
+ raise MissingInputError, "Missing input stream!"
51
+ end
43
52
 
44
- def extract_multipart(req, params = Rack::Utils.default_query_parser)
45
- io = req.get_header(RACK_INPUT)
46
- io.rewind
47
- content_length = req.content_length
48
- content_length = content_length.to_i if content_length
53
+ if content_length = env['CONTENT_LENGTH']
54
+ content_length = content_length.to_i
55
+ end
49
56
 
50
- tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
51
- bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
57
+ content_type = env['CONTENT_TYPE']
52
58
 
53
- info = Parser.parse io, content_length, req.get_header('CONTENT_TYPE'), tempfile, bufsize, params
54
- req.set_header(RACK_TEMPFILES, info.tmp_files)
55
- info.params
59
+ tempfile = env[RACK_MULTIPART_TEMPFILE_FACTORY] || Parser::TEMPFILE_FACTORY
60
+ bufsize = env[RACK_MULTIPART_BUFFER_SIZE] || Parser::BUFSIZE
61
+
62
+ info = Parser.parse(io, content_length, content_type, tempfile, bufsize, params)
63
+ env[RACK_TEMPFILES] = info.tmp_files
64
+
65
+ return info.params
66
+ end
67
+
68
+ def extract_multipart(request, params = Rack::Utils.default_query_parser)
69
+ parse_multipart(request.env)
56
70
  end
57
71
 
58
72
  def build_multipart(params, first = true)
59
73
  Generator.new(params, first).dump
60
74
  end
61
75
  end
62
-
63
76
  end
64
77
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'constants'
4
+
3
5
  module Rack
4
6
  class NullLogger
5
7
  def initialize(app)
@@ -22,6 +24,11 @@ module Rack
22
24
  def warn? ; end
23
25
  def error? ; end
24
26
  def fatal? ; end
27
+ def debug! ; end
28
+ def error! ; end
29
+ def fatal! ; end
30
+ def info! ; end
31
+ def warn! ; end
25
32
  def level ; end
26
33
  def progname ; end
27
34
  def datetime_format ; end
@@ -34,6 +41,8 @@ module Rack
34
41
  def sev_threshold=(sev_threshold); end
35
42
  def close ; end
36
43
  def add(severity, message = nil, progname = nil, &block); end
44
+ def log(severity, message = nil, progname = nil, &block); end
37
45
  def <<(msg); end
46
+ def reopen(logdev = nil); end
38
47
  end
39
48
  end
@@ -1,48 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'bad_request'
4
+ require 'uri'
5
+
3
6
  module Rack
4
7
  class QueryParser
5
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
6
-
7
- DEFAULT_SEP = /[&;] */n
8
- COMMON_SEP = { ";" => /[;] */n, ";," => /[;,] */n, "&" => /[&] */n }
8
+ DEFAULT_SEP = /& */n
9
+ COMMON_SEP = { ";" => /; */n, ";," => /[;,] */n, "&" => /& */n }
9
10
 
10
11
  # ParameterTypeError is the error that is raised when incoming structural
11
12
  # parameters (parsed by parse_nested_query) contain conflicting types.
12
- class ParameterTypeError < TypeError; end
13
+ class ParameterTypeError < TypeError
14
+ include BadRequest
15
+ end
13
16
 
14
17
  # InvalidParameterError is the error that is raised when incoming structural
15
18
  # parameters (parsed by parse_nested_query) contain invalid format or byte
16
19
  # sequence.
17
- class InvalidParameterError < ArgumentError; end
20
+ class InvalidParameterError < ArgumentError
21
+ include BadRequest
22
+ end
18
23
 
19
24
  # ParamsTooDeepError is the error that is raised when params are recursively
20
25
  # nested over the specified limit.
21
- class ParamsTooDeepError < RangeError; end
26
+ class ParamsTooDeepError < RangeError
27
+ include BadRequest
28
+ end
22
29
 
23
- def self.make_default(key_space_limit, param_depth_limit)
24
- new Params, key_space_limit, param_depth_limit
30
+ def self.make_default(param_depth_limit)
31
+ new Params, param_depth_limit
25
32
  end
26
33
 
27
- attr_reader :key_space_limit, :param_depth_limit
34
+ attr_reader :param_depth_limit
28
35
 
29
- def initialize(params_class, key_space_limit, param_depth_limit)
36
+ def initialize(params_class, param_depth_limit)
30
37
  @params_class = params_class
31
- @key_space_limit = key_space_limit
32
38
  @param_depth_limit = param_depth_limit
33
39
  end
34
40
 
35
41
  # Stolen from Mongrel, with some small modifications:
36
- # Parses a query string by breaking it up at the '&'
37
- # and ';' characters. You can also use this to parse
38
- # cookies by changing the characters used in the second
39
- # parameter (which defaults to '&;').
40
- def parse_query(qs, d = nil, &unescaper)
42
+ # Parses a query string by breaking it up at the '&'. You can also use this
43
+ # to parse cookies by changing the characters used in the second parameter
44
+ # (which defaults to '&').
45
+ def parse_query(qs, separator = nil, &unescaper)
41
46
  unescaper ||= method(:unescape)
42
47
 
43
48
  params = make_params
44
49
 
45
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
50
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
46
51
  next if p.empty?
47
52
  k, v = p.split('=', 2).map!(&unescaper)
48
53
 
@@ -65,14 +70,14 @@ module Rack
65
70
  # query strings with parameters of conflicting types, in this case a
66
71
  # ParameterTypeError is raised. Users are encouraged to return a 400 in this
67
72
  # case.
68
- def parse_nested_query(qs, d = nil)
73
+ def parse_nested_query(qs, separator = nil)
69
74
  params = make_params
70
75
 
71
76
  unless qs.nil? || qs.empty?
72
- (qs || '').split(d ? (COMMON_SEP[d] || /[#{d}] */n) : DEFAULT_SEP).each do |p|
77
+ (qs || '').split(separator ? (COMMON_SEP[separator] || /[#{separator}] */n) : DEFAULT_SEP).each do |p|
73
78
  k, v = p.split('=', 2).map! { |s| unescape(s) }
74
79
 
75
- normalize_params(params, k, v, param_depth_limit)
80
+ _normalize_params(params, k, v, 0)
76
81
  end
77
82
  end
78
83
 
@@ -83,58 +88,87 @@ module Rack
83
88
 
84
89
  # normalize_params recursively expands parameters into structural types. If
85
90
  # the structural types represented by two different parameter names are in
86
- # conflict, a ParameterTypeError is raised.
87
- def normalize_params(params, name, v, depth)
88
- raise ParamsTooDeepError if depth <= 0
89
-
90
- name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
91
- k = $1 || ''
92
- after = $' || ''
91
+ # conflict, a ParameterTypeError is raised. The depth argument is deprecated
92
+ # and should no longer be used, it is kept for backwards compatibility with
93
+ # earlier versions of rack.
94
+ def normalize_params(params, name, v, _depth=nil)
95
+ _normalize_params(params, name, v, 0)
96
+ end
93
97
 
94
- if k.empty?
95
- if !v.nil? && name == "[]"
96
- return Array(v)
98
+ private def _normalize_params(params, name, v, depth)
99
+ raise ParamsTooDeepError if depth >= param_depth_limit
100
+
101
+ if !name
102
+ # nil name, treat same as empty string (required by tests)
103
+ k = after = ''
104
+ elsif depth == 0
105
+ # Start of parsing, don't treat [] or [ at start of string specially
106
+ if start = name.index('[', 1)
107
+ # Start of parameter nesting, use part before brackets as key
108
+ k = name[0, start]
109
+ after = name[start, name.length]
97
110
  else
98
- return
111
+ # Plain parameter with no nesting
112
+ k = name
113
+ after = ''
99
114
  end
115
+ elsif name.start_with?('[]')
116
+ # Array nesting
117
+ k = '[]'
118
+ after = name[2, name.length]
119
+ elsif name.start_with?('[') && (start = name.index(']', 1))
120
+ # Hash nesting, use the part inside brackets as the key
121
+ k = name[1, start-1]
122
+ after = name[start+1, name.length]
123
+ else
124
+ # Probably malformed input, nested but not starting with [
125
+ # treat full name as key for backwards compatibility.
126
+ k = name
127
+ after = ''
100
128
  end
101
129
 
130
+ return if k.empty?
131
+
102
132
  if after == ''
103
- params[k] = v
133
+ if k == '[]' && depth != 0
134
+ return [v]
135
+ else
136
+ params[k] = v
137
+ end
104
138
  elsif after == "["
105
139
  params[name] = v
106
140
  elsif after == "[]"
107
141
  params[k] ||= []
108
142
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
109
143
  params[k] << v
110
- elsif after =~ %r(^\[\]\[([^\[\]]+)\]$) || after =~ %r(^\[\](.+)$)
111
- child_key = $1
144
+ elsif after.start_with?('[]')
145
+ # Recognize x[][y] (hash inside array) parameters
146
+ unless after[2] == '[' && after.end_with?(']') && (child_key = after[3, after.length-4]) && !child_key.empty? && !child_key.index('[') && !child_key.index(']')
147
+ # Handle other nested array parameters
148
+ child_key = after[2, after.length]
149
+ end
112
150
  params[k] ||= []
113
151
  raise ParameterTypeError, "expected Array (got #{params[k].class.name}) for param `#{k}'" unless params[k].is_a?(Array)
114
152
  if params_hash_type?(params[k].last) && !params_hash_has_key?(params[k].last, child_key)
115
- normalize_params(params[k].last, child_key, v, depth - 1)
153
+ _normalize_params(params[k].last, child_key, v, depth + 1)
116
154
  else
117
- params[k] << normalize_params(make_params, child_key, v, depth - 1)
155
+ params[k] << _normalize_params(make_params, child_key, v, depth + 1)
118
156
  end
119
157
  else
120
158
  params[k] ||= make_params
121
159
  raise ParameterTypeError, "expected Hash (got #{params[k].class.name}) for param `#{k}'" unless params_hash_type?(params[k])
122
- params[k] = normalize_params(params[k], after, v, depth - 1)
160
+ params[k] = _normalize_params(params[k], after, v, depth + 1)
123
161
  end
124
162
 
125
163
  params
126
164
  end
127
165
 
128
166
  def make_params
129
- @params_class.new @key_space_limit
130
- end
131
-
132
- def new_space_limit(key_space_limit)
133
- self.class.new @params_class, key_space_limit, param_depth_limit
167
+ @params_class.new
134
168
  end
135
169
 
136
170
  def new_depth_limit(param_depth_limit)
137
- self.class.new @params_class, key_space_limit, param_depth_limit
171
+ self.class.new @params_class, param_depth_limit
138
172
  end
139
173
 
140
174
  private
@@ -155,66 +189,11 @@ module Rack
155
189
  true
156
190
  end
157
191
 
158
- def unescape(s)
159
- Utils.unescape(s)
192
+ def unescape(string, encoding = Encoding::UTF_8)
193
+ URI.decode_www_form_component(string, encoding)
160
194
  end
161
195
 
162
- class Params
163
- def initialize(limit)
164
- @limit = limit
165
- @size = 0
166
- @params = {}
167
- end
168
-
169
- def [](key)
170
- @params[key]
171
- end
172
-
173
- def []=(key, value)
174
- @size += key.size if key && !@params.key?(key)
175
- raise ParamsTooDeepError, 'exceeded available parameter key space' if @size > @limit
176
- @params[key] = value
177
- end
178
-
179
- def key?(key)
180
- @params.key?(key)
181
- end
182
-
183
- # Recursively unwraps nested `Params` objects and constructs an object
184
- # of the same shape, but using the objects' internal representations
185
- # (Ruby hashes) in place of the objects. The result is a hash consisting
186
- # purely of Ruby primitives.
187
- #
188
- # Mutation warning!
189
- #
190
- # 1. This method mutates the internal representation of the `Params`
191
- # objects in order to save object allocations.
192
- #
193
- # 2. The value you get back is a reference to the internal hash
194
- # representation, not a copy.
195
- #
196
- # 3. Because the `Params` object's internal representation is mutable
197
- # through the `#[]=` method, it is not thread safe. The result of
198
- # getting the hash representation while another thread is adding a
199
- # key to it is non-deterministic.
200
- #
201
- def to_h
202
- @params.each do |key, value|
203
- case value
204
- when self
205
- # Handle circular references gracefully.
206
- @params[key] = @params
207
- when Params
208
- @params[key] = value.to_h
209
- when Array
210
- value.map! { |v| v.kind_of?(Params) ? v.to_h : v }
211
- else
212
- # Ignore anything that is not a `Params` object or
213
- # a collection that can contain one.
214
- end
215
- end
216
- @params
217
- end
196
+ class Params < Hash
218
197
  alias_method :to_params_hash, :to_h
219
198
  end
220
199
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'uri'
4
4
 
5
+ require_relative 'constants'
6
+
5
7
  module Rack
6
8
  # Rack::ForwardRequest gets caught by Rack::Recursive and redirects
7
9
  # the current request to the app at +url+.
data/lib/rack/reloader.rb CHANGED
@@ -22,8 +22,6 @@ module Rack
22
22
  # It is performing a check/reload cycle at the start of every request, but
23
23
  # also respects a cool down time, during which nothing will be done.
24
24
  class Reloader
25
- (require_relative 'core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
26
-
27
25
  def initialize(app, cooldown = 10, backend = Stat)
28
26
  @app = app
29
27
  @cooldown = cooldown