rack 1.4.1 → 1.4.2

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.

Files changed (81) hide show
  1. data/COPYING +1 -1
  2. data/KNOWN-ISSUES +9 -0
  3. data/README.rdoc +72 -7
  4. data/Rakefile +18 -11
  5. data/SPEC +3 -1
  6. data/contrib/rack.png +0 -0
  7. data/contrib/rack.svg +150 -0
  8. data/contrib/rdoc.css +412 -0
  9. data/lib/rack/auth/basic.rb +1 -1
  10. data/lib/rack/auth/digest/nonce.rb +1 -1
  11. data/lib/rack/backports/uri/common_18.rb +14 -28
  12. data/lib/rack/backports/uri/common_192.rb +14 -17
  13. data/lib/rack/backports/uri/common_193.rb +29 -0
  14. data/lib/rack/body_proxy.rb +10 -0
  15. data/lib/rack/builder.rb +1 -1
  16. data/lib/rack/cascade.rb +11 -0
  17. data/lib/rack/commonlogger.rb +18 -5
  18. data/lib/rack/deflater.rb +5 -1
  19. data/lib/rack/directory.rb +1 -1
  20. data/lib/rack/etag.rb +6 -3
  21. data/lib/rack/file.rb +13 -4
  22. data/lib/rack/head.rb +1 -0
  23. data/lib/rack/lint.rb +3 -1
  24. data/lib/rack/lock.rb +3 -4
  25. data/lib/rack/mime.rb +1 -1
  26. data/lib/rack/mock.rb +3 -2
  27. data/lib/rack/multipart.rb +2 -2
  28. data/lib/rack/multipart/parser.rb +6 -4
  29. data/lib/rack/reloader.rb +1 -1
  30. data/lib/rack/request.rb +2 -4
  31. data/lib/rack/response.rb +2 -1
  32. data/lib/rack/server.rb +28 -2
  33. data/lib/rack/session/abstract/id.rb +5 -0
  34. data/lib/rack/session/cookie.rb +9 -0
  35. data/lib/rack/static.rb +90 -8
  36. data/lib/rack/utils.rb +17 -10
  37. data/rack.gemspec +3 -3
  38. data/test/builder/line.ru +1 -0
  39. data/test/cgi/assets/folder/test.js +1 -0
  40. data/test/cgi/assets/fonts/font.eot +1 -0
  41. data/test/cgi/assets/images/image.png +1 -0
  42. data/test/cgi/assets/index.html +1 -0
  43. data/test/cgi/assets/javascripts/app.js +1 -0
  44. data/test/cgi/assets/stylesheets/app.css +1 -0
  45. data/test/spec_auth_basic.rb +8 -0
  46. data/test/spec_auth_digest.rb +14 -0
  47. data/test/spec_body_proxy.rb +4 -0
  48. data/test/spec_builder.rb +7 -1
  49. data/test/spec_cascade.rb +8 -0
  50. data/test/spec_chunked.rb +6 -6
  51. data/test/spec_config.rb +0 -1
  52. data/test/spec_content_length.rb +26 -13
  53. data/test/spec_content_type.rb +15 -5
  54. data/test/spec_deflater.rb +35 -17
  55. data/test/spec_directory.rb +20 -1
  56. data/test/spec_etag.rb +29 -13
  57. data/test/spec_file.rb +42 -25
  58. data/test/spec_head.rb +25 -7
  59. data/test/spec_lobster.rb +20 -5
  60. data/test/spec_lock.rb +46 -21
  61. data/test/spec_logger.rb +2 -7
  62. data/test/spec_methodoverride.rb +21 -22
  63. data/test/spec_mock.rb +12 -7
  64. data/test/spec_multipart.rb +29 -0
  65. data/test/spec_nulllogger.rb +13 -2
  66. data/test/spec_recursive.rb +12 -9
  67. data/test/spec_request.rb +2 -2
  68. data/test/spec_response.rb +30 -0
  69. data/test/spec_runtime.rb +15 -5
  70. data/test/spec_sendfile.rb +11 -8
  71. data/test/spec_server.rb +47 -0
  72. data/test/spec_session_cookie.rb +68 -1
  73. data/test/spec_session_memcache.rb +10 -8
  74. data/test/spec_session_pool.rb +13 -10
  75. data/test/spec_showexceptions.rb +9 -4
  76. data/test/spec_showstatus.rb +10 -5
  77. data/test/spec_static.rb +85 -9
  78. data/test/spec_urlmap.rb +10 -10
  79. data/test/spec_utils.rb +14 -1
  80. data/test/static/another/index.html +1 -0
  81. metadata +21 -8
