ethon 0.18.0 → 0.19.1
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 +4 -4
- data/CHANGELOG.md +705 -2
- data/README.md +183 -40
- data/lib/ethon/curl.rb +22 -0
- data/lib/ethon/curls/classes.rb +284 -12
- data/lib/ethon/curls/codes.rb +28 -9
- data/lib/ethon/curls/constants.rb +61 -4
- data/lib/ethon/curls/form_options.rb +2 -1
- data/lib/ethon/curls/functions.rb +60 -11
- data/lib/ethon/curls/infos.rb +104 -5
- data/lib/ethon/curls/introspection.rb +139 -0
- data/lib/ethon/curls/introspection_constants.rb +42 -0
- data/lib/ethon/curls/mime.rb +187 -0
- data/lib/ethon/curls/optional.rb +14 -0
- data/lib/ethon/curls/options.rb +298 -71
- data/lib/ethon/curls/settings.rb +12 -3
- data/lib/ethon/curls/share.rb +165 -0
- data/lib/ethon/curls/share_constants.rb +47 -0
- data/lib/ethon/curls/url.rb +171 -0
- data/lib/ethon/curls/url_constants.rb +78 -0
- data/lib/ethon/easy/callbacks.rb +177 -51
- data/lib/ethon/easy/features.rb +4 -1
- data/lib/ethon/easy/files.rb +113 -0
- data/lib/ethon/easy/form.rb +65 -6
- data/lib/ethon/easy/header.rb +57 -15
- data/lib/ethon/easy/headers_api.rb +79 -0
- data/lib/ethon/easy/http/actionable.rb +25 -3
- data/lib/ethon/easy/http/get.rb +5 -1
- data/lib/ethon/easy/http/post.rb +1 -0
- data/lib/ethon/easy/http/postable.rb +9 -2
- data/lib/ethon/easy/http/putable.rb +19 -4
- data/lib/ethon/easy/http.rb +16 -4
- data/lib/ethon/easy/informations.rb +51 -2
- data/lib/ethon/easy/mime.rb +29 -0
- data/lib/ethon/easy/mirror.rb +17 -4
- data/lib/ethon/easy/operations.rb +264 -11
- data/lib/ethon/easy/options.rb +327 -8
- data/lib/ethon/easy/queryable.rb +59 -14
- data/lib/ethon/easy/response_callbacks.rb +33 -12
- data/lib/ethon/easy/share.rb +28 -0
- data/lib/ethon/easy/url.rb +32 -0
- data/lib/ethon/easy/util.rb +19 -0
- data/lib/ethon/easy.rb +185 -10
- data/lib/ethon/errors/fork_safety.rb +10 -0
- data/lib/ethon/errors/form_add.rb +12 -0
- data/lib/ethon/errors/global_init.rb +1 -1
- data/lib/ethon/errors/header.rb +12 -0
- data/lib/ethon/errors/invalid_option.rb +3 -1
- data/lib/ethon/errors/invalid_value.rb +3 -1
- data/lib/ethon/errors/mime.rb +17 -0
- data/lib/ethon/errors/multi_add.rb +1 -1
- data/lib/ethon/errors/multi_assign.rb +12 -0
- data/lib/ethon/errors/multi_fdset.rb +1 -1
- data/lib/ethon/errors/multi_perform.rb +12 -0
- data/lib/ethon/errors/multi_poll.rb +12 -0
- data/lib/ethon/errors/multi_remove.rb +1 -1
- data/lib/ethon/errors/multi_timeout.rb +2 -2
- data/lib/ethon/errors/multi_wait.rb +12 -0
- data/lib/ethon/errors/multi_wakeup.rb +12 -0
- data/lib/ethon/errors/select.rb +1 -1
- data/lib/ethon/errors/set_option.rb +27 -0
- data/lib/ethon/errors/share.rb +17 -0
- data/lib/ethon/errors/unsupported_option.rb +23 -0
- data/lib/ethon/errors/url.rb +17 -0
- data/lib/ethon/errors.rb +13 -0
- data/lib/ethon/libc.rb +30 -1
- data/lib/ethon/loggable.rb +1 -1
- data/lib/ethon/multi/operations.rb +269 -25
- data/lib/ethon/multi/options.rb +114 -12
- data/lib/ethon/multi/stack.rb +181 -10
- data/lib/ethon/multi.rb +51 -15
- data/lib/ethon/version.rb +1 -1
- data/lib/ethon.rb +8 -9
- metadata +35 -6
data/lib/ethon/easy/queryable.rb
CHANGED
|
@@ -5,11 +5,51 @@ module Ethon
|
|
|
5
5
|
# This module contains logic about building
|
|
6
6
|
# query parameters for url or form.
|
|
7
7
|
module Queryable
|
|
8
|
+
# Marker for the [filename, content type, path] tuple produced for a
|
|
9
|
+
# file value. It stays an Array for compatibility, but lets a form tell a
|
|
10
|
+
# file upload apart from a plain array value (including the arrays kept
|
|
11
|
+
# intact by params_encoding :none).
|
|
12
|
+
class FileInfo < ::Array
|
|
13
|
+
def initialize(filename, content_type, path)
|
|
14
|
+
super(3)
|
|
15
|
+
self[0] = filename
|
|
16
|
+
self[1] = content_type
|
|
17
|
+
self[2] = path
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attr_reader :escape, :params_encoding
|
|
22
|
+
|
|
23
|
+
def escape=(value)
|
|
24
|
+
@to_s = nil
|
|
25
|
+
@escape = value
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def params_encoding=(value)
|
|
29
|
+
@query_pairs = nil
|
|
30
|
+
@to_s = nil
|
|
31
|
+
@params_encoding = value
|
|
32
|
+
end
|
|
8
33
|
|
|
9
|
-
# :
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
34
|
+
# Loads mime-types on first use rather than when ethon is required:
|
|
35
|
+
# its registry costs megabytes in every process, while only file
|
|
36
|
+
# uploads need it. Concurrent first calls are safe, `require` is.
|
|
37
|
+
#
|
|
38
|
+
# @return [ Boolean ] True if mime-types could be loaded.
|
|
39
|
+
def self.mime_types_available?
|
|
40
|
+
return @mime_types_available unless @mime_types_available.nil?
|
|
41
|
+
|
|
42
|
+
@mime_types_available = begin
|
|
43
|
+
require 'mime/types/columnar'
|
|
44
|
+
true
|
|
45
|
+
rescue LoadError
|
|
46
|
+
begin
|
|
47
|
+
require 'mime/types'
|
|
48
|
+
true
|
|
49
|
+
rescue LoadError
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
end
|
|
13
53
|
end
|
|
14
54
|
|
|
15
55
|
# Return wether there are elements in params or not.
|
|
@@ -19,7 +59,9 @@ module Ethon
|
|
|
19
59
|
#
|
|
20
60
|
# @return [ Boolean ] True if params is empty, else false.
|
|
21
61
|
def empty?
|
|
22
|
-
@params
|
|
62
|
+
# Build the pairs instead of asking @params directly: params may be a
|
|
63
|
+
# String or a scalar, and only the pairs describe what is sent.
|
|
64
|
+
query_pairs.empty?
|
|
23
65
|
end
|
|
24
66
|
|
|
25
67
|
# Return the string representation of params.
|
|
@@ -60,7 +102,11 @@ module Ethon
|
|
|
60
102
|
#
|
|
61
103
|
# @return [ Array ] The array of query pairs.
|
|
62
104
|
def build_query_pairs(hash)
|
|
63
|
-
|
|
105
|
+
if hash.is_a?(String)
|
|
106
|
+
# An empty string describes no pairs. Treating it as one would make
|
|
107
|
+
# #empty? false and leave a stray "?" or "&" in the url.
|
|
108
|
+
return hash.empty? ? [] : [hash]
|
|
109
|
+
end
|
|
64
110
|
|
|
65
111
|
pairs = []
|
|
66
112
|
recursively_generate_pairs(hash, nil, pairs)
|
|
@@ -77,17 +123,17 @@ module Ethon
|
|
|
77
123
|
# @return [ Array ] Array of informations.
|
|
78
124
|
def file_info(file)
|
|
79
125
|
filename = File.basename(file.path)
|
|
80
|
-
|
|
126
|
+
FileInfo.new(
|
|
81
127
|
filename,
|
|
82
128
|
mime_type(filename),
|
|
83
129
|
File.expand_path(file.path)
|
|
84
|
-
|
|
130
|
+
)
|
|
85
131
|
end
|
|
86
132
|
|
|
87
133
|
private
|
|
88
134
|
|
|
89
135
|
def mime_type(filename)
|
|
90
|
-
if defined?(MIME) && t = MIME::Types.type_for(filename).first
|
|
136
|
+
if Queryable.mime_types_available? && defined?(MIME) && t = MIME::Types.type_for(filename).first
|
|
91
137
|
t.to_s
|
|
92
138
|
else
|
|
93
139
|
'application/octet-stream'
|
|
@@ -132,12 +178,11 @@ module Ethon
|
|
|
132
178
|
end
|
|
133
179
|
end
|
|
134
180
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
181
|
+
def encode_multi_array_pairs(h, prefix, pairs)
|
|
182
|
+
h.each do |v|
|
|
183
|
+
pairs_for(v, prefix, pairs)
|
|
184
|
+
end
|
|
139
185
|
end
|
|
140
|
-
end
|
|
141
186
|
|
|
142
187
|
def pairs_for(v, key, pairs)
|
|
143
188
|
case v
|
|
@@ -69,9 +69,30 @@ module Ethon
|
|
|
69
69
|
# @example Execute on_completes.
|
|
70
70
|
# request.complete
|
|
71
71
|
def complete
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
if @completing
|
|
73
|
+
raise Errors::EthonError, "Easy is already completing; wait for the callbacks to finish"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
error = nil
|
|
77
|
+
response_body_io = @response_body_io
|
|
78
|
+
begin
|
|
79
|
+
@completing = true
|
|
80
|
+
headers unless @response_headers.empty?
|
|
81
|
+
if defined?(@on_complete) and not @on_complete.nil?
|
|
82
|
+
@on_complete.each{ |callback| callback.call(self) }
|
|
83
|
+
end
|
|
84
|
+
rescue Exception => exception
|
|
85
|
+
error = exception
|
|
86
|
+
raise
|
|
87
|
+
ensure
|
|
88
|
+
@completing = false
|
|
89
|
+
begin
|
|
90
|
+
finish_response_body_io(response_body_io)
|
|
91
|
+
rescue Exception
|
|
92
|
+
# A failed close or flush must not hide the error that caused the
|
|
93
|
+
# teardown, or a callback error that is about to be raised.
|
|
94
|
+
raise unless error || @callback_error
|
|
95
|
+
end
|
|
75
96
|
end
|
|
76
97
|
end
|
|
77
98
|
|
|
@@ -120,16 +141,16 @@ module Ethon
|
|
|
120
141
|
#
|
|
121
142
|
# @return [ Object ] If there are no on_body callbacks, returns the symbol :unyielded.
|
|
122
143
|
def body(chunk)
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
result
|
|
130
|
-
|
|
131
|
-
:unyielded
|
|
144
|
+
# Merely reading #on_body creates an empty list. It must not change
|
|
145
|
+
# whether the response body is buffered.
|
|
146
|
+
return :unyielded if !defined?(@on_body) || @on_body.nil? || @on_body.empty?
|
|
147
|
+
|
|
148
|
+
result = nil
|
|
149
|
+
@on_body.each do |callback|
|
|
150
|
+
result = callback.call(chunk, self)
|
|
151
|
+
break if result == :abort
|
|
132
152
|
end
|
|
153
|
+
result
|
|
133
154
|
end
|
|
134
155
|
end
|
|
135
156
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Ethon
|
|
3
|
+
class Easy
|
|
4
|
+
module Share
|
|
5
|
+
attr_reader :share
|
|
6
|
+
|
|
7
|
+
# Attach a curl share handle.
|
|
8
|
+
#
|
|
9
|
+
# @param [ Ethon::Curl::Share, nil ] share The share handle.
|
|
10
|
+
#
|
|
11
|
+
# @raise [ TypeError ] If share is not a share handle.
|
|
12
|
+
#
|
|
13
|
+
# @return [ Ethon::Curl::Share, nil ] The share handle.
|
|
14
|
+
def share=(share)
|
|
15
|
+
unless share.nil? || share.is_a?(Curl::Share)
|
|
16
|
+
raise TypeError, "expected an Ethon::Curl::Share, got #{share.class}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
share.check_pid! if share
|
|
20
|
+
Curl.set_option(:share, share, handle)
|
|
21
|
+
@share = share
|
|
22
|
+
share
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
include Share
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Ethon
|
|
3
|
+
class Easy
|
|
4
|
+
module Url
|
|
5
|
+
attr_reader :curlu
|
|
6
|
+
|
|
7
|
+
# Install a URL handle as the request target.
|
|
8
|
+
#
|
|
9
|
+
# @param [ Ethon::Curl::Url, nil ] url The URL handle.
|
|
10
|
+
#
|
|
11
|
+
# @raise [ TypeError ] If url is not a URL handle.
|
|
12
|
+
#
|
|
13
|
+
# @return [ Ethon::Curl::Url, nil ] The URL handle.
|
|
14
|
+
def curlu=(url)
|
|
15
|
+
unless url.nil? || url.is_a?(Curl::Url)
|
|
16
|
+
raise TypeError, "expected an Ethon::Curl::Url, got #{url.class}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
url.check_pid! if url
|
|
20
|
+
Curl.set_option(:curlu, url, handle)
|
|
21
|
+
if url
|
|
22
|
+
Curl.set_option(:url, nil, handle)
|
|
23
|
+
@url = nil
|
|
24
|
+
end
|
|
25
|
+
@curlu = url
|
|
26
|
+
url
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
include Url
|
|
31
|
+
end
|
|
32
|
+
end
|
data/lib/ethon/easy/util.rb
CHANGED
|
@@ -22,6 +22,25 @@ module Ethon
|
|
|
22
22
|
value.to_s.gsub(0.chr, '\\\0')
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
+
# Rejects header lines libcurl would send verbatim as extra headers.
|
|
26
|
+
#
|
|
27
|
+
# @example Validate a header line.
|
|
28
|
+
# Util.validate_header_line(:headers, "X-Test: 1")
|
|
29
|
+
#
|
|
30
|
+
# @param [ Symbol ] option The option name used in the error.
|
|
31
|
+
# @param [ String ] line The header line.
|
|
32
|
+
#
|
|
33
|
+
# @raise [ Errors::InvalidValue ] If the line contains CR, LF, or NUL.
|
|
34
|
+
#
|
|
35
|
+
# @return [ String ] The validated line.
|
|
36
|
+
def validate_header_line(option, line)
|
|
37
|
+
if line.include?("\r") || line.include?("\n") || line.include?("\0")
|
|
38
|
+
raise Errors::InvalidValue.new(option, line)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
line
|
|
42
|
+
end
|
|
43
|
+
|
|
25
44
|
extend self
|
|
26
45
|
end
|
|
27
46
|
end
|
data/lib/ethon/easy.rb
CHANGED
|
@@ -12,11 +12,12 @@ require 'ethon/easy/operations'
|
|
|
12
12
|
require 'ethon/easy/response_callbacks'
|
|
13
13
|
require 'ethon/easy/debug_info'
|
|
14
14
|
require 'ethon/easy/mirror'
|
|
15
|
+
require 'ethon/easy/files'
|
|
15
16
|
|
|
16
17
|
module Ethon
|
|
17
18
|
|
|
18
19
|
# This is the class representing the libcurl easy interface
|
|
19
|
-
# See
|
|
20
|
+
# See https://curl.se/libcurl/c/libcurl-easy.html for more informations.
|
|
20
21
|
#
|
|
21
22
|
# @example You can access the libcurl easy interface through this class, every request is based on it. The simplest setup looks like that:
|
|
22
23
|
#
|
|
@@ -41,6 +42,7 @@ module Ethon
|
|
|
41
42
|
include Ethon::Easy::Http
|
|
42
43
|
include Ethon::Easy::Operations
|
|
43
44
|
include Ethon::Easy::ResponseCallbacks
|
|
45
|
+
include Ethon::Easy::Files
|
|
44
46
|
extend Ethon::Easy::Features
|
|
45
47
|
|
|
46
48
|
# Returns the curl return code.
|
|
@@ -197,7 +199,7 @@ module Ethon
|
|
|
197
199
|
# * :obsolete: These error codes will never be returned. They were used in an old
|
|
198
200
|
# libcurl version and are currently unused.
|
|
199
201
|
#
|
|
200
|
-
# @see
|
|
202
|
+
# @see https://curl.se/libcurl/c/libcurl-errors.html
|
|
201
203
|
attr_accessor :return_code
|
|
202
204
|
|
|
203
205
|
# Initialize a new Easy.
|
|
@@ -214,11 +216,18 @@ module Ethon
|
|
|
214
216
|
# @return [ Easy ] A new Easy.
|
|
215
217
|
#
|
|
216
218
|
# @see Ethon::Easy::Options
|
|
217
|
-
# @see
|
|
219
|
+
# @see https://curl.se/libcurl/c/curl_easy_setopt.html
|
|
218
220
|
def initialize(options = {})
|
|
219
221
|
Curl.init
|
|
220
|
-
|
|
222
|
+
options = options.dup
|
|
223
|
+
# Apply a supplied handle before the other options so they are set on
|
|
224
|
+
# that handle instead of the one Ethon would otherwise create.
|
|
225
|
+
handle_key = options.keys.find { |key| key.to_s == "handle" }
|
|
226
|
+
self.handle = options.delete(handle_key) if handle_key
|
|
227
|
+
# Install Ethon's internal callbacks first so callback options passed to
|
|
228
|
+
# the constructor override them instead of being overwritten here.
|
|
221
229
|
set_callbacks
|
|
230
|
+
set_attributes(options)
|
|
222
231
|
end
|
|
223
232
|
|
|
224
233
|
# Set given options.
|
|
@@ -247,31 +256,144 @@ module Ethon
|
|
|
247
256
|
# @example Reset.
|
|
248
257
|
# easy.reset
|
|
249
258
|
def reset
|
|
259
|
+
ensure_not_attached_to_multi!
|
|
260
|
+
ensure_not_in_transfer!
|
|
261
|
+
# Check the handle before clearing state so reset is a no-op when the
|
|
262
|
+
# handle was cleaned up or inherited across a fork.
|
|
263
|
+
handle
|
|
264
|
+
# Close the response stream before clearing any state. If closing fails,
|
|
265
|
+
# leave the handle and its Ruby-side description untouched so the caller
|
|
266
|
+
# can recover instead of ending up with a half-reset Easy.
|
|
267
|
+
clear_response_body_io
|
|
268
|
+
|
|
269
|
+
@postfields = nil
|
|
270
|
+
@postfields_pointer = nil
|
|
271
|
+
@postfield_body_size = nil
|
|
272
|
+
@postfieldsize_value = nil
|
|
250
273
|
@url = nil
|
|
251
274
|
@escape = nil
|
|
252
|
-
@
|
|
275
|
+
@multipart = nil
|
|
276
|
+
@headers = nil
|
|
277
|
+
@return_code = nil
|
|
278
|
+
# A reset from on_complete is cleanup for the finished transfer. Keep a
|
|
279
|
+
# callback error so perform still raises it afterwards.
|
|
280
|
+
@callback_error = nil unless @completing
|
|
253
281
|
@on_complete = nil
|
|
254
282
|
@on_headers = nil
|
|
255
283
|
@on_body = nil
|
|
256
284
|
@on_progress = nil
|
|
285
|
+
@progress_source = nil
|
|
286
|
+
@internal_progress_option = nil
|
|
287
|
+
@user_debug_callback = nil
|
|
288
|
+
@noprogress_value = nil
|
|
257
289
|
@procs = nil
|
|
290
|
+
@blobs = nil
|
|
291
|
+
@slists = nil
|
|
258
292
|
@mirror = nil
|
|
293
|
+
@read_callback = nil
|
|
294
|
+
@request_body = nil
|
|
295
|
+
@request_body_start = nil
|
|
259
296
|
Curl.easy_reset(handle)
|
|
297
|
+
# libcurl no longer references these after easy_reset.
|
|
298
|
+
@header_list = nil
|
|
299
|
+
handle.error_buffer = nil
|
|
300
|
+
@error_buffer_enabled = nil
|
|
301
|
+
@multipart_form = nil
|
|
302
|
+
@curlu = nil
|
|
303
|
+
@mime = nil
|
|
304
|
+
# curl_easy_reset preserves CURLOPT_SHARE, so detach it explicitly.
|
|
305
|
+
self.share = nil if @share
|
|
260
306
|
set_callbacks
|
|
261
307
|
end
|
|
262
308
|
|
|
263
309
|
# Clones libcurl session handle. This means that all options that is set in
|
|
264
310
|
# the current handle will be set on duplicated handle.
|
|
311
|
+
#
|
|
312
|
+
# libcurl requires the source handle to be idle while it is cloned, and the
|
|
313
|
+
# copy can never be part of the same Multi. An Easy that a Multi owns must
|
|
314
|
+
# therefore be removed first.
|
|
315
|
+
#
|
|
316
|
+
# @raise [ Errors::EthonError ] If the Easy is still attached to a Multi.
|
|
265
317
|
def dup
|
|
318
|
+
ensure_not_performing!
|
|
319
|
+
ensure_not_attached_to_multi!
|
|
320
|
+
unless Curl.respond_to?(:easy_duphandle)
|
|
321
|
+
raise NotImplementedError, "curl_easy_duphandle is not supported by this libcurl"
|
|
322
|
+
end
|
|
323
|
+
|
|
266
324
|
e = super
|
|
267
|
-
|
|
325
|
+
duplicated_handle = Curl.easy_duphandle(handle)
|
|
326
|
+
raise NoMemoryError, "curl_easy_duphandle failed" if duplicated_handle.null?
|
|
327
|
+
|
|
328
|
+
# The copied callback closures still reference the original Easy. Clear
|
|
329
|
+
# them before installing callbacks on the duplicate so the new native
|
|
330
|
+
# handle never points at the original.
|
|
268
331
|
e.instance_variable_set(:@body_write_callback, nil)
|
|
269
332
|
e.instance_variable_set(:@header_write_callback, nil)
|
|
270
333
|
e.instance_variable_set(:@debug_callback, nil)
|
|
271
334
|
e.instance_variable_set(:@progress_callback, nil)
|
|
335
|
+
e.instance_variable_set(:@user_debug_callback, @user_debug_callback)
|
|
336
|
+
# Do not share an open response stream with the original. A path
|
|
337
|
+
# destination stays configured so the copy opens its own stream for the
|
|
338
|
+
# next transfer; a caller-owned IO is dropped because both handles would
|
|
339
|
+
# write into the same object.
|
|
340
|
+
e.instance_variable_set(:@response_body_io, nil)
|
|
341
|
+
e.instance_variable_set(:@owns_response_body_io, false)
|
|
342
|
+
e.instance_variable_set(:@multi, nil)
|
|
343
|
+
# The duplicated native handle already carries the request options, so
|
|
344
|
+
# keep the Ruby-side request state that describes them.
|
|
345
|
+
e.send(:assign_handle, Curl::EasyHandle.new(duplicated_handle), false)
|
|
346
|
+
# curl_easy_duphandle copies the native MIME structure, but there is no
|
|
347
|
+
# way to wrap that copy. Keeping the original wrapper would make both
|
|
348
|
+
# handles appear to share a structure they do not actually share.
|
|
349
|
+
e.instance_variable_set(:@mime, nil)
|
|
272
350
|
e.set_callbacks
|
|
351
|
+
restore_callbacks_on(e)
|
|
352
|
+
# curl_easy_duphandle copies the error buffer pointer, give the copy its own
|
|
353
|
+
e.instance_variable_set(:@error_buffer, nil)
|
|
354
|
+
e.errorbuffer = true if @error_buffer_enabled
|
|
355
|
+
# curl_easy_duphandle does not inherit share state, so reattach it.
|
|
356
|
+
e.share = e.share if e.share
|
|
357
|
+
# The copy starts a fresh transfer and must not report the original's
|
|
358
|
+
# result or a callback error from it.
|
|
359
|
+
e.return_code = nil
|
|
360
|
+
e.clear_callback_error
|
|
273
361
|
e
|
|
274
362
|
end
|
|
363
|
+
|
|
364
|
+
# Install fresh callback closures on a duplicate.
|
|
365
|
+
#
|
|
366
|
+
# curl_easy_duphandle copies callback pointers, but those closures still
|
|
367
|
+
# reference the original Easy. Reinstalling them keeps the duplicate from
|
|
368
|
+
# calling into the original and from depending on it staying alive.
|
|
369
|
+
def restore_callbacks_on(copy)
|
|
370
|
+
if defined?(@request_body) && @request_body
|
|
371
|
+
copy.set_read_callback(@request_body)
|
|
372
|
+
# Replay the original body range instead of wherever the shared body
|
|
373
|
+
# happens to be when the copy is made.
|
|
374
|
+
copy.instance_variable_set(:@request_body_start, @request_body_start)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
(@procs || {}).each do |option, callback|
|
|
378
|
+
next if option == :readfunction && defined?(@request_body) && @request_body
|
|
379
|
+
|
|
380
|
+
copy.public_send("#{option}=", callback)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
# Replay custom progress functions first: on_progress installs its
|
|
384
|
+
# internal callback on top of them, so it has to win in the same order.
|
|
385
|
+
if @progress_source == :internal && defined?(@on_progress) && @on_progress && @progress_callback
|
|
386
|
+
copy.set_progress_callback
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
private :restore_callbacks_on
|
|
390
|
+
|
|
391
|
+
# Cloning shares the native handle, so route it through #dup instead.
|
|
392
|
+
def clone(freeze: true)
|
|
393
|
+
copy = dup
|
|
394
|
+
copy.freeze if freeze && frozen?
|
|
395
|
+
copy
|
|
396
|
+
end
|
|
275
397
|
# Url escapes the value.
|
|
276
398
|
#
|
|
277
399
|
# @example Url escape.
|
|
@@ -284,9 +406,13 @@ module Ethon
|
|
|
284
406
|
# @api private
|
|
285
407
|
def escape(value)
|
|
286
408
|
string_pointer = Curl.easy_escape(handle, value, value.bytesize)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
409
|
+
raise NoMemoryError, "curl_easy_escape failed" if string_pointer.null?
|
|
410
|
+
|
|
411
|
+
begin
|
|
412
|
+
string_pointer.read_string.force_encoding(value.encoding)
|
|
413
|
+
ensure
|
|
414
|
+
Curl.free(string_pointer)
|
|
415
|
+
end
|
|
290
416
|
end
|
|
291
417
|
|
|
292
418
|
# Returns the informations available through libcurl as
|
|
@@ -299,7 +425,7 @@ module Ethon
|
|
|
299
425
|
end
|
|
300
426
|
|
|
301
427
|
def mirror
|
|
302
|
-
|
|
428
|
+
Mirror.from_easy(self)
|
|
303
429
|
end
|
|
304
430
|
|
|
305
431
|
# Return pretty log out.
|
|
@@ -311,5 +437,54 @@ module Ethon
|
|
|
311
437
|
def log_inspect
|
|
312
438
|
"EASY #{mirror.log_informations.map{|k, v| "#{k}=#{v}"}.flatten.join(' ')}"
|
|
313
439
|
end
|
|
440
|
+
|
|
441
|
+
# Url unescapes the value.
|
|
442
|
+
#
|
|
443
|
+
# @example Url unescape.
|
|
444
|
+
# easy.unescape("a%20b")
|
|
445
|
+
# #=> "a b"
|
|
446
|
+
#
|
|
447
|
+
# @param [ String ] value The value to unescape.
|
|
448
|
+
#
|
|
449
|
+
# @return [ String ] The unescaped value.
|
|
450
|
+
def unescape(value)
|
|
451
|
+
unless Curl.respond_to?(:easy_unescape)
|
|
452
|
+
raise NotImplementedError, "curl_easy_unescape is not supported by this libcurl"
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
out_length = FFI::MemoryPointer.new(:int)
|
|
456
|
+
string_pointer = Curl.easy_unescape(handle, value, value.bytesize, out_length)
|
|
457
|
+
raise NoMemoryError, "curl_easy_unescape failed" if string_pointer.null?
|
|
458
|
+
|
|
459
|
+
begin
|
|
460
|
+
string_pointer.read_string(out_length.read_int).force_encoding(value.encoding)
|
|
461
|
+
ensure
|
|
462
|
+
Curl.free(string_pointer)
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# Pause or resume a transfer. See Curl::PAUSE_*.
|
|
467
|
+
#
|
|
468
|
+
# @param [ Integer ] bitmask The parts to pause.
|
|
469
|
+
#
|
|
470
|
+
# @return [ Symbol ] The Curl return code.
|
|
471
|
+
def pause(bitmask = 0)
|
|
472
|
+
unless Curl.respond_to?(:easy_pause)
|
|
473
|
+
raise NotImplementedError, "curl_easy_pause is not supported by this libcurl"
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
Curl.easy_pause(handle, bitmask)
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# Let libcurl perform connection upkeep tasks.
|
|
480
|
+
#
|
|
481
|
+
# @return [ Symbol ] The Curl return code.
|
|
482
|
+
def upkeep
|
|
483
|
+
unless Curl.respond_to?(:easy_upkeep)
|
|
484
|
+
raise NotImplementedError, "curl_easy_upkeep is not supported by this libcurl"
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
Curl.easy_upkeep(handle)
|
|
488
|
+
end
|
|
314
489
|
end
|
|
315
490
|
end
|
|
@@ -5,7 +5,9 @@ module Ethon
|
|
|
5
5
|
# Raises when option is invalid.
|
|
6
6
|
class InvalidOption < EthonError
|
|
7
7
|
def initialize(option)
|
|
8
|
-
|
|
8
|
+
# Inspect so control characters in an untrusted option name cannot
|
|
9
|
+
# inject lines into logs that record the message.
|
|
10
|
+
super("The option: #{option.inspect} is invalid.")
|
|
9
11
|
end
|
|
10
12
|
end
|
|
11
13
|
end
|
|
@@ -5,7 +5,9 @@ module Ethon
|
|
|
5
5
|
# Raises when option is invalid.
|
|
6
6
|
class InvalidValue < EthonError
|
|
7
7
|
def initialize(option, value)
|
|
8
|
-
|
|
8
|
+
# Inspect so control characters in a rejected value cannot inject
|
|
9
|
+
# lines into logs that record the message.
|
|
10
|
+
super("The value: #{value.inspect} is invalid for option: #{option}.")
|
|
9
11
|
end
|
|
10
12
|
end
|
|
11
13
|
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Ethon
|
|
3
|
+
module Errors
|
|
4
|
+
|
|
5
|
+
# Raised when a curl MIME API call fails.
|
|
6
|
+
class Mime < EthonError
|
|
7
|
+
def initialize(code)
|
|
8
|
+
message = if Ethon::Curl.respond_to?(:easy_strerror)
|
|
9
|
+
Ethon::Curl.easy_strerror(code)
|
|
10
|
+
else
|
|
11
|
+
code.to_s
|
|
12
|
+
end
|
|
13
|
+
super("An error occurred building the MIME form: #{message}")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -5,7 +5,7 @@ module Ethon
|
|
|
5
5
|
# Raises when multi_add_handle failed.
|
|
6
6
|
class MultiAdd < EthonError
|
|
7
7
|
def initialize(code, easy)
|
|
8
|
-
super("An error
|
|
8
|
+
super("An error occurred adding the easy handle: #{easy} to the multi: #{code}")
|
|
9
9
|
end
|
|
10
10
|
end
|
|
11
11
|
end
|