rack 1.5.5 → 1.6.0.beta

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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/KNOWN-ISSUES +14 -0
  3. data/README.rdoc +10 -6
  4. data/Rakefile +3 -4
  5. data/SPEC +59 -23
  6. data/lib/rack.rb +2 -1
  7. data/lib/rack/auth/abstract/request.rb +1 -1
  8. data/lib/rack/auth/basic.rb +1 -1
  9. data/lib/rack/auth/digest/md5.rb +1 -1
  10. data/lib/rack/backports/uri/common_18.rb +1 -1
  11. data/lib/rack/builder.rb +19 -4
  12. data/lib/rack/cascade.rb +2 -2
  13. data/lib/rack/chunked.rb +12 -1
  14. data/lib/rack/commonlogger.rb +13 -5
  15. data/lib/rack/conditionalget.rb +14 -2
  16. data/lib/rack/content_length.rb +5 -1
  17. data/lib/rack/deflater.rb +52 -13
  18. data/lib/rack/directory.rb +8 -2
  19. data/lib/rack/etag.rb +14 -6
  20. data/lib/rack/file.rb +10 -14
  21. data/lib/rack/handler.rb +2 -0
  22. data/lib/rack/handler/fastcgi.rb +4 -1
  23. data/lib/rack/handler/mongrel.rb +8 -2
  24. data/lib/rack/handler/scgi.rb +4 -1
  25. data/lib/rack/handler/thin.rb +8 -2
  26. data/lib/rack/handler/webrick.rb +46 -6
  27. data/lib/rack/head.rb +7 -2
  28. data/lib/rack/lint.rb +73 -25
  29. data/lib/rack/lobster.rb +8 -3
  30. data/lib/rack/methodoverride.rb +14 -3
  31. data/lib/rack/mime.rb +1 -15
  32. data/lib/rack/mock.rb +15 -7
  33. data/lib/rack/multipart.rb +2 -2
  34. data/lib/rack/multipart/parser.rb +107 -53
  35. data/lib/rack/multipart/uploaded_file.rb +2 -2
  36. data/lib/rack/nulllogger.rb +21 -2
  37. data/lib/rack/request.rb +38 -24
  38. data/lib/rack/response.rb +5 -0
  39. data/lib/rack/sendfile.rb +10 -5
  40. data/lib/rack/server.rb +45 -17
  41. data/lib/rack/session/abstract/id.rb +7 -6
  42. data/lib/rack/session/cookie.rb +17 -7
  43. data/lib/rack/session/memcache.rb +4 -4
  44. data/lib/rack/session/pool.rb +3 -6
  45. data/lib/rack/showexceptions.rb +20 -11
  46. data/lib/rack/showstatus.rb +1 -1
  47. data/lib/rack/static.rb +27 -30
  48. data/lib/rack/tempfile_reaper.rb +22 -0
  49. data/lib/rack/urlmap.rb +17 -3
  50. data/lib/rack/utils.rb +78 -47
  51. data/lib/rack/utils/okjson.rb +90 -91
  52. data/rack.gemspec +3 -3
  53. data/test/multipart/filename_and_no_name +6 -0
  54. data/test/multipart/invalid_character +6 -0
  55. data/test/spec_builder.rb +13 -4
  56. data/test/spec_chunked.rb +16 -0
  57. data/test/spec_commonlogger.rb +36 -0
  58. data/test/spec_content_length.rb +3 -1
  59. data/test/spec_deflater.rb +283 -148
  60. data/test/spec_etag.rb +11 -2
  61. data/test/spec_file.rb +11 -3
  62. data/test/spec_head.rb +2 -0
  63. data/test/spec_lobster.rb +1 -1
  64. data/test/spec_mock.rb +8 -0
  65. data/test/spec_multipart.rb +111 -49
  66. data/test/spec_request.rb +109 -25
  67. data/test/spec_response.rb +30 -0
  68. data/test/spec_server.rb +20 -5
  69. data/test/spec_session_cookie.rb +45 -2
  70. data/test/spec_session_memcache.rb +1 -1
  71. data/test/spec_showexceptions.rb +29 -36
  72. data/test/spec_showstatus.rb +19 -0
  73. data/test/spec_tempfile_reaper.rb +63 -0
  74. data/test/spec_urlmap.rb +23 -0
  75. data/test/spec_utils.rb +60 -10
  76. data/test/spec_webrick.rb +41 -0
  77. metadata +12 -9
  78. data/test/cgi/lighttpd.errors +0 -1
  79. data/test/multipart/three_files_three_fields +0 -31