@@ -41,7 +41,7 @@ module Rack
41
41
 
42
42
  class Request < Auth::AbstractRequest
43
43
  def basic?
44
- :basic == scheme
44
+ !parts.first.nil? && :basic == scheme
45
45
  end
46
46
 
47
47
  def credentials
@@ -38,7 +38,7 @@ module Rack
38
38
  end
39
39
 
40
40
  def stale?
41
- !self.class.time_limit.nil? && (@timestamp - Time.now.to_i) < self.class.time_limit
41
+ !self.class.time_limit.nil? && (Time.now.to_i - @timestamp) > self.class.time_limit
42
42
  end
43
43
 
44
44
  def fresh?
@@ -8,7 +8,21 @@
8
8
 
9
9
  module URI
10
10
  TBLENCWWWCOMP_ = {} # :nodoc:
11
+ 256.times do |i|
12
+ TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
13
+ end
14
+ TBLENCWWWCOMP_[' '] = '+'
15
+ TBLENCWWWCOMP_.freeze
11
16
  TBLDECWWWCOMP_ = {} # :nodoc:
17
+ 256.times do |i|
18
+ h, l = i>>4, i&15
19
+ TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
20
+ TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
21
+ TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
22
+ TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
23
+ end
24
+ TBLDECWWWCOMP_['+'] = ' '
25
+ TBLDECWWWCOMP_.freeze
12
26
 
13
27
  # Encode given +s+ to URL-encoded form data.
14
28
  #
@@ -26,18 +40,6 @@ module URI
26
40
  '%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase
27
41
  end.tr(' ', '+')
28
42
  else
29
- if TBLENCWWWCOMP_.empty?
30
- tbl = {}
31
- 256.times do |i|
32
- tbl[i.chr] = '%%%02X' % i
33
- end
34
- tbl[' '] = '+'
35
- begin
36
- TBLENCWWWCOMP_.replace(tbl)
37
- TBLENCWWWCOMP_.freeze
38
- rescue
39
- end
40
- end
41
43
  str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]}
42
44
  end
43
45
  end
@@ -48,22 +50,6 @@ module URI
48
50
  #
49
51
  # See URI.encode_www_form_component, URI.decode_www_form
50
52
  def self.decode_www_form_component(str, enc=nil)
51
- if TBLDECWWWCOMP_.empty?
52
- tbl = {}
53
- 256.times do |i|
54
- h, l = i>>4, i&15
55
- tbl['%%%X%X' % [h, l]] = i.chr
56
- tbl['%%%x%X' % [h, l]] = i.chr
57
- tbl['%%%X%x' % [h, l]] = i.chr
58
- tbl['%%%x%x' % [h, l]] = i.chr
59
- end
60
- tbl['+'] = ' '
61
- begin
62
- TBLDECWWWCOMP_.replace(tbl)
63
- TBLDECWWWCOMP_.freeze
64
- rescue
65
- end
66
- end
67
53
  raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%[0-9a-fA-F]{2}|[^%])*\z/ =~ str
68
54
  str.gsub(/\+|%[0-9a-fA-F]{2}/) {|m| TBLDECWWWCOMP_[m]}
69
55
  end
@@ -17,6 +17,19 @@
17
17
  require 'uri/common'
18
18
 
19
19
  module URI
