rack 1.0.1 → 2.2.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +829 -0
  3. data/CONTRIBUTING.md +136 -0
  4. data/{COPYING → MIT-LICENSE} +4 -2
  5. data/README.rdoc +355 -0
  6. data/Rakefile +64 -98
  7. data/SPEC.rdoc +292 -0
  8. data/bin/rackup +3 -174
  9. data/contrib/rack.png +0 -0
  10. data/contrib/rack.svg +150 -0
  11. data/contrib/rack_logo.svg +164 -111
  12. data/contrib/rdoc.css +412 -0
  13. data/example/lobster.ru +2 -0
  14. data/example/protectedlobster.rb +4 -2
  15. data/example/protectedlobster.ru +3 -1
  16. data/lib/rack/auth/abstract/handler.rb +7 -5
  17. data/lib/rack/auth/abstract/request.rb +12 -2
  18. data/lib/rack/auth/basic.rb +6 -4
  19. data/lib/rack/auth/digest/md5.rb +20 -13
  20. data/lib/rack/auth/digest/nonce.rb +6 -4
  21. data/lib/rack/auth/digest/params.rb +11 -12
  22. data/lib/rack/auth/digest/request.rb +15 -12
  23. data/lib/rack/body_proxy.rb +45 -0
  24. data/lib/rack/builder.rb +213 -19
  25. data/lib/rack/cascade.rb +50 -18
  26. data/lib/rack/chunked.rb +95 -27
  27. data/lib/rack/common_logger.rb +84 -0
  28. data/lib/rack/conditional_get.rb +83 -0
  29. data/lib/rack/config.rb +22 -0
  30. data/lib/rack/content_length.rb +19 -10
  31. data/lib/rack/content_type.rb +12 -5
  32. data/lib/rack/core_ext/regexp.rb +14 -0
  33. data/lib/rack/deflater.rb +90 -42
  34. data/lib/rack/directory.rb +118 -72
  35. data/lib/rack/etag.rb +77 -0
  36. data/lib/rack/events.rb +153 -0
  37. data/lib/rack/file.rb +4 -85
  38. data/lib/rack/files.rb +218 -0
  39. data/lib/rack/handler/cgi.rb +22 -24
  40. data/lib/rack/handler/fastcgi.rb +34 -24
  41. data/lib/rack/handler/lsws.rb +16 -15
  42. data/lib/rack/handler/scgi.rb +38 -29
  43. data/lib/rack/handler/thin.rb +23 -7
  44. data/lib/rack/handler/webrick.rb +95 -37
  45. data/lib/rack/handler.rb +49 -14
  46. data/lib/rack/head.rb +19 -13
  47. data/lib/rack/lint.rb +466 -217
  48. data/lib/rack/lobster.rb +19 -14
  49. data/lib/rack/lock.rb +24 -8
  50. data/lib/rack/logger.rb +20 -0
  51. data/lib/rack/media_type.rb +53 -0
  52. data/lib/rack/method_override.rb +52 -0
  53. data/lib/rack/mime.rb +652 -171
  54. data/lib/rack/mock.rb +203 -64
  55. data/lib/rack/multipart/generator.rb +97 -0
  56. data/lib/rack/multipart/parser.rb +440 -0
  57. data/lib/rack/multipart/uploaded_file.rb +41 -0
  58. data/lib/rack/multipart.rb +64 -0
  59. data/lib/rack/null_logger.rb +39 -0
  60. data/lib/rack/query_parser.rb +266 -0
  61. data/lib/rack/recursive.rb +18 -11
  62. data/lib/rack/reloader.rb +13 -5
  63. data/lib/rack/request.rb +607 -191
  64. data/lib/rack/response.rb +245 -110
  65. data/lib/rack/rewindable_input.rb +15 -21
  66. data/lib/rack/runtime.rb +34 -0
  67. data/lib/rack/sendfile.rb +192 -0
  68. data/lib/rack/server.rb +466 -0
  69. data/lib/rack/session/abstract/id.rb +449 -68
  70. data/lib/rack/session/cookie.rb +159 -47
  71. data/lib/rack/session/memcache.rb +4 -103
  72. data/lib/rack/session/pool.rb +39 -49
  73. data/lib/rack/show_exceptions.rb +390 -0
  74. data/lib/rack/{showstatus.rb → show_status.rb} +21 -14
  75. data/lib/rack/static.rb +164 -14
  76. data/lib/rack/tempfile_reaper.rb +22 -0
  77. data/lib/rack/urlmap.rb +60 -18
  78. data/lib/rack/utils.rb +508 -277
  79. data/lib/rack/version.rb +29 -0
  80. data/lib/rack.rb +85 -34
  81. data/rack.gemspec +43 -51
  82. metadata +119 -207
  83. data/KNOWN-ISSUES +0 -18
  84. data/RDOX +0 -428
  85. data/README +0 -364
  86. data/SPEC +0 -164
  87. data/lib/rack/adapter/camping.rb +0 -22
  88. data/lib/rack/auth/openid.rb +0 -480
  89. data/lib/rack/commonlogger.rb +0 -61
  90. data/lib/rack/conditionalget.rb +0 -47
  91. data/lib/rack/handler/evented_mongrel.rb +0 -8
  92. data/lib/rack/handler/mongrel.rb +0 -87
  93. data/lib/rack/handler/swiftiplied_mongrel.rb +0 -8
  94. data/lib/rack/methodoverride.rb +0 -27
  95. data/lib/rack/showexceptions.rb +0 -349
  96. data/test/cgi/lighttpd.conf +0 -20
  97. data/test/cgi/test +0 -9
  98. data/test/cgi/test.fcgi +0 -8
  99. data/test/cgi/test.ru +0 -7
  100. data/test/multipart/binary +0 -0
  101. data/test/multipart/empty +0 -10
  102. data/test/multipart/ie +0 -6
  103. data/test/multipart/nested +0 -10
  104. data/test/multipart/none +0 -9
  105. data/test/multipart/semicolon +0 -6
  106. data/test/multipart/text +0 -10
  107. data/test/spec_rack_auth_basic.rb +0 -73
  108. data/test/spec_rack_auth_digest.rb +0 -226
  109. data/test/spec_rack_auth_openid.rb +0 -84
  110. data/test/spec_rack_builder.rb +0 -84
  111. data/test/spec_rack_camping.rb +0 -51
  112. data/test/spec_rack_cascade.rb +0 -50
  113. data/test/spec_rack_cgi.rb +0 -89
  114. data/test/spec_rack_chunked.rb +0 -62
  115. data/test/spec_rack_commonlogger.rb +0 -32
  116. data/test/spec_rack_conditionalget.rb +0 -41
  117. data/test/spec_rack_content_length.rb +0 -43
  118. data/test/spec_rack_content_type.rb +0 -30
  119. data/test/spec_rack_deflater.rb +0 -127
  120. data/test/spec_rack_directory.rb +0 -61
  121. data/test/spec_rack_fastcgi.rb +0 -89
  122. data/test/spec_rack_file.rb +0 -75
  123. data/test/spec_rack_handler.rb +0 -43
  124. data/test/spec_rack_head.rb +0 -30
  125. data/test/spec_rack_lint.rb +0 -521
  126. data/test/spec_rack_lobster.rb +0 -45
  127. data/test/spec_rack_lock.rb +0 -38
  128. data/test/spec_rack_methodoverride.rb +0 -60
  129. data/test/spec_rack_mock.rb +0 -157
  130. data/test/spec_rack_mongrel.rb +0 -189
  131. data/test/spec_rack_recursive.rb +0 -77
  132. data/test/spec_rack_request.rb +0 -504
  133. data/test/spec_rack_response.rb +0 -218
  134. data/test/spec_rack_rewindable_input.rb +0 -118
  135. data/test/spec_rack_session_cookie.rb +0 -82
  136. data/test/spec_rack_session_memcache.rb +0 -240
  137. data/test/spec_rack_session_pool.rb +0 -172
  138. data/test/spec_rack_showexceptions.rb +0 -21
  139. data/test/spec_rack_showstatus.rb +0 -72
  140. data/test/spec_rack_static.rb +0 -37
  141. data/test/spec_rack_thin.rb +0 -91
  142. data/test/spec_rack_urlmap.rb +0 -185
  143. data/test/spec_rack_utils.rb +0 -387
  144. data/test/spec_rack_webrick.rb +0 -130
  145. data/test/testrequest.rb +0 -57
  146. data/test/unregistered_handler/rack/handler/unregistered.rb +0 -7
  147. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +0 -7
