patron 0.13.4 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +97 -0
  3. data/README.md +113 -50
  4. data/ext/patron/extconf.rb +55 -20
  5. data/ext/patron/session_ext.c +1092 -761
  6. data/lib/patron/compat.rb +57 -0
  7. data/lib/patron/error.rb +25 -13
  8. data/lib/patron/header_parser.rb +27 -25
  9. data/lib/patron/request.rb +98 -142
  10. data/lib/patron/response.rb +33 -24
  11. data/lib/patron/response_decoding.rb +60 -67
  12. data/lib/patron/session.rb +127 -104
  13. data/lib/patron/util.rb +14 -12
  14. data/lib/patron/version.rb +3 -1
  15. data/lib/patron.rb +9 -15
  16. metadata +12 -49
  17. data/.autotest +0 -15
  18. data/.github/workflows/ci.yml +0 -26
  19. data/.gitignore +0 -10
  20. data/.rspec +0 -0
  21. data/.travis.yml +0 -13
  22. data/.yardopts +0 -1
  23. data/Gemfile +0 -17
  24. data/Rakefile +0 -31
  25. data/ext/patron/.gitignore +0 -4
  26. data/ext/patron/membuffer.c +0 -85
  27. data/ext/patron/membuffer.h +0 -78
  28. data/ext/patron/sglib.h +0 -1952
  29. data/lib/patron/proxy_type.rb +0 -11
  30. data/patron.gemspec +0 -38
  31. data/pic.png +0 -0
  32. data/script/console +0 -11
  33. data/script/test_server +0 -7
  34. data/spec/header_parser_spec.rb +0 -101
  35. data/spec/patron_spec.rb +0 -28
  36. data/spec/request_spec.rb +0 -77
  37. data/spec/response_spec.rb +0 -145
  38. data/spec/sample_response_headers/headers_wetransfer.txt +0 -18
  39. data/spec/sample_response_headers/headers_wetransfer_with_proxy_status.txt +0 -20
  40. data/spec/sample_response_headers/headers_wetransfer_with_redirect.txt +0 -24
  41. data/spec/sample_response_headers/headers_with_set_cookie.txt +0 -9
  42. data/spec/sample_response_headers/sample_http2_header.txt +0 -16
  43. data/spec/sample_response_headers/webmock_headers_without_trailing_crlf.txt +0 -15
  44. data/spec/session_spec.rb +0 -698
  45. data/spec/session_ssl_spec.rb +0 -313
  46. data/spec/spec_helper.rb +0 -34
  47. data/spec/support/certs/cacert.pem +0 -36
  48. data/spec/support/certs/privkey.pem +0 -51
  49. data/spec/support/config.ru +0 -146
  50. data/spec/support/fork.rb +0 -70
  51. data/spec/support/test_server.rb +0 -26
  52. data/spec/util_spec.rb +0 -71
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f900014f4c841d87c4db54f3db3f33d9b624d5a6dde0eb9352b7a70cc6f330b7
4
- data.tar.gz: 31b35805166302cb5527734b70c3607f9dbb951e2c1e3aafd465f7f8d5dbbb75
3
+ metadata.gz: 8f1026343c941740b978f220122296a7accd4de8770b18b317cdb6310e86dd5e
4
+ data.tar.gz: 0b90aed0ce0f16d4ae6b66ad0f44131b69e65b06d9e0e5e516d18c209b464160
5
5
  SHA512:
