rack 1.2.8 → 1.3.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 (89) hide show
  1. data/README +9 -177
  2. data/Rakefile +2 -1
  3. data/SPEC +2 -2
  4. data/lib/rack.rb +2 -13
  5. data/lib/rack/auth/abstract/request.rb +7 -5
  6. data/lib/rack/auth/digest/md5.rb +6 -2
  7. data/lib/rack/auth/digest/params.rb +5 -7
  8. data/lib/rack/auth/digest/request.rb +1 -1
  9. data/lib/rack/backports/uri/common.rb +64 -0
  10. data/lib/rack/builder.rb +60 -3
  11. data/lib/rack/chunked.rb +29 -22
  12. data/lib/rack/conditionalget.rb +35 -16
  13. data/lib/rack/content_length.rb +3 -3
  14. data/lib/rack/deflater.rb +5 -2
  15. data/lib/rack/etag.rb +38 -10
  16. data/lib/rack/file.rb +76 -43
  17. data/lib/rack/handler.rb +13 -7
  18. data/lib/rack/handler/cgi.rb +0 -2
  19. data/lib/rack/handler/fastcgi.rb +13 -4
  20. data/lib/rack/handler/lsws.rb +0 -2
  21. data/lib/rack/handler/mongrel.rb +12 -2
  22. data/lib/rack/handler/scgi.rb +9 -1
  23. data/lib/rack/handler/thin.rb +7 -1
  24. data/lib/rack/handler/webrick.rb +12 -5
  25. data/lib/rack/lint.rb +2 -2
  26. data/lib/rack/lock.rb +29 -3
  27. data/lib/rack/methodoverride.rb +1 -1
  28. data/lib/rack/mime.rb +2 -2
  29. data/lib/rack/mock.rb +28 -33
  30. data/lib/rack/multipart.rb +34 -0
  31. data/lib/rack/multipart/generator.rb +93 -0
  32. data/lib/rack/multipart/parser.rb +164 -0
  33. data/lib/rack/multipart/uploaded_file.rb +30 -0
  34. data/lib/rack/request.rb +55 -19
  35. data/lib/rack/response.rb +10 -8
  36. data/lib/rack/sendfile.rb +14 -18
  37. data/lib/rack/server.rb +55 -8
  38. data/lib/rack/session/abstract/id.rb +233 -22
  39. data/lib/rack/session/cookie.rb +99 -46
  40. data/lib/rack/session/memcache.rb +30 -56
  41. data/lib/rack/session/pool.rb +22 -43
  42. data/lib/rack/showexceptions.rb +40 -11
  43. data/lib/rack/showstatus.rb +9 -2
  44. data/lib/rack/static.rb +29 -9
  45. data/lib/rack/urlmap.rb +6 -1
  46. data/lib/rack/utils.rb +67 -326
  47. data/rack.gemspec +2 -3
  48. data/test/builder/anything.rb +5 -0
  49. data/test/builder/comment.ru +4 -0
  50. data/test/builder/end.ru +3 -0
  51. data/test/builder/options.ru +2 -0
  52. data/test/cgi/lighttpd.conf +1 -1
  53. data/test/cgi/lighttpd.errors +412 -0
  54. data/test/multipart/content_type_and_no_filename +6 -0
  55. data/test/multipart/text +5 -0
  56. data/test/multipart/webkit +32 -0
  57. data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
  58. data/test/spec_auth_digest.rb +20 -5
  59. data/test/spec_builder.rb +29 -0
  60. data/test/spec_cgi.rb +11 -0
  61. data/test/spec_chunked.rb +1 -1
  62. data/test/spec_commonlogger.rb +1 -1
  63. data/test/spec_conditionalget.rb +47 -0
  64. data/test/spec_content_length.rb +0 -6
  65. data/test/spec_content_type.rb +5 -5
  66. data/test/spec_deflater.rb +46 -2
  67. data/test/spec_etag.rb +68 -1
  68. data/test/spec_fastcgi.rb +11 -0
  69. data/test/spec_file.rb +54 -3
  70. data/test/spec_handler.rb +23 -5
  71. data/test/spec_lint.rb +2 -2
  72. data/test/spec_lock.rb +111 -5
  73. data/test/spec_methodoverride.rb +2 -2
  74. data/test/spec_mock.rb +3 -3
  75. data/test/spec_mongrel.rb +1 -2
  76. data/test/spec_multipart.rb +279 -0
  77. data/test/spec_request.rb +222 -38
  78. data/test/spec_response.rb +9 -3
  79. data/test/spec_server.rb +74 -0
  80. data/test/spec_session_abstract_id.rb +43 -0
  81. data/test/spec_session_cookie.rb +97 -15
  82. data/test/spec_session_memcache.rb +60 -50
  83. data/test/spec_session_pool.rb +63 -40
  84. data/test/spec_showexceptions.rb +64 -0
  85. data/test/spec_static.rb +23 -0
  86. data/test/spec_utils.rb +65 -351
  87. data/test/spec_webrick.rb +23 -4
  88. metadata +35 -15
  89. data/test/spec_auth.rb +0 -57
