rack-test 0.6.3 → 2.1.0
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.
- checksums.yaml +7 -0
- data/History.md +375 -0
- data/MIT-LICENSE.txt +1 -0
- data/README.md +139 -0
- data/lib/rack/test/cookie_jar.rb +134 -64
- data/lib/rack/test/methods.rb +42 -31
- data/lib/rack/test/uploaded_file.rb +79 -19
- data/lib/rack/test/utils.rb +86 -80
- data/lib/rack/test/version.rb +5 -0
- data/lib/rack/test.rb +268 -204
- metadata +76 -68
- data/.document +0 -4
- data/.gitignore +0 -6
- data/Gemfile +0 -8
- data/Gemfile.lock +0 -41
- data/History.txt +0 -179
- data/README.rdoc +0 -85
- data/Rakefile +0 -33
- data/Thorfile +0 -114
- data/lib/rack/mock_session.rb +0 -66
- data/lib/rack/test/mock_digest_request.rb +0 -29
- data/rack-test.gemspec +0 -77
- data/spec/fixtures/bar.txt +0 -1
- data/spec/fixtures/config.ru +0 -3
- data/spec/fixtures/fake_app.rb +0 -143
- data/spec/fixtures/foo.txt +0 -1
- data/spec/rack/test/cookie_spec.rb +0 -219
- data/spec/rack/test/digest_auth_spec.rb +0 -46
- data/spec/rack/test/multipart_spec.rb +0 -145
- data/spec/rack/test/uploaded_file_spec.rb +0 -24
- data/spec/rack/test/utils_spec.rb +0 -193
- data/spec/rack/test_spec.rb +0 -550
- data/spec/spec_helper.rb +0 -69
- data/spec/support/matchers/body.rb +0 -9
- data/spec/support/matchers/challenge.rb +0 -11
data/lib/rack/test/cookie_jar.rb
CHANGED
@@ -1,132 +1,191 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'uri'
|
4
|
+
require 'time'
|
3
5
|
|
4
6
|
module Rack
|
5
7
|
module Test
|
6
|
-
|
8
|
+
# Represents individual cookies in the cookie jar. This is considered private
|
9
|
+
# API and behavior of this class can change at any time.
|
7
10
|
class Cookie # :nodoc:
|
8
11
|
include Rack::Utils
|
9
12
|
|
10
|
-
#
|
11
|
-
attr_reader :name
|
13
|
+
# The name of the cookie, will be a string
|
14
|
+
attr_reader :name
|
15
|
+
|
16
|
+
# The value of the cookie, will be a string or nil if there is no value.
|
17
|
+
attr_reader :value
|
18
|
+
|
19
|
+
# The raw string for the cookie, without options. Will generally be in
|
20
|
+
# name=value format is name and value are provided.
|
21
|
+
attr_reader :raw
|
12
22
|
|
13
|
-
# :api: private
|
14
23
|
def initialize(raw, uri = nil, default_host = DEFAULT_HOST)
|
15
24
|
@default_host = default_host
|
16
25
|
uri ||= default_uri
|
17
26
|
|
18
27
|
# separate the name / value pair from the cookie options
|
19
|
-
@
|
28
|
+
@raw, options = raw.split(/[;,] */n, 2)
|
20
29
|
|
21
|
-
@name, @value = parse_query(@
|
30
|
+
@name, @value = parse_query(@raw, ';').to_a.first
|
22
31
|
@options = parse_query(options, ';')
|
23
32
|
|
24
|
-
@options[
|
25
|
-
|
33
|
+
if domain = @options['domain']
|
34
|
+
@exact_domain_match = false
|
35
|
+
domain[0] = '' if domain[0] == '.'
|
36
|
+
else
|
37
|
+
# If the domain attribute is not present in the cookie,
|
38
|
+
# the domain must match exactly.
|
39
|
+
@exact_domain_match = true
|
40
|
+
@options['domain'] = (uri.host || default_host)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Set the path for the cookie to the directory containing
|
44
|
+
# the request if it isn't set.
|
45
|
+
@options['path'] ||= uri.path.sub(/\/[^\/]*\Z/, '')
|
26
46
|
end
|
27
47
|
|
48
|
+
# Wether the given cookie can replace the current cookie in the cookie jar.
|
28
49
|
def replaces?(other)
|
29
50
|
[name.downcase, domain, path] == [other.name.downcase, other.domain, other.path]
|
30
51
|
end
|
31
52
|
|
32
|
-
#
|
33
|
-
def raw
|
34
|
-
@name_value_raw
|
35
|
-
end
|
36
|
-
|
37
|
-
# :api: private
|
53
|
+
# Whether the cookie has a value.
|
38
54
|
def empty?
|
39
55
|
@value.nil? || @value.empty?
|
40
56
|
end
|
41
57
|
|
42
|
-
#
|
58
|
+
# The explicit or implicit domain for the cookie.
|
43
59
|
def domain
|
44
|
-
@options[
|
60
|
+
@options['domain']
|
45
61
|
end
|
46
62
|
|
63
|
+
# Whether the cookie has the secure flag, indicating it can only be sent over
|
64
|
+
# an encrypted connection.
|
47
65
|
def secure?
|
48
|
-
@options.
|
66
|
+
@options.key?('secure')
|
49
67
|
end
|
50
68
|
|
51
|
-
#
|
69
|
+
# Whether the cookie has the httponly flag, indicating it is not available via
|
70
|
+
# a javascript API.
|
71
|
+
def http_only?
|
72
|
+
@options.key?('HttpOnly') || @options.key?('httponly')
|
73
|
+
end
|
74
|
+
|
75
|
+
# The explicit or implicit path for the cookie.
|
52
76
|
def path
|
53
|
-
|
77
|
+
([*@options['path']].first.split(',').first || '/').strip
|
54
78
|
end
|
55
79
|
|
56
|
-
#
|
80
|
+
# A Time value for when the cookie expires, if the expires option is set.
|
57
81
|
def expires
|
58
|
-
Time.parse(@options[
|
82
|
+
Time.parse(@options['expires']) if @options['expires']
|
59
83
|
end
|
60
84
|
|
61
|
-
#
|
85
|
+
# Whether the cookie is currently expired.
|
62
86
|
def expired?
|
63
87
|
expires && expires < Time.now
|
64
88
|
end
|
65
89
|
|
66
|
-
#
|
90
|
+
# Whether the cookie is valid for the given URI.
|
67
91
|
def valid?(uri)
|
68
92
|
uri ||= default_uri
|
69
93
|
|
70
|
-
if uri.host.nil?
|
71
|
-
uri.host = @default_host
|
72
|
-
end
|
94
|
+
uri.host = @default_host if uri.host.nil?
|
73
95
|
|
74
96
|
real_domain = domain =~ /^\./ ? domain[1..-1] : domain
|
75
|
-
(!secure? || (secure? && uri.scheme ==
|
76
|
-
|
77
|
-
uri.path =~ Regexp.new("^#{Regexp.escape(path)}")
|
97
|
+
!!((!secure? || (secure? && uri.scheme == 'https')) &&
|
98
|
+
uri.host =~ Regexp.new("#{'^' if @exact_domain_match}#{Regexp.escape(real_domain)}$", Regexp::IGNORECASE))
|
78
99
|
end
|
79
100
|
|
80
|
-
#
|
101
|
+
# Cookies that do not match the URI will not be sent in requests to the URI.
|
81
102
|
def matches?(uri)
|
82
|
-
!
|
103
|
+
!expired? && valid?(uri) && uri.path.start_with?(path)
|
83
104
|
end
|
84
105
|
|
85
|
-
#
|
106
|
+
# Order cookies by name, path, and domain.
|
86
107
|
def <=>(other)
|
87
|
-
# Orders the cookies from least specific to most
|
88
108
|
[name, path, domain.reverse] <=> [other.name, other.path, other.domain.reverse]
|
89
109
|
end
|
90
110
|
|
91
|
-
|
111
|
+
# A hash of cookie options, including the cookie value, but excluding the cookie name.
|
112
|
+
def to_h
|
113
|
+
@options.merge(
|
114
|
+
'value' => @value,
|
115
|
+
'HttpOnly' => http_only?,
|
116
|
+
'secure' => secure?
|
117
|
+
)
|
118
|
+
end
|
119
|
+
alias to_hash to_h
|
120
|
+
|
121
|
+
private
|
92
122
|
|
123
|
+
# The default URI to use for the cookie, including just the host.
|
93
124
|
def default_uri
|
94
|
-
URI.parse(
|
125
|
+
URI.parse('//' + @default_host + '/')
|
95
126
|
end
|
96
|
-
|
97
127
|
end
|
98
128
|
|
129
|
+
# Represents all cookies for a session, handling adding and
|
130
|
+
# removing cookies, and finding which cookies apply to a given
|
131
|
+
# request. This is considered private API and behavior of this
|
132
|
+
# class can change at any time.
|
99
133
|
class CookieJar # :nodoc:
|
134
|
+
DELIMITER = '; '.freeze
|
100
135
|
|
101
|
-
# :api: private
|
102
136
|
def initialize(cookies = [], default_host = DEFAULT_HOST)
|
103
137
|
@default_host = default_host
|
104
|
-
@cookies = cookies
|
105
|
-
|
138
|
+
@cookies = cookies.sort!
|
139
|
+
end
|
140
|
+
|
141
|
+
# Ensure the copy uses a distinct cookies array.
|
142
|
+
def initialize_copy(other)
|
143
|
+
super
|
144
|
+
@cookies = @cookies.dup
|
106
145
|
end
|
107
146
|
|
147
|
+
# Return the value for first cookie with the given name, or nil
|
148
|
+
# if no such cookie exists.
|
108
149
|
def [](name)
|
109
|
-
|
110
|
-
|
111
|
-
|
150
|
+
name = name.to_s
|
151
|
+
@cookies.each do |cookie|
|
152
|
+
return cookie.value if cookie.name == name
|
153
|
+
end
|
154
|
+
nil
|
112
155
|
end
|
113
156
|
|
157
|
+
# Set a cookie with the given name and value in the
|
158
|
+
# cookie jar.
|
114
159
|
def []=(name, value)
|
115
160
|
merge("#{name}=#{Rack::Utils.escape(value)}")
|
116
161
|
end
|
117
162
|
|
163
|
+
# Return the first cookie with the given name, or nil if
|
164
|
+
# no such cookie exists.
|
165
|
+
def get_cookie(name)
|
166
|
+
@cookies.each do |cookie|
|
167
|
+
return cookie if cookie.name == name
|
168
|
+
end
|
169
|
+
nil
|
170
|
+
end
|
171
|
+
|
172
|
+
# Delete all cookies with the given name from the cookie jar.
|
118
173
|
def delete(name)
|
119
174
|
@cookies.reject! do |cookie|
|
120
175
|
cookie.name == name
|
121
176
|
end
|
177
|
+
nil
|
122
178
|
end
|
123
179
|
|
180
|
+
# Add a string of raw cookie information to the cookie jar,
|
181
|
+
# if the cookie is valid for the given URI.
|
182
|
+
# Cookies should be separated with a newline.
|
124
183
|
def merge(raw_cookies, uri = nil)
|
125
184
|
return unless raw_cookies
|
126
185
|
|
127
186
|
if raw_cookies.is_a? String
|
128
187
|
raw_cookies = raw_cookies.split("\n")
|
129
|
-
raw_cookies.reject!
|
188
|
+
raw_cookies.reject!(&:empty?)
|
130
189
|
end
|
131
190
|
|
132
191
|
raw_cookies.each do |raw_cookie|
|
@@ -135,6 +194,7 @@ module Rack
|
|
135
194
|
end
|
136
195
|
end
|
137
196
|
|
197
|
+
# Add a Cookie to the cookie jar.
|
138
198
|
def <<(new_cookie)
|
139
199
|
@cookies.reject! do |existing_cookie|
|
140
200
|
new_cookie.replaces?(existing_cookie)
|
@@ -144,39 +204,49 @@ module Rack
|
|
144
204
|
@cookies.sort!
|
145
205
|
end
|
146
206
|
|
147
|
-
#
|
207
|
+
# Return a raw cookie string for the cookie header to
|
208
|
+
# use for the given URI.
|
148
209
|
def for(uri)
|
149
|
-
|
210
|
+
buf = String.new
|
211
|
+
delimiter = nil
|
212
|
+
|
213
|
+
each_cookie_for(uri) do |cookie|
|
214
|
+
if delimiter
|
215
|
+
buf << delimiter
|
216
|
+
else
|
217
|
+
delimiter = DELIMITER
|
218
|
+
end
|
219
|
+
buf << cookie.raw
|
220
|
+
end
|
221
|
+
|
222
|
+
buf
|
150
223
|
end
|
151
224
|
|
225
|
+
# Return a hash cookie names and cookie values for cookies in the jar.
|
152
226
|
def to_hash
|
153
227
|
cookies = {}
|
154
228
|
|
155
|
-
|
156
|
-
cookies[name] = cookie.value
|
229
|
+
@cookies.each do |cookie|
|
230
|
+
cookies[cookie.name] = cookie.value
|
157
231
|
end
|
158
232
|
|
159
|
-
|
233
|
+
cookies
|
160
234
|
end
|
161
235
|
|
162
|
-
|
163
|
-
|
164
|
-
def hash_for(uri = nil)
|
165
|
-
cookies = {}
|
236
|
+
private
|
166
237
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
238
|
+
# Yield each cookie that matches for the URI.
|
239
|
+
#
|
240
|
+
# The cookies are sorted by most specific first. So, we loop through
|
241
|
+
# all the cookies in order and add it to a hash by cookie name if
|
242
|
+
# the cookie can be sent to the current URI. It's added to the hash
|
243
|
+
# so that when we are done, the cookies will be unique by name and
|
244
|
+
# we'll have grabbed the most specific to the URI.
|
245
|
+
def each_cookie_for(uri)
|
172
246
|
@cookies.each do |cookie|
|
173
|
-
|
247
|
+
yield cookie if !uri || cookie.matches?(uri)
|
174
248
|
end
|
175
|
-
|
176
|
-
return cookies
|
177
249
|
end
|
178
|
-
|
179
250
|
end
|
180
|
-
|
181
251
|
end
|
182
252
|
end
|
data/lib/rack/test/methods.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
2
4
|
|
3
5
|
module Rack
|
4
6
|
module Test
|
5
|
-
|
6
7
|
# This module serves as the primary integration point for using Rack::Test
|
7
8
|
# in a testing environment. It depends on an app method being defined in the
|
8
9
|
# same context, and provides the Rack::Test API methods (see Rack::Test::Session
|
9
|
-
# for their documentation).
|
10
|
+
# for their documentation). It defines the following methods that are delegated
|
11
|
+
# to the current session: :request, :get, :post, :put, :patch, :delete, :options,
|
12
|
+
# :head, :custom_request, :follow_redirect!, :header, :env, :set_cookie,
|
13
|
+
# :clear_cookies, :authorize, :basic_authorize, :last_response, and :last_request.
|
10
14
|
#
|
11
15
|
# Example:
|
12
16
|
#
|
@@ -14,23 +18,14 @@ module Rack
|
|
14
18
|
# include Rack::Test::Methods
|
15
19
|
#
|
16
20
|
# def app
|
17
|
-
# MyApp
|
21
|
+
# MyApp
|
18
22
|
# end
|
19
23
|
# end
|
20
24
|
module Methods
|
21
25
|
extend Forwardable
|
22
26
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
@_rack_mock_sessions ||= {}
|
27
|
-
@_rack_mock_sessions[name] ||= build_rack_mock_session
|
28
|
-
end
|
29
|
-
|
30
|
-
def build_rack_mock_session # :nodoc:
|
31
|
-
Rack::MockSession.new(app)
|
32
|
-
end
|
33
|
-
|
27
|
+
# Return the existing session with the given name, or a new
|
28
|
+
# rack session. Always use a new session if name is nil.
|
34
29
|
def rack_test_session(name = :default) # :nodoc:
|
35
30
|
return build_rack_test_session(name) unless name
|
36
31
|
|
@@ -38,25 +33,39 @@ module Rack
|
|
38
33
|
@_rack_test_sessions[name] ||= build_rack_test_session(name)
|
39
34
|
end
|
40
35
|
|
41
|
-
|
42
|
-
|
43
|
-
end
|
36
|
+
# For backwards compatibility with older rack-test versions.
|
37
|
+
alias rack_mock_session rack_test_session # :nodoc:
|
44
38
|
|
45
|
-
|
46
|
-
|
39
|
+
# Create a new Rack::Test::Session for #app.
|
40
|
+
def build_rack_test_session(_name) # :nodoc:
|
41
|
+
if respond_to?(:build_rack_mock_session, true)
|
42
|
+
# Backwards compatibility for capybara
|
43
|
+
build_rack_mock_session
|
44
|
+
else
|
45
|
+
if respond_to?(:default_host)
|
46
|
+
Session.new(app, default_host)
|
47
|
+
else
|
48
|
+
Session.new(app)
|
49
|
+
end
|
50
|
+
end
|
47
51
|
end
|
48
52
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
+
# Return the currently actively session. This is the session to
|
54
|
+
# which the delegated methods are sent.
|
55
|
+
def current_session
|
56
|
+
@_rack_test_current_session ||= rack_test_session
|
53
57
|
end
|
54
58
|
|
55
|
-
|
56
|
-
|
59
|
+
# Create a new session (or reuse an existing session with the given name),
|
60
|
+
# and make it the current session for the given block.
|
61
|
+
def with_session(name)
|
62
|
+
session = _rack_test_current_session
|
63
|
+
yield(@_rack_test_current_session = rack_test_session(name))
|
64
|
+
ensure
|
65
|
+
@_rack_test_current_session = session
|
57
66
|
end
|
58
67
|
|
59
|
-
|
68
|
+
def_delegators(:current_session,
|
60
69
|
:request,
|
61
70
|
:get,
|
62
71
|
:post,
|
@@ -65,6 +74,7 @@ module Rack
|
|
65
74
|
:delete,
|
66
75
|
:options,
|
67
76
|
:head,
|
77
|
+
:custom_request,
|
68
78
|
:follow_redirect!,
|
69
79
|
:header,
|
70
80
|
:env,
|
@@ -72,12 +82,13 @@ module Rack
|
|
72
82
|
:clear_cookies,
|
73
83
|
:authorize,
|
74
84
|
:basic_authorize,
|
75
|
-
:digest_authorize,
|
76
85
|
:last_response,
|
77
|
-
:last_request
|
78
|
-
|
86
|
+
:last_request,
|
87
|
+
)
|
79
88
|
|
80
|
-
|
89
|
+
# Private accessor to avoid uninitialized instance variable warning in Ruby 2.*
|
90
|
+
attr_accessor :_rack_test_current_session
|
91
|
+
private :_rack_test_current_session
|
81
92
|
end
|
82
93
|
end
|
83
94
|
end
|
@@ -1,16 +1,17 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'stringio'
|
3
6
|
|
4
7
|
module Rack
|
5
8
|
module Test
|
6
|
-
|
7
9
|
# Wraps a Tempfile with a content type. Including one or more UploadedFile's
|
8
10
|
# in the params causes Rack::Test to build and issue a multipart request.
|
9
11
|
#
|
10
12
|
# Example:
|
11
13
|
# post "/photos", "file" => Rack::Test::UploadedFile.new("me.jpg", "image/jpeg")
|
12
14
|
class UploadedFile
|
13
|
-
|
14
15
|
# The filename, *not* including the path, of the "uploaded" file
|
15
16
|
attr_reader :original_filename
|
16
17
|
|
@@ -20,34 +21,93 @@ module Rack
|
|
20
21
|
# The content type of the "uploaded" file
|
21
22
|
attr_accessor :content_type
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
# Creates a new UploadedFile instance.
|
25
|
+
#
|
26
|
+
# Arguments:
|
27
|
+
# content :: is a path to a file, or an {IO} or {StringIO} object representing the content.
|
28
|
+
# content_type :: MIME type of the file
|
29
|
+
# binary :: Whether the file should be set to binmode (content treated as binary).
|
30
|
+
# original_filename :: The filename to use for the file. Required if content is StringIO, optional override if not
|
31
|
+
def initialize(content, content_type = 'text/plain', binary = false, original_filename: nil)
|
26
32
|
@content_type = content_type
|
27
|
-
@original_filename =
|
33
|
+
@original_filename = original_filename
|
28
34
|
|
29
|
-
|
30
|
-
|
31
|
-
|
35
|
+
case content
|
36
|
+
when StringIO
|
37
|
+
initialize_from_stringio(content)
|
38
|
+
else
|
39
|
+
initialize_from_file_path(content)
|
40
|
+
end
|
32
41
|
|
33
|
-
|
42
|
+
@tempfile.binmode if binary
|
34
43
|
end
|
35
44
|
|
45
|
+
# The path to the tempfile. Will not work if the receiver's content is from a StringIO.
|
36
46
|
def path
|
37
|
-
|
47
|
+
tempfile.path
|
48
|
+
end
|
49
|
+
alias local_path path
|
50
|
+
|
51
|
+
# Delegate all methods not handled to the tempfile.
|
52
|
+
def method_missing(method_name, *args, &block)
|
53
|
+
tempfile.public_send(method_name, *args, &block)
|
38
54
|
end
|
39
55
|
|
40
|
-
|
56
|
+
# Append to given buffer in 64K chunks to avoid multiple large
|
57
|
+
# copies of file data in memory. Rewind tempfile before and
|
58
|
+
# after to make sure all data in tempfile is appended to the
|
59
|
+
# buffer.
|
60
|
+
def append_to(buffer)
|
61
|
+
tempfile.rewind
|
62
|
+
|
63
|
+
buf = String.new
|
64
|
+
buffer << tempfile.readpartial(65_536, buf) until tempfile.eof?
|
41
65
|
|
42
|
-
|
43
|
-
|
66
|
+
tempfile.rewind
|
67
|
+
|
68
|
+
nil
|
44
69
|
end
|
45
70
|
|
46
|
-
def
|
47
|
-
|
71
|
+
def respond_to_missing?(method_name, include_private = false) #:nodoc:
|
72
|
+
tempfile.respond_to?(method_name, include_private) || super
|
48
73
|
end
|
49
74
|
|
50
|
-
|
75
|
+
# A proc that can be used as a finalizer to close and unlink the tempfile.
|
76
|
+
def self.finalize(file)
|
77
|
+
proc { actually_finalize file }
|
78
|
+
end
|
51
79
|
|
80
|
+
# Close and unlink the given file, used as a finalizer for the tempfile,
|
81
|
+
# if the tempfile is backed by a file in the filesystem.
|
82
|
+
def self.actually_finalize(file)
|
83
|
+
file.close
|
84
|
+
file.unlink
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
# Use the StringIO as the tempfile.
|
90
|
+
def initialize_from_stringio(stringio)
|
91
|
+
raise(ArgumentError, 'Missing `original_filename` for StringIO object') unless @original_filename
|
92
|
+
|
93
|
+
@tempfile = stringio
|
94
|
+
end
|
95
|
+
|
96
|
+
# Create a tempfile and copy the content from the given path into the tempfile, optionally renaming if
|
97
|
+
# original_filename has been set.
|
98
|
+
def initialize_from_file_path(path)
|
99
|
+
raise "#{path} file does not exist" unless ::File.exist?(path)
|
100
|
+
|
101
|
+
@original_filename ||= ::File.basename(path)
|
102
|
+
extension = ::File.extname(@original_filename)
|
103
|
+
|
104
|
+
@tempfile = Tempfile.new([::File.basename(@original_filename, extension), extension])
|
105
|
+
@tempfile.set_encoding(Encoding::BINARY)
|
106
|
+
|
107
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(@tempfile))
|
108
|
+
|
109
|
+
FileUtils.copy_file(path, @tempfile.path)
|
110
|
+
end
|
111
|
+
end
|
52
112
|
end
|
53
113
|
end
|