@@ -0,0 +1,440 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'strscan'
4
+
5
+ module Rack
6
+ module Multipart
7
+ class MultipartPartLimitError < Errno::EMFILE; end
8
+ class MultipartTotalPartLimitError < StandardError; end
9
+
10
+ class Parser
11
+ (require_relative '../core_ext/regexp'; using ::Rack::RegexpExtensions) if RUBY_VERSION < '2.4'
12
+
13
+ BUFSIZE = 1_048_576
14
+ TEXT_PLAIN = "text/plain"
15
+ TEMPFILE_FACTORY = lambda { |filename, content_type|
16
+ extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
17
+
18
+ Tempfile.new(["RackMultipart", extension])
19
+ }
20
+
21
+ BOUNDARY_REGEX = /\A([^\n]*(?:\n|\Z))/
22
+
23
+ BOUNDARY_START_LIMIT = 16 * 1024
24
+ private_constant :BOUNDARY_START_LIMIT
25
+
26
+ MIME_HEADER_BYTESIZE_LIMIT = 64 * 1024
27
+ private_constant :MIME_HEADER_BYTESIZE_LIMIT
28
+
29
+ env_int = lambda do |key, val|
30
+ if str_val = ENV[key]
31
+ begin
32
+ val = Integer(str_val, 10)
33
+ rescue ArgumentError
34
+ raise ArgumentError, "non-integer value provided for environment variable #{key}"
35
+ end
36
+ end
37
+
38
+ val
39
+ end
40
+
41
+ BUFFERED_UPLOAD_BYTESIZE_LIMIT = env_int.call("RACK_MULTIPART_BUFFERED_UPLOAD_BYTESIZE_LIMIT", 16 * 1024 * 1024)
42
+ private_constant :BUFFERED_UPLOAD_BYTESIZE_LIMIT
43
+
44
+ class BoundedIO # :nodoc:
45
+ def initialize(io, content_length)
46
+ @io = io
47
+ @content_length = content_length
48
+ @cursor = 0
49
+ end
50
+
51
+ def read(size, outbuf = nil)
52
+ return if @cursor >= @content_length
53
+
54
+ left = @content_length - @cursor
55
+
56
+ str = if left < size
57
+ @io.read left, outbuf
58
+ else
59
+ @io.read size, outbuf
60
+ end
61
+
62
+ if str
63
+ @cursor += str.bytesize
64
+ else
65
+ # Raise an error for mismatching Content-Length and actual contents
66
+ raise EOFError, "bad content body"
67
+ end
68
+
69
+ str
70
+ end
71
+
72
+ def rewind
73
+ @io.rewind
74
+ end
75
+ end
76
+
77
+ MultipartInfo = Struct.new :params, :tmp_files
78
+ EMPTY = MultipartInfo.new(nil, [])
79
+
80
+ def self.parse_boundary(content_type)
81
+ return unless content_type
82
+ data = content_type.match(MULTIPART)
83
+ return unless data
84
+ data[1]
85
+ end
86
+
87
+ def self.parse(io, content_length, content_type, tmpfile, bufsize, qp)
88
+ return EMPTY if 0 == content_length
89
+
90
+ boundary = parse_boundary content_type
91
+ return EMPTY unless boundary
92
+
93
+ io = BoundedIO.new(io, content_length) if content_length
94
+ outbuf = String.new
95
+
96
+ parser = new(boundary, tmpfile, bufsize, qp)
97
+ parser.on_read io.read(bufsize, outbuf)
98
+
99
+ loop do
100
+ break if parser.state == :DONE
101
+ parser.on_read io.read(bufsize, outbuf)
102
+ end
103
+
104
+ io.rewind
105
+ parser.result
106
+ end
107
+
108
+ class Collector
109
+ class MimePart < Struct.new(:body, :head, :filename, :content_type, :name)
110
+ def get_data
111
+ data = body
112
+ if filename == ""
113
+ # filename is blank which means no file has been selected
114
+ return
115
+ elsif filename
116
+ body.rewind if body.respond_to?(:rewind)
117
+
118
+ # Take the basename of the upload's original filename.
119
+ # This handles the full Windows paths given by Internet Explorer
120
+ # (and perhaps other broken user agents) without affecting
121
+ # those which give the lone filename.
122
+ fn = filename.split(/[\/\\]/).last
123
+
124
+ data = { filename: fn, type: content_type,
125
+ name: name, tempfile: body, head: head }
126
+ end
127
+
128
+ yield data
129
+ end
130
+ end
131
+
132
+ class BufferPart < MimePart
133
+ def file?; false; end
134
+ def close; end
135
+ end
136
+
137
+ class TempfilePart < MimePart
138
+ def file?; true; end
139
+ def close; body.close; end
140
+ end
141
+
142
+ include Enumerable
143
+
144
+ def initialize(tempfile)
145
+ @tempfile = tempfile
146
+ @mime_parts = []
147
+ @open_files = 0
148
+ end
149
+
150
+ def each
151
+ @mime_parts.each { |part| yield part }
152
+ end
153
+
154
+ def on_mime_head(mime_index, head, filename, content_type, name)
155
+ if filename
156
+ body = @tempfile.call(filename, content_type)
157
+ body.binmode if body.respond_to?(:binmode)
158
+ klass = TempfilePart
159
+ @open_files += 1
160
+ else
161
+ body = String.new
162
+ klass = BufferPart
163
+ end
164
+
165
+ @mime_parts[mime_index] = klass.new(body, head, filename, content_type, name)
166
+
167
+ check_part_limits
168
+ end
169
+
170
+ def on_mime_body(mime_index, content)
171
+ @mime_parts[mime_index].body << content
172
+ end
173
+
174
+ def on_mime_finish(mime_index)
175
+ end
176
+
177
+ private
178
+
179
+ def check_part_limits
180
+ file_limit = Utils.multipart_file_limit
181
+ part_limit = Utils.multipart_total_part_limit
182
+
183
+ if file_limit && file_limit > 0
184
+ if @open_files >= file_limit
185
+ @mime_parts.each(&:close)
186
+ raise MultipartPartLimitError, 'Maximum file multiparts in content reached'
187
+ end
188
+ end
189
+
190
+ if part_limit && part_limit > 0
191
+ if @mime_parts.size >= part_limit
192
+ @mime_parts.each(&:close)
193
+ raise MultipartTotalPartLimitError, 'Maximum total multiparts in content reached'
194
+ end
195
+ end
196
+ end
197
+ end
198
+
199
+ attr_reader :state
200
+
201
+ def initialize(boundary, tempfile, bufsize, query_parser)
202
+ @query_parser = query_parser
203
+ @params = query_parser.make_params
204
+ @boundary = "--#{boundary}"
205
+ @bufsize = bufsize
206
+
207
+ @full_boundary = @boundary
208
+ @end_boundary = @boundary + '--'
209
+ @state = :FAST_FORWARD
210
+ @mime_index = 0
211
+ @body_retained = nil
212
+ @retained_size = 0
213
+ @collector = Collector.new tempfile
214
+
215
+ @sbuf = StringScanner.new("".dup)
216
+ @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m
217
+ @end_boundary_size = boundary.bytesize + 6 # (-- at start, -- at finish, EOL at end)
218
+ @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max
219
+ @head_regex = /(.*?#{EOL})#{EOL}/m
220
+ end
221
+
222
+ def on_read(content)
223
+ handle_empty_content!(content)
224
+ @sbuf.concat content
225
+ run_parser
226
+ end
227
+
228
+ def result
229
+ @collector.each do |part|
230
+ part.get_data do |data|
231
+ tag_multipart_encoding(part.filename, part.content_type, part.name, data)
232
+ @query_parser.normalize_params(@params, part.name, data, @query_parser.param_depth_limit)
233
+ end
234
+ end
235
+ MultipartInfo.new @params.to_params_hash, @collector.find_all(&:file?).map(&:body)
236
+ end
237
+
238
+ private
239
+
240
+ def run_parser
241
+ loop do
242
+ case @state
243
+ when :FAST_FORWARD
244
+ break if handle_fast_forward == :want_read
245
+ when :CONSUME_TOKEN
246
+ break if handle_consume_token == :want_read
247
+ when :MIME_HEAD
248
+ break if handle_mime_head == :want_read
249
+ when :MIME_BODY
250
+ break if handle_mime_body == :want_read
251
+ when :DONE
252
+ break
253
+ end
254
+ end
255
+ end
256
+
257
+ def handle_fast_forward
258
+ tok = consume_boundary
259
+
260
+ if tok == :END_BOUNDARY && @sbuf.pos == @end_boundary_size && @sbuf.eos?
261
+ # stop parsing a buffer if a buffer is only an end boundary.
262
+ @state = :DONE
263
+ elsif tok
264
+ @state = :MIME_HEAD
265
+ else
266
+ raise EOFError, "bad content body" if @sbuf.rest_size >= @bufsize
267
+
268
+ # We raise if we don't find the multipart boundary, to avoid unbounded memory
269
+ # buffering. Note that the actual limit is the higher of 16KB and the buffer size (1MB by default)
270
+ raise EOFError, "multipart boundary not found within limit" if @sbuf.string.bytesize > BOUNDARY_START_LIMIT
271
+
272
+ # no boundary found, keep reading data
273
+ return :want_read
274
+ end
275
+ end
276
+
277
+ def handle_consume_token
278
+ tok = consume_boundary
279
+ # break if we're at the end of a buffer, but not if it is the end of a field
280
+ @state = if tok == :END_BOUNDARY || (@sbuf.eos? && tok != :BOUNDARY)
281
+ :DONE
282
+ else
283
+ :MIME_HEAD
284
+ end
285
+ end
286
+
287
+ def handle_mime_head
288
+ if @sbuf.scan_until(@head_regex)
289
+ head = @sbuf[1]
290
+ content_type = head[MULTIPART_CONTENT_TYPE, 1]
291
+ if name = head[MULTIPART_CONTENT_DISPOSITION, 1]
292
+ name = Rack::Auth::Digest::Params::dequote(name)
293
+ else
294
+ name = head[MULTIPART_CONTENT_ID, 1]
295
+ end
296
+
297
+ filename = get_filename(head)
298
+
299
+ if name.nil? || name.empty?
300
+ name = filename || "#{content_type || TEXT_PLAIN}[]".dup
301
+ end
302
+
303
+ # Mime part head data is retained for both TempfilePart and BufferPart
304
+ # for the entireity of the parse, even though it isn't used for BufferPart.
305
+ update_retained_size(head.bytesize)
306
+
307
+ # If a filename is given, a TempfilePart will be used, so the body will
308
+ # not be buffered in memory. However, if a filename is not given, a BufferPart
309
+ # will be used, and the body will be buffered in memory.
310
+ @body_retained = !filename
311
+
312
+ @collector.on_mime_head @mime_index, head, filename, content_type, name
313
+ @state = :MIME_BODY
314
+ else
315
+ # We raise if the mime part header is too large, to avoid unbounded memory
316
+ # buffering. Note that the actual limit is the higher of 64KB and the buffer size (1MB by default)
317
+ raise EOFError, "multipart mime part header too large" if @sbuf.rest.bytesize > MIME_HEADER_BYTESIZE_LIMIT
318
+
319
+ return :want_read
320
+ end
321
+ end
322
+
323
+ def handle_mime_body
324
+ if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet
325
+ body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string
326
+ update_retained_size(body.bytesize) if @body_retained
327
+ @collector.on_mime_body @mime_index, body
328
+ @sbuf.pos += body.length + 2 # skip \r\n after the content
329
+ @state = :CONSUME_TOKEN
330
+ @mime_index += 1
331
+ else
332
+ # Save what we have so far
333
+ if @rx_max_size < @sbuf.rest_size
334
+ delta = @sbuf.rest_size - @rx_max_size
335
+ body = @sbuf.peek(delta)
336
+ update_retained_size(body.bytesize) if @body_retained
337
+ @collector.on_mime_body @mime_index, body
338
+ @sbuf.pos += delta
339
+ @sbuf.string = @sbuf.rest
340
+ end
341
+ :want_read
342
+ end
343
+ end
344
+
345
+ def full_boundary; @full_boundary; end
346
+
347
+ def update_retained_size(size)
348
+ @retained_size += size
349
+ if @retained_size > BUFFERED_UPLOAD_BYTESIZE_LIMIT
350
+ raise EOFError, "multipart data over retained size limit"
351
+ end
352
+ end
353
+
354
+ # Scan until the we find the start or end of the boundary.
355
+ # If we find it, return the appropriate symbol for the start or
356
+ # end of the boundary. If we don't find the start or end of the
357
+ # boundary, clear the buffer and return nil.
358
+ def consume_boundary
359
+ while read_buffer = @sbuf.scan_until(BOUNDARY_REGEX)
360
+ case read_buffer.strip
361
+ when full_boundary then return :BOUNDARY
362
+ when @end_boundary then return :END_BOUNDARY
363
+ end
364
+ return if @sbuf.eos?
365
+ end
366
+ end
367
+
368
+ def get_filename(head)
369
+ filename = nil
370
+ case head
371
+ when RFC2183
372
+ params = Hash[*head.scan(DISPPARM).flat_map(&:compact)]
373
+
374
+ if filename = params['filename']
375
+ filename = $1 if filename =~ /^"(.*)"$/
376
+ elsif filename = params['filename*']
377
+ encoding, _, filename = filename.split("'", 3)
378
+ end
379
+ when BROKEN
380
+ filename = $1
381
+ filename = $1 if filename =~ /^"(.*)"$/
382
+ end
383
+
384
+ return unless filename
385
+
386
+ if filename.scan(/%.?.?/).all? { |s| /%[0-9a-fA-F]{2}/.match?(s) }
387
+ filename = Utils.unescape_path(filename)
388
+ end
389
+
390
+ filename.scrub!
391
+
392
+ if filename !~ /\\[^\\"]/
393
+ filename = filename.gsub(/\\(.)/, '\1')
394
+ end
395
+
396
+ if encoding
397
+ filename.force_encoding ::Encoding.find(encoding)
398
+ end
399
+
400
+ filename
401
+ end
402
+
403
+ CHARSET = "charset"
404
+
405
+ def tag_multipart_encoding(filename, content_type, name, body)
406
+ name = name.to_s
407
+ encoding = Encoding::UTF_8
408
+
409
+ name.force_encoding(encoding)
410
+
411
+ return if filename
412
+
413
+ if content_type
414
+ list = content_type.split(';')
415
+ type_subtype = list.first
416
+ type_subtype.strip!
417
+ if TEXT_PLAIN == type_subtype
418
+ rest = list.drop 1
419
+ rest.each do |param|
420
+ k, v = param.split('=', 2)
421
+ k.strip!
422
+ v.strip!
423
+ v = v[1..-2] if v.start_with?('"') && v.end_with?('"')
424
+ encoding = Encoding.find v if k == CHARSET
425
+ end
426
+ end
427
+ end
428
+
429
+ name.force_encoding(encoding)
430
+ body.force_encoding(encoding)
431
+ end
432
+
433
+ def handle_empty_content!(content)
434
+ if content.nil? || content.empty?
435
+ raise EOFError
436
+ end
437
+ end
438
+ end
439
+ end
440
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module Multipart
5
+ class UploadedFile
6
+ # The filename, *not* including the path, of the "uploaded" file
7
+ attr_reader :original_filename
8
+
9
+ # The content type of the "uploaded" file
10
+ attr_accessor :content_type
11
+
12
+ def initialize(filepath = nil, ct = "text/plain", bin = false,
13
+ path: filepath, content_type: ct, binary: bin, filename: nil, io: nil)
14
+ if io
15
+ @tempfile = io
16
+ @original_filename = filename
17
+ else
18
+ raise "#{path} file does not exist" unless ::File.exist?(path)
19
+ @original_filename = filename || ::File.basename(path)
20
+ @tempfile = Tempfile.new([@original_filename, ::File.extname(path)], encoding: Encoding::BINARY)
21
+ @tempfile.binmode if binary
22
+ FileUtils.copy_file(path, @tempfile.path)
23
+ end
24
+ @content_type = content_type
25
+ end
26
+
27
+ def path
28
+ @tempfile.path if @tempfile.respond_to?(:path)
29
+ end
30
+ alias_method :local_path, :path
31
+
32
+ def respond_to?(*args)
33
+ super or @tempfile.respond_to?(*args)
34
+ end
35
+
36
+ def method_missing(method_name, *args, &block) #:nodoc:
37
+ @tempfile.__send__(method_name, *args, &block)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'multipart/parser'
4
+
5
+ module Rack
6
+ # A multipart form data parser, adapted from IOWA.
7
+ #
8
+ # Usually, Rack::Request#POST takes care of calling this.
9
+ module Multipart
10
+ autoload :UploadedFile, 'rack/multipart/uploaded_file'
11
+ autoload :Generator, 'rack/multipart/generator'
12
+
13
+ EOL = "\r\n"
14
+ 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
38
+
39
+ class << self
40
+ def parse_multipart(env, params = Rack::Utils.default_query_parser)
41
+ extract_multipart Rack::Request.new(env), params
42
+ end
43
+
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
49
+
50
+ tempfile = req.get_header(RACK_MULTIPART_TEMPFILE_FACTORY) || Parser::TEMPFILE_FACTORY
51
+ bufsize = req.get_header(RACK_MULTIPART_BUFFER_SIZE) || Parser::BUFSIZE
52
+
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
56
+ end
57
+
58
+ def build_multipart(params, first = true)
59
+ Generator.new(params, first).dump
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ class NullLogger
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ env[RACK_LOGGER] = self
11
+ @app.call(env)
12
+ end
13
+
14
+ def info(progname = nil, &block); end
15
+ def debug(progname = nil, &block); end
16
+ def warn(progname = nil, &block); end
17
+ def error(progname = nil, &block); end
18
+ def fatal(progname = nil, &block); end
19
+ def unknown(progname = nil, &block); end
20
+ def info? ; end
21
+ def debug? ; end
22
+ def warn? ; end
23
+ def error? ; end
24
+ def fatal? ; end
25
+ def level ; end
26
+ def progname ; end
27
+ def datetime_format ; end
28
+ def formatter ; end
29
+ def sev_threshold ; end
30
+ def level=(level); end
31
+ def progname=(progname); end
32
+ def datetime_format=(datetime_format); end
33
+ def formatter=(formatter); end
34
+ def sev_threshold=(sev_threshold); end
35
+ def close ; end
36
+ def add(severity, message = nil, progname = nil, &block); end
37
+ def <<(msg); end
38
+ end
39
+ end