@@ -23,9 +23,16 @@ module Rack
23
23
 
24
24
  # client or server error, or explicit message
25
25
  if (status.to_i >= 400 && empty) || env["rack.showstatus.detail"]
26
- req = Rack::Request.new(env)
26
+ # This double assignment is to prevent an "unused variable" warning on
27
+ # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
28
+ req = req = Rack::Request.new(env)
29
+
27
30
  message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s
28
- detail = env["rack.showstatus.detail"] || message
31
+
32
+ # This double assignment is to prevent an "unused variable" warning on
33
+ # Ruby 1.9.3. Yes, it is dumb, but I don't like Ruby yelling at me.
34
+ detail = detail = env["rack.showstatus.detail"] || message
35
+
29
36
  body = @template.result(binding)
30
37
  size = Rack::Utils.bytesize(body)
31
38
  [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]]
@@ -1,18 +1,31 @@
1
1
  module Rack
2
2
 
3
3
  # The Rack::Static middleware intercepts requests for static files
4
- # (javascript files, images, stylesheets, etc) based on the url prefixes
5
- # passed in the options, and serves them using a Rack::File object. This
6
- # allows a Rack stack to serve both static and dynamic content.
4
+ # (javascript files, images, stylesheets, etc) based on the url prefixes or
5
+ # route mappings passed in the options, and serves them using a Rack::File
6
+ # object. This allows a Rack stack to serve both static and dynamic content.
7
7
  #
8
8
  # Examples:
9
+ #
10
+ # Serve all requests beginning with /media from the "media" folder located
11
+ # in the current directory (ie media/*):
12
+ #
9
13
  # use Rack::Static, :urls => ["/media"]
10
- # will serve all requests beginning with /media from the "media" folder
11
- # located in the current directory (ie media/*).
14
+ #
15
+ # Serve all requests beginning with /css or /images from the folder "public"
16
+ # in the current directory (ie public/css/* and public/images/*):
12
17
  #
13
18
  # use Rack::Static, :urls => ["/css", "/images"], :root => "public"
14
- # will serve all requests beginning with /css or /images from the folder
15
- # "public" in the current directory (ie public/css/* and public/images/*)
19
+ #
20
+ # Serve all requests to / with "index.html" from the folder "public" in the
21
+ # current directory (ie public/index.html):
22
+ #
23
+ # use Rack::Static, :urls => {"/" => 'index.html'}, :root => 'public'
24
+ #
25
+ # Set a fixed Cache-Control header for all served files:
26
+ #
27
+ # use Rack::Static, :root => 'public', :cache_control => 'public'
28
+ #
16
29
 
17
30
  class Static
18
31
 
@@ -20,14 +33,21 @@ module Rack
20
33
  @app = app