6
- metadata.gz: 1989abb0ba1be73ed08df186d05349c6519a90663a7263799a1e97e024e5183ff6de9f49ab329ebf4e3b49c4c571d0e3ca7efed3bc5f803ad8edbe96a6554ff6
7
- data.tar.gz: dc03c98c0f363d7d7bf273b72a62954dadb590c1236baf06739f6fdebd6ae5007afc243dc526c97359165c19244868779fc4168cc9f972c32cbf8fef81a56521
6
+ metadata.gz: cce780fab9bb4444992709dc18e2222bcb6e25fa03987b903270347e0a651080fe9024fa848da519b70a1511f429a4199f7e62425f554e4fb12b3bce198e31e7
7
+ data.tar.gz: d49a557d56dc10e57bc362f1af616eb779333da820c2899c4bb958347317d3bcc6eb171f8090ca03c2aae9ed94c5a53039e5c455206bb1372d743f6d029b97c3
data/CHANGELOG.md CHANGED
@@ -1,3 +1,100 @@
1
+ ### Unreleased
2
+
3
+ ### 1.0.0
4
+
5
+ Patron 1.0 is a rewrite of the C extension for modern Ruby and modern libcurl. It follows Ivo Anjo's
6
+ guide ["Tips for updating Ruby C extensions for efficiently using Ruby 4 VM APIs"](https://github.com/ruby/ruby/blob/master/doc/efficient_extensions.md),
7
+ which has since been merged into the Ruby documentation, and his post
8
+ ["Updating a C extension for a modern VM"](https://ivoanjo.me/blog/2026/09/23/updating-a-c-extension-for-a-modern-vm/).
9
+ It requires **Ruby 3.3+** and **libcurl 7.81+**.
10
+
11
+ **Transfers**
12
+
13
+ * A `Session` used inside a Fiber scheduler (`Async { }`, Falcon) yields to the scheduler while libcurl
14
+ waits for the network instead of blocking the thread, so requests from several fibers overlap and
15
+ `Async::Task#stop` / `with_timeout` interrupt a request cleanly. No API change, nothing to configure
16
+
17
+ * Requests are driven through libcurl's multi interface: every `Session` owns a multi handle with its own
18
+ connection cache (connection pooling, #138), `curl_multi_perform` runs with the GVL held and only
19
+ `curl_multi_poll` releases it, and `curl_multi_wakeup` is the unblocking function. `Thread#kill`,
20
+ `Thread#raise`, `Timeout.timeout`, signals and interpreter exit now interrupt a request immediately
21
+ in any phase of the transfer, including DNS resolution and connect, instead of at the next progress tick
22
+ * `Session#interrupt` aborts the request in flight from another thread immediately and raises `Patron::Aborted`
23
+ * Using one `Session` from two threads at once raises `Patron::Error` instead of corrupting memory
24
+ * Response headers and bodies are accumulated directly into Ruby Strings, no more intermediate C buffer and copy
25
+ * Multipart requests use libcurl's `curl_mime` API: field values are binary-safe, names and values are copied,
26
+ and multipart `PUT`/`PATCH` work too
27
+ * `download_byte_limit` raises `Patron::DownloadLimitExceeded` (a subclass of `Patron::Aborted`)
28
+ * Exceptions raised inside a `progress_callback` propagate out of the request after the transfer is cleaned up;
29
+ previously they unwound through libcurl's frames. Returning `false` from the callback aborts the request
30
+ * Every `Patron::Error` raised for a transfer carries the libcurl code as `#curl_code`
31
+ * A forked child no longer reuses the parent's libcurl handles and connections
32
+ * Cookies collected with `handle_cookies` (and TLS sessions) are kept across requests on the same
33
+ `Session`, not only across the redirects of one request
34
+ * `enable_debug` accepts an `IO` as well as a path, writes libcurl's verbose output through Ruby, and
35
+ never clobbers the process' stderr
36
+
37
+ **Memory and VM**
38
+
39
+ * The `Session` TypedData is write-barrier protected and compaction-aware; the request body is pinned
40
+ only while libcurl reads it; memory size is reported to `ObjectSpace`
41
+ * The extension declares itself Ractor-safe: Patron can be used from non-main Ractors. A `Session`
42
+ belongs to the Ractor that created it (Ruby refuses to copy, move or share one); `Response` and
43
+ `Request` objects can be made shareable, and a frozen `Response` still decodes its body. The 0.x
44
+ compatibility warnings are tracked per Ractor. Covered by specs on Ruby 4.0+
45
+ * Statically held exception classes are registered with the GC (they could dangle under compaction before)
46
+ * The global list of live sessions, the `at_exit` hook and the bundled `sglib.h` are gone
47
+
48
+ **Ruby API**
49
+
50
+ * `Patron::Request` keeps its setters (build one, change the URL, send it again) and also takes keyword
51
+ arguments; any HTTP token is accepted as a verb (#180), verbs libcurl does not know natively are sent as
52
+ custom requests, and `Marshal` now covers every attribute instead of 22 of 31
53
+ * `Session.new` takes keyword arguments
54
+ * `Response.new` takes keyword arguments (`url:`, `status:`, `redirect_count:`, `header_data:`, `body:`,
55
+ `default_charset:`, `timings:`); a custom `response_class` must accept the same
56
+ * `default_response_charset` is now honoured as the fallback for `Response#charset`
57
+ * `Response#body` is no longer mutated by `decoded_body`/`inspectable_body`
58
+ * `Patron.libcurl_features` reports the runtime libcurl version, TLS backend and feature flags
59
+ * `Patron::Request::SSL_VERSIONS` and `HTTP_VERSIONS` list the accepted version names; `"HTTPv3"` is accepted
60
+ * `Patron::ProxyType::HTTPS` added
61
+ * `base_url` and the request path are joined as URLs, not with `File.join`
62
+ * The 0.x positional forms of `Response.new`, `Session.new` and `Session#request` still work in 1.0 with a
63
+ deprecation warning (see `lib/patron/compat.rb` and #209); they go away in 1.1
64
+
65
+ **Removed**
66
+
67
+ * `Session#reset` (it was an alias of `interrupt`)
68
+ * `ssl_version` values `"SSLv2"` and `"SSLv3"` (refused by every supported libcurl anyway)
69
+ * Positional arguments to `Response.new`; `Request::READER_VARS`, `WRITER_VARS` and `VALID_ACTIONS` (see `Request::ATTRIBUTES`)
70
+ * Ruby < 3.3, libcurl < 7.81, and the `#if LIBCURL_VERSION_NUM` compatibility branches for libcurl 7.19 through 7.6x
71
+
72
+ **Build, packaging and tests**
73
+
74
+ * Require Ruby 3.3 or newer and libcurl 7.81.0 or newer
75
+ * Build with pkg-config when available; `--with-curl-dir` now takes precedence over a `curl-config` found on `PATH`
76
+ * Remove the dead `patron/proxy_type.rb` file; the `Patron::ProxyType` constants were always defined by the extension
77
+ * Stop shipping test fixtures and CI configuration inside the gem
78
+ * Test suite: Puma 6 and Rack 3, JSON request readback, configurable ports (`PATRON_TEST_HTTP_PORT` and friends), `PATRON_GC_STRESS=1` for GC and compaction stress runs
79
+
80
+ ### 0.14.0
81
+
82
+ * Expose libcurl's transfer timings (name lookup, connect, TLS, first byte, total) as `Response#timings`
83
+ * Add `ssl_cert`, `ssl_cert_type`, `ssl_key` and `ssl_key_password` for presenting a client certificate (mutual TLS). Thanks @lenage for the original contribution in #165
84
+ * Raise `Patron::ClientCertificateError` when the client certificate or its key can't be used
85
+ * Accept a `Pathname` for `cacert`
86
+ * Fix a request whose options fail to apply (for example an option of the wrong type) leaking its headers and open files into the next request on the same `Session`
87
+ * Fix the request body being read from freed memory when `upload_data` is not a String (its `#to_s` result was not kept alive during the transfer)
88
+ * Fix multipart field names and values pointing at temporary Strings, and binary multipart values being cut at the first NUL byte
89
+ * Fix an exception raised inside `progress_callback` (including `Timeout.timeout` and `Thread#raise`) unwinding through libcurl; it is now re-raised after the transfer is aborted
90
+ * Raise `Patron::Error` when a second thread uses a `Session` that is already performing a request, instead of corrupting both requests
91
+ * Fix the progress callback being declared with `size_t` arguments where libcurl passes `curl_off_t`
92
+ * Accept `download_byte_limit` above 2 GiB, and Floats for `dns_cache_timeout`, `low_speed_time` and `low_speed_limit`
93
+ * Fix `Session#unescape` cutting the result at a `%00`, and raise instead of crashing when libcurl fails to escape or unescape
94
+ * Reject file paths containing NUL bytes for cookie files, debug output and uploads
95
+ * Raise `NoMemoryError` instead of crashing later when libcurl cannot allocate a Session's handles
96
+ * Register the exception classes the extension looks up at load time with the GC, so heap compaction (`GC.compact`, `GC.auto_compact`) cannot move them out from under `rb_raise`
97
+
1
98
  ### 0.13.4
2
99
 
3
100
  * Format README a bit better using code fences
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Patron
2
2
 
3
- [![Build Status](https://travis-ci.org/toland/patron.svg?branch=master)](https://travis-ci.org/toland/patron)
3
+ [![Tests](https://github.com/toland/patron/actions/workflows/ci.yml/badge.svg)](https://github.com/toland/patron/actions/workflows/ci.yml)
4
4
 
5
5
  Patron is a Ruby HTTP client library based on libcurl. It does not try to expose
6
6
  the full "power" (read complexity) of libcurl but instead tries to provide a
@@ -22,9 +22,9 @@ sess.headers['User-Agent'] = 'myapp/1.0'
22
22
  You can set options with a hash in the constructor:
23
23
 
24
24
  ```ruby
25
- sess = Patron::Session.new({ :timeout => 10,
26
- :base_url => 'http://myserver.com:9900',
27
- :headers => {'User-Agent' => 'myapp/1.0'} } )
25
+ sess = Patron::Session.new(timeout: 10,
26
+ base_url: 'http://myserver.com:9900',
27
+ headers: {'User-Agent' => 'myapp/1.0'})
28
28
  ```
29
29
 
30
30
  Or the set options in a block:
@@ -37,10 +37,11 @@ sess = Patron::Session.new do |patron|
37
37
  end
38
38
  ```
39
39
 
40
- Output debug log:
40
+ Output libcurl's verbose log to a file, to any IO, or to `$stderr` when called without arguments:
41
41
 
42
42
  ```ruby
43
43
  sess.enable_debug "/tmp/patron.debug"
44
+ sess.enable_debug $stdout
44
45
  ```
45
46
 
46
47
  The Session is used to make HTTP requests.
@@ -57,26 +58,60 @@ if resp.status < 400
57
58
  end
58
59
  ```
59
60
 
60
- The GET, HEAD, PUT, POST and DELETE operations are all supported.
61
+ The GET, HEAD, PUT, PATCH, POST and DELETE operations have their own methods, and any other
62
+ HTTP verb can be sent through `request`:
61
63
 
62
64
  ```ruby
63
65
  sess.put("/foo/baz", "some data")
64
66
  sess.delete("/foo/baz")
67
+ sess.request(:propfind, "/foo", {"Depth" => "1"})
65
68
  ```
66
69
 
70
+ A failed transfer raises a subclass of `Patron::Error` (`Patron::TimeoutError`, `Patron::ConnectionFailed`,
71
+ `Patron::HostResolutionError` and so on). Every such error carries the libcurl error code as `curl_code`.
72
+
67
73
  You can ship custom headers with a single request:
68
74
 
69
75
  ```ruby
70
76
  sess.post("/foo/stuff", "some data", {"Content-Type" => "text/plain"})
71
77
  ```
72
78
 
79
+ libcurl's transfer timings are available on the response, in seconds since the request started:
80
+
81
+ ```ruby
82
+ resp = sess.get("/foo/bar")
83
+ resp.timings # => {namelookup: 0.001, connect: 0.012, appconnect: 0.045, pretransfer: 0.045,
84
+ # starttransfer: 0.210, redirect: 0.0, total: 0.214}
85
+ ```
86
+
87
+ To authenticate with a client certificate (mutual TLS), point Patron at a PKCS#12 bundle:
88
+
89
+ ```ruby
90
+ sess.ssl_cert = "/path/to/client.p12"
91
+ sess.ssl_cert_type = "P12"
92
+ sess.ssl_key_password = "secret"
93
+ ```
94
+
95
+ or at a PEM certificate with a separate private key:
96
+
97
+ ```ruby
98
+ sess.ssl_cert = "/path/to/client.pem"
99
+ sess.ssl_key = "/path/to/client_key.pem"
100
+ sess.ssl_key_password = "secret" # if the key is encrypted
101
+ ```
102
+
103
+ If the certificate or key can't be loaded (wrong password, missing file) the request raises `Patron::ClientCertificateError`.
104
+ This depends on the TLS library libcurl is built against: with GnuTLS these surface as a plain `Patron::Error`.
105
+ Client certificates are tested with the OpenSSL and LibreSSL backends - libcurl's Secure Transport backend
106
+ can't load a PEM certificate from a file at all.
107
+
73
108
  ## Threading
74
109
 
75
- By itself, the `Patron::Session` objects are not thread safe (each `Session` holds a single `curl_state` pointer
76
- from initialization to garbage collection). At this time, Patron has no support for `curl_multi_*` family of functions
77
- for doing concurrent requests. However, the actual code that interacts with libCURL does unlock the RVM GIL,
78
- so using multiple `Session` objects in different threads actually enables a high degree of parallelism.
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.
110
+ A `Patron::Session` performs one request at a time and must not be shared between threads: each `Session`
111
+ owns its own libcurl handles and buffers, and using it from two threads at once raises `Patron::Error`.
112
+ Patron releases the GVL (the global VM lock) whenever it waits for the network, so using one `Session`
113
+ per thread gives you real parallelism.
114
+ For sharing a pool of sessions between threads we recommend the excellent [connection_pool](https://rubygems.org/gems/connection_pool) gem by Mike Perham.
80
115
 
81
116
  ```ruby
82
117
  patron_pool = ConnectionPool.new(size: 5, timeout: 5) { Patron::Session.new }
@@ -89,62 +124,90 @@ Sharing Session objects between requests will also allow you to benefit from per
89
124
 
90
125
  ## Persistent connections
91
126
 
92
- Patron follows the libCURL guidelines on [connection reuse.](https://everything.curl.dev/libcurl/connectionreuse.html) If you create the Session
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
94
- the same hostname/port/protocol the connection should get reused.
127
+ Every `Session` owns a libcurl multi handle with its own connection cache, plus a share handle for
128
+ cookies, DNS entries and TLS sessions. If you create the Session once and use it for multiple requests
129
+ to the same hostname/port/protocol, the connection gets reused, following libcurl's rules on
130
+ [connection reuse.](https://everything.curl.dev/libcurl/connectionreuse.html)
95
131
 
96
- ## Performance with parallel requests
132
+ ## Interrupting requests
97
133
 
98
- When performing the libCURL request, Patron goes out of it's way to unlock the GVL (global VM lock) to allow other threads to be scheduled
99
- in parallel. The GVL is going to be released when the libCURL request starts, and will then be shortly re-acquired to provide the progress
100
- callback - if the callback has been configured, and then released again until the libCURL request has been performed and the response has
101
- been read in full. This allows one to execute multiple libCURL requests in parallel, as well as perform other activities on other MRI threads
102
- that are currently active in the process.
134
+ A request in flight can be aborted from another thread with `Session#interrupt`, after which the
135
+ request raises `Patron::Aborted`. `Thread#kill`, `Thread#raise`, `Timeout.timeout` and signals interrupt
136
+ a request the same way, immediately and in any phase of the transfer, including DNS resolution and
137
+ connecting. A `progress_callback` that returns `false` aborts the request as well, and an exception
138
+ raised inside the callback propagates out of the request once libcurl is done with it.
103
139
 
104
- ## Requirements
140
+ ## Fibers and the async gem
105
141
 
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.
142
+ When a Fiber scheduler is installed for the current thread (as inside `Async { }`), Patron hands the
143
+ wait for the network to the scheduler instead of blocking the thread, so requests from several fibers
144
+ overlap and other fibers keep running while one waits:
108
145
 
109
- A recent version of libCURL is required. We recommend at least 7.19.4 because
110
- it [supports limiting the protocols](https://curl.haxx.se/libcurl/c/CURLOPT_PROTOCOLS.html),
111
- and that is very important for security - especially if you follow redirects.
146
+ ```ruby
147
+ Async do |task|
148
+ pages = urls.map { |url| task.async { Patron::Session.new(timeout: 10).get(url) } }.map(&:wait)
149
+ end
150
+ ```
112
151
 
113
- On OSX the provided libcurl is sufficient if you are not using fork+SSL combination (see below).
114
- You will have to install the libcurl development packages on Debian or Ubuntu. Other Linux systems are probably
115
- similar. For Windows we do not have an established build instruction at the moment, unfortunately.
152
+ Each fiber needs its own `Session`, just like each thread does. `Async::Task#stop` and
153
+ `Task#with_timeout` interrupt a request in flight the same way `Thread#kill` and `Timeout` do.
116
154
 
117
- ## Forking webservers on macOS and SSL
155
+ ## Ractors
118
156
 
119
- Currently, [an issue is at play](https://github.com/curl/curl/issues/788) with OSX builds of `curl` which use
120
- Apple's SecureTransport. Such builds (which Patron is linking to), are causing segfaults when performing HTTPS
121
- requests in forked subprocesses. If you need to check whether your system is affected,
122
- run the Patron test suite by performing
157
+ Patron works from non-main Ractors, so Ractors give you parallelism for the Ruby side of your
158
+ requests as well, not just for the waits. A `Session` belongs to the Ractor that created it: Ruby refuses
159
+ to copy, move or share one (`Ractor::Error`), so create the `Session` inside the Ractor and ship
160
+ `Response` objects out. A worker pool looks like this on Ruby 4.0:
123
161
 
124
- $ bundle install && bundle exec rspec
162
+ ```ruby
163
+ results = Ractor::Port.new
164
+ workers = 4.times.map do
165
+ Ractor.new(results) do |results|
166
+ session = Patron::Session.new(timeout: 10)
167
+ while (url = Ractor.receive)
168
+ results << Ractor.make_shareable(session.get(url))
169
+ end
170
+ end
171
+ end
172
+ urls.each_with_index { |url, i| workers[i % workers.size].send(url) }
173
+ responses = urls.size.times.map { results.receive }
174
+ ```
175
+
176
+ `Ractor.make_shareable` on the `Response` is optional: it saves copying the body when the Response crosses
177
+ over, and a shareable (frozen) `Response` still decodes its body. A `Patron::Error` raised inside the
178
+ Ractor comes out of `Ractor#value` as the `cause` of a `Ractor::RemoteError`, `curl_code` and all.
179
+ `Patron::Request` objects can be made shareable and used from any Ractor too.
180
+
181
+ ## How the GVL is handled
182
+
183
+ Patron drives libcurl through its multi interface. The part of libcurl that moves bytes
184
+ (`curl_multi_perform`) runs with the GVL held, which is what makes it safe to run your progress callback
185
+ and to write straight into Ruby Strings from inside libcurl. Whenever libcurl has nothing to do but wait
186
+ for the network, Patron releases the GVL (`curl_multi_poll`), so other Ruby threads run in parallel with
187
+ the transfer, and that wait is what Ruby interrupts when it needs the thread back.
188
+
189
+ ## Requirements
125
190
 
126
- in the Patron install directory. Most default curl configurations on OSX (both
127
- the Apple-shipped version and the version available via Homebrew) are linked to
128
- SecureTransport and are likely to be affected. This issue may also manifest in
129
- forking webserver implementations (such as Unicorn or Passenger) and in forking
130
- job execution engines (such as resque), so even though you may not be using
131
- `fork()` directly your server engine might be doing it for you.
191
+ Patron 1.0 and up requires Ruby 3.3 or newer and libcurl 7.81.0 or newer (Ubuntu 22.04 and up,
192
+ Debian 12 and up, any current macOS). The 0.x versions support older Rubies and stay on the `v0.x` branch.
132
193
 
133
- To circumvent the issue, you need to build `curl` with OpenSSL via homebrew.
134
- When doing so, `curl` will use openssl as it's SSL driver. You also need to
135
- change the Patron compile flag:
194
+ You will have to install the libcurl development package on Debian or Ubuntu (`libcurl4-openssl-dev`),
195
+ Alpine (`curl-dev`) and the like. The build looks libcurl up through `pkg-config`, then `curl-config`,
196
+ then the default include and library paths. To build against a specific libcurl, for example
197
+ Homebrew's OpenSSL-backed one instead of the SecureTransport-backed one macOS ships:
136
198
 
199
+ $ gem install patron -- --with-curl-dir=$(brew --prefix curl)
137
200
 
138
- $ brew install curl-openssl && \
139
- gem install patron -- --with-curl-config=/usr/local/opt/curl-openssl/bin/curl-config
201
+ or, for every Bundler-driven install:
140
202
 
141
- You can also save this parameter for all future Bundler-driven gem installs by
142
- setting this flag in Bundler proper:
203
+ $ bundle config build.patron --with-curl-dir=$(brew --prefix curl)
143
204
 
144
- $ bundle config build.patron --with-curl-config=/usr/local/opt/curl-openssl/bin/curl-config
205
+ The SecureTransport backend of the macOS system libcurl cannot load PEM client certificates,
206
+ and [older versions of it crash](https://github.com/curl/curl/issues/788) when doing HTTPS in a forked
207
+ subprocess, so the Homebrew libcurl is the better choice on macOS.
145
208
 
146
209
  ## Installation
147
210
 
148
- sudo gem install patron
211
+ gem install patron
149
212
 
150
213
  Copyright (c) 2008 The Hive
@@ -1,26 +1,61 @@
1
- require 'mkmf'
2
- require 'rbconfig'
3
-
4
- dir_config('curl')
5
- curl_config_path = with_config('curl-config') || find_executable('curl-config')
6
- if curl_config_path
7
- $CFLAGS << " " << `#{curl_config_path} --cflags`.strip
8
- $LIBS << " " << `#{curl_config_path} --libs`.strip
9
- elsif !have_library('curl') or !have_header('curl/curl.h')
10
- fail <<-EOM
11
- Can't find libcurl or curl/curl.h
12
-
13
- Try passing --with-curl-config, --with-curl-dir, or --with-curl-lib and --with-curl-include
14
- options to extconf.
15
- EOM
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+
5
+ MIN_LIBCURL = "7.81.0"
6
+ MIN_LIBCURL_NUM = 0x075100
7
+
8
+ # An explicit --with-curl-dir (or -include/-lib) wins over anything on PATH, otherwise
9
+ # the SDK's curl-config on macOS silently overrides the Homebrew one the user asked for
10
+ explicit_dir = dir_config("curl").any?
11
+ unless explicit_dir
12
+ pkg_config("libcurl") ||
13
+ begin
14
+ curl_config = with_config("curl-config") || find_executable("curl-config")
15
+ if curl_config
16
+ $CFLAGS << " " << `#{curl_config} --cflags`.strip
17
+ $LIBS << " " << `#{curl_config} --libs`.strip
18
+ end
19
+ end
16
20
  end
17
21
 
18
- if CONFIG['CC'] =~ /gcc/
19
- $CFLAGS << ' -pedantic -Wall'
22
+ # pkg-config and curl-config put -lcurl in $LIBS already, so only fall back to have_library
23
+ # when linking does not work as-is - otherwise we get -lcurl twice and a linker warning
24
+ linked = have_header("curl/curl.h") &&
25
+ (have_func("curl_easy_init", "curl/curl.h") || have_library("curl", "curl_easy_init"))
26
+ unless linked
27
+ abort <<~MSG
28
+ Can't find libcurl or curl/curl.h.
29
+
30
+ Install the libcurl development package (libcurl4-openssl-dev, curl-dev, ...) or pass
31
+ --with-curl-dir=/path/to/curl (also --with-curl-include / --with-curl-lib,
32
+ or --with-curl-config=/path/to/curl-config) to extconf.
33
+ MSG
20
34
  end
21
35
 
22
- if CONFIG['CC'] =~ /clang/
23
- $CFLAGS << ' -pedantic -Wall -Wno-void-pointer-to-enum-cast'
36
+ found_num = try_constant("LIBCURL_VERSION_NUM", "curl/curlver.h")
37
+ if found_num.nil? || found_num < MIN_LIBCURL_NUM
38
+ found = found_num ? "%d.%d.%d" % [found_num >> 16, (found_num >> 8) & 0xff, found_num & 0xff] : "an unknown version"
39
+ abort "Patron needs libcurl #{MIN_LIBCURL} or newer, found #{found}."
24
40
  end
25
41
 
26
- create_makefile 'patron/session_ext'
42
+ %w[
43
+ CURLOPT_PROTOCOLS_STR
44
+ CURL_HTTP_VERSION_3
45
+ CURLOPT_CA_CACHE_TIMEOUT
46
+ CURL_VERSION_THREADSAFE
47
+ CURLINFO_QUEUE_TIME_T
48
+ CURLINFO_POSTTRANSFER_TIME_T
49
+ ].each { |const| have_const(const, "curl/curl.h") }
50
+ have_func("curl_easy_header", "curl/curl.h")
51
+
52
+ # Ruby's own warnflags already bring -Wall -Wextra; these two turn the warnings
53
+ # that hide real ABI mistakes into errors
54
+ append_cflags(%w[-Werror=implicit-function-declaration -Werror=incompatible-pointer-types])
55
+ append_cflags("-Werror") if enable_config("werror")
56
+
57
+ # For sanitizer builds in CI: plain CFLAGS gets clobbered by RbConfig, so use our own names
58
+ $CFLAGS << " " << ENV["PATRON_CFLAGS"] if ENV["PATRON_CFLAGS"]
59
+ $LDFLAGS << " " << ENV["PATRON_LDFLAGS"] if ENV["PATRON_LDFLAGS"]
60
+
61
+ create_makefile "patron/session_ext"