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.
Files changed (74) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +681 -2
  3. data/README.md +183 -40
  4. data/lib/ethon/curl.rb +22 -0
  5. data/lib/ethon/curls/classes.rb +284 -12
  6. data/lib/ethon/curls/codes.rb +28 -9
  7. data/lib/ethon/curls/constants.rb +61 -4
  8. data/lib/ethon/curls/form_options.rb +2 -1
  9. data/lib/ethon/curls/functions.rb +60 -11
  10. data/lib/ethon/curls/infos.rb +104 -5
  11. data/lib/ethon/curls/introspection.rb +139 -0
  12. data/lib/ethon/curls/introspection_constants.rb +42 -0
  13. data/lib/ethon/curls/mime.rb +187 -0
  14. data/lib/ethon/curls/optional.rb +14 -0
  15. data/lib/ethon/curls/options.rb +298 -71
  16. data/lib/ethon/curls/settings.rb +12 -3
  17. data/lib/ethon/curls/share.rb +165 -0
  18. data/lib/ethon/curls/share_constants.rb +47 -0
  19. data/lib/ethon/curls/url.rb +171 -0
  20. data/lib/ethon/curls/url_constants.rb +78 -0
  21. data/lib/ethon/easy/callbacks.rb +177 -51
  22. data/lib/ethon/easy/features.rb +4 -1
  23. data/lib/ethon/easy/files.rb +113 -0
  24. data/lib/ethon/easy/form.rb +65 -6
  25. data/lib/ethon/easy/header.rb +57 -15
  26. data/lib/ethon/easy/headers_api.rb +79 -0
  27. data/lib/ethon/easy/http/actionable.rb +25 -3
  28. data/lib/ethon/easy/http/get.rb +5 -1
  29. data/lib/ethon/easy/http/post.rb +1 -0
  30. data/lib/ethon/easy/http/postable.rb +9 -2
  31. data/lib/ethon/easy/http/putable.rb +19 -4
  32. data/lib/ethon/easy/http.rb +16 -4
  33. data/lib/ethon/easy/informations.rb +51 -2
  34. data/lib/ethon/easy/mime.rb +29 -0
  35. data/lib/ethon/easy/mirror.rb +17 -4
  36. data/lib/ethon/easy/operations.rb +259 -11
  37. data/lib/ethon/easy/options.rb +327 -8
  38. data/lib/ethon/easy/queryable.rb +59 -14
  39. data/lib/ethon/easy/response_callbacks.rb +33 -12
  40. data/lib/ethon/easy/share.rb +28 -0
  41. data/lib/ethon/easy/url.rb +32 -0
  42. data/lib/ethon/easy/util.rb +19 -0
  43. data/lib/ethon/easy.rb +185 -10
  44. data/lib/ethon/errors/fork_safety.rb +10 -0
  45. data/lib/ethon/errors/form_add.rb +12 -0
  46. data/lib/ethon/errors/global_init.rb +1 -1
  47. data/lib/ethon/errors/header.rb +12 -0
  48. data/lib/ethon/errors/invalid_option.rb +3 -1
  49. data/lib/ethon/errors/invalid_value.rb +3 -1
  50. data/lib/ethon/errors/mime.rb +17 -0
  51. data/lib/ethon/errors/multi_add.rb +1 -1
  52. data/lib/ethon/errors/multi_assign.rb +12 -0
  53. data/lib/ethon/errors/multi_fdset.rb +1 -1
  54. data/lib/ethon/errors/multi_perform.rb +12 -0
  55. data/lib/ethon/errors/multi_poll.rb +12 -0
  56. data/lib/ethon/errors/multi_remove.rb +1 -1
  57. data/lib/ethon/errors/multi_timeout.rb +2 -2
  58. data/lib/ethon/errors/multi_wait.rb +12 -0
  59. data/lib/ethon/errors/multi_wakeup.rb +12 -0
  60. data/lib/ethon/errors/select.rb +1 -1
  61. data/lib/ethon/errors/set_option.rb +27 -0
  62. data/lib/ethon/errors/share.rb +17 -0
  63. data/lib/ethon/errors/unsupported_option.rb +23 -0
  64. data/lib/ethon/errors/url.rb +17 -0
  65. data/lib/ethon/errors.rb +13 -0
  66. data/lib/ethon/libc.rb +30 -1
  67. data/lib/ethon/loggable.rb +1 -1
  68. data/lib/ethon/multi/operations.rb +269 -25
  69. data/lib/ethon/multi/options.rb +86 -12
  70. data/lib/ethon/multi/stack.rb +175 -10
  71. data/lib/ethon/multi.rb +44 -12
  72. data/lib/ethon/version.rb +1 -1
  73. data/lib/ethon.rb +8 -9
  74. metadata +35 -6
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+ module Ethon
3
+ module Curl
4
+
5
+ # CURLOPT_MIMEPOST is the modern replacement for the deprecated form API.
6
+ Ethon::Curls::Options.option(:easy, :mimepost, :ffipointer, 269)
7
+
8
+ # Owns a curl_mime structure.
9
+ class Mime < OwnedPointer
10
+ def self.native_release(pointer)
11
+ Curl.mime_free(pointer) if Curl.respond_to?(:mime_free)
12
+ end
13
+
14
+ # @param [ Ethon::Easy, Curl::EasyHandle, FFI::Pointer ] easy The easy
15
+ # handle used to create the MIME structure.
16
+ def initialize(easy)
17
+ Curl.init
18
+ unless Curl.respond_to?(:mime_init)
19
+ raise NotImplementedError, "curl_mime_init is not supported by this libcurl"
20
+ end
21
+
22
+ easy_handle = mime_easy_handle(easy)
23
+ pointer = Curl.mime_init(easy_handle)
24
+ raise NoMemoryError, "curl_mime_init failed" if pointer.null?
25
+
26
+ @easy = easy
27
+ super(pointer)
28
+ end
29
+
30
+ # Add a part to the MIME structure.
31
+ #
32
+ # @return [ MimePart ] The new part.
33
+ def addpart
34
+ check_pid!
35
+ pointer = Curl.mime_addpart(self)
36
+ raise NoMemoryError, "curl_mime_addpart failed" if pointer.null?
37
+
38
+ part = MimePart.new(pointer, self)
39
+ (@parts ||= []) << part
40
+ part
41
+ end
42
+
43
+ private
44
+
45
+ # libcurl dereferences the easy handle while creating a MIME structure,
46
+ # so a Multi or another owned pointer would corrupt memory.
47
+ def mime_easy_handle(easy)
48
+ case easy
49
+ when Ethon::Easy
50
+ easy.handle
51
+ when Ethon::Curl::EasyHandle
52
+ easy.check_pid!
53
+ easy
54
+ when Ethon::Curl::OwnedPointer
55
+ raise TypeError, "expected an Ethon::Easy or easy handle, got #{easy.class}"
56
+ when ::FFI::Pointer
57
+ raise ArgumentError, "cannot create a MIME structure for a null easy handle" if easy.null?
58
+
59
+ easy
60
+ else
61
+ raise TypeError, "expected an Ethon::Easy or easy handle, got #{easy.class}"
62
+ end
63
+ end
64
+ end
65
+
66
+ # A part inside a MIME structure. The part is owned by its Mime.
67
+ class MimePart
68
+ attr_reader :mime, :handle
69
+
70
+ def initialize(pointer, mime)
71
+ @handle = pointer
72
+ @mime = mime
73
+ end
74
+
75
+ def name=(value)
76
+ call(:mime_name, header_line(:name, value))
77
+ value
78
+ end
79
+
80
+ def filename=(value)
81
+ call(:mime_filename, header_line(:filename, value))
82
+ value
83
+ end
84
+
85
+ def type=(value)
86
+ call(:mime_type, header_line(:type, value))
87
+ value
88
+ end
89
+
90
+ def encoder=(value)
91
+ call(:mime_encoder, header_line(:encoder, value))
92
+ value
93
+ end
94
+
95
+ def file=(value)
96
+ call(:mime_filedata, value.nil? ? nil : native_string(:file, value))
97
+ value
98
+ end
99
+
100
+ def data=(value)
101
+ if value.nil?
102
+ call(:mime_data, nil, 0)
103
+ else
104
+ data = value.to_s
105
+ call(:mime_data, data, data.bytesize)
106
+ end
107
+ value
108
+ end
109
+
110
+ # Set extra headers for this part.
111
+ #
112
+ # @param [ String, Array<String>, Hash, Curl::Slist, nil ] value The
113
+ # headers to add. Strings, arrays and hashes are converted into a
114
+ # curl_slist owned by libcurl.
115
+ #
116
+ # @return [ Object ] The value.
117
+ def headers=(value)
118
+ @mime.check_pid!
119
+ list = nil
120
+ retained = nil
121
+ owned = false
122
+
123
+ if value.nil?
124
+ # Clear the headers.
125
+ elsif value.is_a?(Curl::Slist)
126
+ value.check_pid!
127
+ list = value
128
+ retained = value
129
+ else
130
+ lines = value.is_a?(Hash) ? value.map { |key, item| "#{key}: #{item}" } : Array(value)
131
+ lines = header_lines(lines)
132
+ list = Curl::Slist.build(lines)
133
+ owned = !list.nil?
134
+ end
135
+
136
+ code = Curl.mime_headers(@handle, list, owned ? 1 : 0)
137
+ unless code == :ok
138
+ Curl.slist_free_all(list) if owned
139
+ raise Errors::Mime.new(code)
140
+ end
141
+
142
+ # The previous list must stay reachable until libcurl replaces it.
143
+ @headers_list = retained
144
+ value
145
+ end
146
+
147
+ private
148
+
149
+ # Convert a value to the C string libcurl receives, rejecting embedded
150
+ # NUL bytes with Ethon's error type instead of FFI's ArgumentError.
151
+ def native_string(option, value)
152
+ string = value.to_s
153
+ if string.include?("\0")
154
+ raise Errors::InvalidValue.new(option, value)
155
+ end
156
+ string
157
+ end
158
+
159
+ # The content type and encoder are written into the part headers
160
+ # verbatim, so CR or LF would add headers to the part or the request.
161
+ # A nil value clears the field instead of setting an empty string.
162
+ def header_line(option, value)
163
+ return if value.nil?
164
+
165
+ Ethon::Easy::Util.validate_header_line(option, native_string(option, value))
166
+ end
167
+
168
+ # Normalize header lines and reject values libcurl would send verbatim
169
+ # as additional part headers.
170
+ def header_lines(lines)
171
+ # An empty line describes no header, so drop it. That also makes a
172
+ # single empty string clear the part headers.
173
+ lines.map(&:to_s).reject(&:empty?).map do |line|
174
+ Ethon::Easy::Util.validate_header_line(:headers, line)
175
+ end
176
+ end
177
+
178
+ def call(function, *args)
179
+ @mime.check_pid!
180
+ code = Curl.public_send(function, @handle, *args)
181
+ raise Errors::Mime.new(code) unless code == :ok
182
+
183
+ code
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module Ethon
3
+ module Curls
4
+ # Attach libcurl functions that may not exist in the loaded library.
5
+ module Optional
6
+ # Attach a function, ignoring builds that do not export it.
7
+ def self.attach(base, name, func, args, returns, options = {})
8
+ base.attach_function(name, func, args, returns, options)
9
+ rescue ::FFI::NotFoundError
10
+ nil
11
+ end
12
+ end
13
+ end
14
+ end