patron 0.13.4 → 1.0.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 +97 -0
- data/README.md +113 -50
- data/ext/patron/extconf.rb +55 -20
- data/ext/patron/session_ext.c +1092 -761
- data/lib/patron/compat.rb +57 -0
- data/lib/patron/error.rb +25 -13
- data/lib/patron/header_parser.rb +27 -25
- data/lib/patron/request.rb +98 -142
- data/lib/patron/response.rb +33 -24
- data/lib/patron/response_decoding.rb +60 -67
- data/lib/patron/session.rb +127 -104
- data/lib/patron/util.rb +14 -12
- data/lib/patron/version.rb +3 -1
- data/lib/patron.rb +9 -15
- metadata +12 -49
- data/.autotest +0 -15
- data/.github/workflows/ci.yml +0 -26
- data/.gitignore +0 -10
- data/.rspec +0 -0
- data/.travis.yml +0 -13
- data/.yardopts +0 -1
- data/Gemfile +0 -17
- data/Rakefile +0 -31
- data/ext/patron/.gitignore +0 -4
- data/ext/patron/membuffer.c +0 -85
- data/ext/patron/membuffer.h +0 -78
- data/ext/patron/sglib.h +0 -1952
- data/lib/patron/proxy_type.rb +0 -11
- data/patron.gemspec +0 -38
- data/pic.png +0 -0
- data/script/console +0 -11
- data/script/test_server +0 -7
- data/spec/header_parser_spec.rb +0 -101
- data/spec/patron_spec.rb +0 -28
- data/spec/request_spec.rb +0 -77
- data/spec/response_spec.rb +0 -145
- data/spec/sample_response_headers/headers_wetransfer.txt +0 -18
- data/spec/sample_response_headers/headers_wetransfer_with_proxy_status.txt +0 -20
- data/spec/sample_response_headers/headers_wetransfer_with_redirect.txt +0 -24
- data/spec/sample_response_headers/headers_with_set_cookie.txt +0 -9
- data/spec/sample_response_headers/sample_http2_header.txt +0 -16
- data/spec/sample_response_headers/webmock_headers_without_trailing_crlf.txt +0 -15
- data/spec/session_spec.rb +0 -698
- data/spec/session_ssl_spec.rb +0 -313
- data/spec/spec_helper.rb +0 -34
- data/spec/support/certs/cacert.pem +0 -36
- data/spec/support/certs/privkey.pem +0 -51
- data/spec/support/config.ru +0 -146
- data/spec/support/fork.rb +0 -70
- data/spec/support/test_server.rb +0 -26
- data/spec/util_spec.rb +0 -71
data/ext/patron/session_ext.c
CHANGED
|
@@ -1,960 +1,1268 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Patron: libcurl bindings for Ruby.
|
|
3
|
+
*
|
|
4
|
+
* One Patron::Session owns one CURLM (multi handle), one template easy handle that
|
|
5
|
+
* every request is duplicated from, and one CURLSH that carries cookies, DNS entries
|
|
6
|
+
* and TLS sessions across requests. The multi handle owns the connection cache, which
|
|
7
|
+
* is what gives a Session connection reuse between requests.
|
|
8
|
+
*
|
|
9
|
+
* A request is driven by curl_multi_perform() while holding the GVL, so every libcurl
|
|
10
|
+
* callback (write, header, progress, debug) is ordinary Ruby-safe code, and by
|
|
11
|
+
* curl_multi_poll() with the GVL released. Ruby interrupts the poll through the
|
|
12
|
+
* unblocking function, which calls curl_multi_wakeup(); that is what makes Thread#kill,
|
|
13
|
+
* Thread#raise, Timeout and interpreter exit return promptly from any phase of a
|
|
14
|
+
* transfer, including DNS and connect.
|
|
15
|
+
*
|
|
16
|
+
* Requires Ruby >= 3.3 and libcurl >= 7.81.0.
|
|
17
|
+
*/
|
|
1
18
|
#include <ruby.h>
|
|
2
19
|
#include <ruby/thread.h>
|
|
20
|
+
#include <ruby/ractor.h>
|
|
21
|
+
#include <ruby/encoding.h>
|
|
22
|
+
#include <ruby/io.h>
|
|
23
|
+
#include <ruby/fiber/scheduler.h>
|
|
24
|
+
|
|
25
|
+
#include <stdio.h>
|
|
26
|
+
#include <string.h>
|
|
27
|
+
#include <signal.h>
|
|
28
|
+
#include <unistd.h>
|
|
3
29
|
#include <sys/stat.h>
|
|
30
|
+
#include <sys/select.h>
|
|
31
|
+
#include <pthread.h>
|
|
32
|
+
|
|
4
33
|
#include <curl/curl.h>
|
|
5
|
-
#include "membuffer.h"
|
|
6
|
-
#include "sglib.h" /* Simple Generic Library -> http://sglib.sourceforge.net */
|
|
7
34
|
|
|
8
|
-
#
|
|
35
|
+
#if LIBCURL_VERSION_NUM < 0x075100
|
|
36
|
+
#error "Patron requires libcurl 7.81.0 or newer"
|
|
37
|
+
#endif
|
|
38
|
+
|
|
9
39
|
#define INTERRUPT_ABORT 1
|
|
10
40
|
#define INTERRUPT_DOWNLOAD_OVERFLOW 2
|
|
11
41
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
static VALUE
|
|
20
|
-
static VALUE
|
|
21
|
-
static VALUE
|
|
22
|
-
static VALUE
|
|
23
|
-
static VALUE
|
|
24
|
-
static VALUE
|
|
25
|
-
static VALUE
|
|
26
|
-
static VALUE
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
/* How long one curl_multi_poll() may sleep. libcurl wakes us earlier on socket activity,
|
|
43
|
+
* on its own timers, and on curl_multi_wakeup(); this is only the upper bound. */
|
|
44
|
+
#define POLL_TIMEOUT_MS 1000
|
|
45
|
+
|
|
46
|
+
#define BODY_BUFFER_INITIAL_CAPACITY (16 * 1024)
|
|
47
|
+
#define HEADER_BUFFER_INITIAL_CAPACITY 1024
|
|
48
|
+
|
|
49
|
+
static VALUE mPatron;
|
|
50
|
+
static VALUE mProxyType;
|
|
51
|
+
static VALUE cSession;
|
|
52
|
+
static VALUE cRequest;
|
|
53
|
+
static VALUE ePatronError;
|
|
54
|
+
static VALUE eUnsupportedProtocol;
|
|
55
|
+
static VALUE eUnsupportedSSLVersion;
|
|
56
|
+
static VALUE eUnsupportedHTTPVersion;
|
|
57
|
+
static VALUE eURLFormatError;
|
|
58
|
+
static VALUE eHostResolutionError;
|
|
59
|
+
static VALUE eConnectionFailed;
|
|
60
|
+
static VALUE ePartialFileError;
|
|
61
|
+
static VALUE eTimeoutError;
|
|
62
|
+
static VALUE eTooManyRedirects;
|
|
63
|
+
static VALUE eAborted;
|
|
64
|
+
static VALUE eDownloadLimitExceeded;
|
|
65
|
+
static VALUE eClientCertificateError;
|
|
66
|
+
|
|
67
|
+
/* Frozen, Ractor-shareable lookup tables exposed as Patron::Request::SSL_VERSIONS and
|
|
68
|
+
* Patron::Request::HTTP_VERSIONS: version name => libcurl constant. */
|
|
69
|
+
static VALUE hSSLVersions;
|
|
70
|
+
static VALUE hHTTPVersions;
|
|
71
|
+
|
|
72
|
+
#define REQUEST_ATTRIBUTES(X) \
|
|
73
|
+
X(action) X(action_name) X(headers) X(url) X(timeout) X(connect_timeout) \
|
|
74
|
+
X(dns_cache_timeout) X(low_speed_time) X(low_speed_limit) X(max_redirects) \
|
|
75
|
+
X(proxy) X(proxy_type) X(credentials) X(auth_type) X(ignore_content_length) \
|
|
76
|
+
X(insecure) X(ssl_version) X(http_version) X(ssl_cert) X(ssl_cert_type) \
|
|
77
|
+
X(ssl_key) X(ssl_key_password) X(cacert) X(buffer_size) X(download_byte_limit) \
|
|
78
|
+
X(progress_callback) X(automatic_content_encoding) X(force_ipv4) \
|
|
79
|
+
X(upload_data) X(file_name) X(multipart)
|
|
80
|
+
|
|
81
|
+
#define DECLARE_ID(name) static ID id_##name;
|
|
82
|
+
REQUEST_ATTRIBUTES(DECLARE_ID)
|
|
83
|
+
#undef DECLARE_ID
|
|
84
|
+
|
|
85
|
+
static ID id_to_path, id_to_s, id_response_class, id_default_response_charset;
|
|
86
|
+
static ID id_get, id_post, id_put, id_patch, id_delete, id_head;
|
|
87
|
+
static ID id_ivar_curl_code;
|
|
88
|
+
static ID id_url_kw, id_status_kw, id_redirect_count_kw, id_header_data_kw, id_body_kw,
|
|
89
|
+
id_default_charset_kw, id_timings_kw;
|
|
90
|
+
|
|
91
|
+
struct patron_session {
|
|
92
|
+
/* Lives as long as the Session object */
|
|
93
|
+
VALUE self; /* back-pointer for write barriers, never marked */
|
|
94
|
+
CURLM *multi;
|
|
95
|
+
CURL *base; /* template; every request is a curl_easy_duphandle() of it */
|
|
96
|
+
CURLSH *share;
|
|
97
|
+
pthread_mutex_t share_locks[CURL_LOCK_DATA_LAST];
|
|
98
|
+
pid_t pid; /* handles must not be reused across fork() */
|
|
99
|
+
VALUE debug_io; /* IO receiving CURLOPT_VERBOSE output, or nil */
|
|
100
|
+
VALUE socket_ios; /* fd => IO wrapping one of libcurl's sockets, for the Fiber scheduler */
|
|
101
|
+
|
|
102
|
+
/* Per request, torn down in cleanup() */
|
|
103
|
+
CURL *easy;
|
|
104
|
+
curl_mime *mime;
|
|
105
|
+
struct curl_slist *headers;
|
|
106
|
+
FILE *download_file;
|
|
107
|
+
FILE *request_body_file;
|
|
36
108
|
char error_buf[CURL_ERROR_SIZE];
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
109
|
+
curl_off_t download_byte_limit;
|
|
110
|
+
VALUE progress_proc;
|
|
111
|
+
VALUE upload_data; /* pinned while libcurl reads it through CURLOPT_POSTFIELDS */
|
|
112
|
+
VALUE header_buf; /* binary String receiving the raw headers */
|
|
113
|
+
VALUE body_buf; /* binary String receiving the body, or nil when downloading to a file */
|
|
114
|
+
|
|
115
|
+
/* A callback may not let a Ruby exception unwind through libcurl's frames. The tag and
|
|
116
|
+
* errinfo are parked here, the transfer is aborted, and perform() re-raises afterwards. */
|
|
117
|
+
int pending_tag;
|
|
118
|
+
VALUE pending_errinfo;
|
|
119
|
+
|
|
120
|
+
volatile sig_atomic_t interrupt;
|
|
121
|
+
volatile sig_atomic_t busy;
|
|
49
122
|
};
|
|
50
123
|
|
|
51
|
-
|
|
52
124
|
/*----------------------------------------------------------------------------*/
|
|
53
|
-
/*
|
|
125
|
+
/* TypedData */
|
|
126
|
+
|
|
127
|
+
static void session_free(void *ptr) {
|
|
128
|
+
struct patron_session *s = ptr;
|
|
129
|
+
int i;
|
|
130
|
+
|
|
131
|
+
/* A transfer cannot be in flight here: perform() keeps self on the stack. */
|
|
132
|
+
if (s->multi) curl_multi_cleanup(s->multi);
|
|
133
|
+
if (s->base) curl_easy_cleanup(s->base);
|
|
134
|
+
if (s->share) curl_share_cleanup(s->share);
|
|
135
|
+
for (i = 0; i < CURL_LOCK_DATA_LAST; i++) pthread_mutex_destroy(&s->share_locks[i]);
|
|
136
|
+
ruby_xfree(s);
|
|
137
|
+
}
|
|
54
138
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
139
|
+
static void session_mark(void *ptr) {
|
|
140
|
+
struct patron_session *s = ptr;
|
|
141
|
+
|
|
142
|
+
rb_gc_mark_movable(s->debug_io);
|
|
143
|
+
rb_gc_mark_movable(s->socket_ios);
|
|
144
|
+
rb_gc_mark_movable(s->progress_proc);
|
|
145
|
+
rb_gc_mark_movable(s->header_buf);
|
|
146
|
+
rb_gc_mark_movable(s->body_buf);
|
|
147
|
+
rb_gc_mark_movable(s->pending_errinfo);
|
|
148
|
+
/* libcurl holds RSTRING_PTR() of this one for the whole transfer, and an embedded
|
|
149
|
+
* String keeps its bytes inside the object slot, so it must not be moved. */
|
|
150
|
+
rb_gc_mark(s->upload_data);
|
|
151
|
+
}
|
|
58
152
|
|
|
59
|
-
|
|
60
|
-
|
|
153
|
+
static void session_compact(void *ptr) {
|
|
154
|
+
struct patron_session *s = ptr;
|
|
61
155
|
|
|
62
|
-
|
|
63
|
-
|
|
156
|
+
s->debug_io = rb_gc_location(s->debug_io);
|
|
157
|
+
s->socket_ios = rb_gc_location(s->socket_ios);
|
|
158
|
+
s->progress_proc = rb_gc_location(s->progress_proc);
|
|
159
|
+
s->header_buf = rb_gc_location(s->header_buf);
|
|
160
|
+
s->body_buf = rb_gc_location(s->body_buf);
|
|
161
|
+
s->pending_errinfo = rb_gc_location(s->pending_errinfo);
|
|
64
162
|
}
|
|
65
163
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
164
|
+
static size_t session_memsize(const void *ptr) {
|
|
165
|
+
/* The Strings account for themselves; libcurl's own allocations are not visible. */
|
|
166
|
+
return sizeof(struct patron_session);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/* RUBY_TYPED_EMBEDDABLE is deliberately not used: libcurl keeps pointers into this struct
|
|
170
|
+
* (CURLOPT_XFERINFODATA, CURLOPT_ERRORBUFFER, CURLOPT_DEBUGDATA, the write/header userdata)
|
|
171
|
+
* and an embedded struct moves under compaction. */
|
|
172
|
+
static const rb_data_type_t patron_session_data_type = {
|
|
173
|
+
.wrap_struct_name = "Patron::Session",
|
|
174
|
+
.function = {
|
|
175
|
+
.dmark = session_mark,
|
|
176
|
+
.dfree = session_free,
|
|
177
|
+
.dsize = session_memsize,
|
|
178
|
+
.dcompact = session_compact,
|
|
179
|
+
},
|
|
180
|
+
.flags = RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_FREE_IMMEDIATELY
|
|
181
|
+
#ifdef RUBY_TYPED_THREAD_SAFE_FREE
|
|
182
|
+
/* dfree only touches handles owned by this object */
|
|
183
|
+
| RUBY_TYPED_THREAD_SAFE_FREE
|
|
184
|
+
#endif
|
|
185
|
+
,
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/* Every entry point refreshes the back-pointer: the Session object itself may have been
|
|
189
|
+
* moved by compaction since the last call. Within a call it is on the C stack and pinned,
|
|
190
|
+
* which is what makes `s->self` safe to use from the libcurl callbacks. */
|
|
191
|
+
static struct patron_session *get_session(VALUE self) {
|
|
192
|
+
struct patron_session *s;
|
|
193
|
+
TypedData_Get_Struct(self, struct patron_session, &patron_session_data_type, s);
|
|
194
|
+
s->self = self;
|
|
195
|
+
return s;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#define SESSION_WRITE(s, field, value) RB_OBJ_WRITE((s)->self, &(s)->field, (value))
|
|
199
|
+
|
|
200
|
+
/*----------------------------------------------------------------------------*/
|
|
201
|
+
/* Errors */
|
|
202
|
+
|
|
203
|
+
static VALUE error_class_for(CURLcode code) {
|
|
204
|
+
switch (code) {
|
|
205
|
+
case CURLE_UNSUPPORTED_PROTOCOL: return eUnsupportedProtocol;
|
|
206
|
+
case CURLE_URL_MALFORMAT: return eURLFormatError;
|
|
207
|
+
case CURLE_COULDNT_RESOLVE_HOST: return eHostResolutionError;
|
|
208
|
+
case CURLE_COULDNT_CONNECT: return eConnectionFailed;
|
|
209
|
+
case CURLE_PARTIAL_FILE: return ePartialFileError;
|
|
210
|
+
case CURLE_OPERATION_TIMEDOUT: return eTimeoutError;
|
|
211
|
+
case CURLE_TOO_MANY_REDIRECTS: return eTooManyRedirects;
|
|
212
|
+
case CURLE_ABORTED_BY_CALLBACK: return eAborted;
|
|
213
|
+
case CURLE_SSL_CERTPROBLEM: return eClientCertificateError;
|
|
214
|
+
default: return ePatronError;
|
|
74
215
|
}
|
|
75
216
|
}
|
|
76
217
|
|
|
77
|
-
static
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
LONG2NUM(state->ulnow));
|
|
86
|
-
return 0;
|
|
218
|
+
NORETURN(static void raise_curl_error(VALUE klass, CURLcode code, const char *message));
|
|
219
|
+
static void raise_curl_error(VALUE klass, CURLcode code, const char *message) {
|
|
220
|
+
VALUE exc;
|
|
221
|
+
|
|
222
|
+
if (!message || !*message) message = curl_easy_strerror(code);
|
|
223
|
+
exc = rb_exc_new_cstr(klass, message);
|
|
224
|
+
rb_ivar_set(exc, id_ivar_curl_code, INT2NUM((int)code));
|
|
225
|
+
rb_exc_raise(exc);
|
|
87
226
|
}
|
|
88
227
|
|
|
228
|
+
/* curl_easy_setopt() only fails for options the installed libcurl cannot honour; that must
|
|
229
|
+
* surface instead of silently running the request with defaults. */
|
|
230
|
+
#define setopt_or_raise(handle, option, value) do { \
|
|
231
|
+
CURLcode setopt_rc_ = curl_easy_setopt((handle), (option), (value)); \
|
|
232
|
+
if (setopt_rc_ != CURLE_OK) raise_curl_error(ePatronError, setopt_rc_, NULL); \
|
|
233
|
+
} while (0)
|
|
89
234
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
* user calls the "interrupt" method on the session or when the Ruby interpreter
|
|
93
|
-
* is attempting to exit.
|
|
94
|
-
*/
|
|
95
|
-
static int session_progress_handler(void* clientp, size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) {
|
|
96
|
-
struct patron_curl_state* state = (struct patron_curl_state*) clientp;
|
|
97
|
-
state->dltotal = dltotal;
|
|
98
|
-
state->dlnow = dlnow;
|
|
99
|
-
state->ultotal = ultotal;
|
|
100
|
-
state->ulnow = ulnow;
|
|
235
|
+
/*----------------------------------------------------------------------------*/
|
|
236
|
+
/* Deferred exceptions from inside libcurl callbacks */
|
|
101
237
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
238
|
+
static void park_exception(struct patron_session *s, int tag) {
|
|
239
|
+
if (s->pending_tag) return;
|
|
240
|
+
s->pending_tag = tag;
|
|
241
|
+
SESSION_WRITE(s, pending_errinfo, rb_errinfo());
|
|
242
|
+
s->interrupt = INTERRUPT_ABORT;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/* A raise can be re-raised as such. A throw or a Thread#kill leaves a non-exception in
|
|
246
|
+
* errinfo, which rb_set_errinfo refuses, so those jump by tag and rely on errinfo being
|
|
247
|
+
* untouched since the callback parked it (nothing in between rescues). */
|
|
248
|
+
NORETURN(static void reraise_parked(struct patron_session *s));
|
|
249
|
+
static void reraise_parked(struct patron_session *s) {
|
|
250
|
+
int tag = s->pending_tag;
|
|
251
|
+
VALUE errinfo = s->pending_errinfo;
|
|
252
|
+
|
|
253
|
+
s->pending_tag = 0;
|
|
254
|
+
SESSION_WRITE(s, pending_errinfo, Qnil);
|
|
255
|
+
if (RB_TYPE_P(errinfo, T_OBJECT) && rb_obj_is_kind_of(errinfo, rb_eException)) rb_exc_raise(errinfo);
|
|
256
|
+
rb_jump_tag(tag);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/*----------------------------------------------------------------------------*/
|
|
260
|
+
/* libcurl callbacks. All of them run with the GVL held, from curl_multi_perform(). */
|
|
261
|
+
|
|
262
|
+
struct append_args {
|
|
263
|
+
VALUE str;
|
|
264
|
+
const char *ptr;
|
|
265
|
+
size_t len;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
static VALUE append_protected(VALUE arg) {
|
|
269
|
+
struct append_args *a = (struct append_args *)arg;
|
|
270
|
+
rb_str_cat(a->str, a->ptr, (long)a->len);
|
|
271
|
+
return Qnil;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
static size_t append_to_string(struct patron_session *s, VALUE str, const char *ptr, size_t len) {
|
|
275
|
+
struct append_args args = { str, ptr, len };
|
|
276
|
+
int tag = 0;
|
|
277
|
+
|
|
278
|
+
rb_protect(append_protected, (VALUE)&args, &tag);
|
|
279
|
+
if (tag) {
|
|
280
|
+
park_exception(s, tag);
|
|
281
|
+
return 0;
|
|
107
282
|
}
|
|
283
|
+
return len;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
static size_t body_write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
287
|
+
struct patron_session *s = userdata;
|
|
288
|
+
size_t len = size * nmemb;
|
|
108
289
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
290
|
+
if (s->download_file) {
|
|
291
|
+
if (fwrite(ptr, 1, len, s->download_file) != len) return 0;
|
|
292
|
+
return len;
|
|
112
293
|
}
|
|
113
|
-
|
|
114
|
-
// If the interrupt value is anything except 0, the perform() call
|
|
115
|
-
// will be aborted by libCURL.
|
|
116
|
-
// Note however that some older versions of libcurl have a bug which
|
|
117
|
-
// makes the return value of this callback be ignored, and the request
|
|
118
|
-
// will actually proceed undeterred.
|
|
119
|
-
// See https://sourceforge.net/p/curl/bugs/1318/
|
|
120
|
-
return state->interrupt;
|
|
294
|
+
return append_to_string(s, s->body_buf, ptr, len);
|
|
121
295
|
}
|
|
122
296
|
|
|
297
|
+
static size_t header_write_cb(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
|
298
|
+
struct patron_session *s = userdata;
|
|
299
|
+
return append_to_string(s, s->header_buf, ptr, size * nmemb);
|
|
300
|
+
}
|
|
123
301
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
struct
|
|
302
|
+
static size_t file_read_cb(char *buffer, size_t size, size_t nitems, void *userdata) {
|
|
303
|
+
FILE *fp = userdata;
|
|
304
|
+
return fread(buffer, 1, size * nitems, fp);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
struct progress_args {
|
|
308
|
+
struct patron_session *s;
|
|
309
|
+
curl_off_t dltotal, dlnow, ultotal, ulnow;
|
|
131
310
|
};
|
|
132
311
|
|
|
133
|
-
|
|
312
|
+
static VALUE progress_protected(VALUE arg) {
|
|
313
|
+
struct progress_args *a = (struct progress_args *)arg;
|
|
314
|
+
VALUE argv[4] = { LL2NUM(a->dltotal), LL2NUM(a->dlnow), LL2NUM(a->ultotal), LL2NUM(a->ulnow) };
|
|
315
|
+
return rb_proc_call_with_block(a->s->progress_proc, 4, argv, Qnil);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/* Non-zero return aborts the transfer with CURLE_ABORTED_BY_CALLBACK. */
|
|
319
|
+
static int xferinfo_cb(void *userdata, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
|
|
320
|
+
struct patron_session *s = userdata;
|
|
134
321
|
|
|
135
|
-
|
|
322
|
+
if (s->interrupt) return 1;
|
|
136
323
|
|
|
137
|
-
|
|
138
|
-
|
|
324
|
+
if (s->download_byte_limit > 0 && (dltotal > s->download_byte_limit || dlnow > s->download_byte_limit)) {
|
|
325
|
+
s->interrupt = INTERRUPT_DOWNLOAD_OVERFLOW;
|
|
326
|
+
return 1;
|
|
327
|
+
}
|
|
139
328
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
329
|
+
if (!NIL_P(s->progress_proc)) {
|
|
330
|
+
struct progress_args args = { s, dltotal, dlnow, ultotal, ulnow };
|
|
331
|
+
int tag = 0;
|
|
332
|
+
VALUE result = rb_protect(progress_protected, (VALUE)&args, &tag);
|
|
144
333
|
|
|
145
|
-
|
|
334
|
+
if (tag) {
|
|
335
|
+
park_exception(s, tag);
|
|
336
|
+
return 1;
|
|
337
|
+
}
|
|
338
|
+
/* Only an explicit false aborts; a callback that happens to end in nil keeps going. */
|
|
339
|
+
if (result == Qfalse) {
|
|
340
|
+
s->interrupt = INTERRUPT_ABORT;
|
|
341
|
+
return 1;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return 0;
|
|
146
345
|
}
|
|
147
346
|
|
|
148
|
-
|
|
149
|
-
|
|
347
|
+
struct debug_args {
|
|
348
|
+
VALUE io;
|
|
349
|
+
const char *prefix;
|
|
350
|
+
const char *data;
|
|
351
|
+
size_t len;
|
|
352
|
+
};
|
|
150
353
|
|
|
151
|
-
|
|
354
|
+
static VALUE debug_protected(VALUE arg) {
|
|
355
|
+
struct debug_args *a = (struct debug_args *)arg;
|
|
356
|
+
VALUE line = rb_str_buf_new((long)(a->len + 2));
|
|
357
|
+
rb_str_cat_cstr(line, a->prefix);
|
|
358
|
+
rb_str_cat(line, a->data, (long)a->len);
|
|
359
|
+
rb_io_write(a->io, line);
|
|
360
|
+
return Qnil;
|
|
361
|
+
}
|
|
152
362
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
363
|
+
/* Mirrors what libcurl itself prints with CURLOPT_VERBOSE: informational text and the
|
|
364
|
+
* headers in both directions, never the payload. */
|
|
365
|
+
static int debug_cb(CURL *handle, curl_infotype type, char *data, size_t len, void *userdata) {
|
|
366
|
+
struct patron_session *s = userdata;
|
|
367
|
+
const char *prefix;
|
|
368
|
+
struct debug_args args;
|
|
369
|
+
int tag = 0;
|
|
370
|
+
|
|
371
|
+
if (NIL_P(s->debug_io)) return 0;
|
|
372
|
+
|
|
373
|
+
switch (type) {
|
|
374
|
+
case CURLINFO_TEXT: prefix = "* "; break;
|
|
375
|
+
case CURLINFO_HEADER_IN: prefix = "< "; break;
|
|
376
|
+
case CURLINFO_HEADER_OUT: prefix = "> "; break;
|
|
377
|
+
default: return 0;
|
|
156
378
|
}
|
|
157
|
-
}
|
|
158
379
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
380
|
+
args.io = s->debug_io;
|
|
381
|
+
args.prefix = prefix;
|
|
382
|
+
args.data = data;
|
|
383
|
+
args.len = len;
|
|
384
|
+
rb_protect(debug_protected, (VALUE)&args, &tag);
|
|
385
|
+
if (tag) park_exception(s, tag);
|
|
386
|
+
return 0;
|
|
387
|
+
}
|
|
162
388
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
389
|
+
static void share_lock_cb(CURL *handle, curl_lock_data data, curl_lock_access access, void *userptr) {
|
|
390
|
+
struct patron_session *s = userptr;
|
|
391
|
+
pthread_mutex_lock(&s->share_locks[data]);
|
|
166
392
|
}
|
|
167
393
|
|
|
394
|
+
static void share_unlock_cb(CURL *handle, curl_lock_data data, void *userptr) {
|
|
395
|
+
struct patron_session *s = userptr;
|
|
396
|
+
pthread_mutex_unlock(&s->share_locks[data]);
|
|
397
|
+
}
|
|
168
398
|
|
|
169
399
|
/*----------------------------------------------------------------------------*/
|
|
170
|
-
/*
|
|
171
|
-
|
|
172
|
-
static void
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
curl_easy_setopt(
|
|
217
|
-
curl_easy_setopt(
|
|
218
|
-
curl_easy_setopt(state->base_handle, CURLOPT_WRITEDATA, &state->body_buffer);
|
|
219
|
-
curl_easy_setopt(state->base_handle, CURLOPT_HEADERFUNCTION, &session_write_handler);
|
|
220
|
-
curl_easy_setopt(state->base_handle, CURLOPT_HEADERDATA, &state->header_buffer);
|
|
221
|
-
curl_easy_setopt(state->base_handle, CURLOPT_NOSIGNAL, 1);
|
|
222
|
-
curl_easy_setopt(state->base_handle, CURLOPT_NOPROGRESS, 0);
|
|
223
|
-
#if LIBCURL_VERSION_NUM >= 0x072000
|
|
224
|
-
/* this is libCURLv7.32.0 or later, supports CURLOPT_XFERINFOFUNCTION */
|
|
225
|
-
curl_easy_setopt(state->base_handle, CURLOPT_XFERINFOFUNCTION, &session_progress_handler);
|
|
400
|
+
/* Handle lifecycle */
|
|
401
|
+
|
|
402
|
+
static void init_handles(struct patron_session *s) {
|
|
403
|
+
int i;
|
|
404
|
+
|
|
405
|
+
for (i = 0; i < CURL_LOCK_DATA_LAST; i++) pthread_mutex_init(&s->share_locks[i], NULL);
|
|
406
|
+
|
|
407
|
+
s->share = curl_share_init();
|
|
408
|
+
s->multi = curl_multi_init();
|
|
409
|
+
s->base = curl_easy_init();
|
|
410
|
+
if (!s->share || !s->multi || !s->base) {
|
|
411
|
+
if (s->base) curl_easy_cleanup(s->base);
|
|
412
|
+
if (s->multi) curl_multi_cleanup(s->multi);
|
|
413
|
+
if (s->share) curl_share_cleanup(s->share);
|
|
414
|
+
s->base = NULL; s->multi = NULL; s->share = NULL;
|
|
415
|
+
rb_memerror();
|
|
416
|
+
}
|
|
417
|
+
s->pid = getpid();
|
|
418
|
+
|
|
419
|
+
curl_share_setopt(s->share, CURLSHOPT_LOCKFUNC, share_lock_cb);
|
|
420
|
+
curl_share_setopt(s->share, CURLSHOPT_UNLOCKFUNC, share_unlock_cb);
|
|
421
|
+
curl_share_setopt(s->share, CURLSHOPT_USERDATA, s);
|
|
422
|
+
curl_share_setopt(s->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
|
|
423
|
+
curl_share_setopt(s->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
|
|
424
|
+
curl_share_setopt(s->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
|
|
425
|
+
curl_share_setopt(s->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
|
|
426
|
+
|
|
427
|
+
/* The multi handle owns the connection cache. Multiplexing costs nothing with one
|
|
428
|
+
* transfer at a time and is ready for concurrent transfers later. */
|
|
429
|
+
curl_multi_setopt(s->multi, CURLMOPT_PIPELINING, CURLPIPE_MULTIPLEX);
|
|
430
|
+
|
|
431
|
+
curl_easy_setopt(s->base, CURLOPT_SHARE, s->share);
|
|
432
|
+
curl_easy_setopt(s->base, CURLOPT_WRITEFUNCTION, body_write_cb);
|
|
433
|
+
curl_easy_setopt(s->base, CURLOPT_WRITEDATA, s);
|
|
434
|
+
curl_easy_setopt(s->base, CURLOPT_HEADERFUNCTION, header_write_cb);
|
|
435
|
+
curl_easy_setopt(s->base, CURLOPT_HEADERDATA, s);
|
|
436
|
+
curl_easy_setopt(s->base, CURLOPT_NOSIGNAL, 1L);
|
|
437
|
+
curl_easy_setopt(s->base, CURLOPT_NOPROGRESS, 0L);
|
|
438
|
+
curl_easy_setopt(s->base, CURLOPT_XFERINFOFUNCTION, xferinfo_cb);
|
|
439
|
+
curl_easy_setopt(s->base, CURLOPT_XFERINFODATA, s);
|
|
440
|
+
curl_easy_setopt(s->base, CURLOPT_DEBUGFUNCTION, debug_cb);
|
|
441
|
+
curl_easy_setopt(s->base, CURLOPT_DEBUGDATA, s);
|
|
442
|
+
curl_easy_setopt(s->base, CURLOPT_ERRORBUFFER, s->error_buf);
|
|
443
|
+
|
|
444
|
+
/* Never let a URL or a redirect take us to gopher, smtp, file and friends. */
|
|
445
|
+
#if LIBCURL_VERSION_NUM >= 0x075500
|
|
446
|
+
curl_easy_setopt(s->base, CURLOPT_PROTOCOLS_STR, "http,https");
|
|
447
|
+
curl_easy_setopt(s->base, CURLOPT_REDIR_PROTOCOLS_STR, "http,https");
|
|
226
448
|
#else
|
|
227
|
-
curl_easy_setopt(
|
|
228
|
-
|
|
229
|
-
curl_easy_setopt(state->base_handle, CURLOPT_PROGRESSDATA, state);
|
|
230
|
-
#ifdef CURLPROTO_HTTP
|
|
231
|
-
// Security: do not allow Curl to go looking on gopher/SMTP etc.
|
|
232
|
-
// Must prevent situations like this:
|
|
233
|
-
// https://hackerone.com/reports/115748
|
|
234
|
-
curl_easy_setopt(state->base_handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
235
|
-
curl_easy_setopt(state->base_handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
449
|
+
curl_easy_setopt(s->base, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
450
|
+
curl_easy_setopt(s->base, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
236
451
|
#endif
|
|
452
|
+
}
|
|
237
453
|
|
|
238
|
-
|
|
454
|
+
/* A forked child must not use connections it shares with the parent. The inherited
|
|
455
|
+
* handles are abandoned rather than cleaned up, since cleaning up would shut down
|
|
456
|
+
* TLS sessions on sockets the parent is still using. */
|
|
457
|
+
static void ensure_handles_for_this_process(struct patron_session *s) {
|
|
458
|
+
if (s->pid == getpid()) return;
|
|
459
|
+
s->multi = NULL;
|
|
460
|
+
s->base = NULL;
|
|
461
|
+
s->share = NULL;
|
|
462
|
+
init_handles(s);
|
|
463
|
+
/* Cookie jar and debug settings live on the base handle; the Ruby side re-applies them. */
|
|
464
|
+
rb_funcall(s->self, rb_intern("reapply_base_options"), 0);
|
|
239
465
|
}
|
|
240
466
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
struct
|
|
244
|
-
|
|
245
|
-
|
|
467
|
+
static VALUE session_alloc(VALUE klass) {
|
|
468
|
+
struct patron_session *s;
|
|
469
|
+
VALUE obj = TypedData_Make_Struct(klass, struct patron_session, &patron_session_data_type, s);
|
|
470
|
+
|
|
471
|
+
s->self = obj;
|
|
472
|
+
s->debug_io = Qnil;
|
|
473
|
+
s->socket_ios = Qnil;
|
|
474
|
+
s->progress_proc = Qnil;
|
|
475
|
+
s->upload_data = Qnil;
|
|
476
|
+
s->header_buf = Qnil;
|
|
477
|
+
s->body_buf = Qnil;
|
|
478
|
+
s->pending_errinfo = Qnil;
|
|
479
|
+
init_handles(s);
|
|
480
|
+
return obj;
|
|
246
481
|
}
|
|
247
482
|
|
|
248
483
|
/*----------------------------------------------------------------------------*/
|
|
249
|
-
/*
|
|
484
|
+
/* Module functions */
|
|
250
485
|
|
|
251
486
|
/*
|
|
252
|
-
* Returns the version of the
|
|
253
|
-
*
|
|
254
|
-
*
|
|
487
|
+
* Returns the version string of the libcurl in use, e.g. "libcurl/8.7.1 OpenSSL/3.3.0".
|
|
488
|
+
*
|
|
489
|
+
* @return [String]
|
|
255
490
|
*/
|
|
256
491
|
static VALUE libcurl_version(VALUE klass) {
|
|
257
|
-
|
|
258
|
-
UNUSED_ARGUMENT(klass);
|
|
259
|
-
return rb_str_new2(value);
|
|
492
|
+
return rb_str_new_cstr(curl_version());
|
|
260
493
|
}
|
|
261
494
|
|
|
262
495
|
/*
|
|
263
|
-
* Returns the version of the
|
|
264
|
-
*
|
|
265
|
-
*
|
|
496
|
+
* Returns the version of the libcurl Patron was compiled against.
|
|
497
|
+
*
|
|
498
|
+
* @return [Array<Integer>] major, minor and patch
|
|
266
499
|
*/
|
|
267
500
|
static VALUE libcurl_version_exact(VALUE klass) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
501
|
+
return rb_ary_new_from_args(3, INT2FIX(LIBCURL_VERSION_MAJOR), INT2FIX(LIBCURL_VERSION_MINOR), INT2FIX(LIBCURL_VERSION_PATCH));
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/*
|
|
505
|
+
* Describes the libcurl in use at runtime.
|
|
506
|
+
*
|
|
507
|
+
* @return [Hash] `:version`, `:ssl_version`, `:libz_version` Strings (the latter two may be nil)
|
|
508
|
+
* and `:features`, an Array of Symbols such as `:ssl`, `:http2`, `:http3`, `:asynchdns`, `:threadsafe`
|
|
509
|
+
*/
|
|
510
|
+
static VALUE libcurl_features(VALUE klass) {
|
|
511
|
+
curl_version_info_data *info = curl_version_info(CURLVERSION_NOW);
|
|
512
|
+
VALUE features = rb_ary_new();
|
|
513
|
+
VALUE result = rb_hash_new();
|
|
514
|
+
|
|
515
|
+
#define FEATURE(flag, name) if (info->features & (flag)) rb_ary_push(features, ID2SYM(rb_intern(name)))
|
|
516
|
+
FEATURE(CURL_VERSION_SSL, "ssl");
|
|
517
|
+
FEATURE(CURL_VERSION_IPV6, "ipv6");
|
|
518
|
+
FEATURE(CURL_VERSION_LIBZ, "libz");
|
|
519
|
+
FEATURE(CURL_VERSION_ASYNCHDNS, "asynchdns");
|
|
520
|
+
FEATURE(CURL_VERSION_HTTP2, "http2");
|
|
521
|
+
FEATURE(CURL_VERSION_HTTP3, "http3");
|
|
522
|
+
FEATURE(CURL_VERSION_BROTLI, "brotli");
|
|
523
|
+
FEATURE(CURL_VERSION_UNIX_SOCKETS, "unix_sockets");
|
|
524
|
+
#ifdef CURL_VERSION_ZSTD
|
|
525
|
+
FEATURE(CURL_VERSION_ZSTD, "zstd");
|
|
526
|
+
#endif
|
|
527
|
+
#ifdef CURL_VERSION_THREADSAFE
|
|
528
|
+
FEATURE(CURL_VERSION_THREADSAFE, "threadsafe");
|
|
529
|
+
#endif
|
|
530
|
+
#undef FEATURE
|
|
531
|
+
|
|
532
|
+
rb_hash_aset(result, ID2SYM(rb_intern("version")), rb_str_new_cstr(info->version));
|
|
533
|
+
rb_hash_aset(result, ID2SYM(rb_intern("ssl_version")), info->ssl_version ? rb_str_new_cstr(info->ssl_version) : Qnil);
|
|
534
|
+
rb_hash_aset(result, ID2SYM(rb_intern("libz_version")), info->libz_version ? rb_str_new_cstr(info->libz_version) : Qnil);
|
|
535
|
+
rb_hash_aset(result, ID2SYM(rb_intern("features")), features);
|
|
536
|
+
return result;
|
|
274
537
|
}
|
|
275
538
|
|
|
276
539
|
/*
|
|
277
|
-
*
|
|
540
|
+
* URL-escapes a String using libcurl.
|
|
278
541
|
*
|
|
279
|
-
* @param [String]
|
|
280
|
-
*
|
|
542
|
+
* @param value [String]
|
|
543
|
+
* @return [String] the escaped string
|
|
281
544
|
*/
|
|
282
545
|
static VALUE session_escape(VALUE self, VALUE value) {
|
|
283
|
-
|
|
284
546
|
VALUE string = StringValue(value);
|
|
285
|
-
char*
|
|
286
|
-
VALUE
|
|
287
|
-
|
|
288
|
-
#if LIBCURL_VERSION_NUM >= 0x075200
|
|
289
|
-
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
|
290
|
-
CURL *curl = NULL;
|
|
291
|
-
#else
|
|
292
|
-
CURL* curl = curl_easy_init();
|
|
293
|
-
#endif
|
|
294
|
-
escaped = curl_easy_escape(curl,
|
|
295
|
-
RSTRING_PTR(string),
|
|
296
|
-
(int) RSTRING_LEN(string));
|
|
547
|
+
char *escaped = curl_easy_escape(NULL, RSTRING_PTR(string), rb_long2int(RSTRING_LEN(string)));
|
|
548
|
+
VALUE result;
|
|
297
549
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
|
301
|
-
#else
|
|
302
|
-
curl_easy_cleanup(curl);
|
|
303
|
-
#endif
|
|
550
|
+
if (!escaped) rb_raise(ePatronError, "libcurl could not escape the string");
|
|
551
|
+
result = rb_usascii_str_new_cstr(escaped);
|
|
304
552
|
curl_free(escaped);
|
|
305
|
-
|
|
306
|
-
return retval;
|
|
553
|
+
return result;
|
|
307
554
|
}
|
|
308
555
|
|
|
309
556
|
/*
|
|
310
|
-
*
|
|
557
|
+
* URL-unescapes a String using libcurl. The result is binary, since percent-escapes can
|
|
558
|
+
* encode arbitrary bytes.
|
|
311
559
|
*
|
|
312
|
-
* @param [String]
|
|
313
|
-
*
|
|
560
|
+
* @param value [String]
|
|
561
|
+
* @return [String] the unescaped string
|
|
314
562
|
*/
|
|
315
563
|
static VALUE session_unescape(VALUE self, VALUE value) {
|
|
316
564
|
VALUE string = StringValue(value);
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
#if LIBCURL_VERSION_NUM >= 0x075200
|
|
321
|
-
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
|
322
|
-
CURL *curl = NULL;
|
|
323
|
-
#else
|
|
324
|
-
CURL *curl = curl_easy_init();
|
|
325
|
-
#endif
|
|
326
|
-
unescaped = curl_easy_unescape(curl,
|
|
327
|
-
RSTRING_PTR(string),
|
|
328
|
-
(int) RSTRING_LEN(string),
|
|
329
|
-
NULL);
|
|
565
|
+
int outlen = 0;
|
|
566
|
+
char *unescaped = curl_easy_unescape(NULL, RSTRING_PTR(string), rb_long2int(RSTRING_LEN(string)), &outlen);
|
|
567
|
+
VALUE result;
|
|
330
568
|
|
|
331
|
-
|
|
569
|
+
if (!unescaped) rb_raise(ePatronError, "libcurl could not unescape the string");
|
|
570
|
+
result = rb_str_new(unescaped, outlen);
|
|
332
571
|
curl_free(unescaped);
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
#else
|
|
336
|
-
curl_easy_cleanup(curl);
|
|
337
|
-
#endif
|
|
572
|
+
return result;
|
|
573
|
+
}
|
|
338
574
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
/* Callback used to iterate over the HTTP headers and store them in an slist. */
|
|
343
|
-
static int each_http_header(VALUE header_key, VALUE header_value, VALUE self) {
|
|
344
|
-
struct patron_curl_state *state = get_patron_curl_state(self);
|
|
345
|
-
CURL* curl = state->handle;
|
|
346
|
-
|
|
347
|
-
VALUE name = rb_obj_as_string(header_key);
|
|
348
|
-
VALUE value = rb_obj_as_string(header_value);
|
|
349
|
-
VALUE header_str = Qnil;
|
|
350
|
-
|
|
351
|
-
// TODO: see how to combine this with automatic_content_encoding
|
|
352
|
-
if (rb_str_cmp(name, rb_str_new2("Accept-Encoding")) == 0) {
|
|
353
|
-
if (rb_funcall(value, rb_intern("include?"), 1, rb_str_new2("gzip"))) {
|
|
354
|
-
#ifdef CURLOPT_ACCEPT_ENCODING
|
|
355
|
-
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip");
|
|
356
|
-
#elif defined CURLOPT_ENCODING
|
|
357
|
-
curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
|
|
358
|
-
#else
|
|
359
|
-
rb_raise(rb_eArgError,
|
|
360
|
-
"The libcurl version installed doesn't support 'gzip'.");
|
|
361
|
-
#endif
|
|
362
|
-
}
|
|
363
|
-
}
|
|
575
|
+
/*----------------------------------------------------------------------------*/
|
|
576
|
+
/* Request configuration */
|
|
364
577
|
|
|
365
|
-
|
|
366
|
-
|
|
578
|
+
static VALUE request_attr(VALUE request, ID id) {
|
|
579
|
+
return rb_funcallv(request, id, 0, NULL);
|
|
580
|
+
}
|
|
367
581
|
|
|
368
|
-
|
|
582
|
+
static int header_name_is(VALUE name, const char *expected) {
|
|
583
|
+
long len = (long)strlen(expected);
|
|
584
|
+
long i;
|
|
585
|
+
const char *p;
|
|
369
586
|
|
|
370
|
-
return 0;
|
|
587
|
+
if (RSTRING_LEN(name) != len) return 0;
|
|
588
|
+
p = RSTRING_PTR(name);
|
|
589
|
+
for (i = 0; i < len; i++) {
|
|
590
|
+
if (rb_tolower((unsigned char)p[i]) != rb_tolower((unsigned char)expected[i])) return 0;
|
|
591
|
+
}
|
|
592
|
+
return 1;
|
|
371
593
|
}
|
|
372
594
|
|
|
373
|
-
static int
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
curl_formadd(&state->post, &state->last, CURLFORM_PTRNAME, RSTRING_PTR(name),
|
|
379
|
-
CURLFORM_PTRCONTENTS, RSTRING_PTR(value), CURLFORM_END);
|
|
595
|
+
static int string_contains(VALUE str, const char *needle) {
|
|
596
|
+
const char *haystack = RSTRING_PTR(str);
|
|
597
|
+
long len = RSTRING_LEN(str);
|
|
598
|
+
long needle_len = (long)strlen(needle);
|
|
599
|
+
long i;
|
|
380
600
|
|
|
601
|
+
for (i = 0; i + needle_len <= len; i++) {
|
|
602
|
+
if (memcmp(haystack + i, needle, (size_t)needle_len) == 0) return 1;
|
|
603
|
+
}
|
|
381
604
|
return 0;
|
|
382
605
|
}
|
|
383
606
|
|
|
384
|
-
static int
|
|
385
|
-
struct
|
|
386
|
-
VALUE name = rb_obj_as_string(
|
|
387
|
-
VALUE
|
|
607
|
+
static int each_http_header(VALUE key, VALUE value, VALUE self) {
|
|
608
|
+
struct patron_session *s = get_session(self);
|
|
609
|
+
VALUE name = rb_obj_as_string(key);
|
|
610
|
+
VALUE line;
|
|
388
611
|
|
|
389
|
-
|
|
390
|
-
|
|
612
|
+
/* "Accept-Encoding: gzip" also asks libcurl to inflate the response for us. */
|
|
613
|
+
if (header_name_is(name, "Accept-Encoding") && string_contains(rb_obj_as_string(value), "gzip")) {
|
|
614
|
+
setopt_or_raise(s->easy, CURLOPT_ACCEPT_ENCODING, "gzip");
|
|
615
|
+
}
|
|
391
616
|
|
|
392
|
-
|
|
617
|
+
line = rb_sprintf("%"PRIsVALUE": %"PRIsVALUE, name, value);
|
|
618
|
+
s->headers = curl_slist_append(s->headers, StringValueCStr(line));
|
|
619
|
+
return ST_CONTINUE;
|
|
393
620
|
}
|
|
394
621
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
622
|
+
static curl_mimepart *add_mime_part(struct patron_session *s, VALUE key) {
|
|
623
|
+
curl_mimepart *part;
|
|
624
|
+
VALUE name = rb_obj_as_string(key);
|
|
625
|
+
|
|
626
|
+
if (!s->mime) s->mime = curl_mime_init(s->easy);
|
|
627
|
+
part = curl_mime_addpart(s->mime);
|
|
628
|
+
if (!part) rb_memerror();
|
|
629
|
+
curl_mime_name(part, StringValueCStr(name));
|
|
630
|
+
return part;
|
|
403
631
|
}
|
|
404
632
|
|
|
405
|
-
static
|
|
406
|
-
|
|
633
|
+
static int each_mime_value(VALUE key, VALUE value, VALUE self) {
|
|
634
|
+
struct patron_session *s = get_session(self);
|
|
635
|
+
VALUE str = rb_obj_as_string(value);
|
|
636
|
+
curl_mimepart *part = add_mime_part(s, key);
|
|
637
|
+
|
|
638
|
+
/* curl_mime_data() copies, so the String may be collected right after this call. */
|
|
639
|
+
curl_mime_data(part, RSTRING_PTR(str), (size_t)RSTRING_LEN(str));
|
|
640
|
+
return ST_CONTINUE;
|
|
407
641
|
}
|
|
408
642
|
|
|
409
|
-
static
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
643
|
+
static int each_mime_file(VALUE key, VALUE value, VALUE self) {
|
|
644
|
+
struct patron_session *s = get_session(self);
|
|
645
|
+
VALUE path = rb_obj_as_string(value);
|
|
646
|
+
curl_mimepart *part = add_mime_part(s, key);
|
|
647
|
+
|
|
648
|
+
FilePathValue(path);
|
|
649
|
+
if (curl_mime_filedata(part, StringValueCStr(path)) != CURLE_OK) {
|
|
650
|
+
rb_raise(rb_eArgError, "Unable to read file for multipart upload: %"PRIsVALUE, path);
|
|
413
651
|
}
|
|
652
|
+
return ST_CONTINUE;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
static FILE *open_file(VALUE filename, const char *mode) {
|
|
656
|
+
FILE *handle;
|
|
414
657
|
|
|
658
|
+
FilePathValue(filename);
|
|
659
|
+
handle = fopen(StringValueCStr(filename), mode);
|
|
660
|
+
if (!handle) rb_sys_fail_str(filename);
|
|
415
661
|
return handle;
|
|
416
662
|
}
|
|
417
663
|
|
|
418
|
-
static void
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
#else
|
|
429
|
-
curl_easy_setopt(curl, CURLOPT_INFILESIZE, stat_info.st_size);
|
|
430
|
-
#endif
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
static long floating_rb_seconds_to_milliseconds(VALUE r_seconds) {
|
|
434
|
-
double seconds_f = NUM2DBL(r_seconds);
|
|
435
|
-
long millis = seconds_f * (double)1000.0;
|
|
436
|
-
return millis;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
static void set_request_body(struct patron_curl_state* state, VALUE stringable_or_file) {
|
|
440
|
-
CURL* curl = state->handle;
|
|
441
|
-
if(rb_respond_to(stringable_or_file, rb_intern("to_path"))) {
|
|
442
|
-
// Set up a file read callback (read the entire request body from a file).
|
|
443
|
-
// Instead of using the Ruby file reads, use #to_path to obtain the
|
|
444
|
-
// file path on the file system and open a file pointer to it
|
|
445
|
-
VALUE r_path_str = rb_funcall(stringable_or_file, rb_intern("to_path"), 0);
|
|
446
|
-
r_path_str = rb_funcall(r_path_str, rb_intern("to_s"), 0);
|
|
447
|
-
set_request_body_file(state, r_path_str);
|
|
448
|
-
} else {
|
|
449
|
-
// Set the request body from a String
|
|
450
|
-
VALUE data = rb_funcall(stringable_or_file, rb_intern("to_s"), 0);
|
|
451
|
-
long len = RSTRING_LEN(data);
|
|
452
|
-
state->upload_buf = StringValuePtr(data);
|
|
453
|
-
set_curl_request_body(curl, state->upload_buf, len);
|
|
454
|
-
}
|
|
664
|
+
static void set_request_body_from_file(struct patron_session *s, VALUE path) {
|
|
665
|
+
struct stat st;
|
|
666
|
+
|
|
667
|
+
s->request_body_file = open_file(path, "rb");
|
|
668
|
+
if (fstat(fileno(s->request_body_file), &st) != 0) rb_sys_fail_str(path);
|
|
669
|
+
|
|
670
|
+
setopt_or_raise(s->easy, CURLOPT_UPLOAD, 1L);
|
|
671
|
+
setopt_or_raise(s->easy, CURLOPT_READFUNCTION, file_read_cb);
|
|
672
|
+
setopt_or_raise(s->easy, CURLOPT_READDATA, s->request_body_file);
|
|
673
|
+
setopt_or_raise(s->easy, CURLOPT_INFILESIZE_LARGE, (curl_off_t)st.st_size);
|
|
455
674
|
}
|
|
456
675
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
static void set_options_from_request(VALUE self, VALUE request) {
|
|
462
|
-
struct patron_curl_state* state = get_patron_curl_state(self);
|
|
463
|
-
CURL* curl = curl_easy_duphandle(state->base_handle);
|
|
464
|
-
|
|
465
|
-
ID action = Qnil;
|
|
466
|
-
VALUE headers = Qnil;
|
|
467
|
-
VALUE url = Qnil;
|
|
468
|
-
VALUE timeout = Qnil;
|
|
469
|
-
VALUE redirects = Qnil;
|
|
470
|
-
VALUE proxy = Qnil;
|
|
471
|
-
VALUE proxy_type = Qnil;
|
|
472
|
-
VALUE credentials = Qnil;
|
|
473
|
-
VALUE ignore_content_length = Qnil;
|
|
474
|
-
VALUE insecure = Qnil;
|
|
475
|
-
VALUE cacert = Qnil;
|
|
476
|
-
VALUE ssl_version = Qnil;
|
|
477
|
-
VALUE http_version = Qnil;
|
|
478
|
-
VALUE buffer_size = Qnil;
|
|
479
|
-
VALUE action_name = rb_funcall(request, rb_intern("action"), 0);
|
|
480
|
-
VALUE a_c_encoding = rb_funcall(request, rb_intern("automatic_content_encoding"), 0);
|
|
481
|
-
VALUE download_byte_limit = rb_funcall(request, rb_intern("download_byte_limit"), 0);
|
|
482
|
-
VALUE maybe_progress_proc = rb_funcall(request, rb_intern("progress_callback"), 0);
|
|
483
|
-
|
|
484
|
-
state->handle = curl;
|
|
485
|
-
curl_easy_setopt(curl, CURLOPT_SHARE, state->share);
|
|
486
|
-
|
|
487
|
-
if (RTEST(download_byte_limit)) {
|
|
488
|
-
state->download_byte_limit = FIX2INT(download_byte_limit);
|
|
676
|
+
static void set_request_body(struct patron_session *s, VALUE stringable_or_file) {
|
|
677
|
+
if (rb_respond_to(stringable_or_file, id_to_path)) {
|
|
678
|
+
VALUE path = rb_funcallv(stringable_or_file, id_to_path, 0, NULL);
|
|
679
|
+
set_request_body_from_file(s, rb_obj_as_string(path));
|
|
489
680
|
} else {
|
|
490
|
-
|
|
681
|
+
VALUE data = rb_obj_as_string(stringable_or_file);
|
|
682
|
+
SESSION_WRITE(s, upload_data, data);
|
|
683
|
+
setopt_or_raise(s->easy, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)RSTRING_LEN(data));
|
|
684
|
+
setopt_or_raise(s->easy, CURLOPT_POSTFIELDS, RSTRING_PTR(data));
|
|
491
685
|
}
|
|
686
|
+
}
|
|
492
687
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
688
|
+
static long seconds_to_milliseconds(VALUE seconds) {
|
|
689
|
+
return (long)(NUM2DBL(seconds) * 1000.0);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
static long lookup_version(VALUE table, VALUE requested, VALUE error_class, const char *what) {
|
|
693
|
+
VALUE key = rb_obj_as_string(requested);
|
|
694
|
+
VALUE found = rb_hash_lookup2(table, key, Qundef);
|
|
498
695
|
|
|
499
|
-
|
|
696
|
+
if (found == Qundef) rb_raise(error_class, "Unsupported %s: %"PRIsVALUE, what, key);
|
|
697
|
+
return NUM2LONG(found);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
static void set_string_option(struct patron_session *s, CURLoption option, VALUE value, int is_path) {
|
|
701
|
+
VALUE str = is_path ? rb_funcallv(value, id_to_s, 0, NULL) : rb_obj_as_string(value);
|
|
702
|
+
if (is_path) FilePathValue(str);
|
|
703
|
+
setopt_or_raise(s->easy, option, StringValueCStr(str));
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
static void set_options_from_request(struct patron_session *s, VALUE request) {
|
|
707
|
+
VALUE self = s->self;
|
|
708
|
+
CURL *easy;
|
|
709
|
+
VALUE action_name, headers, url, value, upload_data, file_name;
|
|
710
|
+
ID action;
|
|
711
|
+
|
|
712
|
+
easy = curl_easy_duphandle(s->base);
|
|
713
|
+
if (!easy) rb_memerror();
|
|
714
|
+
/* Assigned before anything can raise so that cleanup() releases it. */
|
|
715
|
+
s->easy = easy;
|
|
716
|
+
/* duphandle() hands back a handle "as if CURLOPT_SHARE was unset" (its man page), so the
|
|
717
|
+
* share with the cookies and TLS sessions must be attached to every request handle anew. */
|
|
718
|
+
curl_easy_setopt(easy, CURLOPT_SHARE, s->share);
|
|
719
|
+
|
|
720
|
+
value = request_attr(request, id_download_byte_limit);
|
|
721
|
+
s->download_byte_limit = RTEST(value) ? NUM2OFFT(value) : 0;
|
|
722
|
+
|
|
723
|
+
value = request_attr(request, id_progress_callback);
|
|
724
|
+
SESSION_WRITE(s, progress_proc, rb_obj_is_proc(value) ? value : Qnil);
|
|
725
|
+
|
|
726
|
+
headers = request_attr(request, id_headers);
|
|
500
727
|
if (RTEST(headers)) {
|
|
501
|
-
|
|
502
|
-
rb_raise(rb_eArgError, "Headers must be passed in a hash.");
|
|
503
|
-
}
|
|
728
|
+
Check_Type(headers, T_HASH);
|
|
504
729
|
rb_hash_foreach(headers, each_http_header, self);
|
|
505
730
|
}
|
|
506
731
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
|
732
|
+
if (RTEST(request_attr(request, id_force_ipv4))) {
|
|
733
|
+
setopt_or_raise(easy, CURLOPT_IPRESOLVE, (long)CURL_IPRESOLVE_V4);
|
|
510
734
|
}
|
|
511
|
-
if (action == rb_intern("get")) {
|
|
512
|
-
VALUE data = rb_funcall(request, rb_intern("upload_data"), 0);
|
|
513
|
-
VALUE download_file = rb_funcall(request, rb_intern("file_name"), 0);
|
|
514
735
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
// to avoid issues on Windows
|
|
526
|
-
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &file_write_handler);
|
|
527
|
-
} else {
|
|
528
|
-
state->download_file = NULL;
|
|
736
|
+
action_name = request_attr(request, id_action);
|
|
737
|
+
action = SYM2ID(action_name);
|
|
738
|
+
upload_data = request_attr(request, id_upload_data);
|
|
739
|
+
file_name = request_attr(request, id_file_name);
|
|
740
|
+
|
|
741
|
+
if (action == id_get) {
|
|
742
|
+
setopt_or_raise(easy, CURLOPT_HTTPGET, 1L);
|
|
743
|
+
if (RTEST(upload_data)) {
|
|
744
|
+
set_request_body(s, upload_data);
|
|
745
|
+
setopt_or_raise(easy, CURLOPT_CUSTOMREQUEST, "GET");
|
|
529
746
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
VALUE multipart = rb_funcall(request, rb_intern("multipart"), 0);
|
|
534
|
-
|
|
535
|
-
if (action == rb_intern("post")) {
|
|
536
|
-
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
|
|
537
|
-
} else if (action == rb_intern("put")) {
|
|
538
|
-
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
|
539
|
-
} else if (action == rb_intern("patch")) {
|
|
540
|
-
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH");
|
|
747
|
+
if (RTEST(file_name)) {
|
|
748
|
+
s->download_file = open_file(file_name, "wb");
|
|
749
|
+
SESSION_WRITE(s, body_buf, Qnil);
|
|
541
750
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
751
|
+
} else if (action == id_post || action == id_put || action == id_patch) {
|
|
752
|
+
VALUE multipart = request_attr(request, id_multipart);
|
|
753
|
+
const char *verb = action == id_post ? "POST" : action == id_put ? "PUT" : "PATCH";
|
|
754
|
+
|
|
755
|
+
setopt_or_raise(easy, CURLOPT_CUSTOMREQUEST, verb);
|
|
756
|
+
|
|
757
|
+
if (RTEST(multipart)) {
|
|
758
|
+
if (RTEST(upload_data)) {
|
|
759
|
+
Check_Type(upload_data, T_HASH);
|
|
760
|
+
rb_hash_foreach(upload_data, each_mime_value, self);
|
|
546
761
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
set_request_body_file(state, filename);
|
|
551
|
-
} else if (RTEST(multipart)) {
|
|
552
|
-
if (action == rb_intern("post")) {
|
|
553
|
-
if(RTEST(data) && RTEST(filename)) {
|
|
554
|
-
if (rb_type(data) == T_HASH && rb_type(filename) == T_HASH) {
|
|
555
|
-
rb_hash_foreach(data, formadd_values, self);
|
|
556
|
-
rb_hash_foreach(filename, formadd_files, self);
|
|
557
|
-
} else {
|
|
558
|
-
rb_raise(rb_eArgError, "Data and Filename must be passed in a hash.");
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
curl_easy_setopt(curl, CURLOPT_HTTPPOST, state->post);
|
|
562
|
-
} else {
|
|
563
|
-
rb_raise(rb_eArgError, "Multipart PUT not supported");
|
|
762
|
+
if (RTEST(file_name)) {
|
|
763
|
+
Check_Type(file_name, T_HASH);
|
|
764
|
+
rb_hash_foreach(file_name, each_mime_file, self);
|
|
564
765
|
}
|
|
565
|
-
|
|
766
|
+
if (!s->mime) s->mime = curl_mime_init(easy);
|
|
767
|
+
setopt_or_raise(easy, CURLOPT_MIMEPOST, s->mime);
|
|
768
|
+
} else if (RTEST(upload_data)) {
|
|
769
|
+
if (action == id_post) setopt_or_raise(easy, CURLOPT_POST, 1L);
|
|
770
|
+
set_request_body(s, upload_data);
|
|
771
|
+
} else if (RTEST(file_name)) {
|
|
772
|
+
s->headers = curl_slist_append(s->headers, "Transfer-Encoding: chunked");
|
|
773
|
+
set_request_body_from_file(s, rb_obj_as_string(file_name));
|
|
566
774
|
} else {
|
|
567
|
-
rb_raise(rb_eArgError, "Must provide either data or a filename when doing a PUT or
|
|
775
|
+
rb_raise(rb_eArgError, "Must provide either data or a filename when doing a PUT, POST or PATCH");
|
|
568
776
|
}
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, state->upload_buf);
|
|
578
|
-
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
|
|
579
|
-
}
|
|
580
|
-
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
581
|
-
|
|
582
|
-
} else if (action == rb_intern("head")) {
|
|
583
|
-
curl_easy_setopt(curl, CURLOPT_NOBODY, 1);
|
|
777
|
+
} else if (action == id_delete) {
|
|
778
|
+
if (RTEST(upload_data)) {
|
|
779
|
+
setopt_or_raise(easy, CURLOPT_POST, 1L);
|
|
780
|
+
set_request_body(s, upload_data);
|
|
781
|
+
}
|
|
782
|
+
setopt_or_raise(easy, CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
783
|
+
} else if (action == id_head) {
|
|
784
|
+
setopt_or_raise(easy, CURLOPT_NOBODY, 1L);
|
|
584
785
|
} else {
|
|
585
|
-
VALUE
|
|
586
|
-
|
|
786
|
+
VALUE verb = request_attr(request, id_action_name);
|
|
787
|
+
setopt_or_raise(easy, CURLOPT_CUSTOMREQUEST, StringValueCStr(verb));
|
|
587
788
|
}
|
|
588
789
|
|
|
589
|
-
|
|
590
|
-
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->error_buf);
|
|
790
|
+
setopt_or_raise(easy, CURLOPT_HTTPHEADER, s->headers);
|
|
591
791
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
if(RTEST(a_c_encoding)) {
|
|
595
|
-
#ifdef CURLOPT_ACCEPT_ENCODING
|
|
596
|
-
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
|
|
597
|
-
#elif defined CURLOPT_ENCODING
|
|
598
|
-
curl_easy_setopt(curl, CURLOPT_ENCODING, "");
|
|
599
|
-
#else
|
|
600
|
-
rb_raise(rb_eArgError,
|
|
601
|
-
"The libcurl version installed doesn't support automatic content negotiation");
|
|
602
|
-
#endif
|
|
792
|
+
if (RTEST(request_attr(request, id_automatic_content_encoding))) {
|
|
793
|
+
setopt_or_raise(easy, CURLOPT_ACCEPT_ENCODING, "");
|
|
603
794
|
}
|
|
604
|
-
|
|
605
|
-
url =
|
|
606
|
-
if (!RTEST(url))
|
|
607
|
-
|
|
795
|
+
|
|
796
|
+
url = request_attr(request, id_url);
|
|
797
|
+
if (!RTEST(url)) rb_raise(rb_eArgError, "Must provide a URL");
|
|
798
|
+
set_string_option(s, CURLOPT_URL, url, 0);
|
|
799
|
+
|
|
800
|
+
value = request_attr(request, id_timeout);
|
|
801
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_TIMEOUT_MS, seconds_to_milliseconds(value));
|
|
802
|
+
|
|
803
|
+
value = request_attr(request, id_connect_timeout);
|
|
804
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_CONNECTTIMEOUT_MS, seconds_to_milliseconds(value));
|
|
805
|
+
|
|
806
|
+
value = request_attr(request, id_dns_cache_timeout);
|
|
807
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_DNS_CACHE_TIMEOUT, NUM2LONG(value));
|
|
808
|
+
|
|
809
|
+
value = request_attr(request, id_low_speed_time);
|
|
810
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_LOW_SPEED_TIME, NUM2LONG(value));
|
|
811
|
+
|
|
812
|
+
value = request_attr(request, id_low_speed_limit);
|
|
813
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_LOW_SPEED_LIMIT, NUM2LONG(value));
|
|
814
|
+
|
|
815
|
+
value = request_attr(request, id_max_redirects);
|
|
816
|
+
if (RTEST(value)) {
|
|
817
|
+
long redirects = NUM2LONG(value);
|
|
818
|
+
setopt_or_raise(easy, CURLOPT_FOLLOWLOCATION, redirects == 0 ? 0L : 1L);
|
|
819
|
+
setopt_or_raise(easy, CURLOPT_MAXREDIRS, redirects);
|
|
608
820
|
}
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
821
|
+
|
|
822
|
+
value = request_attr(request, id_proxy);
|
|
823
|
+
if (RTEST(value)) set_string_option(s, CURLOPT_PROXY, value, 0);
|
|
824
|
+
|
|
825
|
+
value = request_attr(request, id_proxy_type);
|
|
826
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_PROXYTYPE, NUM2LONG(value));
|
|
827
|
+
|
|
828
|
+
value = request_attr(request, id_credentials);
|
|
829
|
+
if (RTEST(value)) {
|
|
830
|
+
setopt_or_raise(easy, CURLOPT_HTTPAUTH, NUM2LONG(request_attr(request, id_auth_type)));
|
|
831
|
+
set_string_option(s, CURLOPT_USERPWD, value, 0);
|
|
615
832
|
}
|
|
616
833
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, floating_rb_seconds_to_milliseconds(timeout));
|
|
834
|
+
if (RTEST(request_attr(request, id_ignore_content_length))) {
|
|
835
|
+
setopt_or_raise(easy, CURLOPT_IGNORE_CONTENT_LENGTH, 1L);
|
|
620
836
|
}
|
|
621
837
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
838
|
+
if (RTEST(request_attr(request, id_insecure))) {
|
|
839
|
+
setopt_or_raise(easy, CURLOPT_SSL_VERIFYPEER, 0L);
|
|
840
|
+
setopt_or_raise(easy, CURLOPT_SSL_VERIFYHOST, 0L);
|
|
625
841
|
}
|
|
626
842
|
|
|
627
|
-
|
|
628
|
-
if(RTEST(
|
|
629
|
-
|
|
843
|
+
value = request_attr(request, id_ssl_version);
|
|
844
|
+
if (RTEST(value)) {
|
|
845
|
+
setopt_or_raise(easy, CURLOPT_SSLVERSION, lookup_version(hSSLVersions, value, eUnsupportedSSLVersion, "SSL version"));
|
|
630
846
|
}
|
|
631
847
|
|
|
632
|
-
|
|
633
|
-
if(RTEST(
|
|
634
|
-
|
|
848
|
+
value = request_attr(request, id_http_version);
|
|
849
|
+
if (RTEST(value)) {
|
|
850
|
+
setopt_or_raise(easy, CURLOPT_HTTP_VERSION, lookup_version(hHTTPVersions, value, eUnsupportedHTTPVersion, "HTTP version"));
|
|
635
851
|
}
|
|
636
852
|
|
|
637
|
-
|
|
638
|
-
if (RTEST(
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
853
|
+
value = request_attr(request, id_ssl_cert);
|
|
854
|
+
if (RTEST(value)) set_string_option(s, CURLOPT_SSLCERT, value, 1);
|
|
855
|
+
|
|
856
|
+
value = request_attr(request, id_ssl_cert_type);
|
|
857
|
+
if (RTEST(value)) set_string_option(s, CURLOPT_SSLCERTTYPE, value, 0);
|
|
858
|
+
|
|
859
|
+
value = request_attr(request, id_ssl_key);
|
|
860
|
+
if (RTEST(value)) set_string_option(s, CURLOPT_SSLKEY, value, 1);
|
|
861
|
+
|
|
862
|
+
value = request_attr(request, id_ssl_key_password);
|
|
863
|
+
if (RTEST(value)) set_string_option(s, CURLOPT_KEYPASSWD, value, 0);
|
|
864
|
+
|
|
865
|
+
value = request_attr(request, id_cacert);
|
|
866
|
+
if (RTEST(value)) set_string_option(s, CURLOPT_CAINFO, value, 1);
|
|
867
|
+
|
|
868
|
+
value = request_attr(request, id_buffer_size);
|
|
869
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_BUFFERSIZE, NUM2LONG(value));
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
/*----------------------------------------------------------------------------*/
|
|
873
|
+
/* Response */
|
|
874
|
+
|
|
875
|
+
static void set_timing(CURL *easy, VALUE timings, const char *key, CURLINFO info) {
|
|
876
|
+
curl_off_t microseconds = 0;
|
|
877
|
+
if (curl_easy_getinfo(easy, info, µseconds) == CURLE_OK) {
|
|
878
|
+
rb_hash_aset(timings, ID2SYM(rb_intern(key)), rb_float_new((double)microseconds / 1000000.0));
|
|
642
879
|
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
/* libcurl's cumulative transfer marks in seconds since the request started:
|
|
883
|
+
* namelookup <= connect <= appconnect <= pretransfer <= starttransfer <= total. */
|
|
884
|
+
static VALUE create_timings(CURL *easy) {
|
|
885
|
+
VALUE timings = rb_hash_new();
|
|
886
|
+
set_timing(easy, timings, "namelookup", CURLINFO_NAMELOOKUP_TIME_T);
|
|
887
|
+
set_timing(easy, timings, "connect", CURLINFO_CONNECT_TIME_T);
|
|
888
|
+
set_timing(easy, timings, "appconnect", CURLINFO_APPCONNECT_TIME_T);
|
|
889
|
+
set_timing(easy, timings, "pretransfer", CURLINFO_PRETRANSFER_TIME_T);
|
|
890
|
+
set_timing(easy, timings, "starttransfer", CURLINFO_STARTTRANSFER_TIME_T);
|
|
891
|
+
set_timing(easy, timings, "redirect", CURLINFO_REDIRECT_TIME_T);
|
|
892
|
+
set_timing(easy, timings, "total", CURLINFO_TOTAL_TIME_T);
|
|
893
|
+
return timings;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
static VALUE create_response(struct patron_session *s) {
|
|
897
|
+
VALUE self = s->self;
|
|
898
|
+
CURL *easy = s->easy;
|
|
899
|
+
VALUE kwargs = rb_hash_new();
|
|
900
|
+
char *effective_url = NULL;
|
|
901
|
+
long code = 0;
|
|
902
|
+
long redirect_count = 0;
|
|
903
|
+
VALUE klass;
|
|
904
|
+
|
|
905
|
+
curl_easy_getinfo(easy, CURLINFO_EFFECTIVE_URL, &effective_url);
|
|
906
|
+
curl_easy_getinfo(easy, CURLINFO_RESPONSE_CODE, &code);
|
|
907
|
+
curl_easy_getinfo(easy, CURLINFO_REDIRECT_COUNT, &redirect_count);
|
|
908
|
+
|
|
909
|
+
rb_hash_aset(kwargs, ID2SYM(id_url_kw), effective_url ? rb_usascii_str_new_cstr(effective_url) : Qnil);
|
|
910
|
+
rb_hash_aset(kwargs, ID2SYM(id_status_kw), LONG2NUM(code));
|
|
911
|
+
rb_hash_aset(kwargs, ID2SYM(id_redirect_count_kw), LONG2NUM(redirect_count));
|
|
912
|
+
rb_hash_aset(kwargs, ID2SYM(id_header_data_kw), s->header_buf);
|
|
913
|
+
rb_hash_aset(kwargs, ID2SYM(id_body_kw), s->body_buf);
|
|
914
|
+
rb_hash_aset(kwargs, ID2SYM(id_default_charset_kw), rb_funcallv(self, id_default_response_charset, 0, NULL));
|
|
915
|
+
rb_hash_aset(kwargs, ID2SYM(id_timings_kw), create_timings(easy));
|
|
916
|
+
|
|
917
|
+
klass = rb_funcallv(self, id_response_class, 0, NULL);
|
|
918
|
+
return rb_class_new_instance_kw(1, &kwargs, klass, RB_PASS_KEYWORDS);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/*----------------------------------------------------------------------------*/
|
|
922
|
+
/* Performing a request */
|
|
923
|
+
|
|
924
|
+
struct poll_context {
|
|
925
|
+
CURLM *multi;
|
|
926
|
+
CURLMcode rc;
|
|
927
|
+
};
|
|
928
|
+
|
|
929
|
+
static void *poll_without_gvl(void *ptr) {
|
|
930
|
+
struct poll_context *ctx = ptr;
|
|
931
|
+
ctx->rc = curl_multi_poll(ctx->multi, NULL, 0, POLL_TIMEOUT_MS, NULL);
|
|
932
|
+
return NULL;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
/* Runs without the GVL whenever Ruby needs this thread back (Thread#kill, Thread#raise,
|
|
936
|
+
* signals, interpreter exit). Only wakes the poll; Ruby decides after it returns whether
|
|
937
|
+
* there is an exception to raise. */
|
|
938
|
+
static void wakeup_poll(void *ptr) {
|
|
939
|
+
struct patron_session *s = ptr;
|
|
940
|
+
curl_multi_wakeup(s->multi);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
/* Blocks the whole thread until libcurl has something to do, with the GVL released */
|
|
944
|
+
static void wait_without_scheduler(struct patron_session *s) {
|
|
945
|
+
struct poll_context ctx = { s->multi, CURLM_OK };
|
|
946
|
+
/* Pending Ruby interrupts are raised right here as rb_nogvl returns; the rb_ensure
|
|
947
|
+
* around the request takes care of the half-finished transfer. */
|
|
948
|
+
rb_nogvl(poll_without_gvl, &ctx, wakeup_poll, s, RB_NOGVL_UBF_ASYNC_SAFE);
|
|
949
|
+
if (ctx.rc != CURLM_OK) rb_raise(ePatronError, "%s", curl_multi_strerror(ctx.rc));
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
static VALUE float_seconds(long milliseconds) {
|
|
953
|
+
return rb_float_new((double)milliseconds / 1000.0);
|
|
954
|
+
}
|
|
643
955
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
956
|
+
/* An IO wrapping one of libcurl's sockets, for handing to the scheduler. FMODE_EXTERNAL keeps
|
|
957
|
+
* Ruby from ever closing the descriptor; libcurl owns it. The wrappers are dropped whenever the
|
|
958
|
+
* set of sockets changes, so a descriptor number the kernel reused gets a fresh one. */
|
|
959
|
+
static VALUE socket_io(struct patron_session *s, int fd) {
|
|
960
|
+
VALUE key = INT2FIX(fd);
|
|
961
|
+
VALUE io = rb_hash_lookup2(s->socket_ios, key, Qnil);
|
|
962
|
+
|
|
963
|
+
if (NIL_P(io)) {
|
|
964
|
+
io = rb_io_open_descriptor(rb_cIO, fd, FMODE_READWRITE | FMODE_EXTERNAL, Qnil, Qnil, NULL);
|
|
965
|
+
rb_hash_aset(s->socket_ios, key, io);
|
|
647
966
|
}
|
|
967
|
+
return io;
|
|
968
|
+
}
|
|
648
969
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
970
|
+
#define MAX_WAIT_FDS 16
|
|
971
|
+
|
|
972
|
+
/* Yields to the Fiber scheduler until libcurl has something to do, so other fibers on this thread
|
|
973
|
+
* run meanwhile. Uses io_wait for the usual single socket, io_select when libcurl juggles several
|
|
974
|
+
* (happy eyeballs), and a plain sleep while it has none (threaded resolver at work). */
|
|
975
|
+
static void wait_with_scheduler(struct patron_session *s, VALUE scheduler) {
|
|
976
|
+
fd_set rd, wr, ex;
|
|
977
|
+
int maxfd = -1, fd, nfds = 0, i;
|
|
978
|
+
int fds[MAX_WAIT_FDS], events[MAX_WAIT_FDS];
|
|
979
|
+
long timeout_ms = -1;
|
|
980
|
+
VALUE timeout;
|
|
981
|
+
|
|
982
|
+
FD_ZERO(&rd); FD_ZERO(&wr); FD_ZERO(&ex);
|
|
983
|
+
curl_multi_fdset(s->multi, &rd, &wr, &ex, &maxfd);
|
|
984
|
+
curl_multi_timeout(s->multi, &timeout_ms);
|
|
985
|
+
if (timeout_ms < 0 || timeout_ms > POLL_TIMEOUT_MS) timeout_ms = POLL_TIMEOUT_MS;
|
|
986
|
+
|
|
987
|
+
for (fd = 0; fd <= maxfd && nfds < MAX_WAIT_FDS; fd++) {
|
|
988
|
+
int ev = 0;
|
|
989
|
+
if (FD_ISSET(fd, &rd)) ev |= RUBY_IO_READABLE;
|
|
990
|
+
if (FD_ISSET(fd, &wr)) ev |= RUBY_IO_WRITABLE;
|
|
991
|
+
if (FD_ISSET(fd, &ex)) ev |= RUBY_IO_PRIORITY;
|
|
992
|
+
if (!ev) continue;
|
|
993
|
+
fds[nfds] = fd;
|
|
994
|
+
events[nfds] = ev;
|
|
995
|
+
nfds++;
|
|
652
996
|
}
|
|
653
997
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
998
|
+
if (nfds == 0) {
|
|
999
|
+
/* libcurl asks to be called back within 100 ms when it has no socket to wait on */
|
|
1000
|
+
if (timeout_ms > 100) timeout_ms = 100;
|
|
1001
|
+
rb_fiber_scheduler_kernel_sleep(scheduler, float_seconds(timeout_ms));
|
|
1002
|
+
return;
|
|
659
1003
|
}
|
|
660
1004
|
|
|
661
|
-
|
|
662
|
-
if (
|
|
663
|
-
|
|
1005
|
+
if (NIL_P(s->socket_ios)) SESSION_WRITE(s, socket_ios, rb_hash_new());
|
|
1006
|
+
if (RHASH_SIZE(s->socket_ios) != (st_index_t)nfds) rb_hash_clear(s->socket_ios);
|
|
1007
|
+
for (i = 0; i < nfds; i++) {
|
|
1008
|
+
if (rb_hash_lookup2(s->socket_ios, INT2FIX(fds[i]), Qundef) == Qundef) {
|
|
1009
|
+
rb_hash_clear(s->socket_ios);
|
|
1010
|
+
break;
|
|
1011
|
+
}
|
|
664
1012
|
}
|
|
665
1013
|
|
|
666
|
-
|
|
667
|
-
if(
|
|
668
|
-
|
|
669
|
-
|
|
1014
|
+
timeout = float_seconds(timeout_ms);
|
|
1015
|
+
if (nfds == 1) {
|
|
1016
|
+
rb_fiber_scheduler_io_wait(scheduler, socket_io(s, fds[0]), INT2NUM(events[0]), timeout);
|
|
1017
|
+
return;
|
|
670
1018
|
}
|
|
671
1019
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
VALUE
|
|
675
|
-
char* version = StringValuePtr(ssl_version_str);
|
|
1020
|
+
{
|
|
1021
|
+
VALUE readables = rb_ary_new(), writables = rb_ary_new(), exceptables = rb_ary_new();
|
|
1022
|
+
VALUE ready;
|
|
676
1023
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
|
|
683
|
-
}
|
|
684
|
-
#if LIBCURL_VERSION_NUM >= 0x072200
|
|
685
|
-
/* this is libCURLv7.34.0 or later */
|
|
686
|
-
else if(strcmp(version, "TLSv1_0") == 0) {
|
|
687
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0); //libcurl v7.34.0+
|
|
688
|
-
} else if(strcmp(version, "TLSv1_1") == 0) {
|
|
689
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1); //libcurl v7.34.0+
|
|
690
|
-
} else if(strcmp(version, "TLSv1_2") == 0) {
|
|
691
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); //libcurl v7.34.0+
|
|
1024
|
+
for (i = 0; i < nfds; i++) {
|
|
1025
|
+
VALUE io = socket_io(s, fds[i]);
|
|
1026
|
+
if (events[i] & RUBY_IO_READABLE) rb_ary_push(readables, io);
|
|
1027
|
+
if (events[i] & RUBY_IO_WRITABLE) rb_ary_push(writables, io);
|
|
1028
|
+
if (events[i] & RUBY_IO_PRIORITY) rb_ary_push(exceptables, io);
|
|
692
1029
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_3); //libcurl v7.52.0+
|
|
698
|
-
}
|
|
699
|
-
#endif
|
|
700
|
-
else {
|
|
701
|
-
rb_raise(eUnsupportedSSLVersion, "Unsupported SSL version: %s", version);
|
|
1030
|
+
ready = rb_fiber_scheduler_io_select(scheduler, readables, writables, exceptables, timeout);
|
|
1031
|
+
if (ready == Qundef) {
|
|
1032
|
+
/* A scheduler without io_select; poll instead. Only ever happens while connecting. */
|
|
1033
|
+
rb_fiber_scheduler_kernel_sleep(scheduler, float_seconds(timeout_ms > 50 ? 50 : timeout_ms));
|
|
702
1034
|
}
|
|
703
1035
|
}
|
|
1036
|
+
}
|
|
704
1037
|
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
else if(strcmp(version, "HTTPv2_TLS") == 0) {
|
|
725
|
-
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); //libcurl v7.47.0+
|
|
726
|
-
}
|
|
727
|
-
#endif
|
|
728
|
-
#if LIBCURL_VERSION_NUM >= 0x073100
|
|
729
|
-
/* this is libCURLv7.49.0 or later */
|
|
730
|
-
else if(strcmp(version, "HTTPv2_PRIOR") == 0) {
|
|
731
|
-
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE); //libcurl v7.49.0+
|
|
732
|
-
}
|
|
733
|
-
#endif
|
|
734
|
-
else {
|
|
735
|
-
rb_raise(eUnsupportedHTTPVersion, "Unsupported HTTP version: %s", version);
|
|
1038
|
+
static void drive_transfer(struct patron_session *s) {
|
|
1039
|
+
int running = 1;
|
|
1040
|
+
CURLMcode mc;
|
|
1041
|
+
/* Set per thread; a Session inside Async { } yields to it instead of blocking the thread */
|
|
1042
|
+
VALUE scheduler = rb_fiber_scheduler_current();
|
|
1043
|
+
|
|
1044
|
+
mc = curl_multi_add_handle(s->multi, s->easy);
|
|
1045
|
+
if (mc != CURLM_OK) rb_raise(ePatronError, "%s", curl_multi_strerror(mc));
|
|
1046
|
+
|
|
1047
|
+
while (running) {
|
|
1048
|
+
mc = curl_multi_perform(s->multi, &running);
|
|
1049
|
+
if (mc != CURLM_OK) rb_raise(ePatronError, "%s", curl_multi_strerror(mc));
|
|
1050
|
+
if (s->pending_tag || s->interrupt) break;
|
|
1051
|
+
if (!running) break;
|
|
1052
|
+
|
|
1053
|
+
if (NIL_P(scheduler)) {
|
|
1054
|
+
wait_without_scheduler(s);
|
|
1055
|
+
} else {
|
|
1056
|
+
wait_with_scheduler(s, scheduler);
|
|
736
1057
|
}
|
|
1058
|
+
if (s->interrupt) break;
|
|
737
1059
|
}
|
|
1060
|
+
}
|
|
738
1061
|
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
1062
|
+
static CURLcode transfer_result(struct patron_session *s) {
|
|
1063
|
+
CURLcode result = CURLE_OK;
|
|
1064
|
+
CURLMsg *msg;
|
|
1065
|
+
int remaining = 0;
|
|
743
1066
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, NUM2LONG(buffer_size));
|
|
1067
|
+
while ((msg = curl_multi_info_read(s->multi, &remaining))) {
|
|
1068
|
+
if (msg->msg == CURLMSG_DONE && msg->easy_handle == s->easy) result = msg->data.result;
|
|
747
1069
|
}
|
|
1070
|
+
return result;
|
|
748
1071
|
}
|
|
749
1072
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
VALUE
|
|
753
|
-
|
|
754
|
-
long code = 0;
|
|
755
|
-
long count = 0;
|
|
756
|
-
VALUE responseKlass = Qnil;
|
|
757
|
-
|
|
758
|
-
curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
|
|
759
|
-
args[0] = rb_str_new2(effective_url);
|
|
1073
|
+
struct perform_args {
|
|
1074
|
+
struct patron_session *s;
|
|
1075
|
+
VALUE request;
|
|
1076
|
+
};
|
|
760
1077
|
|
|
761
|
-
|
|
762
|
-
args
|
|
1078
|
+
static VALUE perform(VALUE ptr) {
|
|
1079
|
+
struct perform_args *args = (struct perform_args *)ptr;
|
|
1080
|
+
struct patron_session *s = args->s;
|
|
1081
|
+
CURLcode result;
|
|
763
1082
|
|
|
764
|
-
|
|
765
|
-
|
|
1083
|
+
s->interrupt = 0;
|
|
1084
|
+
s->pending_tag = 0;
|
|
1085
|
+
s->error_buf[0] = '\0';
|
|
1086
|
+
SESSION_WRITE(s, header_buf, rb_str_buf_new(HEADER_BUFFER_INITIAL_CAPACITY));
|
|
1087
|
+
SESSION_WRITE(s, body_buf, rb_str_buf_new(BODY_BUFFER_INITIAL_CAPACITY));
|
|
766
1088
|
|
|
767
|
-
args
|
|
768
|
-
|
|
769
|
-
args[5] = rb_funcall(self, rb_intern("default_response_charset"), 0);
|
|
770
|
-
|
|
771
|
-
responseKlass = rb_funcall(self, rb_intern("response_class"), 0);
|
|
772
|
-
return rb_class_new_instance(6, args, responseKlass);
|
|
773
|
-
}
|
|
1089
|
+
set_options_from_request(s, args->request);
|
|
1090
|
+
drive_transfer(s);
|
|
774
1091
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
case CURLE_COULDNT_CONNECT: error = eConnectionFailed; break;
|
|
783
|
-
case CURLE_PARTIAL_FILE: error = ePartialFileError; break;
|
|
784
|
-
case CURLE_OPERATION_TIMEDOUT: error = eTimeoutError; break;
|
|
785
|
-
case CURLE_TOO_MANY_REDIRECTS: error = eTooManyRedirects; break;
|
|
786
|
-
case CURLE_ABORTED_BY_CALLBACK: error = eAborted; break;
|
|
787
|
-
default: error = ePatronError;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
return error;
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
/* Uses as the unblocking function when the thread running Patron gets
|
|
795
|
-
signaled. The important difference with session_interrupt is that we
|
|
796
|
-
are not allowed to touch any Ruby structures while outside the GIL,
|
|
797
|
-
but we _are_ permitted to touch our internal curl state struct
|
|
798
|
-
*/
|
|
799
|
-
void session_ubf_abort(void* patron_state) {
|
|
800
|
-
struct patron_curl_state* state = (struct patron_curl_state*) patron_state;
|
|
801
|
-
state->interrupt = INTERRUPT_ABORT;
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
/* Perform the actual HTTP request by calling libcurl. */
|
|
805
|
-
static VALUE perform_request(VALUE self) {
|
|
806
|
-
struct patron_curl_state *state = get_patron_curl_state(self);
|
|
807
|
-
CURL* curl = state->handle;
|
|
808
|
-
CURLcode ret = 0;
|
|
809
|
-
|
|
810
|
-
state->interrupt = 0; /* clear the interrupt flag */
|
|
811
|
-
|
|
812
|
-
ret = (CURLcode) rb_thread_call_without_gvl(
|
|
813
|
-
(void *(*)(void *)) curl_easy_perform, curl,
|
|
814
|
-
session_ubf_abort, (void*)state
|
|
815
|
-
);
|
|
816
|
-
|
|
817
|
-
if (CURLE_OK == ret) {
|
|
818
|
-
VALUE header_str = membuffer_to_rb_str(&state->header_buffer);
|
|
819
|
-
VALUE body_str = Qnil;
|
|
820
|
-
if (!state->download_file) { body_str = membuffer_to_rb_str(&state->body_buffer); }
|
|
821
|
-
|
|
822
|
-
curl_easy_setopt(curl, CURLOPT_COOKIELIST, "FLUSH"); // Flush cookies to the cookie jar
|
|
823
|
-
|
|
824
|
-
return create_response(self, curl, header_str, body_str);
|
|
825
|
-
} else {
|
|
826
|
-
rb_raise(select_error(ret), "%s", state->error_buf);
|
|
1092
|
+
if (s->pending_tag) reraise_parked(s);
|
|
1093
|
+
|
|
1094
|
+
if (s->interrupt == INTERRUPT_DOWNLOAD_OVERFLOW) {
|
|
1095
|
+
raise_curl_error(eDownloadLimitExceeded, CURLE_ABORTED_BY_CALLBACK, "Download byte limit exceeded");
|
|
1096
|
+
}
|
|
1097
|
+
if (s->interrupt) {
|
|
1098
|
+
raise_curl_error(eAborted, CURLE_ABORTED_BY_CALLBACK, "Request interrupted");
|
|
827
1099
|
}
|
|
1100
|
+
|
|
1101
|
+
result = transfer_result(s);
|
|
1102
|
+
if (result != CURLE_OK) raise_curl_error(error_class_for(result), result, s->error_buf);
|
|
1103
|
+
|
|
1104
|
+
/* Write the cookies accumulated in the share out to the jar, if one is configured */
|
|
1105
|
+
curl_easy_setopt(s->easy, CURLOPT_COOKIELIST, "FLUSH");
|
|
1106
|
+
|
|
1107
|
+
return create_response(s);
|
|
828
1108
|
}
|
|
829
1109
|
|
|
830
|
-
/*
|
|
831
|
-
* all request related objects such as the header slist.
|
|
832
|
-
*/
|
|
1110
|
+
/* Runs under rb_ensure after every request, whether it finished, raised or was interrupted. */
|
|
833
1111
|
static VALUE cleanup(VALUE self) {
|
|
834
|
-
struct
|
|
835
|
-
curl_easy_cleanup(state->handle);
|
|
1112
|
+
struct patron_session *s = get_session(self);
|
|
836
1113
|
|
|
837
|
-
if (
|
|
838
|
-
|
|
839
|
-
|
|
1114
|
+
if (s->easy) {
|
|
1115
|
+
curl_multi_remove_handle(s->multi, s->easy);
|
|
1116
|
+
curl_easy_cleanup(s->easy);
|
|
1117
|
+
s->easy = NULL;
|
|
840
1118
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
fclose(state->download_file);
|
|
845
|
-
state->download_file = NULL;
|
|
846
|
-
} else {
|
|
847
|
-
membuffer_clear(&state->body_buffer);
|
|
1119
|
+
if (s->mime) {
|
|
1120
|
+
curl_mime_free(s->mime);
|
|
1121
|
+
s->mime = NULL;
|
|
848
1122
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
state->request_body_file = NULL;
|
|
1123
|
+
if (s->headers) {
|
|
1124
|
+
curl_slist_free_all(s->headers);
|
|
1125
|
+
s->headers = NULL;
|
|
853
1126
|
}
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
1127
|
+
if (s->download_file) {
|
|
1128
|
+
fclose(s->download_file);
|
|
1129
|
+
s->download_file = NULL;
|
|
1130
|
+
}
|
|
1131
|
+
if (s->request_body_file) {
|
|
1132
|
+
fclose(s->request_body_file);
|
|
1133
|
+
s->request_body_file = NULL;
|
|
859
1134
|
}
|
|
860
1135
|
|
|
861
|
-
|
|
862
|
-
|
|
1136
|
+
SESSION_WRITE(s, progress_proc, Qnil);
|
|
1137
|
+
SESSION_WRITE(s, upload_data, Qnil);
|
|
1138
|
+
SESSION_WRITE(s, header_buf, Qnil);
|
|
1139
|
+
SESSION_WRITE(s, body_buf, Qnil);
|
|
1140
|
+
s->download_byte_limit = 0;
|
|
1141
|
+
s->busy = 0;
|
|
863
1142
|
return Qnil;
|
|
864
1143
|
}
|
|
865
1144
|
|
|
866
1145
|
/*
|
|
867
|
-
*
|
|
868
|
-
*
|
|
869
|
-
* library. After the request completes, a Response object will be created and
|
|
870
|
-
* returned.
|
|
1146
|
+
* Performs the HTTP request described by +request+ and returns a Response built with
|
|
1147
|
+
* #response_class. Raises a Patron::Error subclass when libcurl reports a failure.
|
|
871
1148
|
*
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
* error message.
|
|
875
|
-
*
|
|
876
|
-
* @param request[Patron::Request] the request to use when filling the CURL options
|
|
877
|
-
* @return [Patron::Response] the result of calling `response_class` on the Session
|
|
1149
|
+
* @param request [Patron::Request]
|
|
1150
|
+
* @return [Patron::Response]
|
|
878
1151
|
*/
|
|
879
1152
|
static VALUE session_handle_request(VALUE self, VALUE request) {
|
|
880
|
-
|
|
881
|
-
|
|
1153
|
+
struct patron_session *s = get_session(self);
|
|
1154
|
+
struct perform_args args = { s, request };
|
|
1155
|
+
|
|
1156
|
+
if (s->busy) {
|
|
1157
|
+
rb_raise(ePatronError, "This Session is already performing a request in another thread or fiber; use one Session per thread or fiber");
|
|
1158
|
+
}
|
|
1159
|
+
ensure_handles_for_this_process(s);
|
|
1160
|
+
s->busy = 1;
|
|
1161
|
+
return rb_ensure(perform, (VALUE)&args, cleanup, self);
|
|
882
1162
|
}
|
|
883
1163
|
|
|
884
|
-
/*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
1164
|
+
/*
|
|
1165
|
+
* Aborts the request currently in flight on this Session, which then raises Patron::Aborted.
|
|
1166
|
+
* Safe to call from another thread.
|
|
887
1167
|
*
|
|
888
|
-
* @return
|
|
1168
|
+
* @return self
|
|
889
1169
|
*/
|
|
890
1170
|
static VALUE session_interrupt(VALUE self) {
|
|
891
|
-
struct
|
|
892
|
-
|
|
1171
|
+
struct patron_session *s = get_session(self);
|
|
1172
|
+
|
|
1173
|
+
s->interrupt = INTERRUPT_ABORT;
|
|
1174
|
+
if (s->multi) curl_multi_wakeup(s->multi);
|
|
893
1175
|
return self;
|
|
894
1176
|
}
|
|
895
1177
|
|
|
896
1178
|
/*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
* writable. Calling multiple times will add more files.
|
|
900
|
-
* FIXME: what does the empty string actually do here?
|
|
901
|
-
*
|
|
902
|
-
* @param [String] file path to the existing cookie file, or nil to store in memory.
|
|
903
|
-
* @return self
|
|
1179
|
+
* Enables cookie handling: in memory when +path+ is empty, otherwise read from and written
|
|
1180
|
+
* back to the file at +path+.
|
|
904
1181
|
*/
|
|
905
|
-
static VALUE
|
|
906
|
-
struct
|
|
907
|
-
|
|
908
|
-
char* file_path = NULL;
|
|
909
|
-
|
|
910
|
-
// FIXME: http://websystemsengineering.blogspot.nl/2013/03/curloptcookiefile-vs-curloptcookiejar.html
|
|
911
|
-
file_path = RSTRING_PTR(file);
|
|
912
|
-
if (file_path != NULL && strlen(file_path) != 0) {
|
|
913
|
-
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, file_path);
|
|
914
|
-
}
|
|
915
|
-
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, file_path);
|
|
1182
|
+
static VALUE session_set_cookie_file(VALUE self, VALUE path) {
|
|
1183
|
+
struct patron_session *s = get_session(self);
|
|
1184
|
+
const char *file_path = StringValueCStr(path);
|
|
916
1185
|
|
|
1186
|
+
if (*file_path) setopt_or_raise(s->base, CURLOPT_COOKIEJAR, file_path);
|
|
1187
|
+
setopt_or_raise(s->base, CURLOPT_COOKIEFILE, file_path);
|
|
917
1188
|
return self;
|
|
918
1189
|
}
|
|
919
1190
|
|
|
920
1191
|
/*
|
|
921
|
-
*
|
|
922
|
-
*
|
|
923
|
-
* @param [String, nil] file path to the debug file, or nil to write to STDERR
|
|
924
|
-
* @return self
|
|
1192
|
+
* Sends libcurl's verbose output to +io+, or disables it when +io+ is nil.
|
|
925
1193
|
*/
|
|
926
|
-
static VALUE
|
|
927
|
-
struct
|
|
928
|
-
CURL *curl = state->base_handle;
|
|
929
|
-
char *file_path = RSTRING_PTR(file);
|
|
1194
|
+
static VALUE session_set_debug_io(VALUE self, VALUE io) {
|
|
1195
|
+
struct patron_session *s = get_session(self);
|
|
930
1196
|
|
|
931
|
-
|
|
1197
|
+
SESSION_WRITE(s, debug_io, io);
|
|
1198
|
+
setopt_or_raise(s->base, CURLOPT_VERBOSE, NIL_P(io) ? 0L : 1L);
|
|
1199
|
+
return self;
|
|
1200
|
+
}
|
|
932
1201
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
} else {
|
|
936
|
-
state->debug_file = stderr;
|
|
937
|
-
}
|
|
938
|
-
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
|
|
939
|
-
curl_easy_setopt(curl, CURLOPT_STDERR, state->debug_file);
|
|
1202
|
+
/*----------------------------------------------------------------------------*/
|
|
1203
|
+
/* Init */
|
|
940
1204
|
|
|
941
|
-
|
|
1205
|
+
static VALUE shareable_table(void) {
|
|
1206
|
+
return rb_hash_new();
|
|
942
1207
|
}
|
|
943
1208
|
|
|
1209
|
+
static void table_entry(VALUE table, const char *name, long value) {
|
|
1210
|
+
rb_hash_aset(table, rb_interned_str_cstr(name), LONG2NUM(value));
|
|
1211
|
+
}
|
|
944
1212
|
|
|
945
|
-
|
|
946
|
-
/*
|
|
1213
|
+
void Init_session_ext(void) {
|
|
1214
|
+
/* Must precede the method definitions it applies to. There is no mutable global state:
|
|
1215
|
+
* every handle belongs to one Session, and libcurl's own globals are initialised once here. */
|
|
1216
|
+
rb_ext_ractor_safe(true);
|
|
947
1217
|
|
|
948
|
-
void Init_session_ext() {
|
|
949
1218
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
950
1219
|
rb_require("patron/error");
|
|
951
|
-
|
|
952
|
-
|
|
1220
|
+
rb_require("patron/request");
|
|
1221
|
+
|
|
1222
|
+
#define DEFINE_ID(name) id_##name = rb_intern_const(#name);
|
|
1223
|
+
REQUEST_ATTRIBUTES(DEFINE_ID)
|
|
1224
|
+
#undef DEFINE_ID
|
|
1225
|
+
id_to_path = rb_intern_const("to_path");
|
|
1226
|
+
id_to_s = rb_intern_const("to_s");
|
|
1227
|
+
id_response_class = rb_intern_const("response_class");
|
|
1228
|
+
id_default_response_charset = rb_intern_const("default_response_charset");
|
|
1229
|
+
id_get = rb_intern_const("get");
|
|
1230
|
+
id_post = rb_intern_const("post");
|
|
1231
|
+
id_put = rb_intern_const("put");
|
|
1232
|
+
id_patch = rb_intern_const("patch");
|
|
1233
|
+
id_delete = rb_intern_const("delete");
|
|
1234
|
+
id_head = rb_intern_const("head");
|
|
1235
|
+
id_ivar_curl_code = rb_intern_const("@curl_code");
|
|
1236
|
+
id_url_kw = rb_intern_const("url");
|
|
1237
|
+
id_status_kw = rb_intern_const("status");
|
|
1238
|
+
id_redirect_count_kw = rb_intern_const("redirect_count");
|
|
1239
|
+
id_header_data_kw = rb_intern_const("header_data");
|
|
1240
|
+
id_body_kw = rb_intern_const("body");
|
|
1241
|
+
id_default_charset_kw = rb_intern_const("default_charset");
|
|
1242
|
+
id_timings_kw = rb_intern_const("timings");
|
|
953
1243
|
|
|
954
1244
|
mPatron = rb_define_module("Patron");
|
|
955
1245
|
|
|
956
|
-
|
|
1246
|
+
/* These are Ruby-defined classes and tables, not immortal rb_define_class() ones,
|
|
1247
|
+
* so the GC has to know about the slots or compaction leaves them dangling */
|
|
1248
|
+
rb_global_variable(&cRequest);
|
|
1249
|
+
rb_global_variable(&hSSLVersions);
|
|
1250
|
+
rb_global_variable(&hHTTPVersions);
|
|
1251
|
+
rb_global_variable(&ePatronError);
|
|
1252
|
+
rb_global_variable(&eUnsupportedProtocol);
|
|
1253
|
+
rb_global_variable(&eUnsupportedSSLVersion);
|
|
1254
|
+
rb_global_variable(&eUnsupportedHTTPVersion);
|
|
1255
|
+
rb_global_variable(&eURLFormatError);
|
|
1256
|
+
rb_global_variable(&eHostResolutionError);
|
|
1257
|
+
rb_global_variable(&eConnectionFailed);
|
|
1258
|
+
rb_global_variable(&ePartialFileError);
|
|
1259
|
+
rb_global_variable(&eTimeoutError);
|
|
1260
|
+
rb_global_variable(&eTooManyRedirects);
|
|
1261
|
+
rb_global_variable(&eAborted);
|
|
1262
|
+
rb_global_variable(&eDownloadLimitExceeded);
|
|
1263
|
+
rb_global_variable(&eClientCertificateError);
|
|
957
1264
|
|
|
1265
|
+
ePatronError = rb_const_get(mPatron, rb_intern("Error"));
|
|
958
1266
|
eUnsupportedProtocol = rb_const_get(mPatron, rb_intern("UnsupportedProtocol"));
|
|
959
1267
|
eUnsupportedSSLVersion = rb_const_get(mPatron, rb_intern("UnsupportedSSLVersion"));
|
|
960
1268
|
eUnsupportedHTTPVersion = rb_const_get(mPatron, rb_intern("UnsupportedHTTPVersion"));
|
|
@@ -965,37 +1273,60 @@ void Init_session_ext() {
|
|
|
965
1273
|
eTimeoutError = rb_const_get(mPatron, rb_intern("TimeoutError"));
|
|
966
1274
|
eTooManyRedirects = rb_const_get(mPatron, rb_intern("TooManyRedirects"));
|
|
967
1275
|
eAborted = rb_const_get(mPatron, rb_intern("Aborted"));
|
|
1276
|
+
eDownloadLimitExceeded = rb_const_get(mPatron, rb_intern("DownloadLimitExceeded"));
|
|
1277
|
+
eClientCertificateError = rb_const_get(mPatron, rb_intern("ClientCertificateError"));
|
|
968
1278
|
|
|
969
|
-
rb_define_module_function(mPatron, "libcurl_version",
|
|
1279
|
+
rb_define_module_function(mPatron, "libcurl_version", libcurl_version, 0);
|
|
970
1280
|
rb_define_module_function(mPatron, "libcurl_version_exact", libcurl_version_exact, 0);
|
|
1281
|
+
rb_define_module_function(mPatron, "libcurl_features", libcurl_features, 0);
|
|
971
1282
|
|
|
972
1283
|
cSession = rb_define_class_under(mPatron, "Session", rb_cObject);
|
|
973
|
-
|
|
1284
|
+
/* Defined in Ruby; we only hang constants off it */
|
|
1285
|
+
cRequest = rb_path2class("Patron::Request");
|
|
974
1286
|
rb_define_alloc_func(cSession, session_alloc);
|
|
975
1287
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
rb_define_singleton_method(cSession, "
|
|
980
|
-
rb_define_method(cSession, "
|
|
981
|
-
rb_define_singleton_method(cSession, "unescape", session_unescape, 1);
|
|
982
|
-
rb_define_method(cSession, "unescape", session_unescape, 1);
|
|
983
|
-
|
|
984
|
-
rb_define_private_method(cSession, "handle_request", session_handle_request, 1);
|
|
985
|
-
rb_define_method(cSession, "reset", session_interrupt, 0);
|
|
986
|
-
rb_define_method(cSession, "interrupt", session_interrupt, 0);
|
|
987
|
-
rb_define_private_method(cSession, "add_cookie_file", add_cookie_file, 1);
|
|
988
|
-
rb_define_private_method(cSession, "set_debug_file", set_debug_file, 1);
|
|
1288
|
+
/* escape/unescape are available on the class as well, for Patron::Util */
|
|
1289
|
+
rb_define_singleton_method(cSession, "escape", session_escape, 1);
|
|
1290
|
+
rb_define_method(cSession, "escape", session_escape, 1);
|
|
1291
|
+
rb_define_singleton_method(cSession, "unescape", session_unescape, 1);
|
|
1292
|
+
rb_define_method(cSession, "unescape", session_unescape, 1);
|
|
989
1293
|
rb_define_alias(cSession, "urlencode", "escape");
|
|
990
1294
|
rb_define_alias(cSession, "urldecode", "unescape");
|
|
991
1295
|
|
|
992
|
-
|
|
1296
|
+
rb_define_private_method(cSession, "handle_request", session_handle_request, 1);
|
|
1297
|
+
rb_define_private_method(cSession, "set_cookie_file", session_set_cookie_file, 1);
|
|
1298
|
+
rb_define_private_method(cSession, "set_debug_io", session_set_debug_io, 1);
|
|
1299
|
+
rb_define_method(cSession, "interrupt", session_interrupt, 0);
|
|
1300
|
+
|
|
1301
|
+
rb_define_const(cRequest, "AuthBasic", LONG2NUM(CURLAUTH_BASIC));
|
|
993
1302
|
rb_define_const(cRequest, "AuthDigest", LONG2NUM(CURLAUTH_DIGEST));
|
|
994
|
-
rb_define_const(cRequest, "AuthAny",
|
|
1303
|
+
rb_define_const(cRequest, "AuthAny", LONG2NUM(CURLAUTH_ANY));
|
|
1304
|
+
|
|
1305
|
+
hSSLVersions = shareable_table();
|
|
1306
|
+
table_entry(hSSLVersions, "TLSv1", CURL_SSLVERSION_TLSv1);
|
|
1307
|
+
table_entry(hSSLVersions, "TLSv1_0", CURL_SSLVERSION_TLSv1_0);
|
|
1308
|
+
table_entry(hSSLVersions, "TLSv1_1", CURL_SSLVERSION_TLSv1_1);
|
|
1309
|
+
table_entry(hSSLVersions, "TLSv1_2", CURL_SSLVERSION_TLSv1_2);
|
|
1310
|
+
table_entry(hSSLVersions, "TLSv1_3", CURL_SSLVERSION_TLSv1_3);
|
|
1311
|
+
rb_define_const(cRequest, "SSL_VERSIONS", rb_ractor_make_shareable(hSSLVersions));
|
|
1312
|
+
|
|
1313
|
+
hHTTPVersions = shareable_table();
|
|
1314
|
+
table_entry(hHTTPVersions, "None", CURL_HTTP_VERSION_NONE);
|
|
1315
|
+
table_entry(hHTTPVersions, "HTTPv1_0", CURL_HTTP_VERSION_1_0);
|
|
1316
|
+
table_entry(hHTTPVersions, "HTTPv1_1", CURL_HTTP_VERSION_1_1);
|
|
1317
|
+
table_entry(hHTTPVersions, "HTTPv2_0", CURL_HTTP_VERSION_2_0);
|
|
1318
|
+
table_entry(hHTTPVersions, "HTTPv2_TLS", CURL_HTTP_VERSION_2TLS);
|
|
1319
|
+
table_entry(hHTTPVersions, "HTTPv2_PRIOR", CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE);
|
|
1320
|
+
table_entry(hHTTPVersions, "HTTPv3", CURL_HTTP_VERSION_3);
|
|
1321
|
+
#if LIBCURL_VERSION_NUM >= 0x075800
|
|
1322
|
+
table_entry(hHTTPVersions, "HTTPv3_ONLY", CURL_HTTP_VERSION_3ONLY);
|
|
1323
|
+
#endif
|
|
1324
|
+
rb_define_const(cRequest, "HTTP_VERSIONS", rb_ractor_make_shareable(hHTTPVersions));
|
|
995
1325
|
|
|
996
1326
|
mProxyType = rb_define_module_under(mPatron, "ProxyType");
|
|
997
1327
|
rb_define_const(mProxyType, "HTTP", LONG2NUM(CURLPROXY_HTTP));
|
|
998
1328
|
rb_define_const(mProxyType, "HTTP_1_0", LONG2NUM(CURLPROXY_HTTP_1_0));
|
|
1329
|
+
rb_define_const(mProxyType, "HTTPS", LONG2NUM(CURLPROXY_HTTPS));
|
|
999
1330
|
rb_define_const(mProxyType, "SOCKS4", LONG2NUM(CURLPROXY_SOCKS4));
|
|
1000
1331
|
rb_define_const(mProxyType, "SOCKS5", LONG2NUM(CURLPROXY_SOCKS5));
|
|
1001
1332
|
rb_define_const(mProxyType, "SOCKS4A", LONG2NUM(CURLPROXY_SOCKS4A));
|