21
34
  @urls = options[:urls] || ["/favicon.ico"]
22
35
  root = options[:root] || Dir.pwd
23
- @file_server = Rack::File.new(root)
36
+ cache_control = options[:cache_control]
37
+ @file_server = Rack::File.new(root, cache_control)
24
38
  end
25
39
 
26
40
  def call(env)
27
41
  path = env["PATH_INFO"]
28
- can_serve = @urls.any? { |url| path.index(url) == 0 }
42
+
43
+ unless @urls.kind_of? Hash
44
+ can_serve = @urls.any? { |url| path.index(url) == 0 }
45
+ else
46
+ can_serve = @urls.key? path
47
+ end
29
48
 
30
49
  if can_serve
50
+ env["PATH_INFO"] = @urls[path] if @urls.kind_of? Hash
31
51
  @file_server.call(env)
32
52
  else
33
53
  @app.call(env)
@@ -12,11 +12,16 @@ module Rack
12
12
  # first, since they are most specific.
13
13
 
14
14
  class URLMap
15
+ NEGATIVE_INFINITY = -1.0 / 0.0
16
+
15
17
  def initialize(map = {})
16
18
  remap(map)
17
19
  end
18
20
 
19
21
  def remap(map)
22
+ longest_path_first = lambda do |(host, location, _, _)|
23
+ [host ? -host.size : NEGATIVE_INFINITY, -location.size]
24
+ end
20
25
  @mapping = map.map { |location, app|
21
26
  if location =~ %r{\Ahttps?://(.*?)(/.*)}
22
27
  host, location = $1, $2
@@ -31,7 +36,7 @@ module Rack
31
36
  match = Regexp.new("^#{Regexp.quote(location).gsub('/', '/+')}(.*)", nil, 'n')
32
37
 
33
38
  [host, location, match, app]
34
- }.sort_by { |(h, l, m, a)| [h ? -h.size : (-1.0 / 0.0), -l.size] } # Longest path first
39
+ }.sort_by(&longest_path_first)
35
40
  end
36
41
 
37
42
  def call(env)
@@ -4,39 +4,41 @@ require 'fileutils'
4
4
  require 'set'
5
5
  require 'tempfile'
6
6
 
7
+ require 'rack/multipart'
8
+
9
+ if RUBY_VERSION[/^\d+\.\d+/] == '1.8'
10
+ # pull in backports
11
+ require 'rack/backports/uri/common'
12
+ else
13
+ require 'uri/common'
14
+ end
15
+
7
16
  module Rack
8
17
  # Rack::Utils contains a grab-bag of useful methods for writing web
9
18
  # applications adopted from all kinds of Ruby libraries.
10
19
 
11
20
  module Utils
12
- # Performs URI escaping so that you can construct proper
13
- # query strings faster. Use this rather than the cgi.rb
14
- # version since it's faster. (Stolen from Camping).
21
+ # URI escapes a string. (CGI style space to +)
15
22
  def escape(s)
16
- s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
17
- '%'+$1.unpack('H2'*bytesize($1)).join('%').upcase
18
- }.tr(' ', '+')
23
+ URI.encode_www_form_component(s)
19
24
  end
20
25
  module_function :escape
21
26
 
22
- # Unescapes a URI escaped string. (Stolen from Camping).
27
+ # Like URI escaping, but with %20 instead of +. Strictly speaking this is
28
+ # true URI escaping.
29
+ def escape_path(s)
30
+ escape(s).gsub('+', '%20')
31
+ end
32
+ module_function :escape_path
33
+
34
+ # Unescapes a URI escaped string.
23
35
  def unescape(s)
24
- s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
25
- [$1.delete('%')].pack('H*')
26
- }
36
+ URI.decode_www_form_component(s)
27
37
  end
28
38
  module_function :unescape
29
39
 
30
40
  DEFAULT_SEP = /[&;] */n
31
41
 