20
+ TBLDECWWWCOMP_ = {} unless const_defined?(:TBLDECWWWCOMP_) #:nodoc:
21
+ if TBLDECWWWCOMP_.empty?
22
+ 256.times do |i|
23
+ h, l = i>>4, i&15
24
+ TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
25
+ TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
26
+ TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
27
+ TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
28
+ end
29
+ TBLDECWWWCOMP_['+'] = ' '
30
+ TBLDECWWWCOMP_.freeze
31
+ end
32
+
20
33
  def self.decode_www_form(str, enc=Encoding::UTF_8)
21
34
  return [] if str.empty?
22
35
  unless /\A#{WFKV_}=#{WFKV_}(?:[;&]#{WFKV_}=#{WFKV_})*\z/o =~ str
@@ -30,26 +43,10 @@ module URI
30
43
  end
31
44
 
32
45
  def self.decode_www_form_component(str, enc=Encoding::UTF_8)
33
- if TBLDECWWWCOMP_.empty?
34
- tbl = {}
35
- 256.times do |i|
36
- h, l = i>>4, i&15
37
- tbl['%%%X%X' % [h, l]] = i.chr
38
- tbl['%%%x%X' % [h, l]] = i.chr
39
- tbl['%%%X%x' % [h, l]] = i.chr
40
- tbl['%%%x%x' % [h, l]] = i.chr
41
- end
42
- tbl['+'] = ' '
43
- begin
44
- TBLDECWWWCOMP_.replace(tbl)
45
- TBLDECWWWCOMP_.freeze
46
- rescue
47
- end
48
- end
49
46
  raise ArgumentError, "invalid %-encoding (#{str})" unless /\A[^%]*(?:%\h\h[^%]*)*\z/ =~ str
50
47
  str.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc)
51
48
  end
52
49
 
53
- remove_const :WFKV_
50
+ remove_const :WFKV_ if const_defined?(:WFKV_)
54
51
  WFKV_ = '(?:[^%#=;&]*(?:%\h\h[^%#=;&]*)*)' # :nodoc:
55
52
  end
@@ -0,0 +1,29 @@
1
+ # :stopdoc:
2
+
3
+ require 'uri/common'
4
+
5
+ # Issue:
6
+ # http://bugs.ruby-lang.org/issues/5925
7
+ #
8
+ # Relevant commit:
9
+ # https://github.com/ruby/ruby/commit/edb7cdf1eabaff78dfa5ffedfbc2e91b29fa9ca1
10
+
11
+ module URI
12
+ 256.times do |i|
13
+ TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
14
+ end
15
+ TBLENCWWWCOMP_[' '] = '+'
16
+ TBLENCWWWCOMP_.freeze
17
+
18
+ 256.times do |i|
19
+ h, l = i>>4, i&15
20
+ TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
21
+ TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
22
+ TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
23
+ TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
24
+ end
25
+ TBLDECWWWCOMP_['+'] = ' '
26
+ TBLDECWWWCOMP_.freeze
27
+ end
28
+
29
+ # :startdoc:
@@ -5,6 +5,7 @@ module Rack
5
5
  end
6
6
 
7
7
  def respond_to?(*args)
8
+ return false if args.first.to_s =~ /^to_ary$/
8
9
  super or @body.respond_to?(*args)
9
10
  end
10
11
 
@@ -22,7 +23,16 @@ module Rack
22
23
  @closed
23
24
  end
24
25
 
26
+ # N.B. This method is a special case to address the bug described by #434.
27
+ # We are applying this special case for #each only. Future bugs of this
28
+ # class will be handled by requesting users to patch their ruby
29
+ # implementation, to save adding too many methods in this class.
30
+ def each(*args, &block)
31
+ @body.each(*args, &block)
32
+ end
33
+
25
34
  def method_missing(*args, &block)
35
+ super if args.first.to_s =~ /^to_ary$/
26
36
  @body.__send__(*args, &block)
27
37
  end
28
38
  end
