ethon 0.18.0 → 0.19.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 +4 -4
- data/CHANGELOG.md +681 -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 +259 -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 +86 -12
- data/lib/ethon/multi/stack.rb +175 -10
- data/lib/ethon/multi.rb +44 -12
- data/lib/ethon/version.rb +1 -1
- data/lib/ethon.rb +8 -9
- metadata +35 -6
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Ethon
|
|
3
|
+
class Easy
|
|
4
|
+
# Structured access to response headers via curl_easy_header.
|
|
5
|
+
module HeadersApi
|
|
6
|
+
# libcurl's request number for the last request in a transfer. Redirects
|
|
7
|
+
# and multi-state authentication can produce several requests, and the
|
|
8
|
+
# response the caller sees comes from the last one.
|
|
9
|
+
LAST_REQUEST = -1
|
|
10
|
+
|
|
11
|
+
# Return a parsed response header.
|
|
12
|
+
#
|
|
13
|
+
# @param [ String ] name The header name.
|
|
14
|
+
# @param [ Integer ] index The instance index for repeated headers.
|
|
15
|
+
# @param [ Integer, Symbol, Array ] origin CURLH_* origin bits.
|
|
16
|
+
# @param [ Integer ] request The request number, -1 for the last request.
|
|
17
|
+
#
|
|
18
|
+
# @return [ Ethon::Curl::Header ] The header.
|
|
19
|
+
def header(name, index = 0, origin = Curl::HeaderOrigin[:header], request = LAST_REQUEST)
|
|
20
|
+
unless Curl.respond_to?(:easy_header)
|
|
21
|
+
raise NotImplementedError, "curl_easy_header is not supported by this libcurl"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
output = ::FFI::MemoryPointer.new(:pointer)
|
|
25
|
+
code = Curl.easy_header(handle, name.to_s, index, header_origin(origin), request, output)
|
|
26
|
+
raise Errors::Header.new(code) unless code == :ok
|
|
27
|
+
|
|
28
|
+
Curl::Header.new(Curl::HeaderData.new(output.read_pointer))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Enumerate response headers.
|
|
32
|
+
#
|
|
33
|
+
# @param [ Integer, Symbol, Array ] origin CURLH_* origin bits.
|
|
34
|
+
# @param [ Integer ] request The request number, -1 for the last request.
|
|
35
|
+
#
|
|
36
|
+
# @return [ Enumerator, self ] The headers.
|
|
37
|
+
def each_header(origin = Curl::HeaderOrigin[:header], request = LAST_REQUEST)
|
|
38
|
+
unless Curl.respond_to?(:easy_nextheader)
|
|
39
|
+
raise NotImplementedError, "curl_easy_nextheader is not supported by this libcurl"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
origin = header_origin(origin)
|
|
43
|
+
return enum_for(:each_header, origin, request) unless block_given?
|
|
44
|
+
|
|
45
|
+
previous = nil
|
|
46
|
+
loop do
|
|
47
|
+
header = Curl.easy_nextheader(handle, origin, request, previous)
|
|
48
|
+
break if header.null?
|
|
49
|
+
|
|
50
|
+
yield Curl::Header.new(header)
|
|
51
|
+
previous = header
|
|
52
|
+
end
|
|
53
|
+
self
|
|
54
|
+
end
|
|
55
|
+
alias headers_list each_header
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
|
|
59
|
+
def header_origin(origin)
|
|
60
|
+
case origin
|
|
61
|
+
when Array
|
|
62
|
+
origin.inject(0) do |mask, item|
|
|
63
|
+
value = Curl::HeaderOrigin[item]
|
|
64
|
+
raise Errors::InvalidValue.new(:header_origin, item) unless value && value != 0
|
|
65
|
+
mask | value
|
|
66
|
+
end
|
|
67
|
+
when Symbol
|
|
68
|
+
value = Curl::HeaderOrigin[origin]
|
|
69
|
+
raise Errors::InvalidValue.new(:header_origin, origin) unless value && value != 0
|
|
70
|
+
value
|
|
71
|
+
else
|
|
72
|
+
origin
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
include HeadersApi
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -93,6 +93,18 @@ module Ethon
|
|
|
93
93
|
# @param [ easy ] easy the easy to setup.
|
|
94
94
|
def setup(easy)
|
|
95
95
|
@easy = easy
|
|
96
|
+
easy.customrequest = nil
|
|
97
|
+
# A reused handle keeps the previous transfer's method and body
|
|
98
|
+
# options. Clear them before applying this request so a stale
|
|
99
|
+
# upload body cannot win over the body configured below.
|
|
100
|
+
# CURLOPT_POSTFIELDS selects POST even when cleared, so clear it
|
|
101
|
+
# before the method flags to leave the handle on GET.
|
|
102
|
+
easy.postfields = nil
|
|
103
|
+
easy.readfunction = nil
|
|
104
|
+
Curl.set_option(:upload, false, easy.handle)
|
|
105
|
+
Curl.set_option(:httpget, false, easy.handle)
|
|
106
|
+
Curl.set_option(:nobody, false, easy.handle)
|
|
107
|
+
Curl.set_option(:post, false, easy.handle)
|
|
96
108
|
|
|
97
109
|
# Order is important, @easy will be used to provide access to options
|
|
98
110
|
# relevant to the following operations (like whether or not to escape
|
|
@@ -100,6 +112,12 @@ module Ethon
|
|
|
100
112
|
easy.set_attributes(options)
|
|
101
113
|
|
|
102
114
|
set_form(easy) unless form.empty?
|
|
115
|
+
if form.empty?
|
|
116
|
+
easy.multipart_form = nil
|
|
117
|
+
# Clear any MIME body left over from an earlier request (or copied
|
|
118
|
+
# by Easy#dup) so this request does not inherit it.
|
|
119
|
+
easy.mime = nil if Ethon::Curl.respond_to?(:mime_init)
|
|
120
|
+
end
|
|
103
121
|
|
|
104
122
|
if params.empty?
|
|
105
123
|
easy.url = url
|
|
@@ -118,11 +136,15 @@ module Ethon
|
|
|
118
136
|
params.escape = easy.escape?
|
|
119
137
|
params.params_encoding = params_encoding
|
|
120
138
|
|
|
121
|
-
|
|
139
|
+
url_without_fragment, fragment_separator, fragment = url.partition('#')
|
|
140
|
+
fragment = fragment_separator.empty? ? nil : "#{fragment_separator}#{fragment}"
|
|
141
|
+
base_url, query_separator, base_params = url_without_fragment.partition('?')
|
|
142
|
+
|
|
122
143
|
base_url << '?'
|
|
123
|
-
base_url << base_params
|
|
124
|
-
base_url << '&' if base_params
|
|
144
|
+
base_url << base_params
|
|
145
|
+
base_url << '&' if query_separator == '?' && !base_params.empty?
|
|
125
146
|
base_url << params.to_s
|
|
147
|
+
base_url << fragment.to_s
|
|
126
148
|
|
|
127
149
|
easy.url = base_url
|
|
128
150
|
end
|
data/lib/ethon/easy/http/get.rb
CHANGED
data/lib/ethon/easy/http/post.rb
CHANGED
|
@@ -18,9 +18,16 @@ module Ethon
|
|
|
18
18
|
form.params_encoding = params_encoding
|
|
19
19
|
if form.multipart?
|
|
20
20
|
form.escape = false
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
if Ethon::Curl.respond_to?(:mime_init)
|
|
22
|
+
easy.multipart_form = nil
|
|
23
|
+
easy.mime = form.mime
|
|
24
|
+
else
|
|
25
|
+
form.materialize
|
|
26
|
+
easy.multipart_form = form
|
|
27
|
+
end
|
|
23
28
|
else
|
|
29
|
+
easy.multipart_form = nil
|
|
30
|
+
easy.mime = nil if Ethon::Curl.respond_to?(:mime_init)
|
|
24
31
|
form.escape = easy.escape?
|
|
25
32
|
easy.postfieldsize = form.to_s.bytesize
|
|
26
33
|
easy.copypostfields = form.to_s
|
|
@@ -13,11 +13,26 @@ module Ethon
|
|
|
13
13
|
#
|
|
14
14
|
# @param [ Easy ] easy The easy to setup.
|
|
15
15
|
def set_form(easy)
|
|
16
|
-
easy.
|
|
17
|
-
form.escape = true
|
|
16
|
+
easy.url ||= url
|
|
18
17
|
form.params_encoding = params_encoding
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
if form.multipart?
|
|
19
|
+
form.escape = false
|
|
20
|
+
if Ethon::Curl.respond_to?(:mime_init)
|
|
21
|
+
easy.multipart_form = nil
|
|
22
|
+
easy.mime = form.mime
|
|
23
|
+
else
|
|
24
|
+
form.materialize
|
|
25
|
+
easy.multipart_form = form
|
|
26
|
+
end
|
|
27
|
+
easy.customrequest = "PUT"
|
|
28
|
+
else
|
|
29
|
+
easy.multipart_form = nil
|
|
30
|
+
easy.mime = nil if Ethon::Curl.respond_to?(:mime_init)
|
|
31
|
+
easy.upload = true
|
|
32
|
+
form.escape = easy.escape?
|
|
33
|
+
easy.infilesize = form.to_s.bytesize
|
|
34
|
+
easy.set_read_callback(form.to_s)
|
|
35
|
+
end
|
|
21
36
|
end
|
|
22
37
|
end
|
|
23
38
|
end
|
data/lib/ethon/easy/http.rb
CHANGED
|
@@ -15,6 +15,18 @@ module Ethon
|
|
|
15
15
|
# This module contains logic about making valid HTTP requests.
|
|
16
16
|
module Http
|
|
17
17
|
|
|
18
|
+
# Built-in request actions. Custom verbs must not resolve to helper
|
|
19
|
+
# modules or classes that happen to live in this namespace.
|
|
20
|
+
ACTION_CLASSES = {
|
|
21
|
+
"get" => Get,
|
|
22
|
+
"head" => Head,
|
|
23
|
+
"post" => Post,
|
|
24
|
+
"put" => Put,
|
|
25
|
+
"delete" => Delete,
|
|
26
|
+
"patch" => Patch,
|
|
27
|
+
"options" => Options
|
|
28
|
+
}.freeze
|
|
29
|
+
|
|
18
30
|
# Set specified options in order to make a HTTP request.
|
|
19
31
|
# Look at {Ethon::Easy::Options Options} to see what you can
|
|
20
32
|
# provide in the options hash.
|
|
@@ -54,12 +66,12 @@ module Ethon
|
|
|
54
66
|
#
|
|
55
67
|
# @return [ Easy::Ethon::Actionable ] The request instance.
|
|
56
68
|
def fabricate(url, action_name, options)
|
|
57
|
-
|
|
69
|
+
action_class = ACTION_CLASSES[action_name.to_s.downcase]
|
|
58
70
|
|
|
59
|
-
if
|
|
60
|
-
|
|
71
|
+
if action_class
|
|
72
|
+
action_class.new(url, options)
|
|
61
73
|
else
|
|
62
|
-
Ethon::Easy::Http::Custom.new(
|
|
74
|
+
Ethon::Easy::Http::Custom.new(action_name.to_s.upcase, url, options)
|
|
63
75
|
end
|
|
64
76
|
end
|
|
65
77
|
|
|
@@ -3,13 +3,52 @@ module Ethon
|
|
|
3
3
|
class Easy
|
|
4
4
|
|
|
5
5
|
# This module contains the methods to return informations
|
|
6
|
-
# from the easy handle. See
|
|
6
|
+
# from the easy handle. See https://curl.se/libcurl/c/curl_easy_getinfo.html
|
|
7
7
|
# for more information.
|
|
8
8
|
module Informations
|
|
9
9
|
|
|
10
10
|
# Holds available informations and their type, which is needed to
|
|
11
11
|
# request the informations from libcurl.
|
|
12
12
|
AVAILABLE_INFORMATIONS = {
|
|
13
|
+
# The _T variants use curl_off_t and do not lose precision for large
|
|
14
|
+
# values or long-running transfers.
|
|
15
|
+
:size_upload_t => :off_t,
|
|
16
|
+
:size_download_t => :off_t,
|
|
17
|
+
:speed_download_t => :off_t,
|
|
18
|
+
:speed_upload_t => :off_t,
|
|
19
|
+
:filetime_t => :off_t,
|
|
20
|
+
:content_length_download_t => :off_t,
|
|
21
|
+
:content_length_upload_t => :off_t,
|
|
22
|
+
:total_time_t => :off_t,
|
|
23
|
+
:namelookup_time_t => :off_t,
|
|
24
|
+
:connect_time_t => :off_t,
|
|
25
|
+
:pretransfer_time_t => :off_t,
|
|
26
|
+
:starttransfer_time_t => :off_t,
|
|
27
|
+
:redirect_time_t => :off_t,
|
|
28
|
+
:appconnect_time_t => :off_t,
|
|
29
|
+
:retry_after => :off_t,
|
|
30
|
+
:xfer_id => :off_t,
|
|
31
|
+
:conn_id => :off_t,
|
|
32
|
+
:queue_time_t => :off_t,
|
|
33
|
+
:posttransfer_time_t => :off_t,
|
|
34
|
+
:earlydata_sent_t => :off_t,
|
|
35
|
+
:size_delivered => :off_t,
|
|
36
|
+
|
|
37
|
+
# Connection/request metadata.
|
|
38
|
+
:http_version => :long,
|
|
39
|
+
:httpauth_used => :long,
|
|
40
|
+
:proxyauth_used => :long,
|
|
41
|
+
:proxy_ssl_verifyresult => :long,
|
|
42
|
+
:proxy_error => :long,
|
|
43
|
+
:used_proxy => :long,
|
|
44
|
+
:scheme => :string,
|
|
45
|
+
:effective_method => :string,
|
|
46
|
+
:referer => :string,
|
|
47
|
+
:cainfo => :string,
|
|
48
|
+
:capath => :string,
|
|
49
|
+
:activesocket => :socket,
|
|
50
|
+
:tls_ssl_ptr => :ptr,
|
|
51
|
+
|
|
13
52
|
# Return the available HTTP auth methods.
|
|
14
53
|
:httpauth_avail => :long,
|
|
15
54
|
|
|
@@ -95,10 +134,20 @@ module Ethon
|
|
|
95
134
|
:speed_download => :double
|
|
96
135
|
}
|
|
97
136
|
|
|
137
|
+
LIST_INFORMATIONS = [:ssl_engines, :cookielist].freeze
|
|
138
|
+
|
|
98
139
|
AVAILABLE_INFORMATIONS.each do |name, type|
|
|
99
140
|
eval %Q|def #{name}; Curl.send(:get_info_#{type}, :#{name}, handle); end|
|
|
100
141
|
end
|
|
101
142
|
|
|
143
|
+
LIST_INFORMATIONS.each do |name|
|
|
144
|
+
define_method(name) { Curl.get_info_slist(name, handle) }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def certinfo
|
|
148
|
+
Curl.get_info_certinfo(:certinfo, handle)
|
|
149
|
+
end
|
|
150
|
+
|
|
102
151
|
# Returns true if this curl version supports zlib.
|
|
103
152
|
#
|
|
104
153
|
# @example Return wether zlib is supported.
|
|
@@ -107,7 +156,7 @@ module Ethon
|
|
|
107
156
|
# @return [ Boolean ] True if supported, else false.
|
|
108
157
|
# @deprecated Please use the static version instead
|
|
109
158
|
def supports_zlib?
|
|
110
|
-
Kernel.warn("Ethon: Easy#supports_zlib? is deprecated and will be removed, please use Easy
|
|
159
|
+
Kernel.warn("Ethon: Easy#supports_zlib? is deprecated and will be removed, please use Easy.supports_zlib? instead.")
|
|
111
160
|
Easy.supports_zlib?
|
|
112
161
|
end
|
|
113
162
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
module Ethon
|
|
3
|
+
class Easy
|
|
4
|
+
module Mime
|
|
5
|
+
attr_reader :mime
|
|
6
|
+
|
|
7
|
+
# Install a MIME structure as the request body.
|
|
8
|
+
#
|
|
9
|
+
# @param [ Ethon::Curl::Mime, nil ] mime The MIME structure.
|
|
10
|
+
#
|
|
11
|
+
# @raise [ TypeError ] If mime is not a MIME structure.
|
|
12
|
+
#
|
|
13
|
+
# @return [ Ethon::Curl::Mime, nil ] The MIME structure.
|
|
14
|
+
def mime=(mime)
|
|
15
|
+
unless mime.nil? || mime.is_a?(Curl::Mime)
|
|
16
|
+
raise TypeError, "expected an Ethon::Curl::Mime, got #{mime.class}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
mime.check_pid! if mime
|
|
20
|
+
Curl.set_option(:mimepost, mime, handle)
|
|
21
|
+
@mime = mime
|
|
22
|
+
mime
|
|
23
|
+
end
|
|
24
|
+
alias mimepost= mime=
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
include Mime
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/ethon/easy/mirror.rb
CHANGED
|
@@ -5,11 +5,20 @@ module Ethon
|
|
|
5
5
|
attr_reader :options
|
|
6
6
|
alias_method :to_hash, :options
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
# Raw pointers cannot be marshaled and point into native state that can
|
|
9
|
+
# go away when the handle is reset. Applications serialize a mirror (for
|
|
10
|
+
# example as a cached Typhoeus response), so leave them on the Easy.
|
|
11
|
+
INFORMATIONS_TO_MIRROR = Informations::AVAILABLE_INFORMATIONS.
|
|
12
|
+
reject { |_, type| type == :ptr }.keys +
|
|
13
|
+
[:return_code, :response_headers, :response_body, :debug_info, :error_message]
|
|
10
14
|
|
|
11
15
|
INFORMATIONS_TO_LOG = [:effective_url, :response_code, :return_code, :total_time]
|
|
12
16
|
|
|
17
|
+
# Credentials embedded in the URL are not useful in logs and may end up
|
|
18
|
+
# in a log aggregator. Keep the scheme and host so the line is still
|
|
19
|
+
# identifiable, but replace the userinfo.
|
|
20
|
+
URL_CREDENTIALS = %r{\A([a-z][a-z0-9+.-]*://)[^/?#@]*@}i
|
|
21
|
+
|
|
13
22
|
def self.from_easy(easy)
|
|
14
23
|
options = {}
|
|
15
24
|
INFORMATIONS_TO_MIRROR.each do |info|
|
|
@@ -24,12 +33,16 @@ module Ethon
|
|
|
24
33
|
|
|
25
34
|
def log_informations
|
|
26
35
|
Hash[*INFORMATIONS_TO_LOG.map do |info|
|
|
27
|
-
|
|
36
|
+
value = options[info]
|
|
37
|
+
if info == :effective_url && value.is_a?(String)
|
|
38
|
+
value = value.sub(URL_CREDENTIALS, "\\1[REDACTED]@")
|
|
39
|
+
end
|
|
40
|
+
[info, value]
|
|
28
41
|
end.flatten]
|
|
29
42
|
end
|
|
30
43
|
|
|
31
44
|
INFORMATIONS_TO_MIRROR.each do |info|
|
|
32
|
-
|
|
45
|
+
define_method(info) { options[info] }
|
|
33
46
|
end
|
|
34
47
|
end
|
|
35
48
|
end
|