patron 0.13.3 → 0.13.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +26 -0
- data/.travis.yml +10 -7
- data/CHANGELOG.md +12 -1
- data/Gemfile +14 -1
- data/README.md +51 -30
- data/ext/patron/extconf.rb +3 -4
- data/ext/patron/session_ext.c +93 -98
- data/lib/patron/request.rb +2 -2
- data/lib/patron/session.rb +2 -4
- data/lib/patron/version.rb +1 -1
- data/patron.gemspec +6 -14
- data/spec/session_spec.rb +113 -60
- data/spec/session_ssl_spec.rb +39 -21
- data/spec/spec_helper.rb +10 -7
- data/spec/support/config.ru +11 -3
- data/spec/support/fork.rb +70 -0
- metadata +11 -123
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f900014f4c841d87c4db54f3db3f33d9b624d5a6dde0eb9352b7a70cc6f330b7
|
4
|
+
data.tar.gz: 31b35805166302cb5527734b70c3607f9dbb951e2c1e3aafd465f7f8d5dbbb75
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1989abb0ba1be73ed08df186d05349c6519a90663a7263799a1e97e024e5183ff6de9f49ab329ebf4e3b49c4c571d0e3ca7efed3bc5f803ad8edbe96a6554ff6
|
7
|
+
data.tar.gz: dc03c98c0f363d7d7bf273b72a62954dadb590c1236baf06739f6fdebd6ae5007afc243dc526c97359165c19244868779fc4168cc9f972c32cbf8fef81a56521
|
@@ -0,0 +1,26 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
test:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
strategy:
|
9
|
+
fail-fast: false
|
10
|
+
matrix:
|
11
|
+
ruby-version: [2.4, 2.5, 2.6, 2.7, '3.0', 3.1, 3.2, 3.3, 3.4.1]
|
12
|
+
|
13
|
+
name: Specs - Ruby ${{ matrix.ruby-version }}
|
14
|
+
steps:
|
15
|
+
- uses: actions/checkout@v4
|
16
|
+
- name: Install libcurl
|
17
|
+
run: |
|
18
|
+
sudo apt-get update
|
19
|
+
sudo apt-get -y install libcurl4 libcurl4-openssl-dev
|
20
|
+
- name: Set up Ruby ${{ matrix.ruby-version }}
|
21
|
+
uses: ruby/setup-ruby@v1
|
22
|
+
with:
|
23
|
+
ruby-version: ${{ matrix.ruby-version }}
|
24
|
+
bundler-cache: true # 'bundle install' and cache
|
25
|
+
- name: Run tests
|
26
|
+
run: bundle exec rake
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,17 @@
|
|
1
|
+
### 0.13.4
|
2
|
+
|
3
|
+
* Format README a bit better using code fences
|
4
|
+
* Depend explicitly on base64 for running tests
|
5
|
+
* Remove C code used for Ruby support below < 2.4 (GVL unlock etc.) and drop support for these versions
|
6
|
+
* In tests, close Tempfiles before allowing libcurl to use them (cookie jar)
|
7
|
+
* Fix incorrect ptr type in uses of `curl_easy_escape` / `_unescape`
|
8
|
+
* Define methods on `Session` as private instance methods from C instead of privatising them from Ruby
|
9
|
+
* Speed up the /slow endpoint test
|
10
|
+
* Remove use of `curl_easy_reset`. This should fix the bug with sessions resetting each other's state during `session_free`
|
11
|
+
|
1
12
|
### 0.13.3
|
2
13
|
|
3
|
-
*
|
14
|
+
* Run Puma test servers in separate processes instead of threads
|
4
15
|
|
5
16
|
### 0.13.2
|
6
17
|
|
data/Gemfile
CHANGED
@@ -1,4 +1,17 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
#
|
3
|
+
# Runtime dependencies are in the gemspec
|
4
4
|
gemspec
|
5
|
+
|
6
|
+
# Gems below are only needed for Patron development
|
7
|
+
# and tests
|
8
|
+
gem "rake", ">= 12.3.3"
|
9
|
+
gem "rspec", ">= 2.3.0"
|
10
|
+
gem "simplecov", "~> 0.10"
|
11
|
+
gem "yard", "~> 0.9.20"
|
12
|
+
rack_version = RUBY_VERSION >= "3.3" ? "~> 2.2" : "~> 2.1.4"
|
13
|
+
gem "rack", rack_version
|
14
|
+
gem "puma", '~> 3.11'
|
15
|
+
gem "rake-compiler"
|
16
|
+
gem "webrick", "~> 1.8"
|
17
|
+
gem "base64"
|
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# Patron
|
2
|
+
|
3
|
+
[](https://travis-ci.org/toland/patron)
|
4
|
+
|
1
5
|
Patron is a Ruby HTTP client library based on libcurl. It does not try to expose
|
2
6
|
the full "power" (read complexity) of libcurl but instead tries to provide a
|
3
7
|
sane API while taking advantage of libcurl under the hood.
|
@@ -8,47 +12,63 @@ First, you instantiate a Session object. You can set a few
|
|
8
12
|
default options on the Session instance that will be used by all subsequent
|
9
13
|
requests:
|
10
14
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
+
```ruby
|
16
|
+
sess = Patron::Session.new
|
17
|
+
sess.timeout = 10
|
18
|
+
sess.base_url = "http://myserver.com:9900"
|
19
|
+
sess.headers['User-Agent'] = 'myapp/1.0'
|
20
|
+
```
|
15
21
|
|
16
22
|
You can set options with a hash in the constructor:
|
17
23
|
|
18
|
-
|
19
|
-
|
20
|
-
|
24
|
+
```ruby
|
25
|
+
sess = Patron::Session.new({ :timeout => 10,
|
26
|
+
:base_url => 'http://myserver.com:9900',
|
27
|
+
:headers => {'User-Agent' => 'myapp/1.0'} } )
|
28
|
+
```
|
21
29
|
|
22
30
|
Or the set options in a block:
|
23
31
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
32
|
+
```ruby
|
33
|
+
sess = Patron::Session.new do |patron|
|
34
|
+
patron.timeout = 10
|
35
|
+
patron.base_url = 'http://myserver.com:9900'
|
36
|
+
patron.headers = {'User-Agent' => 'myapp/1.0'}
|
37
|
+
end
|
38
|
+
```
|
29
39
|
|
30
40
|
Output debug log:
|
31
41
|
|
32
|
-
|
42
|
+
```ruby
|
43
|
+
sess.enable_debug "/tmp/patron.debug"
|
44
|
+
```
|
33
45
|
|
34
46
|
The Session is used to make HTTP requests.
|
35
47
|
|
36
|
-
|
48
|
+
```ruby
|
49
|
+
resp = sess.get("/foo/bar")
|
50
|
+
```
|
37
51
|
|
38
52
|
Requests return a Response object:
|
39
53
|
|
40
|
-
|
41
|
-
|
42
|
-
|
54
|
+
```ruby
|
55
|
+
if resp.status < 400
|
56
|
+
puts resp.body
|
57
|
+
end
|
58
|
+
```
|
43
59
|
|
44
60
|
The GET, HEAD, PUT, POST and DELETE operations are all supported.
|
45
61
|
|
46
|
-
|
47
|
-
|
62
|
+
```ruby
|
63
|
+
sess.put("/foo/baz", "some data")
|
64
|
+
sess.delete("/foo/baz")
|
65
|
+
```
|
48
66
|
|
49
67
|
You can ship custom headers with a single request:
|
50
68
|
|
51
|
-
|
69
|
+
```ruby
|
70
|
+
sess.post("/foo/stuff", "some data", {"Content-Type" => "text/plain"})
|
71
|
+
```
|
52
72
|
|
53
73
|
## Threading
|
54
74
|
|
@@ -58,16 +78,18 @@ for doing concurrent requests. However, the actual code that interacts with libC
|
|
58
78
|
so using multiple `Session` objects in different threads actually enables a high degree of parallelism.
|
59
79
|
For sharing a resource of sessions between threads we recommend using the excellent [connection_pool](https://rubygems.org/gems/connection_pool) gem by Mike Perham.
|
60
80
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
81
|
+
```ruby
|
82
|
+
patron_pool = ConnectionPool.new(size: 5, timeout: 5) { Patron::Session.new }
|
83
|
+
patron_pool.with do |session|
|
84
|
+
session.get(...)
|
85
|
+
end
|
86
|
+
```
|
65
87
|
|
66
88
|
Sharing Session objects between requests will also allow you to benefit from persistent connections (connection reuse), see below.
|
67
89
|
|
68
90
|
## Persistent connections
|
69
91
|
|
70
|
-
Patron follows the libCURL guidelines on [connection reuse.](https://
|
92
|
+
Patron follows the libCURL guidelines on [connection reuse.](https://everything.curl.dev/libcurl/connectionreuse.html) If you create the Session
|
71
93
|
object once and use it for multiple requests, the same libCURL handle is going to be used across these requests and if requests go to
|
72
94
|
the same hostname/port/protocol the connection should get reused.
|
73
95
|
|
@@ -81,8 +103,10 @@ that are currently active in the process.
|
|
81
103
|
|
82
104
|
## Requirements
|
83
105
|
|
84
|
-
Patron
|
85
|
-
|
106
|
+
Patron 1.0 and up requires MRI Ruby 2.3 or newer. The 0.x versions support
|
107
|
+
Ruby 1.9.3 and these versions get tagged and developed on the `v0.x` branch.
|
108
|
+
|
109
|
+
A recent version of libCURL is required. We recommend at least 7.19.4 because
|
86
110
|
it [supports limiting the protocols](https://curl.haxx.se/libcurl/c/CURLOPT_PROTOCOLS.html),
|
87
111
|
and that is very important for security - especially if you follow redirects.
|
88
112
|
|
@@ -90,9 +114,6 @@ On OSX the provided libcurl is sufficient if you are not using fork+SSL combinat
|
|
90
114
|
You will have to install the libcurl development packages on Debian or Ubuntu. Other Linux systems are probably
|
91
115
|
similar. For Windows we do not have an established build instruction at the moment, unfortunately.
|
92
116
|
|
93
|
-
Versions of Patron below 1.0 will maintain compatibility down to Ruby 1.9.3,
|
94
|
-
versions below 2.0 will maintain compatibility down to Ruby 2.3
|
95
|
-
|
96
117
|
## Forking webservers on macOS and SSL
|
97
118
|
|
98
119
|
Currently, [an issue is at play](https://github.com/curl/curl/issues/788) with OSX builds of `curl` which use
|
data/ext/patron/extconf.rb
CHANGED
@@ -19,9 +19,8 @@ if CONFIG['CC'] =~ /gcc/
|
|
19
19
|
$CFLAGS << ' -pedantic -Wall'
|
20
20
|
end
|
21
21
|
|
22
|
-
|
23
|
-
$
|
24
|
-
|
25
|
-
$defs.push("-DHAVE_TCWOGVL") if have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h')
|
22
|
+
if CONFIG['CC'] =~ /clang/
|
23
|
+
$CFLAGS << ' -pedantic -Wall -Wno-void-pointer-to-enum-cast'
|
24
|
+
end
|
26
25
|
|
27
26
|
create_makefile 'patron/session_ext'
|
data/ext/patron/session_ext.c
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
#include <ruby.h>
|
2
|
-
#if defined(USE_TBR) && defined(HAVE_THREAD_H)
|
3
2
|
#include <ruby/thread.h>
|
4
|
-
#endif
|
5
3
|
#include <sys/stat.h>
|
6
4
|
#include <curl/curl.h>
|
7
5
|
#include "membuffer.h"
|
@@ -29,6 +27,8 @@ static VALUE eAborted = Qnil;
|
|
29
27
|
|
30
28
|
struct patron_curl_state {
|
31
29
|
CURL* handle;
|
30
|
+
CURL* base_handle;
|
31
|
+
CURLSH* share;
|
32
32
|
char* upload_buf;
|
33
33
|
FILE* download_file;
|
34
34
|
FILE* debug_file;
|
@@ -77,7 +77,7 @@ static size_t file_write_handler(void* stream, size_t size, size_t nmemb, FILE*
|
|
77
77
|
static int call_user_rb_progress_blk(void* vd_curl_state) {
|
78
78
|
struct patron_curl_state* state = (struct patron_curl_state*)vd_curl_state;
|
79
79
|
// Invoke the block with the array
|
80
|
-
|
80
|
+
rb_funcall(state->user_progress_blk,
|
81
81
|
rb_intern("call"), 4,
|
82
82
|
LONG2NUM(state->dltotal),
|
83
83
|
LONG2NUM(state->dlnow),
|
@@ -103,15 +103,7 @@ static int session_progress_handler(void* clientp, size_t dltotal, size_t dlnow,
|
|
103
103
|
// `call_user_rb_progress_blk`. TODO: use the retval of that proc
|
104
104
|
// to permit premature abort
|
105
105
|
if(RTEST(state->user_progress_blk)) {
|
106
|
-
|
107
|
-
// rb_thread_call_without_gvl is not. See https://bugs.ruby-lang.org/issues/5543#note-4
|
108
|
-
// > rb_thread_call_with_gvl() is globally-visible (but not in headers)
|
109
|
-
// > for 1.9.3: https://bugs.ruby-lang.org/issues/4328
|
110
|
-
#if (defined(HAVE_TBR) || defined(HAVE_TCWOGVL)) && defined(USE_TBR)
|
111
|
-
rb_thread_call_with_gvl((void *(*)(void *)) call_user_rb_progress_blk, (void*)state);
|
112
|
-
#else
|
113
|
-
call_user_rb_progress_blk((void*)state);
|
114
|
-
#endif
|
106
|
+
rb_thread_call_with_gvl((void *(*)(void *)) call_user_rb_progress_blk, (void*)state);
|
115
107
|
}
|
116
108
|
|
117
109
|
// Set the interrupt if the download byte limit has been reached
|
@@ -129,9 +121,10 @@ static int session_progress_handler(void* clientp, size_t dltotal, size_t dlnow,
|
|
129
121
|
}
|
130
122
|
|
131
123
|
|
132
|
-
|
133
|
-
|
134
|
-
|
124
|
+
/*
|
125
|
+
List of active curl sessions, used exclusively to be able to set interrupts
|
126
|
+
for all of them if the Ruby interpreter gets shut down with libCURL requests still in flight.
|
127
|
+
*/
|
135
128
|
struct patron_curl_state_list {
|
136
129
|
struct patron_curl_state *state;
|
137
130
|
struct patron_curl_state_list *next;
|
@@ -184,20 +177,18 @@ static void session_close_debug_file(struct patron_curl_state *curl) {
|
|
184
177
|
}
|
185
178
|
|
186
179
|
/* Cleans up the patron_curl_state data when the Session object is garbage collected. */
|
187
|
-
void session_free(struct patron_curl_state *
|
188
|
-
|
189
|
-
|
190
|
-
curl->handle = NULL;
|
191
|
-
}
|
180
|
+
void session_free(struct patron_curl_state *state) {
|
181
|
+
curl_easy_cleanup(state->base_handle);
|
182
|
+
curl_share_cleanup(state->share);
|
192
183
|
|
193
|
-
session_close_debug_file(
|
184
|
+
session_close_debug_file(state);
|
194
185
|
|
195
|
-
membuffer_destroy(
|
196
|
-
membuffer_destroy(
|
186
|
+
membuffer_destroy(&state->header_buffer);
|
187
|
+
membuffer_destroy(&state->body_buffer);
|
197
188
|
|
198
|
-
cs_list_remove(
|
189
|
+
cs_list_remove(state);
|
199
190
|
|
200
|
-
free(
|
191
|
+
free(state);
|
201
192
|
}
|
202
193
|
|
203
194
|
/* Allocates patron_curl_state data needed for a new Session object. */
|
@@ -215,16 +206,34 @@ VALUE session_alloc(VALUE klass) {
|
|
215
206
|
reuse the TCP connection and can speed things up if the same resource - like a backend service -
|
216
207
|
gets accessed over and over with requests.
|
217
208
|
*/
|
218
|
-
state->
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
curl_easy_setopt(state->
|
209
|
+
state->share = curl_share_init();
|
210
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
|
211
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
|
212
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
|
213
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
|
214
|
+
curl_share_setopt(state->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
|
215
|
+
state->base_handle = curl_easy_init();
|
216
|
+
curl_easy_setopt(state->base_handle, CURLOPT_SHARE, state->share);
|
217
|
+
curl_easy_setopt(state->base_handle, CURLOPT_WRITEFUNCTION, &session_write_handler);
|
218
|
+
curl_easy_setopt(state->base_handle, CURLOPT_WRITEDATA, &state->body_buffer);
|
219
|
+
curl_easy_setopt(state->base_handle, CURLOPT_HEADERFUNCTION, &session_write_handler);
|
220
|
+
curl_easy_setopt(state->base_handle, CURLOPT_HEADERDATA, &state->header_buffer);
|
221
|
+
curl_easy_setopt(state->base_handle, CURLOPT_NOSIGNAL, 1);
|
222
|
+
curl_easy_setopt(state->base_handle, CURLOPT_NOPROGRESS, 0);
|
223
|
+
#if LIBCURL_VERSION_NUM >= 0x072000
|
224
|
+
/* this is libCURLv7.32.0 or later, supports CURLOPT_XFERINFOFUNCTION */
|
225
|
+
curl_easy_setopt(state->base_handle, CURLOPT_XFERINFOFUNCTION, &session_progress_handler);
|
226
|
+
#else
|
227
|
+
curl_easy_setopt(state->base_handle, CURLOPT_PROGRESSFUNCTION, &session_progress_handler);
|
228
|
+
#endif
|
229
|
+
curl_easy_setopt(state->base_handle, CURLOPT_PROGRESSDATA, state);
|
230
|
+
#ifdef CURLPROTO_HTTP
|
231
|
+
// Security: do not allow Curl to go looking on gopher/SMTP etc.
|
232
|
+
// Must prevent situations like this:
|
233
|
+
// https://hackerone.com/reports/115748
|
234
|
+
curl_easy_setopt(state->base_handle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
235
|
+
curl_easy_setopt(state->base_handle, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
236
|
+
#endif
|
228
237
|
|
229
238
|
return obj;
|
230
239
|
}
|
@@ -276,13 +285,22 @@ static VALUE session_escape(VALUE self, VALUE value) {
|
|
276
285
|
char* escaped = NULL;
|
277
286
|
VALUE retval = Qnil;
|
278
287
|
|
279
|
-
|
280
|
-
|
288
|
+
#if LIBCURL_VERSION_NUM >= 0x075200
|
289
|
+
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
290
|
+
CURL *curl = NULL;
|
291
|
+
#else
|
292
|
+
CURL* curl = curl_easy_init();
|
293
|
+
#endif
|
294
|
+
escaped = curl_easy_escape(curl,
|
281
295
|
RSTRING_PTR(string),
|
282
296
|
(int) RSTRING_LEN(string));
|
283
297
|
|
284
298
|
retval = rb_str_new2(escaped);
|
285
|
-
|
299
|
+
#if LIBCURL_VERSION_NUM >= 0x075200
|
300
|
+
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
301
|
+
#else
|
302
|
+
curl_easy_cleanup(curl);
|
303
|
+
#endif
|
286
304
|
curl_free(escaped);
|
287
305
|
|
288
306
|
return retval;
|
@@ -299,15 +317,24 @@ static VALUE session_unescape(VALUE self, VALUE value) {
|
|
299
317
|
char* unescaped = NULL;
|
300
318
|
VALUE retval = Qnil;
|
301
319
|
|
302
|
-
|
303
|
-
|
320
|
+
#if LIBCURL_VERSION_NUM >= 0x075200
|
321
|
+
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
322
|
+
CURL *curl = NULL;
|
323
|
+
#else
|
324
|
+
CURL *curl = curl_easy_init();
|
325
|
+
#endif
|
326
|
+
unescaped = curl_easy_unescape(curl,
|
304
327
|
RSTRING_PTR(string),
|
305
328
|
(int) RSTRING_LEN(string),
|
306
329
|
NULL);
|
307
330
|
|
308
331
|
retval = rb_str_new2(unescaped);
|
309
332
|
curl_free(unescaped);
|
310
|
-
|
333
|
+
#if LIBCURL_VERSION_NUM >= 0x075200
|
334
|
+
/* this is libCURLv7.82.0 or later, the curl parameter is ignored */
|
335
|
+
#else
|
336
|
+
curl_easy_cleanup(curl);
|
337
|
+
#endif
|
311
338
|
|
312
339
|
return retval;
|
313
340
|
}
|
@@ -433,7 +460,7 @@ static void set_request_body(struct patron_curl_state* state, VALUE stringable_o
|
|
433
460
|
*/
|
434
461
|
static void set_options_from_request(VALUE self, VALUE request) {
|
435
462
|
struct patron_curl_state* state = get_patron_curl_state(self);
|
436
|
-
CURL* curl = state->
|
463
|
+
CURL* curl = curl_easy_duphandle(state->base_handle);
|
437
464
|
|
438
465
|
ID action = Qnil;
|
439
466
|
VALUE headers = Qnil;
|
@@ -454,6 +481,9 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
454
481
|
VALUE download_byte_limit = rb_funcall(request, rb_intern("download_byte_limit"), 0);
|
455
482
|
VALUE maybe_progress_proc = rb_funcall(request, rb_intern("progress_callback"), 0);
|
456
483
|
|
484
|
+
state->handle = curl;
|
485
|
+
curl_easy_setopt(curl, CURLOPT_SHARE, state->share);
|
486
|
+
|
457
487
|
if (RTEST(download_byte_limit)) {
|
458
488
|
state->download_byte_limit = FIX2INT(download_byte_limit);
|
459
489
|
} else {
|
@@ -475,7 +505,7 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
475
505
|
}
|
476
506
|
|
477
507
|
action = SYM2ID(action_name);
|
478
|
-
if(rb_funcall(request, rb_intern("force_ipv4"), 0)) {
|
508
|
+
if (RTEST(rb_funcall(request, rb_intern("force_ipv4"), 0))) {
|
479
509
|
curl_easy_setopt(curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
|
480
510
|
}
|
481
511
|
if (action == rb_intern("get")) {
|
@@ -578,13 +608,6 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
578
608
|
}
|
579
609
|
curl_easy_setopt(curl, CURLOPT_URL, StringValuePtr(url));
|
580
610
|
|
581
|
-
#ifdef CURLPROTO_HTTP
|
582
|
-
// Security: do not allow Curl to go looking on gopher/SMTP etc.
|
583
|
-
// Must prevent situations like this:
|
584
|
-
// https://hackerone.com/reports/115748
|
585
|
-
curl_easy_setopt(curl, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
586
|
-
curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
587
|
-
#endif
|
588
611
|
|
589
612
|
timeout = rb_funcall(request, rb_intern("timeout"), 0);
|
590
613
|
if (RTEST(timeout)) {
|
@@ -722,11 +745,6 @@ static void set_options_from_request(VALUE self, VALUE request) {
|
|
722
745
|
if (RTEST(buffer_size)) {
|
723
746
|
curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, NUM2LONG(buffer_size));
|
724
747
|
}
|
725
|
-
|
726
|
-
if(state->debug_file) {
|
727
|
-
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
|
728
|
-
curl_easy_setopt(curl, CURLOPT_STDERR, state->debug_file);
|
729
|
-
}
|
730
748
|
}
|
731
749
|
|
732
750
|
/* Use the info in a Curl handle to create a new Response object. */
|
@@ -741,10 +759,10 @@ static VALUE create_response(VALUE self, CURL* curl, VALUE header_buffer, VALUE
|
|
741
759
|
args[0] = rb_str_new2(effective_url);
|
742
760
|
|
743
761
|
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &code);
|
744
|
-
args[1] =
|
762
|
+
args[1] = LONG2NUM(code);
|
745
763
|
|
746
764
|
curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &count);
|
747
|
-
args[2] =
|
765
|
+
args[2] = LONG2NUM(count);
|
748
766
|
|
749
767
|
args[3] = header_buffer;
|
750
768
|
args[4] = body_buffer;
|
@@ -787,48 +805,19 @@ void session_ubf_abort(void* patron_state) {
|
|
787
805
|
static VALUE perform_request(VALUE self) {
|
788
806
|
struct patron_curl_state *state = get_patron_curl_state(self);
|
789
807
|
CURL* curl = state->handle;
|
790
|
-
membuffer* header_buffer = NULL;
|
791
|
-
membuffer* body_buffer = NULL;
|
792
808
|
CURLcode ret = 0;
|
793
809
|
|
794
810
|
state->interrupt = 0; /* clear the interrupt flag */
|
795
811
|
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
membuffer_clear(body_buffer);
|
801
|
-
|
802
|
-
/* headers */
|
803
|
-
curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &session_write_handler);
|
804
|
-
curl_easy_setopt(curl, CURLOPT_HEADERDATA, header_buffer);
|
805
|
-
|
806
|
-
/* body */
|
807
|
-
if (!state->download_file) {
|
808
|
-
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &session_write_handler);
|
809
|
-
curl_easy_setopt(curl, CURLOPT_WRITEDATA, body_buffer);
|
810
|
-
}
|
811
|
-
|
812
|
-
#if (defined(HAVE_TBR) || defined(HAVE_TCWOGVL)) && defined(USE_TBR)
|
813
|
-
#if defined(HAVE_TCWOGVL)
|
814
|
-
ret = (CURLcode) rb_thread_call_without_gvl(
|
815
|
-
(void *(*)(void *)) curl_easy_perform, curl,
|
816
|
-
session_ubf_abort, (void*)state
|
817
|
-
);
|
818
|
-
#else
|
819
|
-
ret = (CURLcode) rb_thread_blocking_region(
|
820
|
-
(rb_blocking_function_t*) curl_easy_perform, curl,
|
821
|
-
session_ubf_abort, (void*)state
|
822
|
-
);
|
823
|
-
#endif
|
824
|
-
#else
|
825
|
-
ret = curl_easy_perform(curl);
|
826
|
-
#endif
|
812
|
+
ret = (CURLcode) rb_thread_call_without_gvl(
|
813
|
+
(void *(*)(void *)) curl_easy_perform, curl,
|
814
|
+
session_ubf_abort, (void*)state
|
815
|
+
);
|
827
816
|
|
828
817
|
if (CURLE_OK == ret) {
|
829
|
-
VALUE header_str = membuffer_to_rb_str(header_buffer);
|
818
|
+
VALUE header_str = membuffer_to_rb_str(&state->header_buffer);
|
830
819
|
VALUE body_str = Qnil;
|
831
|
-
if (!state->download_file) { body_str = membuffer_to_rb_str(body_buffer); }
|
820
|
+
if (!state->download_file) { body_str = membuffer_to_rb_str(&state->body_buffer); }
|
832
821
|
|
833
822
|
curl_easy_setopt(curl, CURLOPT_COOKIELIST, "FLUSH"); // Flush cookies to the cookie jar
|
834
823
|
|
@@ -843,16 +832,19 @@ static VALUE perform_request(VALUE self) {
|
|
843
832
|
*/
|
844
833
|
static VALUE cleanup(VALUE self) {
|
845
834
|
struct patron_curl_state *state = get_patron_curl_state(self);
|
846
|
-
|
835
|
+
curl_easy_cleanup(state->handle);
|
847
836
|
|
848
837
|
if (state->headers) {
|
849
838
|
curl_slist_free_all(state->headers);
|
850
839
|
state->headers = NULL;
|
851
840
|
}
|
841
|
+
membuffer_clear(&state->header_buffer);
|
852
842
|
|
853
843
|
if (state->download_file) {
|
854
844
|
fclose(state->download_file);
|
855
845
|
state->download_file = NULL;
|
846
|
+
} else {
|
847
|
+
membuffer_clear(&state->body_buffer);
|
856
848
|
}
|
857
849
|
|
858
850
|
if (state->request_body_file) {
|
@@ -912,7 +904,7 @@ static VALUE session_interrupt(VALUE self) {
|
|
912
904
|
*/
|
913
905
|
static VALUE add_cookie_file(VALUE self, VALUE file) {
|
914
906
|
struct patron_curl_state *state = get_patron_curl_state(self);
|
915
|
-
CURL* curl = state->
|
907
|
+
CURL* curl = state->base_handle;
|
916
908
|
char* file_path = NULL;
|
917
909
|
|
918
910
|
// FIXME: http://websystemsengineering.blogspot.nl/2013/03/curloptcookiefile-vs-curloptcookiejar.html
|
@@ -933,7 +925,8 @@ static VALUE add_cookie_file(VALUE self, VALUE file) {
|
|
933
925
|
*/
|
934
926
|
static VALUE set_debug_file(VALUE self, VALUE file) {
|
935
927
|
struct patron_curl_state *state = get_patron_curl_state(self);
|
936
|
-
|
928
|
+
CURL *curl = state->base_handle;
|
929
|
+
char *file_path = RSTRING_PTR(file);
|
937
930
|
|
938
931
|
session_close_debug_file(state);
|
939
932
|
|
@@ -942,6 +935,8 @@ static VALUE set_debug_file(VALUE self, VALUE file) {
|
|
942
935
|
} else {
|
943
936
|
state->debug_file = stderr;
|
944
937
|
}
|
938
|
+
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
|
939
|
+
curl_easy_setopt(curl, CURLOPT_STDERR, state->debug_file);
|
945
940
|
|
946
941
|
return self;
|
947
942
|
}
|
@@ -986,11 +981,11 @@ void Init_session_ext() {
|
|
986
981
|
rb_define_singleton_method(cSession, "unescape", session_unescape, 1);
|
987
982
|
rb_define_method(cSession, "unescape", session_unescape, 1);
|
988
983
|
|
989
|
-
|
984
|
+
rb_define_private_method(cSession, "handle_request", session_handle_request, 1);
|
990
985
|
rb_define_method(cSession, "reset", session_interrupt, 0);
|
991
986
|
rb_define_method(cSession, "interrupt", session_interrupt, 0);
|
992
|
-
|
993
|
-
|
987
|
+
rb_define_private_method(cSession, "add_cookie_file", add_cookie_file, 1);
|
988
|
+
rb_define_private_method(cSession, "set_debug_file", set_debug_file, 1);
|
994
989
|
rb_define_alias(cSession, "urlencode", "escape");
|
995
990
|
rb_define_alias(cSession, "urldecode", "unescape");
|
996
991
|
|
data/lib/patron/request.rb
CHANGED
@@ -34,8 +34,8 @@ module Patron
|
|
34
34
|
:low_speed_time, :low_speed_limit, :progress_callback
|
35
35
|
]
|
36
36
|
|
37
|
-
attr_reader
|
38
|
-
attr_writer
|
37
|
+
attr_reader(*READER_VARS)
|
38
|
+
attr_writer(*WRITER_VARS)
|
39
39
|
|
40
40
|
# Set the type of authentication to use for this request.
|
41
41
|
#
|
data/lib/patron/session.rb
CHANGED
@@ -103,8 +103,6 @@ module Patron
|
|
103
103
|
# @see low_speed_time
|
104
104
|
attr_accessor :low_speed_limit
|
105
105
|
|
106
|
-
private :handle_request, :add_cookie_file, :set_debug_file
|
107
|
-
|
108
106
|
# @return [#call, nil] callable object that will be called with 4 arguments
|
109
107
|
# during request/response execution - `dltotal`, `dlnow`, `ultotal`, `ulnow`.
|
110
108
|
# All these arguments are in bytes.
|
@@ -146,9 +144,9 @@ module Patron
|
|
146
144
|
if file_path
|
147
145
|
path = Pathname(file_path).expand_path
|
148
146
|
|
149
|
-
if !File.
|
147
|
+
if !File.exist?(file_path) && !File.writable?(path.dirname)
|
150
148
|
raise ArgumentError, "Can't create file #{path} (permission error)"
|
151
|
-
elsif File.
|
149
|
+
elsif File.exist?(file_path) && !File.writable?(file_path)
|
152
150
|
raise ArgumentError, "Can't read or write file #{path} (permission error)"
|
153
151
|
end
|
154
152
|
else
|
data/lib/patron/version.rb
CHANGED