@@ -38,7 +38,7 @@ module Rack
38
38
  end
39
39
  cfgfile.sub!(/^__END__\n.*\Z/m, '')
40
40
  app = eval "Rack::Builder.new {\n" + cfgfile + "\n}.to_app",
41
- TOPLEVEL_BINDING, config
41
+ TOPLEVEL_BINDING, config, 0
42
42
  else
43
43
  require config
44
44
  app = Object.const_get(::File.basename(config, '.rb').capitalize)
@@ -19,8 +19,19 @@ module Rack
19
19
  def call(env)
20
20
  result = NotFound
21
21
 
22
+ last_body = nil
23
+
22
24
  @apps.each do |app|
25
+ # The SPEC says that the body must be closed after it has been iterated
26
+ # by the server, or if it is replaced by a middleware action. Cascade
27
+ # replaces the body each time a cascade happens. It is assumed that nil
28
+ # does not respond to close, otherwise the previous application body
29
+ # will be closed. The final application body will not be closed, as it
30
+ # will be passed to the server as a result.
31
+ last_body.close if last_body.respond_to? :close
32
+
23
33
  result = app.call(env)
34
+ last_body = result[2]
24
35
  break unless @catch.include?(result[0].to_i)
25
36
  end
26
37
 
@@ -1,13 +1,26 @@
1
1
  require 'rack/body_proxy'
2
2
 
3
3
  module Rack
4
- # Rack::CommonLogger forwards every request to an +app+ given, and
5
- # logs a line in the Apache common log format to the +logger+, or
6
- # rack.errors by default.
4
+ # Rack::CommonLogger forwards every request to the given +app+, and
5
+ # logs a line in the
6
+ # {Apache common log format}[http://httpd.apache.org/docs/1.3/logs.html#common]
7
+ # to the +logger+.
8
+ #
9
+ # If +logger+ is nil, CommonLogger will fall back +rack.errors+, which is
10
+ # an instance of Rack::NullLogger.
11
+ #
12
+ # +logger+ can be any class, including the standard library Logger, and is
13
+ # expected to have a +write+ method, which accepts the CommonLogger::FORMAT.
14
+ # According to the SPEC, the error stream must also respond to +puts+
15
+ # (which takes a single argument that responds to +to_s+), and +flush+
16
+ # (which is called without arguments in order to make the error appear for
17
+ # sure)
7
18
  class CommonLogger
8
19
  # Common Log Format: http://httpd.apache.org/docs/1.3/logs.html#common
9
- # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
10
- # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
20
+ #
21
+ # lilith.local - - [07/Aug/2006 23:58:02] "GET / HTTP/1.1" 500 -
22
+ #
23
+ # %{%s - %s [%s] "%s %s%s %s" %d %s\n} %
11
24
  FORMAT = %{%s - %s [%s] "%s %s%s %s" %d %s %0.4f\n}
12
25
 
13
26
  def initialize(app, logger=nil)
@@ -45,6 +45,7 @@ module Rack
45
45
  when "identity"
46
46
  [status, headers, body]
47
47
  when nil
48
+ body.close if body.respond_to?(:close)
48
49
  message = "An acceptable encoding for the requested resource #{request.fullpath} could not be found."
49
50
  [406, {"Content-Type" => "text/plain", "Content-Length" => message.length.to_s}, [message]]
50
51
  end
@@ -64,6 +65,7 @@ module Rack
64
65
  gzip.write(part)
65
66
  gzip.flush
66
67
  }
68
+ ensure
67
69
  @body.close if @body.respond_to?(:close)
68
70
  gzip.close
69
71
  @writer = nil
@@ -90,9 +92,11 @@ module Rack
90
92
  def each
91
93
  deflater = ::Zlib::Deflate.new(*DEFLATE_ARGS)
92
94
  @body.each { |part| yield deflater.deflate(part, Zlib::SYNC_FLUSH) }
93
- @body.close if @body.respond_to?(:close)
94
95
  yield deflater.finish