32
- class << self
33
- attr_accessor :key_space_limit
34
- end
35
-
36
- # The default number of bytes to allow parameter keys to take up.
37
- # This helps prevent a rogue client from flooding a Request.
38
- self.key_space_limit = 65536
39
-
40
42
  # Stolen from Mongrel, with some small modifications:
41
43
  # Parses a query string by breaking it up at the '&'
42
44
  # and ';' characters. You can also use this to parse
@@ -45,19 +47,8 @@ module Rack
45
47
  def parse_query(qs, d = nil)
46
48
  params = {}
47
49
 
48
- max_key_space = Utils.key_space_limit
49
- bytes = 0
50
-
51
50
  (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
52
51
  k, v = p.split('=', 2).map { |x| unescape(x) }
53
-
54
- if k
55
- bytes += k.size
56
- if bytes > max_key_space
57
- raise RangeError, "exceeded available parameter key space"
58
- end
59
- end
60
-
61
52
  if cur = params[k]
62
53
  if cur.class == Array
63
54
  params[k] << v
@@ -76,19 +67,8 @@ module Rack
76
67
  def parse_nested_query(qs, d = nil)
77
68
  params = {}
78
69
 
79
- max_key_space = Utils.key_space_limit
80
- bytes = 0
81
-
82
70
  (qs || '').split(d ? /[#{d}] */n : DEFAULT_SEP).each do |p|
83
- k, v = unescape(p).split('=', 2)
84
-
85
- if k
86
- bytes += k.size
87
- if bytes > max_key_space
88
- raise RangeError, "exceeded available parameter key space"
89
- end
90
- end
91
-
71
+ k, v = p.split('=', 2).map { |s| unescape(s) }
92
72
  normalize_params(params, k, v)
93
73
  end
94
74
 
@@ -162,17 +142,11 @@ module Rack
162
142
  "&" => "&amp;",
163
143
  "<" => "&lt;",
164
144
  ">" => "&gt;",
165
- "'" => "&#39;",
145
+ "'" => "&#x27;",
166
146
  '"' => "&quot;",
167
- "/" => "&#47;"
147
+ "/" => "&#x2F;"
168
148
  }
169
- if //.respond_to?(:encoding)
170
- ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
171
- else
172
- # On 1.8, there is a kcode = 'u' bug that allows for XSS otherwhise
173
- # TODO doesn't apply to jruby, so a better condition above might be preferable?
174
- ESCAPE_HTML_PATTERN = /#{Regexp.union(*ESCAPE_HTML.keys)}/n
175
- end
149
+ ESCAPE_HTML_PATTERN = Regexp.union(*ESCAPE_HTML.keys)
176
150
 
177
151
  # Escape ampersands, brackets and quotes to their HTML/XML entities.
178
152
  def escape_html(string)
@@ -269,7 +243,7 @@ module Rack
269
243
  end
270
244
  module_function :delete_cookie_header!
271
245
 
272
- # Return the bytesize of String; uses String#length under Ruby 1.8 and
246
+ # Return the bytesize of String; uses String#size under Ruby 1.8 and
273
247
  # String#bytesize under 1.9.
274
248
  if ''.respond_to?(:bytesize)
275
249
  def bytesize(string)
@@ -294,34 +268,42 @@ module Rack
294
268
  def rfc2822(time)
295
269
  wday = Time::RFC2822_DAY_NAME[time.wday]
296
270
  mon = Time::RFC2822_MONTH_NAME[time.mon - 1]
297
- time.strftime("#{wday}, %d-#{mon}-%Y %T GMT")
271
+ time.strftime("#{wday}, %d-#{mon}-%Y %H:%M:%S GMT")
298
272
  end
299
273
  module_function :rfc2822
300
274
 
301
- # Return the bytesize of String; uses String#length under Ruby 1.8 and
302
- # String#bytesize under 1.9.
303
- if ''.respond_to?(:bytesize)
304
- def bytesize(string)
305
- string.bytesize
306
- end
307
- else
308
- def bytesize(string)
309
- string.size
275
+ # Parses the "Range:" header, if present, into an array of Range objects.
276
+ # Returns nil if the header is missing or syntactically invalid.
277
+ # Returns an empty array if none of the ranges are satisfiable.
278
+ def byte_ranges(env, size)
279
+ # See <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35>
280
+ http_range = env['HTTP_RANGE']
281
+ return nil unless http_range
282
+ ranges = []
283
+ http_range.split(/,\s*/).each do |range_spec|
284
+ matches = range_spec.match(/bytes=(\d*)-(\d*)/)
285
+ return nil unless matches
286
+ r0,r1 = matches[1], matches[2]
287
+ if r0.empty?
288
+ return nil if r1.empty?
289
+ # suffix-byte-range-spec, represents trailing suffix of file
290
+ r0 = [size - r1.to_i, 0].max
291
+ r1 = size - 1
292
+ else
293
+ r0 = r0.to_i
294
+ if r1.empty?
295
+ r1 = size - 1
296
+ else
297
+ r1 = r1.to_i
298
+ return nil if r1 < r0 # backwards range is syntactically invalid
299
+ r1 = size-1 if r1 >= size
300
+ end
301
+ end
302
+ ranges << (r0..r1) if r0 <= r1
310
303
  end
304
+ ranges
311
305
  end
312
- module_function :bytesize
313
-
314
- # Constant time string comparison.
315
- def secure_compare(a, b)
316
- return false unless bytesize(a) == bytesize(b)
317
-
318
- l = a.unpack("C*")
319
-
320
- r, i = 0, -1
321
- b.each_byte { |v| r |= v ^ l[i+=1] }
322
- r == 0
323
- end
324
- module_function :secure_compare
306
+ module_function :byte_ranges
325
307
 
326
308
  # Context allows the use of a compatible middleware at different points
327
309
  # in a request handling stack. A compatible middleware must define
@@ -369,24 +351,19 @@ module Rack
369
351
  end
370
352
 
371
353
  def to_hash
372
- inject({}) do |hash, (k,v)|
373
- if v.respond_to? :to_ary
374
- hash[k] = v.to_ary.join("\n")
375
- else
376
- hash[k] = v
377
- end
378
- hash
379
- end
354
+ Hash[*map do |k, v|
355
+ [k, v.respond_to?(:to_ary) ? v.to_ary.join("\n") : v]
356
+ end.flatten]
380
357
  end
381
358
 
382
359
  def [](k)
383
- super(@names[k]) if @names[k]
384
- super(@names[k.downcase])
360
+ super(k) || super(@names[k.downcase])
385
361
  end
386
362
 
387
363
  def []=(k, v)
388
- delete k
389
- @names[k] = @names[k.downcase] = k
364
+ canonical = k.downcase
365
+ delete k if @names[canonical] && @names[canonical] != k # .delete is expensive, don't invoke it unless necessary
366
+ @names[k] = @names[canonical] = k
390
367
  super k, v
391
368
  end
392
369
 
@@ -484,10 +461,9 @@ module Rack
484
461
  # Responses with HTTP status codes that should not have an entity body
485
462
  STATUS_WITH_NO_ENTITY_BODY = Set.new((100..199).to_a << 204 << 304)
486
463
 
487
- SYMBOL_TO_STATUS_CODE = HTTP_STATUS_CODES.inject({}) { |hash, (code, message)|
488
- hash[message.downcase.gsub(/\s|-/, '_').to_sym] = code
489
- hash
490
- }
464
+ SYMBOL_TO_STATUS_CODE = Hash[*HTTP_STATUS_CODES.map { |code, message|
465
+ [message.downcase.gsub(/\s|-/, '_').to_sym, code]
466
+ }.flatten]
491
467
 
492
468
  def status_code(status)
493
469
  if status.is_a?(Symbol)
@@ -498,242 +474,7 @@ module Rack
498
474
  end
499
475
  module_function :status_code
500
476
 
501
- # A multipart form data parser, adapted from IOWA.
502
- #
503
- # Usually, Rack::Request#POST takes care of calling this.
504
-
505
- module Multipart
506
- class UploadedFile
507
- # The filename, *not* including the path, of the "uploaded" file
508
- attr_reader :original_filename
509
-
510
- # The content type of the "uploaded" file
511
- attr_accessor :content_type
512
-
513
- def initialize(path, content_type = "text/plain", binary = false)
514
- raise "#{path} file does not exist" unless ::File.exist?(path)
515
- @content_type = content_type
516
- @original_filename = ::File.basename(path)
517
- @tempfile = Tempfile.new(@original_filename)
518
- @tempfile.set_encoding(Encoding::BINARY) if @tempfile.respond_to?(:set_encoding)
519
- @tempfile.binmode if binary
520
- FileUtils.copy_file(path, @tempfile.path)
521
- end
522
-
523
- def path
524
- @tempfile.path
525
- end
526
- alias_method :local_path, :path
527
-
528
- def method_missing(method_name, *args, &block) #:nodoc:
529
- @tempfile.__send__(method_name, *args, &block)
530
- end
531
- end
532
-
533
- EOL = "\r\n"
534
- MULTIPART_BOUNDARY = "AaB03x"
477
+ Multipart = Rack::Multipart
535
478
 
536
- def self.parse_multipart(env)
537
- unless env['CONTENT_TYPE'] =~
538
- %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|n
539
- nil
540
- else
541
- boundary = "--#{$1}"
542
-
543
- params = {}
544
- buf = ""
545
- content_length = env['CONTENT_LENGTH'].to_i
546
- input = env['rack.input']
547
- input.rewind
548
-
549
- boundary_size = Utils.bytesize(boundary) + EOL.size
550
- bufsize = 16384
551
-
552
- content_length -= boundary_size
553
-
554
- read_buffer = ''
555
-
556
- status = input.read(boundary_size, read_buffer)
557
- raise EOFError, "bad content body" unless status == boundary + EOL
558
-
559
- rx = /(?:#{EOL})?#{Regexp.quote boundary}(#{EOL}|--)/n
560
-
561
- max_key_space = Utils.key_space_limit
562
- bytes = 0
563
-
564
- loop {
565
- head = nil
566
- body = ''
567
- filename = content_type = name = nil
568
-
569
- until head && buf =~ rx
570
- if !head && i = buf.index(EOL+EOL)
571
- head = buf.slice!(0, i+2) # First \r\n
572
- buf.slice!(0, 2) # Second \r\n
573
-
574
- token = /[^\s()<>,;:\\"\/\[\]?=]+/
575
- condisp = /Content-Disposition:\s*#{token}\s*/i
576
- dispparm = /;\s*(#{token})=("(?:\\"|[^"])*"|#{token})/
577
-
578
- rfc2183 = /^#{condisp}(#{dispparm})+$/i
579
- broken_quoted = /^#{condisp}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{token}=)/i
580
- broken_unquoted = /^#{condisp}.*;\sfilename=(#{token})/i
581
-
582
- if head =~ rfc2183
583
- filename = Hash[head.scan(dispparm)]['filename']
584
- filename = $1 if filename and filename =~ /^"(.*)"$/
585
- elsif head =~ broken_quoted
586
- filename = $1
587
- elsif head =~ broken_unquoted
588
- filename = $1
589
- end
590
-
591
- if filename && filename !~ /\\[^\\"]/
592
- filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
593
- end
594
-
595
- content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
596
- name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
597
-
598
- if name
599
- bytes += name.size
600
- if bytes > max_key_space
601
- raise RangeError, "exceeded available parameter key space"
602
- end
603
- end
604
-
605
- if content_type || filename
606
- body = Tempfile.new("RackMultipart")
607
- body.binmode if body.respond_to?(:binmode)
608
- end
609
-
610
- next
611
- end
612
-
613
- # Save the read body part.
614
- if head && (boundary_size+4 < buf.size)
615
- body << buf.slice!(0, buf.size - (boundary_size+4))
616
- end
617
-
618
- c = input.read(bufsize < content_length ? bufsize : content_length, read_buffer)
619
- raise EOFError, "bad content body" if c.nil? || c.empty?
620
- buf << c
621
- content_length -= c.size
622
- end
623
-
624
- # Save the rest.
625
- if i = buf.index(rx)
626
- body << buf.slice!(0, i)
627
- buf.slice!(0, boundary_size+2)
628
-
629
- content_length = -1 if $1 == "--"
630
- end
631
-
632
- if filename == ""
633
- # filename is blank which means no file has been selected
634
- data = nil
635
- elsif filename
636
- body.rewind
637
-
638
- # Take the basename of the upload's original filename.
639
- # This handles the full Windows paths given by Internet Explorer
640
- # (and perhaps other broken user agents) without affecting
641
- # those which give the lone filename.
642
- filename = filename.split(/[\/\\]/).last
643
-
644
- data = {:filename => filename, :type => content_type,
645
- :name => name, :tempfile => body, :head => head}
646
- elsif !filename && content_type
647
- body.rewind
648
-
649
- # Generic multipart cases, not coming from a form
650
- data = {:type => content_type,
651
- :name => name, :tempfile => body, :head => head}
652
- else
653
- data = body
654
- end
655
-
656
- Utils.normalize_params(params, name, data) unless data.nil?
657
-
658
- # break if we're at the end of a buffer, but not if it is the end of a field
659
- break if (buf.empty? && $1 != EOL) || content_length == -1
660
- }
661
-
662
- input.rewind
663
-
664
- params
665
- end
666
- end
667
-
668
- def self.build_multipart(params, first = true)
669
- if first
670
- unless params.is_a?(Hash)
671
- raise ArgumentError, "value must be a Hash"
672
- end
673
-
674
- multipart = false
675
- query = lambda { |value|
676
- case value
677
- when Array
678
- value.each(&query)
679
- when Hash
680
- value.values.each(&query)
681
- when UploadedFile
682
- multipart = true
683
- end
684
- }
685
- params.values.each(&query)
686
- return nil unless multipart
687
- end
688
-
689
- flattened_params = Hash.new
690
-
691
- params.each do |key, value|
692
- k = first ? key.to_s : "[#{key}]"
693
-
694
- case value
695
- when Array
696
- value.map { |v|
697
- build_multipart(v, false).each { |subkey, subvalue|
698
- flattened_params["#{k}[]#{subkey}"] = subvalue
699
- }
700
- }
701
- when Hash
702
- build_multipart(value, false).each { |subkey, subvalue|
703
- flattened_params[k + subkey] = subvalue
704
- }
705
- else
706
- flattened_params[k] = value
707
- end
708
- end
709
-
710
- if first
711
- flattened_params.map { |name, file|
712
- if file.respond_to?(:original_filename)
713
- ::File.open(file.path, "rb") do |f|
714
- f.set_encoding(Encoding::BINARY) if f.respond_to?(:set_encoding)
715
- <<-EOF
716
- --#{MULTIPART_BOUNDARY}\r
717
- Content-Disposition: form-data; name="#{name}"; filename="#{Utils.escape(file.original_filename)}"\r
718
- Content-Type: #{file.content_type}\r
719
- Content-Length: #{::File.stat(file.path).size}\r
720
- \r
721
- #{f.read}\r
722
- EOF
723
- end
724
- else
725
- <<-EOF
726
- --#{MULTIPART_BOUNDARY}\r
727
- Content-Disposition: form-data; name="#{name}"\r
728
- \r
729
- #{file}\r
730
- EOF
731
- end
732
- }.join + "--#{MULTIPART_BOUNDARY}--\r"
733
- else
734
- flattened_params
735
- end
736
- end
737
- end
738
479
  end
739
480
  end