@@ -102,7 +102,17 @@ module Rack
102
102
  ## follows the <tt>?</tt>, if any. May be
103
103
  ## empty, but is always required!
104
104
 
105
- ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>:: When combined with <tt>SCRIPT_NAME</tt> and <tt>PATH_INFO</tt>, these variables can be used to complete the URL. Note, however, that <tt>HTTP_HOST</tt>, if present, should be used in preference to <tt>SERVER_NAME</tt> for reconstructing the request URL. <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt> can never be empty strings, and so are always required.
105
+ ## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
106
+ ## When combined with <tt>SCRIPT_NAME</tt> and
107
+ ## <tt>PATH_INFO</tt>, these variables can be
108
+ ## used to complete the URL. Note, however,
109
+ ## that <tt>HTTP_HOST</tt>, if present,
110
+ ## should be used in preference to
111
+ ## <tt>SERVER_NAME</tt> for reconstructing
112
+ ## the request URL.
113
+ ## <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
114
+ ## can never be empty strings, and so
115
+ ## are always required.
106
116
 
107
117
  ## <tt>HTTP_</tt> Variables:: Variables corresponding to the
108
118
  ## client-supplied HTTP request
@@ -112,29 +122,60 @@ module Rack
112
122
  ## variables should correspond with
113
123
  ## the presence or absence of the
114
124
  ## appropriate HTTP header in the
115
- ## request. See <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
116
- ## RFC3875 section 4.1.18</a> for specific behavior.
125
+ ## request. See
126
+ ## <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
127
+ ## RFC3875 section 4.1.18</a> for
128
+ ## specific behavior.
117
129
 
118
130
  ## In addition to this, the Rack environment must include these
119
131
  ## Rack-specific variables:
120
132
 
121
- ## <tt>rack.version</tt>:: The Array representing this version of Rack. See Rack::VERSION, that corresponds to the version of this SPEC.
122
- ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the request URL.
133
+ ## <tt>rack.version</tt>:: The Array representing this version of Rack
134
+ ## See Rack::VERSION, that corresponds to
135
+ ## the version of this SPEC.
136
+
137
+ ## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
138
+ ## request URL.
139
+
123
140
  ## <tt>rack.input</tt>:: See below, the input stream.
141
+
124
142
  ## <tt>rack.errors</tt>:: See below, the error stream.
125
- ## <tt>rack.multithread</tt>:: true if the application object may be simultaneously invoked by another thread in the same process, false otherwise.
126
- ## <tt>rack.multiprocess</tt>:: true if an equivalent application object may be simultaneously invoked by another process, false otherwise.
127
- ## <tt>rack.run_once</tt>:: true if the server expects (but does not guarantee!) that the application will only be invoked this one time during the life of its containing process. Normally, this will only be true for a server based on CGI (or something similar).
128
- ## <tt>rack.hijack?</tt>:: present and true if the server supports connection hijacking. See below, hijacking.
129
- ## <tt>rack.hijack</tt>:: an object responding to #call that must be called at least once before using rack.hijack_io. It is recommended #call return rack.hijack_io as well as setting it in env if necessary.
130
- ## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack has received #call, this will contain an object resembling an IO. See hijacking.
131
- ##
143
+
144
+ ## <tt>rack.multithread</tt>:: true if the application object may be
145
+ ## simultaneously invoked by another thread
146
+ ## in the same process, false otherwise.
147
+
148
+ ## <tt>rack.multiprocess</tt>:: true if an equivalent application object
149
+ ## may be simultaneously invoked by another
150
+ ## process, false otherwise.
151
+
152
+ ## <tt>rack.run_once</tt>:: true if the server expects
153
+ ## (but does not guarantee!) that the
154
+ ## application will only be invoked this one
155
+ ## time during the life of its containing
156
+ ## process. Normally, this will only be true
157
+ ## for a server based on CGI
158
+ ## (or something similar).
159
+
160
+ ## <tt>rack.hijack?</tt>:: present and true if the server supports
161
+ ## connection hijacking. See below, hijacking.
162
+
163
+ ## <tt>rack.hijack</tt>:: an object responding to #call that must be
164
+ ## called at least once before using
165
+ ## rack.hijack_io.
166
+ ## It is recommended #call return rack.hijack_io
167
+ ## as well as setting it in env if necessary.
168
+
169
+ ## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
170
+ ## has received #call, this will contain
171
+ ## an object resembling an IO. See hijacking.
132
172
 