95
96
  nil
97
+ ensure
98
+ @body.close if @body.respond_to?(:close)
99
+ deflater.close
96
100
  end
97
101
  end
98
102
  end
@@ -80,7 +80,7 @@ table { width:100%%; }
80
80
  @files = [['../','Parent Directory','','','']]
81
81
  glob = F.join(@path, '*')
82
82
 
83
- url_head = ([@script_name] + @path_info.split('/')).map do |part|
83
+ url_head = (@script_name.split('/') + @path_info.split('/')).map do |part|
84
84
  Rack::Utils.escape part
85
85
  end
86
86
 
@@ -28,8 +28,11 @@ module Rack
28
28
  end
29
29
 
30
30
  unless headers['Cache-Control']
31
- headers['Cache-Control'] =
32
- (digest ? @cache_control : @no_cache_control) || []
31
+ if digest
32
+ headers['Cache-Control'] = @cache_control if @cache_control
33
+ else
34
+ headers['Cache-Control'] = @no_cache_control if @no_cache_control
35
+ end
33
36
  end
34
37
 
35
38
  [status, headers, body]
@@ -46,7 +49,7 @@ module Rack
46
49
  end
47
50
 
48
51
  def skip_caching?(headers)
49
- headers['Cache-Control'] == 'no-cache' ||
52
+ (headers['Cache-Control'] && headers['Cache-Control'].include?('no-cache')) ||
50
53
  headers.key?('ETag') || headers.key?('Last-Modified')
51
54
  end
52
55
 
@@ -21,9 +21,16 @@ module Rack
21
21
 
22
22
  alias :to_path :path
23
23
 
24
- def initialize(root, cache_control = nil)
24
+ def initialize(root, headers={})
25
25
  @root = root
26
- @cache_control = cache_control
26
+ # Allow a cache_control string for backwards compatibility
27
+ if headers.instance_of? String
28
+ warn \
29
+ "Rack::File headers parameter replaces cache_control after Rack 1.5."
30
+ @headers = { 'Cache-Control' => headers }
31
+ else
32
+ @headers = headers
33
+ end
27
34
  end
28
35
 
29
36
  def call(env)
@@ -78,7 +85,9 @@ module Rack
78
85
  },
79
86
  env["REQUEST_METHOD"] == "HEAD" ? [] : self
80
87
  ]
81
- response[1].merge! 'Cache-Control' => @cache_control if @cache_control
88
+
89
+ # Set custom headers
90
+ @headers.each { |field, content| response[1][field] = content } if @headers
82
91
 
83
92
  # NOTE:
84
93
  # We check via File::size? whether this file provides size info
@@ -101,7 +110,7 @@ module Rack
101
110
  # Partial content:
102
111
  @range = ranges[0]
103
112
  response[0] = 206
104
- response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
113
+ response[1]["Content-Range"] = "bytes #{@range.begin}-#{@range.end}/#{size}"
105
114
  size = @range.end - @range.begin + 1
106
115
  end
107
116
 
@@ -9,6 +9,7 @@ class Head
9
9
  status, headers, body = @app.call(env)
10
10
 
11
11
  if env["REQUEST_METHOD"] == "HEAD"
12
+ body.close if body.respond_to? :close
12
13
  [status, headers, []]
13
14
  else
14
15
  [status, headers, body]
@@ -528,7 +528,9 @@ module Rack
528
528
  ## The Body itself should not be an instance of String, as this will
529
529
  ## break in Ruby 1.9.
530
530
  ##
531
- ## If the Body responds to +close+, it will be called after iteration.
531
+ ## If the Body responds to +close+, it will be called after iteration. If
532
+ ## the body is replaced by a middleware after action, the original body
533
+ ## must be closed first, if it repsonds to close.
532
534
  # XXX howto: assert("Body has not been closed") { @closed }
533
535
 
534
536
 
@@ -13,12 +13,11 @@ module Rack
13
13
  old, env[FLAG] = env[FLAG], false
