patron 0.13.3 → 0.14.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/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +79 -0
- data/.travis.yml +10 -7
- data/CHANGELOG.md +32 -1
- data/Gemfile +15 -1
- data/README.md +80 -30
- data/ext/patron/extconf.rb +7 -4
- data/ext/patron/session_ext.c +360 -155
- data/lib/patron/error.rb +4 -0
- data/lib/patron/request.rb +8 -6
- data/lib/patron/response.rb +5 -0
- data/lib/patron/session.rb +19 -4
- data/lib/patron/version.rb +1 -1
- data/patron.gemspec +8 -15
- data/script/generate_client_certs +65 -0
- data/spec/request_spec.rb +12 -0
- data/spec/session_spec.rb +287 -67
- data/spec/session_ssl_spec.rb +163 -27
- data/spec/spec_helper.rb +16 -7
- data/spec/support/certs/client.p12 +0 -0
- data/spec/support/certs/client_ca.pem +20 -0
- data/spec/support/certs/client_cert.pem +20 -0
- data/spec/support/certs/client_key.pem +30 -0
- data/spec/support/certs/rogue_client.p12 +0 -0
- data/spec/support/config.ru +18 -3
- data/spec/support/fork.rb +70 -0
- data/spec/support/raw_request_capture.rb +93 -0
- data/spec/support/test_server.rb +6 -3
- data/spec/support/valgrind.supp +2 -0
- metadata +22 -125
data/ext/patron/session_ext.c
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
#include <ruby.h>
|
|
2
|
-
#if defined(USE_TBR) && defined(HAVE_THREAD_H)
|
|
3
2
|
#include <ruby/thread.h>
|
|
4
|
-
#endif
|
|
5
3
|
#include <sys/stat.h>
|
|
6
4
|
#include <curl/curl.h>
|
|
7
5
|
#include "membuffer.h"
|
|
@@ -26,10 +24,15 @@ static VALUE ePartialFileError = Qnil;
|
|
|
26
24
|
static VALUE eTimeoutError = Qnil;
|
|
27
25
|
static VALUE eTooManyRedirects = Qnil;
|
|
28
26
|
static VALUE eAborted = Qnil;
|
|
27
|
+
static VALUE eClientCertificateError = Qnil;
|
|
29
28
|
|
|
30
29
|
struct patron_curl_state {
|
|
31
30
|
CURL* handle;
|
|
32
|
-
|
|
31
|
+
CURL* base_handle;
|
|
32
|
+
CURLSH* share;
|
|
33
|
+
/* The String libcurl reads the request body from. CURLOPT_POSTFIELDS does not copy,
|
|
34
|
+
so the String has to stay alive (and marked) until cleanup() */
|
|
35
|
+
VALUE upload_data;
|
|
33
36
|
FILE* download_file;
|
|
34
37
|
FILE* debug_file;
|
|
35
38
|
FILE* request_body_file;
|
|
@@ -39,13 +42,21 @@ struct patron_curl_state {
|
|
|
39
42
|
struct curl_httppost* last;
|
|
40
43
|
membuffer header_buffer;
|
|
41
44
|
membuffer body_buffer;
|
|
42
|
-
|
|
45
|
+
curl_off_t download_byte_limit;
|
|
43
46
|
VALUE user_progress_blk;
|
|
47
|
+
/* A non-local exit (raise, Thread#raise, Thread#kill...) from the progress proc can't
|
|
48
|
+
unwind through libcurl's frames, so it is parked here and resumed after perform() */
|
|
49
|
+
int pending_tag;
|
|
50
|
+
VALUE pending_exception;
|
|
51
|
+
/* Set while a request is in flight. There is one handle and one set of buffers per
|
|
52
|
+
Session, and the transfer runs without the GVL, so a second thread entering
|
|
53
|
+
handle_request would corrupt the first one's request */
|
|
54
|
+
volatile int busy;
|
|
44
55
|
int interrupt;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
curl_off_t dltotal;
|
|
57
|
+
curl_off_t dlnow;
|
|
58
|
+
curl_off_t ultotal;
|
|
59
|
+
curl_off_t ulnow;
|
|
49
60
|
};
|
|
50
61
|
|
|
51
62
|
|
|
@@ -74,16 +85,33 @@ static size_t file_write_handler(void* stream, size_t size, size_t nmemb, FILE*
|
|
|
74
85
|
}
|
|
75
86
|
}
|
|
76
87
|
|
|
77
|
-
static
|
|
88
|
+
static VALUE call_user_rb_progress_blk_unprotected(VALUE vd_curl_state) {
|
|
78
89
|
struct patron_curl_state* state = (struct patron_curl_state*)vd_curl_state;
|
|
79
|
-
|
|
80
|
-
VALUE blk_result = rb_funcall(state->user_progress_blk,
|
|
90
|
+
return rb_funcall(state->user_progress_blk,
|
|
81
91
|
rb_intern("call"), 4,
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
LL2NUM(state->dltotal),
|
|
93
|
+
LL2NUM(state->dlnow),
|
|
94
|
+
LL2NUM(state->ultotal),
|
|
95
|
+
LL2NUM(state->ulnow));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Runs with the GVL re-acquired, from inside a libcurl callback. Whatever the proc
|
|
99
|
+
throws must not propagate from here (see pending_tag), so park it and tell libcurl
|
|
100
|
+
to abort the transfer instead. */
|
|
101
|
+
static void *call_user_rb_progress_blk(void *vd_curl_state) {
|
|
102
|
+
struct patron_curl_state* state = (struct patron_curl_state*)vd_curl_state;
|
|
103
|
+
int tag = 0;
|
|
104
|
+
|
|
105
|
+
rb_protect(call_user_rb_progress_blk_unprotected, (VALUE)state, &tag);
|
|
106
|
+
if (tag) {
|
|
107
|
+
/* Whatever rb_protect caught stays in the errinfo of the thread: for a throw (which is
|
|
108
|
+
how Timeout.timeout unwinds on Rubies before 3.1) it is not an Exception and can't be
|
|
109
|
+
put back with rb_set_errinfo, so we only hold a reference to keep it marked */
|
|
110
|
+
state->pending_tag = tag;
|
|
111
|
+
state->pending_exception = rb_errinfo();
|
|
112
|
+
state->interrupt = INTERRUPT_ABORT;
|
|
113
|
+
}
|
|
114
|
+
return NULL;
|
|
87
115
|
}
|
|
88
116
|
|
|
89
117
|
|
|
@@ -92,7 +120,7 @@ static int call_user_rb_progress_blk(void* vd_curl_state) {
|
|
|
92
120
|
* user calls the "interrupt" method on the session or when the Ruby interpreter
|
|
93
121
|
* is attempting to exit.
|
|
94
122
|
*/
|
|
95
|
-
static int session_progress_handler(void* clientp,
|
|
123
|
+
static int session_progress_handler(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
|
|
96
124
|
struct patron_curl_state* state = (struct patron_curl_state*) clientp;
|
|
97
125
|
state->dltotal = dltotal;
|
|
98
126
|
state->dlnow = dlnow;
|
|
@@ -102,16 +130,8 @@ static int session_progress_handler(void* clientp, size_t dltotal, size_t dlnow,
|
|
|
102
130
|
// If a progress proc has been set, re-acquire the GIL and call it using
|
|
103
131
|
// `call_user_rb_progress_blk`. TODO: use the retval of that proc
|
|
104
132
|
// to permit premature abort
|
|
105
|
-
if(RTEST(state->user_progress_blk)) {
|
|
106
|
-
|
|
107
|
-
// rb_thread_call_without_gvl is not. See https://bugs.ruby-lang.org/issues/5543#note-4
|
|
108
|
-
// > rb_thread_call_with_gvl() is globally-visible (but not in headers)
|
|
109
|
-
// > for 1.9.3: https://bugs.ruby-lang.org/issues/4328
|
|
110
|
-
#if (defined(HAVE_TBR) || defined(HAVE_TCWOGVL)) && defined(USE_TBR)
|
|
111
|
-
rb_thread_call_with_gvl((void *(*)(void *)) call_user_rb_progress_blk, (void*)state);
|
|
112
|
-
#else
|
|
113
|
-
call_user_rb_progress_blk((void*)state);
|
|
114
|
-
#endif
|
|
133
|
+
if(RTEST(state->user_progress_blk) && !state->pending_tag) {
|
|
134
|
+
rb_thread_call_with_gvl(call_user_rb_progress_blk, state);
|
|
115
135
|
}
|
|
116
136
|
|
|
117
137
|
// Set the interrupt if the download byte limit has been reached
|
|
@@ -128,10 +148,18 @@ static int session_progress_handler(void* clientp, size_t dltotal, size_t dlnow,
|
|
|
128
148
|
return state->interrupt;
|
|
129
149
|
}
|
|
130
150
|
|
|
151
|
+
#if LIBCURL_VERSION_NUM < 0x072000
|
|
152
|
+
/* Before 7.32.0 there is only CURLOPT_PROGRESSFUNCTION, which hands over doubles */
|
|
153
|
+
static int session_legacy_progress_handler(void* clientp, double dltotal, double dlnow, double ultotal, double ulnow) {
|
|
154
|
+
return session_progress_handler(clientp, (curl_off_t) dltotal, (curl_off_t) dlnow, (curl_off_t) ultotal, (curl_off_t) ulnow);
|
|
155
|
+
}
|
|
156
|
+
#endif
|
|
131
157
|
|
|
132
|
-
/*----------------------------------------------------------------------------*/
|
|
133
|
-
/* List of active curl sessions */
|
|
134
158
|
|
|
159
|
+
/*
|
|
160
|
+
List of active curl sessions, used exclusively to be able to set interrupts
|
|
161
|
+
for all of them if the Ruby interpreter gets shut down with libCURL requests still in flight.
|
|
162
|
+
*/
|
|
135
163
|
struct patron_curl_state_list {
|
|
136
164
|
struct patron_curl_state *state;
|
|
137
165
|
struct patron_curl_state_list *next;
|
|
@@ -184,29 +212,52 @@ static void session_close_debug_file(struct patron_curl_state *curl) {
|
|
|
184
212
|
}
|
|
185
213
|
|
|
186
214
|
/* Cleans up the patron_curl_state data when the Session object is garbage collected. */
|
|
187
|
-
void session_free(
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
215
|
+
static void session_free(void *ptr) {
|
|
216
|
+
struct patron_curl_state *state = ptr;
|
|
217
|
+
|
|
218
|
+
curl_easy_cleanup(state->base_handle);
|
|
219
|
+
curl_share_cleanup(state->share);
|
|
220
|
+
|
|
221
|
+
session_close_debug_file(state);
|
|
222
|
+
|
|
223
|
+
membuffer_destroy(&state->header_buffer);
|
|
224
|
+
membuffer_destroy(&state->body_buffer);
|
|
192
225
|
|
|
193
|
-
|
|
226
|
+
cs_list_remove(state);
|
|
194
227
|
|
|
195
|
-
|
|
196
|
-
|
|
228
|
+
ruby_xfree(state);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
static void session_mark(void *ptr) {
|
|
232
|
+
struct patron_curl_state *state = ptr;
|
|
233
|
+
|
|
234
|
+
rb_gc_mark(state->user_progress_blk);
|
|
235
|
+
rb_gc_mark(state->upload_data);
|
|
236
|
+
rb_gc_mark(state->pending_exception);
|
|
237
|
+
}
|
|
197
238
|
|
|
198
|
-
|
|
239
|
+
static size_t session_memsize(const void *ptr) {
|
|
240
|
+
const struct patron_curl_state *state = ptr;
|
|
199
241
|
|
|
200
|
-
|
|
242
|
+
return sizeof(*state) + state->header_buffer.capacity + state->body_buffer.capacity;
|
|
201
243
|
}
|
|
202
244
|
|
|
245
|
+
static const rb_data_type_t patron_session_data_type = {
|
|
246
|
+
"Patron::Session",
|
|
247
|
+
{session_mark, session_free, session_memsize,},
|
|
248
|
+
0, 0, RUBY_TYPED_FREE_IMMEDIATELY,
|
|
249
|
+
};
|
|
250
|
+
|
|
203
251
|
/* Allocates patron_curl_state data needed for a new Session object. */
|
|
204
252
|
VALUE session_alloc(VALUE klass) {
|
|
205
253
|
struct patron_curl_state* state;
|
|
206
|
-
VALUE obj =
|
|
254
|
+
VALUE obj = TypedData_Make_Struct(klass, struct patron_curl_state, &patron_session_data_type, state);
|
|
207
255
|
|
|
208
256
|
membuffer_init(&state->header_buffer);
|
|
209
257
|
membuffer_init(&state->body_buffer);
|
|
258
|
+
state->user_progress_blk = Qnil;
|
|
259
|
+
state->upload_data = Qnil;
|
|
260
|
+
state->pending_exception = Qnil;
|
|
210
261
|
cs_list_append(state);
|
|
211
262
|
|
|
212
263
|
/*
|
|
@@ -215,16 +266,42 @@ VALUE session_alloc(VALUE klass) {
|
|
|
215
266
|
reuse the TCP connection and can speed things up if the same resource - like a backend service -
|
|
216
267
|
gets accessed over and over with requests.
|
|
217
268
|
*/
|
|
218
|
-
state->
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
269
|
+
state->share = curl_share_init();
|
|
270
|
+
if (!state->share) {
|
|
271
|
+
rb_raise(rb_eNoMemError, "libcurl could not allocate a share handle");
|
|
272
|
+
}
|
|
273
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
|
|
274
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
|
|
275
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
|
|
276
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
|
|
277
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
|
|
278
|
+
state->base_handle = curl_easy_init();
|
|
279
|
+
if (!state->base_handle) {
|
|
280
|
+
/* obj is already allocated, so session_free() will release the share; curl_share_cleanup(NULL)
|
|
281
|
+
and curl_easy_cleanup(NULL) are no-ops, which keeps that path safe for a half-built state */
|
|
282
|
+
rb_raise(rb_eNoMemError, "libcurl could not allocate an easy handle");
|
|
283
|
+
}
|
|
284
|
+
curl_easy_setopt(state->base_handle, CURLOPT_SHARE, state->share);
|
|
285
|
+
curl_easy_setopt(state->base_handle, CURLOPT_WRITEFUNCTION, &session_write_handler);
|
|
286
|
+
curl_easy_setopt(state->base_handle, CURLOPT_WRITEDATA, &state->body_buffer);
|
|
287
|
+
curl_easy_setopt(state->base_handle, CURLOPT_HEADERFUNCTION, &session_write_handler);
|
|
288
|
+
curl_easy_setopt(state->base_handle, CURLOPT_HEADERDATA, &state->header_buffer);
|
|
289
|
+
curl_easy_setopt(state->base_handle, CURLOPT_NOSIGNAL, 1);
|
|
290
|
+
curl_easy_setopt(state->base_handle, CURLOPT_NOPROGRESS, 0);
|
|
291
|
+
#if LIBCURL_VERSION_NUM >= 0x072000
|
|
292
|
+
/* this is libCURLv7.32.0 or later, supports CURLOPT_XFERINFOFUNCTION */
|
|
293
|
+
curl_easy_setopt(state->base_handle, CURLOPT_XFERINFOFUNCTION, &session_progress_handler);
|
|
294
|
+
#else
|
|
295
|
+
curl_easy_setopt(state->base_handle, CURLOPT_PROGRESSFUNCTION, &session_legacy_progress_handler);
|
|
296
|
+
#endif
|
|
297
|
+
curl_easy_setopt(state->base_handle, CURLOPT_PROGRESSDATA, state);
|
|
298
|
+
#ifdef CURLPROTO_HTTP
|
|
299
|
+
// Security: do not allow Curl to go looking on gopher/SMTP etc.
|
|
300
|
+
// Must prevent situations like this:
|
|
301
|
+
// https://hackerone.com/reports/115748
|
|
302
|
+
curl_easy_setopt(state->base_handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
303
|
+
curl_easy_setopt(state->base_handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
304
|
+
#endif
|
|
228
305
|
|
|
229
306
|
return obj;
|
|
230
307
|
}
|
|
@@ -232,7 +309,7 @@ VALUE session_alloc(VALUE klass) {
|
|
|
232
309
|
/* Return the patron_curl_state from the ruby VALUE which is the Session instance. */
|
|
233
310
|
static struct patron_curl_state* get_patron_curl_state(VALUE self) {
|
|
234
311
|
struct patron_curl_state* state;
|
|
235
|
-
|
|
312
|
+
TypedData_Get_Struct(self, struct patron_curl_state, &patron_session_data_type, state);
|
|
236
313
|
return state;
|
|
237
314
|
}
|
|
238
315
|
|
|
@@ -276,13 +353,25 @@ static VALUE session_escape(VALUE self, VALUE value) {
|
|
|
276
353
|
char* escaped = NULL;
|
|
277
354
|
VALUE retval = Qnil;
|
|
278
355
|
|
|
279
|
-
|
|
280
|
-
|
|
356
|
+
#if LIBCURL_VERSION_NUM >= 0x075200
|
|
357
|
+
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
|
358
|
+
CURL *curl = NULL;
|
|
359
|
+
#else
|
|
360
|
+
CURL* curl = curl_easy_init();
|
|
361
|
+
#endif
|
|
362
|
+
escaped = curl_easy_escape(curl,
|
|
281
363
|
RSTRING_PTR(string),
|
|
282
|
-
(
|
|
364
|
+
rb_long2int(RSTRING_LEN(string)));
|
|
365
|
+
#if LIBCURL_VERSION_NUM >= 0x075200
|
|
366
|
+
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
|
367
|
+
#else
|
|
368
|
+
curl_easy_cleanup(curl);
|
|
369
|
+
#endif
|
|
370
|
+
if (!escaped) {
|
|
371
|
+
rb_raise(ePatronError, "libcurl could not escape the string");
|
|
372
|
+
}
|
|
283
373
|
|
|
284
374
|
retval = rb_str_new2(escaped);
|
|
285
|
-
curl_easy_cleanup(state);
|
|
286
375
|
curl_free(escaped);
|
|
287
376
|
|
|
288
377
|
return retval;
|
|
@@ -297,17 +386,31 @@ static VALUE session_escape(VALUE self, VALUE value) {
|
|
|
297
386
|
static VALUE session_unescape(VALUE self, VALUE value) {
|
|
298
387
|
VALUE string = StringValue(value);
|
|
299
388
|
char* unescaped = NULL;
|
|
389
|
+
int unescaped_len = 0;
|
|
300
390
|
VALUE retval = Qnil;
|
|
301
391
|
|
|
302
|
-
|
|
303
|
-
|
|
392
|
+
#if LIBCURL_VERSION_NUM >= 0x075200
|
|
393
|
+
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
|
394
|
+
CURL *curl = NULL;
|
|
395
|
+
#else
|
|
396
|
+
CURL *curl = curl_easy_init();
|
|
397
|
+
#endif
|
|
398
|
+
/* Ask for the length: the decoded bytes may contain NUL ("%00"), so strlen would cut it short */
|
|
399
|
+
unescaped = curl_easy_unescape(curl,
|
|
304
400
|
RSTRING_PTR(string),
|
|
305
|
-
(
|
|
306
|
-
|
|
401
|
+
rb_long2int(RSTRING_LEN(string)),
|
|
402
|
+
&unescaped_len);
|
|
403
|
+
#if LIBCURL_VERSION_NUM >= 0x075200
|
|
404
|
+
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
|
405
|
+
#else
|
|
406
|
+
curl_easy_cleanup(curl);
|
|
407
|
+
#endif
|
|
408
|
+
if (!unescaped) {
|
|
409
|
+
rb_raise(ePatronError, "libcurl could not unescape the string");
|
|
410
|
+
}
|
|
307
411
|
|
|
308
|
-
retval =
|
|
412
|
+
retval = rb_str_new(unescaped, unescaped_len);
|
|
309
413
|
curl_free(unescaped);
|
|
310
|
-
curl_easy_cleanup(state);
|
|
311
414
|
|
|
312
415
|
return retval;
|
|
313
416
|
}
|
|
@@ -348,8 +451,18 @@ static int formadd_values(VALUE data_key, VALUE data_value, VALUE self) {
|
|
|
348
451
|
VALUE name = rb_obj_as_string(data_key);
|
|
349
452
|
VALUE value = rb_obj_as_string(data_value);
|
|
350
453
|
|
|
351
|
-
|
|
352
|
-
|
|
454
|
+
/* COPY* makes libcurl copy both strings: the PTR* variants would keep pointers into
|
|
455
|
+
Strings nothing holds on to (a Symbol key gets stringified into a temporary), and
|
|
456
|
+
PTRCONTENTS stops at the first NUL byte. Passing the length keeps binary values intact. */
|
|
457
|
+
curl_formadd(&state->post, &state->last,
|
|
458
|
+
CURLFORM_COPYNAME, RSTRING_PTR(name), CURLFORM_NAMELENGTH, (long) RSTRING_LEN(name),
|
|
459
|
+
CURLFORM_COPYCONTENTS, RSTRING_PTR(value),
|
|
460
|
+
#if LIBCURL_VERSION_NUM >= 0x072E00
|
|
461
|
+
CURLFORM_CONTENTLEN, (curl_off_t) RSTRING_LEN(value),
|
|
462
|
+
#else
|
|
463
|
+
CURLFORM_CONTENTSLENGTH, (long) RSTRING_LEN(value),
|
|
464
|
+
#endif
|
|
465
|
+
CURLFORM_END);
|
|
353
466
|
|
|
354
467
|
return 0;
|
|
355
468
|
}
|
|
@@ -359,19 +472,24 @@ static int formadd_files(VALUE data_key, VALUE data_value, VALUE self) {
|
|
|
359
472
|
VALUE name = rb_obj_as_string(data_key);
|
|
360
473
|
VALUE value = rb_obj_as_string(data_value);
|
|
361
474
|
|
|
362
|
-
curl_formadd(&state->post, &state->last,
|
|
363
|
-
|
|
475
|
+
curl_formadd(&state->post, &state->last,
|
|
476
|
+
CURLFORM_COPYNAME, RSTRING_PTR(name), CURLFORM_NAMELENGTH, (long) RSTRING_LEN(name),
|
|
477
|
+
CURLFORM_FILE, StringValueCStr(value), CURLFORM_END);
|
|
364
478
|
|
|
365
479
|
return 0;
|
|
366
480
|
}
|
|
367
481
|
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
|
|
482
|
+
// Use the given String as the CURL request body. The String is kept in the state
|
|
483
|
+
// so that it outlives the (GVL-less) transfer which reads from its buffer.
|
|
484
|
+
static void set_curl_request_body(struct patron_curl_state* state, VALUE data) {
|
|
485
|
+
CURL* curl = state->handle;
|
|
486
|
+
StringValue(data);
|
|
487
|
+
state->upload_data = data;
|
|
488
|
+
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, RSTRING_PTR(state->upload_data));
|
|
371
489
|
#ifdef CURLOPT_POSTFIELDSIZE_LARGE
|
|
372
|
-
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE,
|
|
490
|
+
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t) RSTRING_LEN(state->upload_data));
|
|
373
491
|
#else
|
|
374
|
-
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE,
|
|
492
|
+
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long) RSTRING_LEN(state->upload_data));
|
|
375
493
|
#endif
|
|
376
494
|
}
|
|
377
495
|
|
|
@@ -380,7 +498,7 @@ static void set_chunked_encoding(struct patron_curl_state *state) {
|
|
|
380
498
|
}
|
|
381
499
|
|
|
382
500
|
static FILE* open_file(VALUE filename, const char* perms) {
|
|
383
|
-
FILE* handle = fopen(
|
|
501
|
+
FILE* handle = fopen(StringValueCStr(filename), perms);
|
|
384
502
|
if (!handle) {
|
|
385
503
|
rb_raise(rb_eArgError, "Unable to open specified file.");
|
|
386
504
|
}
|
|
@@ -410,7 +528,6 @@ static long floating_rb_seconds_to_milliseconds(VALUE r_seconds) {
|
|
|
410
528
|
}
|
|
411
529
|
|
|
412
530
|
static void set_request_body(struct patron_curl_state* state, VALUE stringable_or_file) {
|
|
413
|
-
CURL* curl = state->handle;
|
|
414
531
|
if(rb_respond_to(stringable_or_file, rb_intern("to_path"))) {
|
|
415
532
|
// Set up a file read callback (read the entire request body from a file).
|
|
416
533
|
// Instead of using the Ruby file reads, use #to_path to obtain the
|
|
@@ -420,10 +537,7 @@ static void set_request_body(struct patron_curl_state* state, VALUE stringable_o
|
|
|
420
537
|
set_request_body_file(state, r_path_str);
|
|
421
538
|
} else {
|
|
422
539
|
// Set the request body from a String
|
|
423
|
-
|
|
424
|
-
long len = RSTRING_LEN(data);
|
|
425
|
-
state->upload_buf = StringValuePtr(data);
|
|
426
|
-
set_curl_request_body(curl, state->upload_buf, len);
|
|
540
|
+
set_curl_request_body(state, rb_funcall(stringable_or_file, rb_intern("to_s"), 0));
|
|
427
541
|
}
|
|
428
542
|
}
|
|
429
543
|
|
|
@@ -433,7 +547,7 @@ static void set_request_body(struct patron_curl_state* state, VALUE stringable_o
|
|
|
433
547
|
*/
|
|
434
548
|
static void set_options_from_request(VALUE self, VALUE request) {
|
|
435
549
|
struct patron_curl_state* state = get_patron_curl_state(self);
|
|
436
|
-
CURL* curl = state->
|
|
550
|
+
CURL* curl = curl_easy_duphandle(state->base_handle);
|
|
437
551
|
|
|
438
552
|
ID action = Qnil;
|
|
439
553
|
VALUE headers = Qnil;
|
|
@@ -447,15 +561,28 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
|
447
561
|
VALUE insecure = Qnil;
|
|
448
562
|
VALUE cacert = Qnil;
|
|
449
563
|
VALUE ssl_version = Qnil;
|
|
564
|
+
VALUE ssl_cert = Qnil;
|
|
565
|
+
VALUE ssl_cert_type = Qnil;
|
|
566
|
+
VALUE ssl_key = Qnil;
|
|
567
|
+
VALUE ssl_key_password = Qnil;
|
|
450
568
|
VALUE http_version = Qnil;
|
|
451
569
|
VALUE buffer_size = Qnil;
|
|
452
|
-
VALUE action_name =
|
|
453
|
-
VALUE a_c_encoding =
|
|
454
|
-
VALUE download_byte_limit =
|
|
455
|
-
VALUE maybe_progress_proc =
|
|
570
|
+
VALUE action_name = Qnil;
|
|
571
|
+
VALUE a_c_encoding = Qnil;
|
|
572
|
+
VALUE download_byte_limit = Qnil;
|
|
573
|
+
VALUE maybe_progress_proc = Qnil;
|
|
574
|
+
|
|
575
|
+
/* Assign before anything can raise, so that cleanup() frees this handle and not a stale one */
|
|
576
|
+
state->handle = curl;
|
|
577
|
+
action_name = rb_funcall(request, rb_intern("action"), 0);
|
|
578
|
+
a_c_encoding = rb_funcall(request, rb_intern("automatic_content_encoding"), 0);
|
|
579
|
+
download_byte_limit = rb_funcall(request, rb_intern("download_byte_limit"), 0);
|
|
580
|
+
maybe_progress_proc = rb_funcall(request, rb_intern("progress_callback"), 0);
|
|
581
|
+
|
|
582
|
+
curl_easy_setopt(curl, CURLOPT_SHARE, state->share);
|
|
456
583
|
|
|
457
584
|
if (RTEST(download_byte_limit)) {
|
|
458
|
-
state->download_byte_limit =
|
|
585
|
+
state->download_byte_limit = (curl_off_t) NUM2LL(download_byte_limit);
|
|
459
586
|
} else {
|
|
460
587
|
state->download_byte_limit = 0;
|
|
461
588
|
}
|
|
@@ -475,7 +602,7 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
|
475
602
|
}
|
|
476
603
|
|
|
477
604
|
action = SYM2ID(action_name);
|
|
478
|
-
if(rb_funcall(request, rb_intern("force_ipv4"), 0)) {
|
|
605
|
+
if (RTEST(rb_funcall(request, rb_intern("force_ipv4"), 0))) {
|
|
479
606
|
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
|
480
607
|
}
|
|
481
608
|
if (action == rb_intern("get")) {
|
|
@@ -541,11 +668,8 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
|
541
668
|
} else if (action == rb_intern("delete")) {
|
|
542
669
|
VALUE data = rb_funcall(request, rb_intern("upload_data"), 0);
|
|
543
670
|
if (RTEST(data)) {
|
|
544
|
-
long len = RSTRING_LEN(data);
|
|
545
|
-
state->upload_buf = StringValuePtr(data);
|
|
546
671
|
curl_easy_setopt(curl, CURLOPT_POST, 1);
|
|
547
|
-
|
|
548
|
-
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, len);
|
|
672
|
+
set_request_body(state, data);
|
|
549
673
|
}
|
|
550
674
|
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
551
675
|
|
|
@@ -578,13 +702,6 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
|
578
702
|
}
|
|
579
703
|
curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
|
|
580
704
|
|
|
581
|
-
#ifdef CURLPROTO_HTTP
|
|
582
|
-
// Security: do not allow Curl to go looking on gopher/SMTP etc.
|
|
583
|
-
// Must prevent situations like this:
|
|
584
|
-
// https://hackerone.com/reports/115748
|
|
585
|
-
curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
586
|
-
curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
|
587
|
-
#endif
|
|
588
705
|
|
|
589
706
|
timeout = rb_funcall(request, rb_intern("timeout"), 0);
|
|
590
707
|
if (RTEST(timeout)) {
|
|
@@ -598,22 +715,22 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
|
598
715
|
|
|
599
716
|
timeout = rb_funcall(request, rb_intern("dns_cache_timeout"), 0);
|
|
600
717
|
if (RTEST(timeout)) {
|
|
601
|
-
curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT,
|
|
718
|
+
curl_easy_setopt(curl, CURLOPT_DNS_CACHE_TIMEOUT, NUM2LONG(timeout));
|
|
602
719
|
}
|
|
603
720
|
|
|
604
721
|
VALUE low_speed_time = rb_funcall(request, rb_intern("low_speed_time"), 0);
|
|
605
722
|
if(RTEST(low_speed_time)) {
|
|
606
|
-
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME,
|
|
723
|
+
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, NUM2LONG(low_speed_time));
|
|
607
724
|
}
|
|
608
725
|
|
|
609
726
|
VALUE low_speed_limit_bytes_per_second = rb_funcall(request, rb_intern("low_speed_limit"), 0);
|
|
610
727
|
if(RTEST(low_speed_limit_bytes_per_second)) {
|
|
611
|
-
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT,
|
|
728
|
+
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, NUM2LONG(low_speed_limit_bytes_per_second));
|
|
612
729
|
}
|
|
613
730
|
|
|
614
731
|
redirects = rb_funcall(request, rb_intern("max_redirects"), 0);
|
|
615
732
|
if (RTEST(redirects)) {
|
|
616
|
-
|
|
733
|
+
long r = NUM2LONG(redirects);
|
|
617
734
|
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, r == 0 ? 0 : 1);
|
|
618
735
|
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, r);
|
|
619
736
|
}
|
|
@@ -713,22 +830,63 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
|
713
830
|
}
|
|
714
831
|
}
|
|
715
832
|
|
|
833
|
+
ssl_cert = rb_funcall(request, rb_intern("ssl_cert"), 0);
|
|
834
|
+
if(RTEST(ssl_cert)) {
|
|
835
|
+
ssl_cert = FilePathValue(ssl_cert);
|
|
836
|
+
curl_easy_setopt(curl, CURLOPT_SSLCERT, StringValueCStr(ssl_cert));
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
ssl_cert_type = rb_funcall(request, rb_intern("ssl_cert_type"), 0);
|
|
840
|
+
if(RTEST(ssl_cert_type)) {
|
|
841
|
+
ssl_cert_type = rb_obj_as_string(ssl_cert_type);
|
|
842
|
+
curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, StringValueCStr(ssl_cert_type));
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
ssl_key = rb_funcall(request, rb_intern("ssl_key"), 0);
|
|
846
|
+
if(RTEST(ssl_key)) {
|
|
847
|
+
ssl_key = FilePathValue(ssl_key);
|
|
848
|
+
curl_easy_setopt(curl, CURLOPT_SSLKEY, StringValueCStr(ssl_key));
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
ssl_key_password = rb_funcall(request, rb_intern("ssl_key_password"), 0);
|
|
852
|
+
if(RTEST(ssl_key_password)) {
|
|
853
|
+
curl_easy_setopt(curl, CURLOPT_KEYPASSWD, StringValueCStr(ssl_key_password));
|
|
854
|
+
}
|
|
855
|
+
|
|
716
856
|
cacert = rb_funcall(request, rb_intern("cacert"), 0);
|
|
717
857
|
if(RTEST(cacert)) {
|
|
718
|
-
|
|
858
|
+
cacert = FilePathValue(cacert);
|
|
859
|
+
curl_easy_setopt(curl, CURLOPT_CAINFO, StringValueCStr(cacert));
|
|
719
860
|
}
|
|
720
861
|
|
|
721
862
|
buffer_size = rb_funcall(request, rb_intern("buffer_size"), 0);
|
|
722
863
|
if (RTEST(buffer_size)) {
|
|
723
864
|
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, NUM2LONG(buffer_size));
|
|
724
865
|
}
|
|
866
|
+
}
|
|
725
867
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
868
|
+
/* Store one of curl's transfer marks (seconds since the request started) under +key+. */
|
|
869
|
+
static void set_timing(CURL* curl, VALUE timings, const char* key, CURLINFO info) {
|
|
870
|
+
double seconds = 0;
|
|
871
|
+
if (curl_easy_getinfo(curl, info, &seconds) == CURLE_OK) {
|
|
872
|
+
rb_hash_aset(timings, ID2SYM(rb_intern(key)), rb_float_new(seconds));
|
|
729
873
|
}
|
|
730
874
|
}
|
|
731
875
|
|
|
876
|
+
/* curl's cumulative transfer marks: namelookup <= connect <= appconnect <= pretransfer
|
|
877
|
+
* <= starttransfer <= total. They are read after the transfer, so they cost no GVL time. */
|
|
878
|
+
static VALUE create_timings(CURL* curl) {
|
|
879
|
+
VALUE timings = rb_hash_new();
|
|
880
|
+
set_timing(curl, timings, "namelookup", CURLINFO_NAMELOOKUP_TIME);
|
|
881
|
+
set_timing(curl, timings, "connect", CURLINFO_CONNECT_TIME);
|
|
882
|
+
set_timing(curl, timings, "appconnect", CURLINFO_APPCONNECT_TIME);
|
|
883
|
+
set_timing(curl, timings, "pretransfer", CURLINFO_PRETRANSFER_TIME);
|
|
884
|
+
set_timing(curl, timings, "starttransfer", CURLINFO_STARTTRANSFER_TIME);
|
|
885
|
+
set_timing(curl, timings, "redirect", CURLINFO_REDIRECT_TIME);
|
|
886
|
+
set_timing(curl, timings, "total", CURLINFO_TOTAL_TIME);
|
|
887
|
+
return timings;
|
|
888
|
+
}
|
|
889
|
+
|
|
732
890
|
/* Use the info in a Curl handle to create a new Response object. */
|
|
733
891
|
static VALUE create_response(VALUE self, CURL* curl, VALUE header_buffer, VALUE body_buffer) {
|
|
734
892
|
VALUE args[6] = { Qnil, Qnil, Qnil, Qnil, Qnil, Qnil };
|
|
@@ -736,22 +894,26 @@ static VALUE create_response(VALUE self, CURL* curl, VALUE header_buffer, VALUE
|
|
|
736
894
|
long code = 0;
|
|
737
895
|
long count = 0;
|
|
738
896
|
VALUE responseKlass = Qnil;
|
|
897
|
+
VALUE response = Qnil;
|
|
739
898
|
|
|
740
899
|
curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &effective_url);
|
|
741
900
|
args[0] = rb_str_new2(effective_url);
|
|
742
901
|
|
|
743
902
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
|
744
|
-
args[1] =
|
|
903
|
+
args[1] = LONG2NUM(code);
|
|
745
904
|
|
|
746
905
|
curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &count);
|
|
747
|
-
args[2] =
|
|
906
|
+
args[2] = LONG2NUM(count);
|
|
748
907
|
|
|
749
908
|
args[3] = header_buffer;
|
|
750
909
|
args[4] = body_buffer;
|
|
751
910
|
args[5] = rb_funcall(self, rb_intern("default_response_charset"), 0);
|
|
752
911
|
|
|
753
912
|
responseKlass = rb_funcall(self, rb_intern("response_class"), 0);
|
|
754
|
-
|
|
913
|
+
response = rb_class_new_instance(6, args, responseKlass);
|
|
914
|
+
/* Set after construction so custom response classes keep their #initialize signature. */
|
|
915
|
+
rb_iv_set(response, "@timings", create_timings(curl));
|
|
916
|
+
return response;
|
|
755
917
|
}
|
|
756
918
|
|
|
757
919
|
/* Raise an exception based on the Curl error code. */
|
|
@@ -766,6 +928,7 @@ static VALUE select_error(CURLcode code) {
|
|
|
766
928
|
case CURLE_OPERATION_TIMEDOUT: error = eTimeoutError; break;
|
|
767
929
|
case CURLE_TOO_MANY_REDIRECTS: error = eTooManyRedirects; break;
|
|
768
930
|
case CURLE_ABORTED_BY_CALLBACK: error = eAborted; break;
|
|
931
|
+
case CURLE_SSL_CERTPROBLEM: error = eClientCertificateError; break;
|
|
769
932
|
default: error = ePatronError;
|
|
770
933
|
}
|
|
771
934
|
|
|
@@ -773,6 +936,18 @@ static VALUE select_error(CURLcode code) {
|
|
|
773
936
|
}
|
|
774
937
|
|
|
775
938
|
|
|
939
|
+
struct perform_context {
|
|
940
|
+
CURL *curl;
|
|
941
|
+
CURLcode code;
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
static void *perform_without_gvl(void *ptr) {
|
|
945
|
+
struct perform_context *context = ptr;
|
|
946
|
+
|
|
947
|
+
context->code = curl_easy_perform(context->curl);
|
|
948
|
+
return NULL;
|
|
949
|
+
}
|
|
950
|
+
|
|
776
951
|
/* Uses as the unblocking function when the thread running Patron gets
|
|
777
952
|
signaled. The important difference with session_interrupt is that we
|
|
778
953
|
are not allowed to touch any Ruby structures while outside the GIL,
|
|
@@ -787,72 +962,71 @@ void session_ubf_abort(void* patron_state) {
|
|
|
787
962
|
static VALUE perform_request(VALUE self) {
|
|
788
963
|
struct patron_curl_state *state = get_patron_curl_state(self);
|
|
789
964
|
CURL* curl = state->handle;
|
|
790
|
-
|
|
791
|
-
membuffer* body_buffer = NULL;
|
|
792
|
-
CURLcode ret = 0;
|
|
965
|
+
struct perform_context context = {curl, 0};
|
|
793
966
|
|
|
794
967
|
state->interrupt = 0; /* clear the interrupt flag */
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
968
|
+
state->pending_tag = 0;
|
|
969
|
+
state->pending_exception = Qnil;
|
|
970
|
+
|
|
971
|
+
rb_thread_call_without_gvl(perform_without_gvl, &context, session_ubf_abort, state);
|
|
972
|
+
|
|
973
|
+
/* The progress proc threw: resume that exit now that libcurl is done with the handle.
|
|
974
|
+
cleanup() still runs, since we are inside the rb_ensure of session_handle_request. */
|
|
975
|
+
if (state->pending_tag) {
|
|
976
|
+
int tag = state->pending_tag;
|
|
977
|
+
VALUE exception = state->pending_exception;
|
|
978
|
+
state->pending_tag = 0;
|
|
979
|
+
state->pending_exception = Qnil;
|
|
980
|
+
/* errinfo is only an Exception for a raise; a throw leaves an internal object there and
|
|
981
|
+
Thread#kill a Fixnum, both of which rb_obj_is_kind_of must not be asked about */
|
|
982
|
+
if (RB_TYPE_P(exception, T_OBJECT) && rb_obj_is_kind_of(exception, rb_eException)) {
|
|
983
|
+
rb_exc_raise(exception);
|
|
984
|
+
}
|
|
985
|
+
rb_jump_tag(tag);
|
|
810
986
|
}
|
|
811
987
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
ret = (CURLcode) rb_thread_call_without_gvl(
|
|
815
|
-
(void *(*)(void *)) curl_easy_perform, curl,
|
|
816
|
-
session_ubf_abort, (void*)state
|
|
817
|
-
);
|
|
818
|
-
#else
|
|
819
|
-
ret = (CURLcode) rb_thread_blocking_region(
|
|
820
|
-
(rb_blocking_function_t*) curl_easy_perform, curl,
|
|
821
|
-
session_ubf_abort, (void*)state
|
|
822
|
-
);
|
|
823
|
-
#endif
|
|
824
|
-
#else
|
|
825
|
-
ret = curl_easy_perform(curl);
|
|
826
|
-
#endif
|
|
827
|
-
|
|
828
|
-
if (CURLE_OK == ret) {
|
|
829
|
-
VALUE header_str = membuffer_to_rb_str(header_buffer);
|
|
988
|
+
if (CURLE_OK == context.code) {
|
|
989
|
+
VALUE header_str = membuffer_to_rb_str(&state->header_buffer);
|
|
830
990
|
VALUE body_str = Qnil;
|
|
831
|
-
if (!state->download_file) { body_str = membuffer_to_rb_str(body_buffer); }
|
|
991
|
+
if (!state->download_file) { body_str = membuffer_to_rb_str(&state->body_buffer); }
|
|
832
992
|
|
|
833
993
|
curl_easy_setopt(curl, CURLOPT_COOKIELIST, "FLUSH"); // Flush cookies to the cookie jar
|
|
834
994
|
|
|
835
995
|
return create_response(self, curl, header_str, body_str);
|
|
836
996
|
} else {
|
|
837
|
-
rb_raise(select_error(
|
|
997
|
+
rb_raise(select_error(context.code), "%s", state->error_buf);
|
|
838
998
|
}
|
|
839
999
|
}
|
|
840
1000
|
|
|
1001
|
+
/* Setting the options can raise too (a Request attribute of the wrong type, say),
|
|
1002
|
+
* and this needs to run under rb_ensure so that the half-configured handle, headers and
|
|
1003
|
+
* open files get cleaned up instead of leaking into the next request on this Session.
|
|
1004
|
+
*/
|
|
1005
|
+
static VALUE prepare_and_perform_request(VALUE args) {
|
|
1006
|
+
VALUE self = rb_ary_entry(args, 0);
|
|
1007
|
+
set_options_from_request(self, rb_ary_entry(args, 1));
|
|
1008
|
+
return perform_request(self);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
841
1011
|
/* Cleanup after each request by resetting the Curl handle and deallocating
|
|
842
1012
|
* all request related objects such as the header slist.
|
|
843
1013
|
*/
|
|
844
1014
|
static VALUE cleanup(VALUE self) {
|
|
845
1015
|
struct patron_curl_state *state = get_patron_curl_state(self);
|
|
846
|
-
|
|
1016
|
+
curl_easy_cleanup(state->handle);
|
|
1017
|
+
state->handle = NULL;
|
|
847
1018
|
|
|
848
1019
|
if (state->headers) {
|
|
849
1020
|
curl_slist_free_all(state->headers);
|
|
850
1021
|
state->headers = NULL;
|
|
851
1022
|
}
|
|
1023
|
+
membuffer_clear(&state->header_buffer);
|
|
852
1024
|
|
|
853
1025
|
if (state->download_file) {
|
|
854
1026
|
fclose(state->download_file);
|
|
855
1027
|
state->download_file = NULL;
|
|
1028
|
+
} else {
|
|
1029
|
+
membuffer_clear(&state->body_buffer);
|
|
856
1030
|
}
|
|
857
1031
|
|
|
858
1032
|
if (state->request_body_file) {
|
|
@@ -866,7 +1040,10 @@ static VALUE cleanup(VALUE self) {
|
|
|
866
1040
|
state->last = NULL;
|
|
867
1041
|
}
|
|
868
1042
|
|
|
869
|
-
state->
|
|
1043
|
+
state->upload_data = Qnil;
|
|
1044
|
+
state->pending_tag = 0;
|
|
1045
|
+
state->pending_exception = Qnil;
|
|
1046
|
+
state->busy = 0;
|
|
870
1047
|
|
|
871
1048
|
return Qnil;
|
|
872
1049
|
}
|
|
@@ -885,8 +1062,16 @@ static VALUE cleanup(VALUE self) {
|
|
|
885
1062
|
* @return [Patron::Response] the result of calling `response_class` on the Session
|
|
886
1063
|
*/
|
|
887
1064
|
static VALUE session_handle_request(VALUE self, VALUE request) {
|
|
888
|
-
|
|
889
|
-
|
|
1065
|
+
struct patron_curl_state *state = get_patron_curl_state(self);
|
|
1066
|
+
VALUE args = Qnil;
|
|
1067
|
+
|
|
1068
|
+
if (state->busy) {
|
|
1069
|
+
rb_raise(ePatronError, "Session is already performing a request on another thread; use one Session per thread");
|
|
1070
|
+
}
|
|
1071
|
+
state->busy = 1;
|
|
1072
|
+
|
|
1073
|
+
args = rb_ary_new3(2, self, request);
|
|
1074
|
+
return rb_ensure(&prepare_and_perform_request, args, &cleanup, self);
|
|
890
1075
|
}
|
|
891
1076
|
|
|
892
1077
|
/* Interrupt any currently executing request. This will cause the current
|
|
@@ -912,12 +1097,12 @@ static VALUE session_interrupt(VALUE self) {
|
|
|
912
1097
|
*/
|
|
913
1098
|
static VALUE add_cookie_file(VALUE self, VALUE file) {
|
|
914
1099
|
struct patron_curl_state *state = get_patron_curl_state(self);
|
|
915
|
-
CURL* curl = state->
|
|
1100
|
+
CURL* curl = state->base_handle;
|
|
916
1101
|
char* file_path = NULL;
|
|
917
1102
|
|
|
918
1103
|
// FIXME: http://websystemsengineering.blogspot.nl/2013/03/curloptcookiefile-vs-curloptcookiejar.html
|
|
919
|
-
file_path =
|
|
920
|
-
if (
|
|
1104
|
+
file_path = StringValueCStr(file);
|
|
1105
|
+
if (strlen(file_path) != 0) {
|
|
921
1106
|
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, file_path);
|
|
922
1107
|
}
|
|
923
1108
|
curl_easy_setopt(curl, CURLOPT_COOKIEFILE, file_path);
|
|
@@ -933,15 +1118,18 @@ static VALUE add_cookie_file(VALUE self, VALUE file) {
|
|
|
933
1118
|
*/
|
|
934
1119
|
static VALUE set_debug_file(VALUE self, VALUE file) {
|
|
935
1120
|
struct patron_curl_state *state = get_patron_curl_state(self);
|
|
936
|
-
|
|
1121
|
+
CURL *curl = state->base_handle;
|
|
1122
|
+
char *file_path = StringValueCStr(file);
|
|
937
1123
|
|
|
938
1124
|
session_close_debug_file(state);
|
|
939
1125
|
|
|
940
|
-
if(
|
|
1126
|
+
if(strlen(file_path) != 0) {
|
|
941
1127
|
state->debug_file = open_file(file, "wb");
|
|
942
1128
|
} else {
|
|
943
1129
|
state->debug_file = stderr;
|
|
944
1130
|
}
|
|
1131
|
+
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
|
|
1132
|
+
curl_easy_setopt(curl, CURLOPT_STDERR, state->debug_file);
|
|
945
1133
|
|
|
946
1134
|
return self;
|
|
947
1135
|
}
|
|
@@ -958,6 +1146,22 @@ void Init_session_ext() {
|
|
|
958
1146
|
|
|
959
1147
|
mPatron = rb_define_module("Patron");
|
|
960
1148
|
|
|
1149
|
+
/* These classes are defined in Ruby and only looked up here, so unlike the ones from
|
|
1150
|
+
rb_define_class the GC does not know we hold them: register them, or compaction moves
|
|
1151
|
+
them out from under us and rb_raise ends up instantiating garbage */
|
|
1152
|
+
rb_global_variable(&ePatronError);
|
|
1153
|
+
rb_global_variable(&eUnsupportedProtocol);
|
|
1154
|
+
rb_global_variable(&eUnsupportedSSLVersion);
|
|
1155
|
+
rb_global_variable(&eUnsupportedHTTPVersion);
|
|
1156
|
+
rb_global_variable(&eURLFormatError);
|
|
1157
|
+
rb_global_variable(&eHostResolutionError);
|
|
1158
|
+
rb_global_variable(&eConnectionFailed);
|
|
1159
|
+
rb_global_variable(&ePartialFileError);
|
|
1160
|
+
rb_global_variable(&eTimeoutError);
|
|
1161
|
+
rb_global_variable(&eTooManyRedirects);
|
|
1162
|
+
rb_global_variable(&eAborted);
|
|
1163
|
+
rb_global_variable(&eClientCertificateError);
|
|
1164
|
+
|
|
961
1165
|
ePatronError = rb_const_get(mPatron, rb_intern("Error"));
|
|
962
1166
|
|
|
963
1167
|
eUnsupportedProtocol = rb_const_get(mPatron, rb_intern("UnsupportedProtocol"));
|
|
@@ -970,6 +1174,7 @@ void Init_session_ext() {
|
|
|
970
1174
|
eTimeoutError = rb_const_get(mPatron, rb_intern("TimeoutError"));
|
|
971
1175
|
eTooManyRedirects = rb_const_get(mPatron, rb_intern("TooManyRedirects"));
|
|
972
1176
|
eAborted = rb_const_get(mPatron, rb_intern("Aborted"));
|
|
1177
|
+
eClientCertificateError = rb_const_get(mPatron, rb_intern("ClientCertificateError"));
|
|
973
1178
|
|
|
974
1179
|
rb_define_module_function(mPatron, "libcurl_version", libcurl_version, 0);
|
|
975
1180
|
rb_define_module_function(mPatron, "libcurl_version_exact", libcurl_version_exact, 0);
|
|
@@ -986,11 +1191,11 @@ void Init_session_ext() {
|
|
|
986
1191
|
rb_define_singleton_method(cSession, "unescape", session_unescape, 1);
|
|
987
1192
|
rb_define_method(cSession, "unescape", session_unescape, 1);
|
|
988
1193
|
|
|
989
|
-
|
|
1194
|
+
rb_define_private_method(cSession, "handle_request", session_handle_request, 1);
|
|
990
1195
|
rb_define_method(cSession, "reset", session_interrupt, 0);
|
|
991
1196
|
rb_define_method(cSession, "interrupt", session_interrupt, 0);
|
|
992
|
-
|
|
993
|
-
|
|
1197
|
+
rb_define_private_method(cSession, "add_cookie_file", add_cookie_file, 1);
|
|
1198
|
+
rb_define_private_method(cSession, "set_debug_file", set_debug_file, 1);
|
|
994
1199
|
rb_define_alias(cSession, "urlencode", "escape");
|
|
995
1200
|
rb_define_alias(cSession, "urldecode", "unescape");
|
|
996
1201
|
|