patron 0.14.0 → 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 +77 -0
- data/README.md +83 -49
- data/ext/patron/extconf.rb +54 -23
- data/ext/patron/session_ext.c +1044 -923
- data/lib/patron/compat.rb +57 -0
- data/lib/patron/error.rb +21 -13
- data/lib/patron/header_parser.rb +27 -25
- data/lib/patron/request.rb +98 -144
- data/lib/patron/response.rb +28 -24
- data/lib/patron/response_decoding.rb +60 -67
- data/lib/patron/session.rb +114 -108
- data/lib/patron/util.rb +14 -12
- data/lib/patron/version.rb +3 -1
- data/lib/patron.rb +9 -15
- metadata +12 -58
- data/.autotest +0 -15
- data/.github/dependabot.yml +0 -6
- data/.github/workflows/ci.yml +0 -79
- data/.gitignore +0 -10
- data/.rspec +0 -0
- data/.travis.yml +0 -13
- data/.yardopts +0 -1
- data/Gemfile +0 -18
- 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 -39
- data/pic.png +0 -0
- data/script/console +0 -11
- data/script/generate_client_certs +0 -65
- 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 -89
- 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 -865
- data/spec/session_ssl_spec.rb +0 -431
- data/spec/spec_helper.rb +0 -40
- data/spec/support/certs/cacert.pem +0 -36
- data/spec/support/certs/client.p12 +0 -0
- data/spec/support/certs/client_ca.pem +0 -20
- data/spec/support/certs/client_cert.pem +0 -20
- data/spec/support/certs/client_key.pem +0 -30
- data/spec/support/certs/privkey.pem +0 -51
- data/spec/support/certs/rogue_client.p12 +0 -0
- data/spec/support/config.ru +0 -153
- data/spec/support/fork.rb +0 -70
- data/spec/support/raw_request_capture.rb +0 -93
- data/spec/support/test_server.rb +0 -29
- data/spec/support/valgrind.supp +0 -2
- data/spec/util_spec.rb +0 -71
data/ext/patron/session_ext.c
CHANGED
|
@@ -1,1154 +1,1253 @@
|
|
|
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
|
-
static VALUE
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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;
|
|
39
108
|
char error_buf[CURL_ERROR_SIZE];
|
|
40
|
-
struct curl_slist* headers;
|
|
41
|
-
struct curl_httppost* post;
|
|
42
|
-
struct curl_httppost* last;
|
|
43
|
-
membuffer header_buffer;
|
|
44
|
-
membuffer body_buffer;
|
|
45
109
|
curl_off_t download_byte_limit;
|
|
46
|
-
VALUE
|
|
47
|
-
/*
|
|
48
|
-
|
|
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. */
|
|
49
117
|
int pending_tag;
|
|
50
|
-
VALUE
|
|
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;
|
|
55
|
-
int interrupt;
|
|
56
|
-
curl_off_t dltotal;
|
|
57
|
-
curl_off_t dlnow;
|
|
58
|
-
curl_off_t ultotal;
|
|
59
|
-
curl_off_t ulnow;
|
|
60
|
-
};
|
|
118
|
+
VALUE pending_errinfo;
|
|
61
119
|
|
|
120
|
+
volatile sig_atomic_t interrupt;
|
|
121
|
+
volatile sig_atomic_t busy;
|
|
122
|
+
};
|
|
62
123
|
|
|
63
124
|
/*----------------------------------------------------------------------------*/
|
|
64
|
-
/*
|
|
65
|
-
|
|
66
|
-
/* Takes data streamed from libcurl and writes it to a Ruby string buffer. */
|
|
67
|
-
static size_t session_write_handler(char* stream, size_t size, size_t nmemb, membuffer* buf) {
|
|
68
|
-
int rc = membuffer_append(buf, stream, size * nmemb);
|
|
125
|
+
/* TypedData */
|
|
69
126
|
|
|
70
|
-
|
|
71
|
-
|
|
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
|
+
}
|
|
72
138
|
|
|
73
|
-
|
|
74
|
-
|
|
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);
|
|
75
151
|
}
|
|
76
152
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
153
|
+
static void session_compact(void *ptr) {
|
|
154
|
+
struct patron_session *s = ptr;
|
|
155
|
+
|
|
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);
|
|
86
162
|
}
|
|
87
163
|
|
|
88
|
-
static
|
|
89
|
-
|
|
90
|
-
return
|
|
91
|
-
rb_intern("call"), 4,
|
|
92
|
-
LL2NUM(state->dltotal),
|
|
93
|
-
LL2NUM(state->dlnow),
|
|
94
|
-
LL2NUM(state->ultotal),
|
|
95
|
-
LL2NUM(state->ulnow));
|
|
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);
|
|
96
167
|
}
|
|
97
168
|
|
|
98
|
-
/*
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
static
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
};
|
|
104
187
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
return NULL;
|
|
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;
|
|
115
196
|
}
|
|
116
197
|
|
|
198
|
+
#define SESSION_WRITE(s, field, value) RB_OBJ_WRITE((s)->self, &(s)->field, (value))
|
|
117
199
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
* user calls the "interrupt" method on the session or when the Ruby interpreter
|
|
121
|
-
* is attempting to exit.
|
|
122
|
-
*/
|
|
123
|
-
static int session_progress_handler(void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
|
|
124
|
-
struct patron_curl_state* state = (struct patron_curl_state*) clientp;
|
|
125
|
-
state->dltotal = dltotal;
|
|
126
|
-
state->dlnow = dlnow;
|
|
127
|
-
state->ultotal = ultotal;
|
|
128
|
-
state->ulnow = ulnow;
|
|
129
|
-
|
|
130
|
-
// If a progress proc has been set, re-acquire the GIL and call it using
|
|
131
|
-
// `call_user_rb_progress_blk`. TODO: use the retval of that proc
|
|
132
|
-
// to permit premature abort
|
|
133
|
-
if(RTEST(state->user_progress_blk) && !state->pending_tag) {
|
|
134
|
-
rb_thread_call_with_gvl(call_user_rb_progress_blk, state);
|
|
135
|
-
}
|
|
200
|
+
/*----------------------------------------------------------------------------*/
|
|
201
|
+
/* Errors */
|
|
136
202
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
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;
|
|
140
215
|
}
|
|
141
|
-
|
|
142
|
-
// If the interrupt value is anything except 0, the perform() call
|
|
143
|
-
// will be aborted by libCURL.
|
|
144
|
-
// Note however that some older versions of libcurl have a bug which
|
|
145
|
-
// makes the return value of this callback be ignored, and the request
|
|
146
|
-
// will actually proceed undeterred.
|
|
147
|
-
// See https://sourceforge.net/p/curl/bugs/1318/
|
|
148
|
-
return state->interrupt;
|
|
149
216
|
}
|
|
150
217
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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);
|
|
155
226
|
}
|
|
156
|
-
#endif
|
|
157
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)
|
|
158
234
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
for all of them if the Ruby interpreter gets shut down with libCURL requests still in flight.
|
|
162
|
-
*/
|
|
163
|
-
struct patron_curl_state_list {
|
|
164
|
-
struct patron_curl_state *state;
|
|
165
|
-
struct patron_curl_state_list *next;
|
|
166
|
-
};
|
|
235
|
+
/*----------------------------------------------------------------------------*/
|
|
236
|
+
/* Deferred exceptions from inside libcurl callbacks */
|
|
167
237
|
|
|
168
|
-
|
|
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
|
+
}
|
|
169
244
|
|
|
170
|
-
|
|
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
|
+
}
|
|
171
258
|
|
|
172
|
-
|
|
173
|
-
|
|
259
|
+
/*----------------------------------------------------------------------------*/
|
|
260
|
+
/* libcurl callbacks. All of them run with the GVL held, from curl_multi_perform(). */
|
|
174
261
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
262
|
+
struct append_args {
|
|
263
|
+
VALUE str;
|
|
264
|
+
const char *ptr;
|
|
265
|
+
size_t len;
|
|
266
|
+
};
|
|
179
267
|
|
|
180
|
-
|
|
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;
|
|
181
272
|
}
|
|
182
273
|
|
|
183
|
-
static
|
|
184
|
-
struct
|
|
185
|
-
|
|
186
|
-
assert(state != NULL);
|
|
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;
|
|
187
277
|
|
|
188
|
-
|
|
189
|
-
if (
|
|
190
|
-
|
|
278
|
+
rb_protect(append_protected, (VALUE)&args, &tag);
|
|
279
|
+
if (tag) {
|
|
280
|
+
park_exception(s, tag);
|
|
281
|
+
return 0;
|
|
191
282
|
}
|
|
283
|
+
return len;
|
|
192
284
|
}
|
|
193
285
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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;
|
|
197
289
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
290
|
+
if (s->download_file) {
|
|
291
|
+
if (fwrite(ptr, 1, len, s->download_file) != len) return 0;
|
|
292
|
+
return len;
|
|
293
|
+
}
|
|
294
|
+
return append_to_string(s, s->body_buf, ptr, len);
|
|
201
295
|
}
|
|
202
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
|
+
}
|
|
203
301
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
static void session_close_debug_file(struct patron_curl_state *curl) {
|
|
208
|
-
if (curl->debug_file && stderr != curl->debug_file) {
|
|
209
|
-
fclose(curl->debug_file);
|
|
210
|
-
}
|
|
211
|
-
curl->debug_file = NULL;
|
|
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);
|
|
212
305
|
}
|
|
213
306
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
307
|
+
struct progress_args {
|
|
308
|
+
struct patron_session *s;
|
|
309
|
+
curl_off_t dltotal, dlnow, ultotal, ulnow;
|
|
310
|
+
};
|
|
311
|
+
|
|
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
|
+
}
|
|
217
317
|
|
|
218
|
-
|
|
219
|
-
|
|
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;
|
|
220
321
|
|
|
221
|
-
|
|
322
|
+
if (s->interrupt) return 1;
|
|
222
323
|
|
|
223
|
-
|
|
224
|
-
|
|
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
|
+
}
|
|
225
328
|
|
|
226
|
-
|
|
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);
|
|
227
333
|
|
|
228
|
-
|
|
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;
|
|
229
345
|
}
|
|
230
346
|
|
|
231
|
-
|
|
232
|
-
|
|
347
|
+
struct debug_args {
|
|
348
|
+
VALUE io;
|
|
349
|
+
const char *prefix;
|
|
350
|
+
const char *data;
|
|
351
|
+
size_t len;
|
|
352
|
+
};
|
|
233
353
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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;
|
|
237
361
|
}
|
|
238
362
|
|
|
239
|
-
|
|
240
|
-
|
|
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;
|
|
378
|
+
}
|
|
241
379
|
|
|
242
|
-
|
|
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;
|
|
243
387
|
}
|
|
244
388
|
|
|
245
|
-
static
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
};
|
|
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]);
|
|
392
|
+
}
|
|
250
393
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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");
|
|
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
|
+
}
|
|
398
|
+
|
|
399
|
+
/*----------------------------------------------------------------------------*/
|
|
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();
|
|
283
416
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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");
|
|
294
448
|
#else
|
|
295
|
-
curl_easy_setopt(
|
|
296
|
-
|
|
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);
|
|
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);
|
|
304
451
|
#endif
|
|
452
|
+
}
|
|
305
453
|
|
|
306
|
-
|
|
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);
|
|
307
465
|
}
|
|
308
466
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
struct
|
|
312
|
-
|
|
313
|
-
|
|
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;
|
|
314
481
|
}
|
|
315
482
|
|
|
316
483
|
/*----------------------------------------------------------------------------*/
|
|
317
|
-
/*
|
|
484
|
+
/* Module functions */
|
|
318
485
|
|
|
319
486
|
/*
|
|
320
|
-
* Returns the version of the
|
|
321
|
-
*
|
|
322
|
-
*
|
|
487
|
+
* Returns the version string of the libcurl in use, e.g. "libcurl/8.7.1 OpenSSL/3.3.0".
|
|
488
|
+
*
|
|
489
|
+
* @return [String]
|
|
323
490
|
*/
|
|
324
491
|
static VALUE libcurl_version(VALUE klass) {
|
|
325
|
-
|
|
326
|
-
UNUSED_ARGUMENT(klass);
|
|
327
|
-
return rb_str_new2(value);
|
|
492
|
+
return rb_str_new_cstr(curl_version());
|
|
328
493
|
}
|
|
329
494
|
|
|
330
495
|
/*
|
|
331
|
-
* Returns the version of the
|
|
332
|
-
*
|
|
333
|
-
*
|
|
496
|
+
* Returns the version of the libcurl Patron was compiled against.
|
|
497
|
+
*
|
|
498
|
+
* @return [Array<Integer>] major, minor and patch
|
|
334
499
|
*/
|
|
335
500
|
static VALUE libcurl_version_exact(VALUE klass) {
|
|
336
|
-
|
|
337
|
-
VALUE cv_major = INT2FIX(LIBCURL_VERSION_MAJOR);
|
|
338
|
-
VALUE cv_minor = INT2FIX(LIBCURL_VERSION_MINOR);
|
|
339
|
-
VALUE cv_patch = INT2FIX(LIBCURL_VERSION_PATCH);
|
|
340
|
-
VALUE version_arr = rb_ary_new3(3, cv_major, cv_minor, cv_patch);
|
|
341
|
-
return version_arr;
|
|
501
|
+
return rb_ary_new_from_args(3, INT2FIX(LIBCURL_VERSION_MAJOR), INT2FIX(LIBCURL_VERSION_MINOR), INT2FIX(LIBCURL_VERSION_PATCH));
|
|
342
502
|
}
|
|
343
503
|
|
|
344
504
|
/*
|
|
345
|
-
*
|
|
505
|
+
* Describes the libcurl in use at runtime.
|
|
346
506
|
*
|
|
347
|
-
* @
|
|
348
|
-
*
|
|
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`
|
|
349
509
|
*/
|
|
350
|
-
static VALUE
|
|
351
|
-
|
|
352
|
-
VALUE
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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");
|
|
361
526
|
#endif
|
|
362
|
-
|
|
363
|
-
|
|
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);
|
|
527
|
+
#ifdef CURL_VERSION_THREADSAFE
|
|
528
|
+
FEATURE(CURL_VERSION_THREADSAFE, "threadsafe");
|
|
369
529
|
#endif
|
|
370
|
-
|
|
371
|
-
rb_raise(ePatronError, "libcurl could not escape the string");
|
|
372
|
-
}
|
|
530
|
+
#undef FEATURE
|
|
373
531
|
|
|
374
|
-
|
|
375
|
-
|
|
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;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/*
|
|
540
|
+
* URL-escapes a String using libcurl.
|
|
541
|
+
*
|
|
542
|
+
* @param value [String]
|
|
543
|
+
* @return [String] the escaped string
|
|
544
|
+
*/
|
|
545
|
+
static VALUE session_escape(VALUE self, VALUE value) {
|
|
546
|
+
VALUE string = StringValue(value);
|
|
547
|
+
char *escaped = curl_easy_escape(NULL, RSTRING_PTR(string), rb_long2int(RSTRING_LEN(string)));
|
|
548
|
+
VALUE result;
|
|
376
549
|
|
|
377
|
-
|
|
550
|
+
if (!escaped) rb_raise(ePatronError, "libcurl could not escape the string");
|
|
551
|
+
result = rb_usascii_str_new_cstr(escaped);
|
|
552
|
+
curl_free(escaped);
|
|
553
|
+
return result;
|
|
378
554
|
}
|
|
379
555
|
|
|
380
556
|
/*
|
|
381
|
-
*
|
|
557
|
+
* URL-unescapes a String using libcurl. The result is binary, since percent-escapes can
|
|
558
|
+
* encode arbitrary bytes.
|
|
382
559
|
*
|
|
383
|
-
* @param [String]
|
|
384
|
-
*
|
|
560
|
+
* @param value [String]
|
|
561
|
+
* @return [String] the unescaped string
|
|
385
562
|
*/
|
|
386
563
|
static VALUE session_unescape(VALUE self, VALUE value) {
|
|
387
564
|
VALUE string = StringValue(value);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
VALUE
|
|
391
|
-
|
|
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,
|
|
400
|
-
RSTRING_PTR(string),
|
|
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
|
-
}
|
|
565
|
+
int outlen = 0;
|
|
566
|
+
char *unescaped = curl_easy_unescape(NULL, RSTRING_PTR(string), rb_long2int(RSTRING_LEN(string)), &outlen);
|
|
567
|
+
VALUE result;
|
|
411
568
|
|
|
412
|
-
|
|
569
|
+
if (!unescaped) rb_raise(ePatronError, "libcurl could not unescape the string");
|
|
570
|
+
result = rb_str_new(unescaped, outlen);
|
|
413
571
|
curl_free(unescaped);
|
|
414
|
-
|
|
415
|
-
return retval;
|
|
572
|
+
return result;
|
|
416
573
|
}
|
|
417
574
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
struct patron_curl_state *state = get_patron_curl_state(self);
|
|
421
|
-
CURL* curl = state->handle;
|
|
422
|
-
|
|
423
|
-
VALUE name = rb_obj_as_string(header_key);
|
|
424
|
-
VALUE value = rb_obj_as_string(header_value);
|
|
425
|
-
VALUE header_str = Qnil;
|
|
426
|
-
|
|
427
|
-
// TODO: see how to combine this with automatic_content_encoding
|
|
428
|
-
if (rb_str_cmp(name, rb_str_new2("Accept-Encoding")) == 0) {
|
|
429
|
-
if (rb_funcall(value, rb_intern("include?"), 1, rb_str_new2("gzip"))) {
|
|
430
|
-
#ifdef CURLOPT_ACCEPT_ENCODING
|
|
431
|
-
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "gzip");
|
|
432
|
-
#elif defined CURLOPT_ENCODING
|
|
433
|
-
curl_easy_setopt(curl, CURLOPT_ENCODING, "gzip");
|
|
434
|
-
#else
|
|
435
|
-
rb_raise(rb_eArgError,
|
|
436
|
-
"The libcurl version installed doesn't support 'gzip'.");
|
|
437
|
-
#endif
|
|
438
|
-
}
|
|
439
|
-
}
|
|
575
|
+
/*----------------------------------------------------------------------------*/
|
|
576
|
+
/* Request configuration */
|
|
440
577
|
|
|
441
|
-
|
|
442
|
-
|
|
578
|
+
static VALUE request_attr(VALUE request, ID id) {
|
|
579
|
+
return rb_funcallv(request, id, 0, NULL);
|
|
580
|
+
}
|
|
443
581
|
|
|
444
|
-
|
|
582
|
+
static int header_name_is(VALUE name, const char *expected) {
|
|
583
|
+
long len = (long)strlen(expected);
|
|
584
|
+
long i;
|
|
585
|
+
const char *p;
|
|
445
586
|
|
|
446
|
-
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;
|
|
447
593
|
}
|
|
448
594
|
|
|
449
|
-
static int
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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);
|
|
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;
|
|
466
600
|
|
|
601
|
+
for (i = 0; i + needle_len <= len; i++) {
|
|
602
|
+
if (memcmp(haystack + i, needle, (size_t)needle_len) == 0) return 1;
|
|
603
|
+
}
|
|
467
604
|
return 0;
|
|
468
605
|
}
|
|
469
606
|
|
|
470
|
-
static int
|
|
471
|
-
struct
|
|
472
|
-
VALUE name = rb_obj_as_string(
|
|
473
|
-
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;
|
|
474
611
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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
|
+
}
|
|
478
616
|
|
|
479
|
-
|
|
617
|
+
line = rb_sprintf("%"PRIsVALUE": %"PRIsVALUE, name, value);
|
|
618
|
+
s->headers = curl_slist_append(s->headers, StringValueCStr(line));
|
|
619
|
+
return ST_CONTINUE;
|
|
480
620
|
}
|
|
481
621
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
#else
|
|
492
|
-
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long) RSTRING_LEN(state->upload_data));
|
|
493
|
-
#endif
|
|
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;
|
|
494
631
|
}
|
|
495
632
|
|
|
496
|
-
static
|
|
497
|
-
|
|
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;
|
|
498
641
|
}
|
|
499
642
|
|
|
500
|
-
static
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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);
|
|
504
651
|
}
|
|
652
|
+
return ST_CONTINUE;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
static FILE *open_file(VALUE filename, const char *mode) {
|
|
656
|
+
FILE *handle;
|
|
505
657
|
|
|
658
|
+
FilePathValue(filename);
|
|
659
|
+
handle = fopen(StringValueCStr(filename), mode);
|
|
660
|
+
if (!handle) rb_sys_fail_str(filename);
|
|
506
661
|
return handle;
|
|
507
662
|
}
|
|
508
663
|
|
|
509
|
-
static void
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
curl_easy_setopt(curl, CURLOPT_READDATA, state->request_body_file);
|
|
515
|
-
struct stat stat_info;
|
|
516
|
-
fstat(fileno(state->request_body_file), &stat_info);
|
|
517
|
-
#ifdef CURLOPT_INFILESIZE_LARGE
|
|
518
|
-
curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, stat_info.st_size);
|
|
519
|
-
#else
|
|
520
|
-
curl_easy_setopt(curl, CURLOPT_INFILESIZE, stat_info.st_size);
|
|
521
|
-
#endif
|
|
522
|
-
}
|
|
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);
|
|
523
669
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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);
|
|
528
674
|
}
|
|
529
675
|
|
|
530
|
-
static void set_request_body(struct
|
|
531
|
-
if(rb_respond_to(stringable_or_file,
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
// file path on the file system and open a file pointer to it
|
|
535
|
-
VALUE r_path_str = rb_funcall(stringable_or_file, rb_intern("to_path"), 0);
|
|
536
|
-
r_path_str = rb_funcall(r_path_str, rb_intern("to_s"), 0);
|
|
537
|
-
set_request_body_file(state, r_path_str);
|
|
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));
|
|
538
680
|
} else {
|
|
539
|
-
|
|
540
|
-
|
|
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));
|
|
541
685
|
}
|
|
542
686
|
}
|
|
543
687
|
|
|
544
|
-
|
|
545
|
-
*
|
|
546
|
-
|
|
547
|
-
*/
|
|
548
|
-
static void set_options_from_request(VALUE self, VALUE request) {
|
|
549
|
-
struct patron_curl_state* state = get_patron_curl_state(self);
|
|
550
|
-
CURL* curl = curl_easy_duphandle(state->base_handle);
|
|
551
|
-
|
|
552
|
-
ID action = Qnil;
|
|
553
|
-
VALUE headers = Qnil;
|
|
554
|
-
VALUE url = Qnil;
|
|
555
|
-
VALUE timeout = Qnil;
|
|
556
|
-
VALUE redirects = Qnil;
|
|
557
|
-
VALUE proxy = Qnil;
|
|
558
|
-
VALUE proxy_type = Qnil;
|
|
559
|
-
VALUE credentials = Qnil;
|
|
560
|
-
VALUE ignore_content_length = Qnil;
|
|
561
|
-
VALUE insecure = Qnil;
|
|
562
|
-
VALUE cacert = Qnil;
|
|
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;
|
|
568
|
-
VALUE http_version = Qnil;
|
|
569
|
-
VALUE buffer_size = Qnil;
|
|
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);
|
|
583
|
-
|
|
584
|
-
if (RTEST(download_byte_limit)) {
|
|
585
|
-
state->download_byte_limit = (curl_off_t) NUM2LL(download_byte_limit);
|
|
586
|
-
} else {
|
|
587
|
-
state->download_byte_limit = 0;
|
|
588
|
-
}
|
|
688
|
+
static long seconds_to_milliseconds(VALUE seconds) {
|
|
689
|
+
return (long)(NUM2DBL(seconds) * 1000.0);
|
|
690
|
+
}
|
|
589
691
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
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);
|
|
695
|
+
|
|
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;
|
|
595
711
|
|
|
596
|
-
|
|
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);
|
|
597
727
|
if (RTEST(headers)) {
|
|
598
|
-
|
|
599
|
-
rb_raise(rb_eArgError, "Headers must be passed in a hash.");
|
|
600
|
-
}
|
|
728
|
+
Check_Type(headers, T_HASH);
|
|
601
729
|
rb_hash_foreach(headers, each_http_header, self);
|
|
602
730
|
}
|
|
603
731
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
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);
|
|
607
734
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
state->download_file = open_file(download_file, "wb");
|
|
620
|
-
curl_easy_setopt(curl, CURLOPT_WRITEDATA, state->download_file);
|
|
621
|
-
// curl docs say that CURLOPT_WRITEFUNCTION must be set too
|
|
622
|
-
// to avoid issues on Windows
|
|
623
|
-
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &file_write_handler);
|
|
624
|
-
} else {
|
|
625
|
-
state->download_file = NULL;
|
|
735
|
+
|
|
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");
|
|
626
746
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
VALUE multipart = rb_funcall(request, rb_intern("multipart"), 0);
|
|
631
|
-
|
|
632
|
-
if (action == rb_intern("post")) {
|
|
633
|
-
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST");
|
|
634
|
-
} else if (action == rb_intern("put")) {
|
|
635
|
-
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
|
|
636
|
-
} else if (action == rb_intern("patch")) {
|
|
637
|
-
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);
|
|
638
750
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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);
|
|
643
761
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
set_request_body_file(state, filename);
|
|
648
|
-
} else if (RTEST(multipart)) {
|
|
649
|
-
if (action == rb_intern("post")) {
|
|
650
|
-
if(RTEST(data) && RTEST(filename)) {
|
|
651
|
-
if (rb_type(data) == T_HASH && rb_type(filename) == T_HASH) {
|
|
652
|
-
rb_hash_foreach(data, formadd_values, self);
|
|
653
|
-
rb_hash_foreach(filename, formadd_files, self);
|
|
654
|
-
} else {
|
|
655
|
-
rb_raise(rb_eArgError, "Data and Filename must be passed in a hash.");
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
curl_easy_setopt(curl, CURLOPT_HTTPPOST, state->post);
|
|
659
|
-
} else {
|
|
660
|
-
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);
|
|
661
765
|
}
|
|
662
|
-
|
|
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));
|
|
663
774
|
} else {
|
|
664
|
-
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");
|
|
665
776
|
}
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");
|
|
675
|
-
|
|
676
|
-
} else if (action == rb_intern("head")) {
|
|
677
|
-
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);
|
|
678
785
|
} else {
|
|
679
|
-
VALUE
|
|
680
|
-
|
|
786
|
+
VALUE verb = request_attr(request, id_action_name);
|
|
787
|
+
setopt_or_raise(easy, CURLOPT_CUSTOMREQUEST, StringValueCStr(verb));
|
|
681
788
|
}
|
|
682
789
|
|
|
683
|
-
|
|
684
|
-
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, state->error_buf);
|
|
685
|
-
|
|
686
|
-
// Enable automatic content-encoding support via gzip/deflate if set in the request,
|
|
687
|
-
// see https://curl.haxx.se/libcurl/c/CURLOPT_ACCEPT_ENCODING.html
|
|
688
|
-
if(RTEST(a_c_encoding)) {
|
|
689
|
-
#ifdef CURLOPT_ACCEPT_ENCODING
|
|
690
|
-
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "");
|
|
691
|
-
#elif defined CURLOPT_ENCODING
|
|
692
|
-
curl_easy_setopt(curl, CURLOPT_ENCODING, "");
|
|
693
|
-
#else
|
|
694
|
-
rb_raise(rb_eArgError,
|
|
695
|
-
"The libcurl version installed doesn't support automatic content negotiation");
|
|
696
|
-
#endif
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
url = rb_funcall(request, rb_intern("url"), 0);
|
|
700
|
-
if (!RTEST(url)) {
|
|
701
|
-
rb_raise(rb_eArgError, "Must provide a URL");
|
|
702
|
-
}
|
|
703
|
-
curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
timeout = rb_funcall(request, rb_intern("timeout"), 0);
|
|
707
|
-
if (RTEST(timeout)) {
|
|
708
|
-
curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, floating_rb_seconds_to_milliseconds(timeout));
|
|
709
|
-
}
|
|
790
|
+
setopt_or_raise(easy, CURLOPT_HTTPHEADER, s->headers);
|
|
710
791
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT_MS, floating_rb_seconds_to_milliseconds(timeout));
|
|
792
|
+
if (RTEST(request_attr(request, id_automatic_content_encoding))) {
|
|
793
|
+
setopt_or_raise(easy, CURLOPT_ACCEPT_ENCODING, "");
|
|
714
794
|
}
|
|
715
795
|
|
|
716
|
-
|
|
717
|
-
if (RTEST(
|
|
718
|
-
|
|
719
|
-
}
|
|
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);
|
|
720
799
|
|
|
721
|
-
|
|
722
|
-
if(RTEST(
|
|
723
|
-
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_TIME, NUM2LONG(low_speed_time));
|
|
724
|
-
}
|
|
800
|
+
value = request_attr(request, id_timeout);
|
|
801
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_TIMEOUT_MS, seconds_to_milliseconds(value));
|
|
725
802
|
|
|
726
|
-
|
|
727
|
-
if(RTEST(
|
|
728
|
-
curl_easy_setopt(curl, CURLOPT_LOW_SPEED_LIMIT, NUM2LONG(low_speed_limit_bytes_per_second));
|
|
729
|
-
}
|
|
803
|
+
value = request_attr(request, id_connect_timeout);
|
|
804
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_CONNECTTIMEOUT_MS, seconds_to_milliseconds(value));
|
|
730
805
|
|
|
731
|
-
|
|
732
|
-
if (RTEST(
|
|
733
|
-
long r = NUM2LONG(redirects);
|
|
734
|
-
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, r == 0 ? 0 : 1);
|
|
735
|
-
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, r);
|
|
736
|
-
}
|
|
806
|
+
value = request_attr(request, id_dns_cache_timeout);
|
|
807
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_DNS_CACHE_TIMEOUT, NUM2LONG(value));
|
|
737
808
|
|
|
738
|
-
|
|
739
|
-
if (RTEST(
|
|
740
|
-
curl_easy_setopt(curl, CURLOPT_PROXY, StringValuePtr(proxy));
|
|
741
|
-
}
|
|
809
|
+
value = request_attr(request, id_low_speed_time);
|
|
810
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_LOW_SPEED_TIME, NUM2LONG(value));
|
|
742
811
|
|
|
743
|
-
|
|
744
|
-
if (RTEST(
|
|
745
|
-
curl_easy_setopt(curl, CURLOPT_PROXYTYPE, NUM2LONG(proxy_type));
|
|
746
|
-
}
|
|
812
|
+
value = request_attr(request, id_low_speed_limit);
|
|
813
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_LOW_SPEED_LIMIT, NUM2LONG(value));
|
|
747
814
|
|
|
748
|
-
|
|
749
|
-
if (RTEST(
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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);
|
|
753
820
|
}
|
|
754
821
|
|
|
755
|
-
|
|
756
|
-
if (RTEST(
|
|
757
|
-
curl_easy_setopt(curl, CURLOPT_IGNORE_CONTENT_LENGTH, 1);
|
|
758
|
-
}
|
|
822
|
+
value = request_attr(request, id_proxy);
|
|
823
|
+
if (RTEST(value)) set_string_option(s, CURLOPT_PROXY, value, 0);
|
|
759
824
|
|
|
760
|
-
|
|
761
|
-
if(RTEST(
|
|
762
|
-
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
|
763
|
-
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
|
|
764
|
-
}
|
|
825
|
+
value = request_attr(request, id_proxy_type);
|
|
826
|
+
if (RTEST(value)) setopt_or_raise(easy, CURLOPT_PROXYTYPE, NUM2LONG(value));
|
|
765
827
|
|
|
766
|
-
|
|
767
|
-
if(RTEST(
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
if(strcmp(version, "SSLv2") == 0) {
|
|
772
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv2);
|
|
773
|
-
} else if(strcmp(version, "SSLv3") == 0) {
|
|
774
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_SSLv3);
|
|
775
|
-
} else if(strcmp(version, "TLSv1") == 0) {
|
|
776
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
|
|
777
|
-
}
|
|
778
|
-
#if LIBCURL_VERSION_NUM >= 0x072200
|
|
779
|
-
/* this is libCURLv7.34.0 or later */
|
|
780
|
-
else if(strcmp(version, "TLSv1_0") == 0) {
|
|
781
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_0); //libcurl v7.34.0+
|
|
782
|
-
} else if(strcmp(version, "TLSv1_1") == 0) {
|
|
783
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_1); //libcurl v7.34.0+
|
|
784
|
-
} else if(strcmp(version, "TLSv1_2") == 0) {
|
|
785
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); //libcurl v7.34.0+
|
|
786
|
-
}
|
|
787
|
-
#endif
|
|
788
|
-
#if LIBCURL_VERSION_NUM >= 0x073400
|
|
789
|
-
/* this is libCURLv7.52.0 or later */
|
|
790
|
-
else if(strcmp(version, "TLSv1_3") == 0) {
|
|
791
|
-
curl_easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_3); //libcurl v7.52.0+
|
|
792
|
-
}
|
|
793
|
-
#endif
|
|
794
|
-
else {
|
|
795
|
-
rb_raise(eUnsupportedSSLVersion, "Unsupported SSL version: %s", version);
|
|
796
|
-
}
|
|
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);
|
|
797
832
|
}
|
|
798
833
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
VALUE http_version_str = rb_funcall(http_version, rb_intern("to_s"), 0);
|
|
802
|
-
char* version = StringValuePtr(http_version_str);
|
|
803
|
-
if(strcmp(version, "None") == 0) {
|
|
804
|
-
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE);
|
|
805
|
-
} else if(strcmp(version, "HTTPv1_0") == 0) {
|
|
806
|
-
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
|
|
807
|
-
} else if(strcmp(version, "HTTPv1_1") == 0) {
|
|
808
|
-
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
|
|
809
|
-
}
|
|
810
|
-
#if LIBCURL_VERSION_NUM >= 0x072100
|
|
811
|
-
/* this is libCURLv7.33.0 or later */
|
|
812
|
-
else if(strcmp(version, "HTTPv2_0") == 0) {
|
|
813
|
-
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); //libcurl v7.33.0+
|
|
814
|
-
}
|
|
815
|
-
#endif
|
|
816
|
-
#if LIBCURL_VERSION_NUM >= 0x072F00
|
|
817
|
-
/* this is libCURLv7.47.0 or later */
|
|
818
|
-
else if(strcmp(version, "HTTPv2_TLS") == 0) {
|
|
819
|
-
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS); //libcurl v7.47.0+
|
|
820
|
-
}
|
|
821
|
-
#endif
|
|
822
|
-
#if LIBCURL_VERSION_NUM >= 0x073100
|
|
823
|
-
/* this is libCURLv7.49.0 or later */
|
|
824
|
-
else if(strcmp(version, "HTTPv2_PRIOR") == 0) {
|
|
825
|
-
curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE); //libcurl v7.49.0+
|
|
826
|
-
}
|
|
827
|
-
#endif
|
|
828
|
-
else {
|
|
829
|
-
rb_raise(eUnsupportedHTTPVersion, "Unsupported HTTP version: %s", version);
|
|
830
|
-
}
|
|
834
|
+
if (RTEST(request_attr(request, id_ignore_content_length))) {
|
|
835
|
+
setopt_or_raise(easy, CURLOPT_IGNORE_CONTENT_LENGTH, 1L);
|
|
831
836
|
}
|
|
832
837
|
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
curl_easy_setopt(curl, CURLOPT_SSLCERT, StringValueCStr(ssl_cert));
|
|
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);
|
|
837
841
|
}
|
|
838
842
|
|
|
839
|
-
|
|
840
|
-
if(RTEST(
|
|
841
|
-
|
|
842
|
-
curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE, StringValueCStr(ssl_cert_type));
|
|
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"));
|
|
843
846
|
}
|
|
844
847
|
|
|
845
|
-
|
|
846
|
-
if(RTEST(
|
|
847
|
-
|
|
848
|
-
curl_easy_setopt(curl, CURLOPT_SSLKEY, StringValueCStr(ssl_key));
|
|
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"));
|
|
849
851
|
}
|
|
850
852
|
|
|
851
|
-
|
|
852
|
-
if(RTEST(
|
|
853
|
-
curl_easy_setopt(curl, CURLOPT_KEYPASSWD, StringValueCStr(ssl_key_password));
|
|
854
|
-
}
|
|
853
|
+
value = request_attr(request, id_ssl_cert);
|
|
854
|
+
if (RTEST(value)) set_string_option(s, CURLOPT_SSLCERT, value, 1);
|
|
855
855
|
|
|
856
|
-
|
|
857
|
-
if(RTEST(
|
|
858
|
-
cacert = FilePathValue(cacert);
|
|
859
|
-
curl_easy_setopt(curl, CURLOPT_CAINFO, StringValueCStr(cacert));
|
|
860
|
-
}
|
|
856
|
+
value = request_attr(request, id_ssl_cert_type);
|
|
857
|
+
if (RTEST(value)) set_string_option(s, CURLOPT_SSLCERTTYPE, value, 0);
|
|
861
858
|
|
|
862
|
-
|
|
863
|
-
if (RTEST(
|
|
864
|
-
|
|
865
|
-
|
|
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));
|
|
866
870
|
}
|
|
867
871
|
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
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));
|
|
873
879
|
}
|
|
874
880
|
}
|
|
875
881
|
|
|
876
|
-
/*
|
|
877
|
-
* <=
|
|
878
|
-
static VALUE create_timings(CURL*
|
|
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) {
|
|
879
885
|
VALUE timings = rb_hash_new();
|
|
880
|
-
set_timing(
|
|
881
|
-
set_timing(
|
|
882
|
-
set_timing(
|
|
883
|
-
set_timing(
|
|
884
|
-
set_timing(
|
|
885
|
-
set_timing(
|
|
886
|
-
set_timing(
|
|
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);
|
|
887
893
|
return timings;
|
|
888
894
|
}
|
|
889
895
|
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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;
|
|
894
901
|
long code = 0;
|
|
895
|
-
long
|
|
896
|
-
VALUE
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
curl_easy_getinfo(
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
responseKlass = rb_funcall(self, rb_intern("response_class"), 0);
|
|
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;
|
|
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);
|
|
917
919
|
}
|
|
918
920
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
VALUE error = Qnil;
|
|
922
|
-
switch (code) {
|
|
923
|
-
case CURLE_UNSUPPORTED_PROTOCOL: error = eUnsupportedProtocol; break;
|
|
924
|
-
case CURLE_URL_MALFORMAT: error = eURLFormatError; break;
|
|
925
|
-
case CURLE_COULDNT_RESOLVE_HOST: error = eHostResolutionError; break;
|
|
926
|
-
case CURLE_COULDNT_CONNECT: error = eConnectionFailed; break;
|
|
927
|
-
case CURLE_PARTIAL_FILE: error = ePartialFileError; break;
|
|
928
|
-
case CURLE_OPERATION_TIMEDOUT: error = eTimeoutError; break;
|
|
929
|
-
case CURLE_TOO_MANY_REDIRECTS: error = eTooManyRedirects; break;
|
|
930
|
-
case CURLE_ABORTED_BY_CALLBACK: error = eAborted; break;
|
|
931
|
-
case CURLE_SSL_CERTPROBLEM: error = eClientCertificateError; break;
|
|
932
|
-
default: error = ePatronError;
|
|
933
|
-
}
|
|
921
|
+
/*----------------------------------------------------------------------------*/
|
|
922
|
+
/* Performing a request */
|
|
934
923
|
|
|
935
|
-
|
|
936
|
-
|
|
924
|
+
struct poll_context {
|
|
925
|
+
CURLM *multi;
|
|
926
|
+
CURLMcode rc;
|
|
927
|
+
};
|
|
937
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
|
+
}
|
|
938
934
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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
|
+
}
|
|
943
942
|
|
|
944
|
-
|
|
945
|
-
|
|
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
|
+
}
|
|
946
951
|
|
|
947
|
-
|
|
948
|
-
return
|
|
952
|
+
static VALUE float_seconds(long milliseconds) {
|
|
953
|
+
return rb_float_new((double)milliseconds / 1000.0);
|
|
949
954
|
}
|
|
950
955
|
|
|
951
|
-
/*
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
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);
|
|
966
|
+
}
|
|
967
|
+
return io;
|
|
959
968
|
}
|
|
960
969
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
if (
|
|
983
|
-
|
|
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++;
|
|
996
|
+
}
|
|
997
|
+
|
|
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;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
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;
|
|
984
1011
|
}
|
|
985
|
-
rb_jump_tag(tag);
|
|
986
1012
|
}
|
|
987
1013
|
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
curl_easy_setopt(curl, CURLOPT_COOKIELIST, "FLUSH"); // Flush cookies to the cookie jar
|
|
994
|
-
|
|
995
|
-
return create_response(self, curl, header_str, body_str);
|
|
996
|
-
} else {
|
|
997
|
-
rb_raise(select_error(context.code), "%s", state->error_buf);
|
|
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;
|
|
998
1018
|
}
|
|
999
|
-
}
|
|
1000
1019
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1020
|
+
{
|
|
1021
|
+
VALUE readables = rb_ary_new(), writables = rb_ary_new(), exceptables = rb_ary_new();
|
|
1022
|
+
VALUE ready;
|
|
1023
|
+
|
|
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);
|
|
1029
|
+
}
|
|
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));
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1009
1036
|
}
|
|
1010
1037
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
curl_easy_cleanup(state->handle);
|
|
1017
|
-
state->handle = NULL;
|
|
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();
|
|
1018
1043
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
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);
|
|
1057
|
+
}
|
|
1058
|
+
if (s->interrupt) break;
|
|
1022
1059
|
}
|
|
1023
|
-
|
|
1060
|
+
}
|
|
1024
1061
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1062
|
+
static CURLcode transfer_result(struct patron_session *s) {
|
|
1063
|
+
CURLcode result = CURLE_OK;
|
|
1064
|
+
CURLMsg *msg;
|
|
1065
|
+
int remaining = 0;
|
|
1066
|
+
|
|
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;
|
|
1030
1069
|
}
|
|
1070
|
+
return result;
|
|
1071
|
+
}
|
|
1031
1072
|
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1073
|
+
struct perform_args {
|
|
1074
|
+
struct patron_session *s;
|
|
1075
|
+
VALUE request;
|
|
1076
|
+
};
|
|
1077
|
+
|
|
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;
|
|
1082
|
+
|
|
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));
|
|
1088
|
+
|
|
1089
|
+
set_options_from_request(s, args->request);
|
|
1090
|
+
drive_transfer(s);
|
|
1091
|
+
|
|
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");
|
|
1035
1096
|
}
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
curl_formfree(state->post);
|
|
1039
|
-
state->post = NULL;
|
|
1040
|
-
state->last = NULL;
|
|
1097
|
+
if (s->interrupt) {
|
|
1098
|
+
raise_curl_error(eAborted, CURLE_ABORTED_BY_CALLBACK, "Request interrupted");
|
|
1041
1099
|
}
|
|
1042
1100
|
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
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");
|
|
1047
1106
|
|
|
1107
|
+
return create_response(s);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/* Runs under rb_ensure after every request, whether it finished, raised or was interrupted. */
|
|
1111
|
+
static VALUE cleanup(VALUE self) {
|
|
1112
|
+
struct patron_session *s = get_session(self);
|
|
1113
|
+
|
|
1114
|
+
if (s->easy) {
|
|
1115
|
+
curl_multi_remove_handle(s->multi, s->easy);
|
|
1116
|
+
curl_easy_cleanup(s->easy);
|
|
1117
|
+
s->easy = NULL;
|
|
1118
|
+
}
|
|
1119
|
+
if (s->mime) {
|
|
1120
|
+
curl_mime_free(s->mime);
|
|
1121
|
+
s->mime = NULL;
|
|
1122
|
+
}
|
|
1123
|
+
if (s->headers) {
|
|
1124
|
+
curl_slist_free_all(s->headers);
|
|
1125
|
+
s->headers = NULL;
|
|
1126
|
+
}
|
|
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;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
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;
|
|
1048
1142
|
return Qnil;
|
|
1049
1143
|
}
|
|
1050
1144
|
|
|
1051
1145
|
/*
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
* library. After the request completes, a Response object will be created and
|
|
1055
|
-
* 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.
|
|
1056
1148
|
*
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1059
|
-
* error message.
|
|
1060
|
-
*
|
|
1061
|
-
* @param request[Patron::Request] the request to use when filling the CURL options
|
|
1062
|
-
* @return [Patron::Response] the result of calling `response_class` on the Session
|
|
1149
|
+
* @param request [Patron::Request]
|
|
1150
|
+
* @return [Patron::Response]
|
|
1063
1151
|
*/
|
|
1064
1152
|
static VALUE session_handle_request(VALUE self, VALUE request) {
|
|
1065
|
-
struct
|
|
1066
|
-
|
|
1153
|
+
struct patron_session *s = get_session(self);
|
|
1154
|
+
struct perform_args args = { s, request };
|
|
1067
1155
|
|
|
1068
|
-
if (
|
|
1069
|
-
rb_raise(ePatronError, "Session is already performing a request
|
|
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");
|
|
1070
1158
|
}
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
return rb_ensure(&prepare_and_perform_request, args, &cleanup, self);
|
|
1159
|
+
ensure_handles_for_this_process(s);
|
|
1160
|
+
s->busy = 1;
|
|
1161
|
+
return rb_ensure(perform, (VALUE)&args, cleanup, self);
|
|
1075
1162
|
}
|
|
1076
1163
|
|
|
1077
|
-
/*
|
|
1078
|
-
*
|
|
1079
|
-
*
|
|
1164
|
+
/*
|
|
1165
|
+
* Aborts the request currently in flight on this Session, which then raises Patron::Aborted.
|
|
1166
|
+
* Safe to call from another thread.
|
|
1080
1167
|
*
|
|
1081
|
-
* @return
|
|
1168
|
+
* @return self
|
|
1082
1169
|
*/
|
|
1083
1170
|
static VALUE session_interrupt(VALUE self) {
|
|
1084
|
-
struct
|
|
1085
|
-
|
|
1171
|
+
struct patron_session *s = get_session(self);
|
|
1172
|
+
|
|
1173
|
+
s->interrupt = INTERRUPT_ABORT;
|
|
1174
|
+
if (s->multi) curl_multi_wakeup(s->multi);
|
|
1086
1175
|
return self;
|
|
1087
1176
|
}
|
|
1088
1177
|
|
|
1089
1178
|
/*
|
|
1090
|
-
*
|
|
1091
|
-
*
|
|
1092
|
-
* writable. Calling multiple times will add more files.
|
|
1093
|
-
* FIXME: what does the empty string actually do here?
|
|
1094
|
-
*
|
|
1095
|
-
* @param [String] file path to the existing cookie file, or nil to store in memory.
|
|
1096
|
-
* @return self
|
|
1179
|
+
* Enables cookie handling: in memory when +path+ is empty, otherwise read from and written
|
|
1180
|
+
* back to the file at +path+.
|
|
1097
1181
|
*/
|
|
1098
|
-
static VALUE
|
|
1099
|
-
struct
|
|
1100
|
-
|
|
1101
|
-
char* file_path = NULL;
|
|
1102
|
-
|
|
1103
|
-
// FIXME: http://websystemsengineering.blogspot.nl/2013/03/curloptcookiefile-vs-curloptcookiejar.html
|
|
1104
|
-
file_path = StringValueCStr(file);
|
|
1105
|
-
if (strlen(file_path) != 0) {
|
|
1106
|
-
curl_easy_setopt(curl, CURLOPT_COOKIEJAR, file_path);
|
|
1107
|
-
}
|
|
1108
|
-
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);
|
|
1109
1185
|
|
|
1186
|
+
if (*file_path) setopt_or_raise(s->base, CURLOPT_COOKIEJAR, file_path);
|
|
1187
|
+
setopt_or_raise(s->base, CURLOPT_COOKIEFILE, file_path);
|
|
1110
1188
|
return self;
|
|
1111
1189
|
}
|
|
1112
1190
|
|
|
1113
1191
|
/*
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1116
|
-
* @param [String, nil] file path to the debug file, or nil to write to STDERR
|
|
1117
|
-
* @return self
|
|
1192
|
+
* Sends libcurl's verbose output to +io+, or disables it when +io+ is nil.
|
|
1118
1193
|
*/
|
|
1119
|
-
static VALUE
|
|
1120
|
-
struct
|
|
1121
|
-
CURL *curl = state->base_handle;
|
|
1122
|
-
char *file_path = StringValueCStr(file);
|
|
1194
|
+
static VALUE session_set_debug_io(VALUE self, VALUE io) {
|
|
1195
|
+
struct patron_session *s = get_session(self);
|
|
1123
1196
|
|
|
1124
|
-
|
|
1197
|
+
SESSION_WRITE(s, debug_io, io);
|
|
1198
|
+
setopt_or_raise(s->base, CURLOPT_VERBOSE, NIL_P(io) ? 0L : 1L);
|
|
1199
|
+
return self;
|
|
1200
|
+
}
|
|
1125
1201
|
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
} else {
|
|
1129
|
-
state->debug_file = stderr;
|
|
1130
|
-
}
|
|
1131
|
-
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
|
|
1132
|
-
curl_easy_setopt(curl, CURLOPT_STDERR, state->debug_file);
|
|
1202
|
+
/*----------------------------------------------------------------------------*/
|
|
1203
|
+
/* Init */
|
|
1133
1204
|
|
|
1134
|
-
|
|
1205
|
+
static VALUE shareable_table(void) {
|
|
1206
|
+
return rb_hash_new();
|
|
1135
1207
|
}
|
|
1136
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
|
+
}
|
|
1137
1212
|
|
|
1138
|
-
|
|
1139
|
-
/*
|
|
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);
|
|
1140
1217
|
|
|
1141
|
-
void Init_session_ext() {
|
|
1142
1218
|
curl_global_init(CURL_GLOBAL_ALL);
|
|
1143
1219
|
rb_require("patron/error");
|
|
1144
|
-
|
|
1145
|
-
|
|
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");
|
|
1146
1243
|
|
|
1147
1244
|
mPatron = rb_define_module("Patron");
|
|
1148
1245
|
|
|
1149
|
-
/* These
|
|
1150
|
-
|
|
1151
|
-
|
|
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);
|
|
1152
1251
|
rb_global_variable(&ePatronError);
|
|
1153
1252
|
rb_global_variable(&eUnsupportedProtocol);
|
|
1154
1253
|
rb_global_variable(&eUnsupportedSSLVersion);
|
|
@@ -1160,10 +1259,10 @@ void Init_session_ext() {
|
|
|
1160
1259
|
rb_global_variable(&eTimeoutError);
|
|
1161
1260
|
rb_global_variable(&eTooManyRedirects);
|
|
1162
1261
|
rb_global_variable(&eAborted);
|
|
1262
|
+
rb_global_variable(&eDownloadLimitExceeded);
|
|
1163
1263
|
rb_global_variable(&eClientCertificateError);
|
|
1164
1264
|
|
|
1165
1265
|
ePatronError = rb_const_get(mPatron, rb_intern("Error"));
|
|
1166
|
-
|
|
1167
1266
|
eUnsupportedProtocol = rb_const_get(mPatron, rb_intern("UnsupportedProtocol"));
|
|
1168
1267
|
eUnsupportedSSLVersion = rb_const_get(mPatron, rb_intern("UnsupportedSSLVersion"));
|
|
1169
1268
|
eUnsupportedHTTPVersion = rb_const_get(mPatron, rb_intern("UnsupportedHTTPVersion"));
|
|
@@ -1174,38 +1273,60 @@ void Init_session_ext() {
|
|
|
1174
1273
|
eTimeoutError = rb_const_get(mPatron, rb_intern("TimeoutError"));
|
|
1175
1274
|
eTooManyRedirects = rb_const_get(mPatron, rb_intern("TooManyRedirects"));
|
|
1176
1275
|
eAborted = rb_const_get(mPatron, rb_intern("Aborted"));
|
|
1276
|
+
eDownloadLimitExceeded = rb_const_get(mPatron, rb_intern("DownloadLimitExceeded"));
|
|
1177
1277
|
eClientCertificateError = rb_const_get(mPatron, rb_intern("ClientCertificateError"));
|
|
1178
1278
|
|
|
1179
|
-
rb_define_module_function(mPatron, "libcurl_version",
|
|
1279
|
+
rb_define_module_function(mPatron, "libcurl_version", libcurl_version, 0);
|
|
1180
1280
|
rb_define_module_function(mPatron, "libcurl_version_exact", libcurl_version_exact, 0);
|
|
1281
|
+
rb_define_module_function(mPatron, "libcurl_features", libcurl_features, 0);
|
|
1181
1282
|
|
|
1182
1283
|
cSession = rb_define_class_under(mPatron, "Session", rb_cObject);
|
|
1183
|
-
|
|
1284
|
+
/* Defined in Ruby; we only hang constants off it */
|
|
1285
|
+
cRequest = rb_path2class("Patron::Request");
|
|
1184
1286
|
rb_define_alloc_func(cSession, session_alloc);
|
|
1185
1287
|
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
rb_define_singleton_method(cSession, "
|
|
1190
|
-
rb_define_method(cSession, "
|
|
1191
|
-
rb_define_singleton_method(cSession, "unescape", session_unescape, 1);
|
|
1192
|
-
rb_define_method(cSession, "unescape", session_unescape, 1);
|
|
1193
|
-
|
|
1194
|
-
rb_define_private_method(cSession, "handle_request", session_handle_request, 1);
|
|
1195
|
-
rb_define_method(cSession, "reset", session_interrupt, 0);
|
|
1196
|
-
rb_define_method(cSession, "interrupt", session_interrupt, 0);
|
|
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);
|
|
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);
|
|
1199
1293
|
rb_define_alias(cSession, "urlencode", "escape");
|
|
1200
1294
|
rb_define_alias(cSession, "urldecode", "unescape");
|
|
1201
1295
|
|
|
1202
|
-
|
|
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));
|
|
1203
1302
|
rb_define_const(cRequest, "AuthDigest", LONG2NUM(CURLAUTH_DIGEST));
|
|
1204
|
-
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));
|
|
1205
1325
|
|
|
1206
1326
|
mProxyType = rb_define_module_under(mPatron, "ProxyType");
|
|
1207
1327
|
rb_define_const(mProxyType, "HTTP", LONG2NUM(CURLPROXY_HTTP));
|
|
1208
1328
|
rb_define_const(mProxyType, "HTTP_1_0", LONG2NUM(CURLPROXY_HTTP_1_0));
|
|
1329
|
+
rb_define_const(mProxyType, "HTTPS", LONG2NUM(CURLPROXY_HTTPS));
|
|
1209
1330
|
rb_define_const(mProxyType, "SOCKS4", LONG2NUM(CURLPROXY_SOCKS4));
|
|
1210
1331
|
rb_define_const(mProxyType, "SOCKS5", LONG2NUM(CURLPROXY_SOCKS5));
|
|
1211
1332
|
rb_define_const(mProxyType, "SOCKS4A", LONG2NUM(CURLPROXY_SOCKS4A));
|