14
14
  @mutex.lock
15
15
  response = @app.call(env)
16
- response[2] = BodyProxy.new(response[2]) { @mutex.unlock }
16
+ body = BodyProxy.new(response[2]) { @mutex.unlock }
17
+ response[2] = body
17
18
  response
18
- rescue Exception
19
- @mutex.unlock
20
- raise
21
19
  ensure
20
+ @mutex.unlock unless body
22
21
  env[FLAG] = old
23
22
  end
24
23
  end
@@ -598,7 +598,7 @@ module Rack
598
598
  ".wmv" => "video/x-ms-wmv",
599
599
  ".wmx" => "video/x-ms-wmx",
600
600
  ".wmz" => "application/x-ms-wmz",
601
- ".woff" => "application/octet-stream",
601
+ ".woff" => "application/font-woff",
602
602
  ".wpd" => "application/vnd.wordperfect",
603
603
  ".wpl" => "application/vnd.ms-wpl",
604
604
  ".wps" => "application/vnd.ms-works",
@@ -9,12 +9,12 @@ module Rack
9
9
  # Rack::MockRequest helps testing your Rack application without
10
10
  # actually using HTTP.
11
11
  #
12
- # After performing a request on a URL with get/post/put/delete, it
12
+ # After performing a request on a URL with get/post/put/patch/delete, it
13
13
  # returns a MockResponse with useful helper methods for effective
14
14
  # testing.
15
15
  #
16
16
  # You can pass a hash with additional configuration to the
17
- # get/post/put/delete.
17
+ # get/post/put/patch/delete.
18
18
  # <tt>:input</tt>:: A String or IO-like to be used as rack.input.
19
19
  # <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
20
20
  # <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
@@ -56,6 +56,7 @@ module Rack
56
56
  def get(uri, opts={}) request("GET", uri, opts) end
57
57
  def post(uri, opts={}) request("POST", uri, opts) end
58
58
  def put(uri, opts={}) request("PUT", uri, opts) end
59
+ def patch(uri, opts={}) request("PATCH", uri, opts) end
59
60
  def delete(uri, opts={}) request("DELETE", uri, opts) end
60
61
  def head(uri, opts={}) request("HEAD", uri, opts) end
61
62
 
@@ -12,7 +12,7 @@ module Rack
12
12
  MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
13
13
  TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
14
14
  CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
15
- DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})*/
15
+ DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/
16
16
  RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
17
17
  BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
18
18
  BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
@@ -31,4 +31,4 @@ module Rack
31
31
  end
32
32
 
33
33
  end
34
- end
34
+ end
@@ -48,13 +48,15 @@ module Rack
48
48
  @buf = ""
49
49
  @params = Utils::KeySpaceConstrainedParams.new
50
50
 
51
- @content_length = @env['CONTENT_LENGTH'].to_i
52
51
  @io = @env['rack.input']
53
52
  @io.rewind
54
53
 
55
54
  @boundary_size = Utils.bytesize(@boundary) + EOL.size
56
55
 
57
- @content_length -= @boundary_size
56
+ if @content_length = @env['CONTENT_LENGTH']
57
+ @content_length = @content_length.to_i
58
+ @content_length -= @boundary_size
59
+ end
58
60
  true
59
61
  end
60
62
 
@@ -104,11 +106,11 @@ module Rack
104
106
  body << @buf.slice!(0, @buf.size - (@boundary_size+4))
105
107
  end
106
108
 
107
- content = @io.read(BUFSIZE < @content_length ? BUFSIZE : @content_length)
109
+ content = @io.read(@content_length && BUFSIZE >= @content_length ? @content_length : BUFSIZE)
108
110
  raise EOFError, "bad content body" if content.nil? || content.empty?
109
111
 
110
112
  @buf << content
111
- @content_length -= content.size
113
+ @content_length -= content.size if @content_length
112
114
  end
113
115
 
114
116
  [head, filename, content_type, name, body]