133
173
  ## Additional environment specifications have approved to
134
174
  ## standardized middleware APIs. None of these are required to
135
175
  ## be implemented by the server.
136
176
 
137
- ## <tt>rack.session</tt>:: A hash like interface for storing request session data.
177
+ ## <tt>rack.session</tt>:: A hash like interface for storing
178
+ ## request session data.
138
179
  ## The store must implement:
139
180
  if session = env['rack.session']
140
181
  ## store(key, value) (aliased as []=);
@@ -218,7 +259,6 @@ module Rack
218
259
  }
219
260
  }
220
261
 
221
- ##
222
262
  ## There are the following restrictions:
223
263
 
224
264
  ## * <tt>rack.version</tt> must be an array of Integers.
@@ -311,15 +351,23 @@ module Rack
311
351
  v
312
352
  end
313
353
 
314
- ## * +read+ behaves like IO#read. Its signature is <tt>read([length, [buffer]])</tt>.
315
- ## If given, +length+ must be a non-negative Integer (>= 0) or +nil+, and +buffer+ must
316
- ## be a String and may not be nil. If +length+ is given and not nil, then this method
317
- ## reads at most +length+ bytes from the input stream. If +length+ is not given or nil,
318
- ## then this method reads all data until EOF.
319
- ## When EOF is reached, this method returns nil if +length+ is given and not nil, or ""
320
- ## if +length+ is not given or is nil.
321
- ## If +buffer+ is given, then the read data will be placed into +buffer+ instead of a
322
- ## newly created String object.
354
+ ## * +read+ behaves like IO#read.
355
+ ## Its signature is <tt>read([length, [buffer]])</tt>.
356
+ ##
357
+ ## If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
358
+ ## and +buffer+ must be a String and may not be nil.
359
+ ##
360
+ ## If +length+ is given and not nil, then this method reads at most
361
+ ## +length+ bytes from the input stream.
362
+ ##
363
+ ## If +length+ is not given or nil, then this method reads
364
+ ## all data until EOF.
365
+ ##
366
+ ## When EOF is reached, this method returns nil if +length+ is given
367
+ ## and not nil, or "" if +length+ is not given or is nil.
368
+ ##
369
+ ## If +buffer+ is given, then the read data will be placed
370
+ ## into +buffer+ instead of a newly created String object.
323
371
  def read(*args)
324
372
  assert("rack.input#read called with too many arguments") {
325
373
  args.size <= 2
@@ -584,7 +632,7 @@ module Rack
584
632
  assert("a header value must be a String, but the value of " +
585
633
  "'#{key}' is a #{value.class}") { value.kind_of? String }
586
634
  ## consisting of lines (for multiple header values, e.g. multiple
587
- ## <tt>Set-Cookie</tt> values) seperated by "\n".
635
+ ## <tt>Set-Cookie</tt> values) separated by "\\n".
588
636
  value.split("\n").each { |item|
589
637
  ## The lines must not contain characters below 037.
590
638
  assert("invalid header value #{key}: #{item.inspect}") {
@@ -660,7 +708,7 @@ module Rack
660
708
  ##
661
709
  ## If the Body responds to +close+, it will be called after iteration. If
662
710
  ## the body is replaced by a middleware after action, the original body
663
- ## must be closed first, if it repsonds to close.
711
+ ## must be closed first, if it responds to close.
664
712
  # XXX howto: assert("Body has not been closed") { @closed }
665
713
 
666
714
 
@@ -32,9 +32,14 @@ module Rack
32
32
  def call(env)
33
33
  req = Request.new(env)
34
34
  if req.GET["flip"] == "left"
35
- lobster = LobsterString.split("\n").
36
- map { |line| line.ljust(42).reverse }.
37
- join("\n")
35
+ lobster = LobsterString.split("\n").map do |line|
36
+ line.ljust(42).reverse.
37
+ gsub('\\', 'TEMP').
38
+ gsub('/', '\\').
39
+ gsub('TEMP', '/').
40
+ gsub('{','}').
41
+ gsub('(',')')
42
+ end.join("\n")
38
43
  href = "?flip=right"
39
44
  elsif req.GET["flip"] == "crash"
40
45
  raise "Lobster crashed"
@@ -1,16 +1,17 @@
1
1
  module Rack
2
2
  class MethodOverride
3
- HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS PATCH)
3
+ HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS PATCH LINK UNLINK)
4
4
 
5
5
  METHOD_OVERRIDE_PARAM_KEY = "_method".freeze
6
6
  HTTP_METHOD_OVERRIDE_HEADER = "HTTP_X_HTTP_METHOD_OVERRIDE".freeze
7
+ ALLOWED_METHODS = ["POST"]
7
8
 
8
9
  def initialize(app)
9
10
  @app = app
10
11
  end
11
12
 
12
13
  def call(env)
13
- if env["REQUEST_METHOD"] == "POST"
14
+ if allowed_methods.include?(env["REQUEST_METHOD"])
14
15
  method = method_override(env)
15
16
  if HTTP_METHODS.include?(method)
16
17
  env["rack.methodoverride.original_method"] = env["REQUEST_METHOD"]
@@ -23,9 +24,19 @@ module Rack
23
24
 
24
25
  def method_override(env)
25
26
  req = Request.new(env)
26
- method = req.POST[METHOD_OVERRIDE_PARAM_KEY] ||
27
+ method = method_override_param(req) ||
27
28
  env[HTTP_METHOD_OVERRIDE_HEADER]
28
29
  method.to_s.upcase
29
30
  end
31
+
32
+ private
33
+
34
+ def allowed_methods
35
+ ALLOWED_METHODS
36
+ end
37
+
38
+ def method_override_param(req)
39
+ req.POST[METHOD_OVERRIDE_PARAM_KEY]
40
+ end
30
41
  end
31
42
  end
@@ -29,21 +29,7 @@ module Rack
29
29
  v1, v2 = value.split('/', 2)
30
30
  m1, m2 = matcher.split('/', 2)
31
31
 
32
- if m1 == '*'
33
- if m2.nil? || m2 == '*'
34
- return true
35
- elsif m2 == v2
36
- return true
37
- else
38
- return false
39
- end
40
- end
41
-
42
- return false if v1 != m1
43
-
44
- return true if m2.nil? || m2 == '*'
45
-
46
- m2 == v2
32
+ (m1 == '*' || v1 == m1) && (m2.nil? || m2 == '*' || m2 == v2)
47
33
  end
48
34
  module_function :match?
49
35
 
@@ -53,12 +53,13 @@ module Rack
53
53
  @app = app
54
54
  end
55
55
 
56
- def get(uri, opts={}) request("GET", uri, opts) end
57
- def post(uri, opts={}) request("POST", uri, opts) end
58
- def put(uri, opts={}) request("PUT", uri, opts) end
59
- def patch(uri, opts={}) request("PATCH", uri, opts) end
60
- def delete(uri, opts={}) request("DELETE", uri, opts) end
61
- def head(uri, opts={}) request("HEAD", uri, opts) end
56
+ def get(uri, opts={}) request("GET", uri, opts) end
57
+ def post(uri, opts={}) request("POST", uri, opts) end
58
+ def put(uri, opts={}) request("PUT", uri, opts) end
59
+ def patch(uri, opts={}) request("PATCH", uri, opts) end
60
+ def delete(uri, opts={}) request("DELETE", uri, opts) end
61
+ def head(uri, opts={}) request("HEAD", uri, opts) end
62
+ def options(uri, opts={}) request("OPTIONS", uri, opts) end
62
63
 
63
64
  def request(method="GET", uri="", opts={})
64
65
  env = self.class.env_for(uri, opts.merge(:method => method))
@@ -76,9 +77,16 @@ module Rack
76
77
  body.close if body.respond_to?(:close)
77
78
  end
78
79
 
80
+ # For historical reasons, we're pinning to RFC 2396. It's easier for users
81
+ # and we get support from ruby 1.8 to 2.2 using this method.
82
+ def self.parse_uri_rfc2396(uri)
83
+ @parser ||= defined?(URI::RFC2396_Parser) ? URI::RFC2396_Parser.new : URI
84
+ @parser.parse(uri)
85
+ end
86
+
79
87
  # Return the Rack environment used for a request to +uri+.
80
88
  def self.env_for(uri="", opts={})
81
- uri = URI(uri)
89
+ uri = parse_uri_rfc2396(uri)
82
90
  uri.path = "/#{uri.path}" unless uri.path[0] == ?/
83
91
 
84
92
  env = DEFAULT_ENV.dup
@@ -9,7 +9,7 @@ module Rack
9
9
 
10
10
  EOL = "\r\n"
11
11
  MULTIPART_BOUNDARY = "AaB03x"
12
- MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
12
+ MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
13
13
  TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
14
14
  CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
15
15
  DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/
@@ -22,7 +22,7 @@ module Rack
22
22
 
23
23
  class << self
24
24
  def parse_multipart(env)
25
- Parser.new(env).parse
25
+ Parser.create(env).parse
26
26
  end
27
27
 
28
28
  def build_multipart(params, first = true)
@@ -2,31 +2,52 @@ require 'rack/utils'
2
2
 
3
3
  module Rack
4
4
  module Multipart
5
- class MultipartLimitError < Errno::EMFILE; end
6
-
7
5
  class Parser
8
6
  BUFSIZE = 16384
9
7
 
10
- def initialize(env)
8
+ DUMMY = Struct.new(:parse).new
9
+
10
+ def self.create(env)
11
+ return DUMMY unless env['CONTENT_TYPE'] =~ MULTIPART
12
+
13
+ io = env['rack.input']
14
+ io.rewind
15
+
16
+ content_length = env['CONTENT_LENGTH']
17
+ content_length = content_length.to_i if content_length
18
+
19
+ new($1, io, content_length, env)
20
+ end
21
+
22
+ def initialize(boundary, io, content_length, env)
23
+ @buf = ""
24
+
25
+ if @buf.respond_to? :force_encoding
26
+ @buf.force_encoding Encoding::ASCII_8BIT
27
+ end
28
+
29
+ @params = Utils::KeySpaceConstrainedParams.new
30
+ @boundary = "--#{boundary}"
31
+ @io = io
32
+ @content_length = content_length
33
+ @boundary_size = Utils.bytesize(@boundary) + EOL.size
11
34
  @env = env
35
+
36
+ if @content_length
37
+ @content_length -= @boundary_size
38
+ end
39
+
40
+ @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
41
+ @full_boundary = @boundary + EOL
12
42
  end
13
43
 
14
44
  def parse
15
- return nil unless setup_parse
16
-
17
45
  fast_forward_to_first_boundary
18
46
 
19
- opened_files = 0
20
47
  loop do
21
-
22
48
  head, filename, content_type, name, body =
23
49
  get_current_head_and_filename_and_content_type_and_name_and_body
24
50
 
25
- if Utils.multipart_part_limit > 0
26
- opened_files += 1 if filename
27
- raise MultipartLimitError, 'Maximum file multiparts in content reached' if opened_files >= Utils.multipart_part_limit
28
- end
29
-
30
51
  # Save the rest.
31
52
  if i = @buf.index(rx)
32
53
  body << @buf.slice!(0, i)
@@ -35,9 +56,11 @@ module Rack
35
56
  @content_length = -1 if $1 == "--"
36
57
  end
37
58
 
38
- filename, data = get_data(filename, body, content_type, name, head)
59
+ get_data(filename, body, content_type, name, head) do |data|
60
+ tag_multipart_encoding(filename, content_type, name, data)
39
61
 
40
- Utils.normalize_params(@params, name, data) unless data.nil?
62
+ Utils.normalize_params(@params, name, data)
63
+ end
41
64
 
42
65
  # break if we're at the end of a buffer, but not if it is the end of a field
43
66
  break if (@buf.empty? && $1 != EOL) || @content_length == -1
@@ -49,33 +72,9 @@ module Rack
49
72
  end
50
73
 
51
74
  private
52
- def setup_parse
53
- return false unless @env['CONTENT_TYPE'] =~ MULTIPART
54
-
55
- @boundary = "--#{$1}"
56
-
57
- @buf = ""
58
- @params = Utils::KeySpaceConstrainedParams.new
75
+ def full_boundary; @full_boundary; end
59
76
 
60
- @io = @env['rack.input']
61
- @io.rewind
62
-
63
- @boundary_size = Utils.bytesize(@boundary) + EOL.size
64
-
65
- if @content_length = @env['CONTENT_LENGTH']
66
- @content_length = @content_length.to_i
67
- @content_length -= @boundary_size
68
- end
69
- true
70
- end
71
-
72
- def full_boundary
73
- @boundary + EOL
74
- end
75
-
76
- def rx
77
- @rx ||= /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
78
- end
77
+ def rx; @rx; end
79
78
 
80
79
  def fast_forward_to_first_boundary
81
80
  loop do
@@ -95,8 +94,12 @@ module Rack
95
94
  def get_current_head_and_filename_and_content_type_and_name_and_body
96
95
  head = nil
97
96
  body = ''
97
+
98
+ if body.respond_to? :force_encoding
99
+ body.force_encoding Encoding::ASCII_8BIT
100
+ end
101
+
98
102
  filename = content_type = name = nil
99
- content = nil
100
103
 
101
104
  until head && @buf =~ rx
102
105
  if !head && i = @buf.index(EOL+EOL)
@@ -109,8 +112,12 @@ module Rack
109
112
 
110
113
  filename = get_filename(head)
111
114
 
115
+ if name.nil? || name.empty? && filename
116
+ name = filename
117
+ end
118
+
112
119
  if filename
113
- body = Tempfile.new("RackMultipart")
120
+ (@env['rack.tempfiles'] ||= []) << body = Tempfile.new("RackMultipart")
114
121
  body.binmode if body.respond_to?(:binmode)
115
122
  end
116
123
 
@@ -134,29 +141,78 @@ module Rack
134
141
 
135
142
  def get_filename(head)
136
143
  filename = nil
137
- if head =~ RFC2183
144
+ case head
145
+ when RFC2183
138
146
  filename = Hash[head.scan(DISPPARM)]['filename']
139
147
  filename = $1 if filename and filename =~ /^"(.*)"$/
140
- elsif head =~ BROKEN_QUOTED
141
- filename = $1
142
- elsif head =~ BROKEN_UNQUOTED
148
+ when BROKEN_QUOTED, BROKEN_UNQUOTED
143
149
  filename = $1
144
150
  end
145
151
 
146
- if filename && filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
152
+ return unless filename
153
+
154
+ if filename.scan(/%.?.?/).all? { |s| s =~ /%[0-9a-fA-F]{2}/ }
147
155
  filename = Utils.unescape(filename)
148
156
  end
149
- if filename && filename !~ /\\[^\\"]/
157
+
158
+ scrub_filename filename
159
+
160
+ if filename !~ /\\[^\\"]/
150
161
  filename = filename.gsub(/\\(.)/, '\1')
151
162
  end
152
163
  filename
153
164
  end
154
165
 
166
+ if "<3".respond_to? :valid_encoding?
167
+ def scrub_filename(filename)
168
+ unless filename.valid_encoding?
169
+ # FIXME: this force_encoding is for Ruby 2.0 and 1.9 support.
170
+ # We can remove it after they are dropped
171
+ filename.force_encoding(Encoding::ASCII_8BIT)
172
+ filename.encode!(:invalid => :replace, :undef => :replace)
173
+ end
174
+ end
175
+
176
+ CHARSET = "charset"
177
+ TEXT_PLAIN = "text/plain"
178
+
179
+ def tag_multipart_encoding(filename, content_type, name, body)
180
+ name.force_encoding Encoding::UTF_8
181
+
182
+ return if filename
183
+
184
+ encoding = Encoding::UTF_8
185
+
186
+ if content_type
187
+ list = content_type.split(';')
188
+ type_subtype = list.first
189
+ type_subtype.strip!
190
+ if TEXT_PLAIN == type_subtype
191
+ rest = list.drop 1
192
+ rest.each do |param|
193
+ k,v = param.split('=', 2)
194
+ k.strip!
195
+ v.strip!
196
+ encoding = Encoding.find v if k == CHARSET
197
+ end
198
+ end
199
+ end
200
+
201
+ name.force_encoding encoding
202
+ body.force_encoding encoding
203
+ end
204
+ else
205
+ def scrub_filename(filename)
206
+ end
207
+ def tag_multipart_encoding(filename, content_type, name, body)
208
+ end
209
+ end
210
+
155
211
  def get_data(filename, body, content_type, name, head)
156
- data = nil
212
+ data = body
157
213
  if filename == ""
158
214
  # filename is blank which means no file has been selected
159
- return data
215
+ return
160
216
  elsif filename
161
217
  body.rewind
162
218
 
@@ -174,11 +230,9 @@ module Rack
174
230
  # Generic multipart cases, not coming from a form
175
231
  data = {:type => content_type,
176
232
  :name => name, :tempfile => body, :head => head}
177
- else
178
- data = body
179
233
  end
180
234
 
181
- [filename, data]
235
+ yield data
182
236
  end
183
237
  end
184
238
  end