patron 0.13.4 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +56 -3
- data/CHANGELOG.md +20 -0
- data/Gemfile +2 -1
- data/README.md +30 -1
- data/ext/patron/extconf.rb +4 -0
- data/ext/patron/session_ext.c +285 -75
- data/lib/patron/error.rb +4 -0
- data/lib/patron/request.rb +6 -4
- data/lib/patron/response.rb +5 -0
- data/lib/patron/session.rb +17 -0
- data/lib/patron/version.rb +1 -1
- data/patron.gemspec +2 -1
- data/script/generate_client_certs +65 -0
- data/spec/request_spec.rb +12 -0
- data/spec/session_spec.rb +178 -11
- data/spec/session_ssl_spec.rb +126 -8
- data/spec/spec_helper.rb +6 -0
- data/spec/support/certs/client.p12 +0 -0
- data/spec/support/certs/client_ca.pem +20 -0
- data/spec/support/certs/client_cert.pem +20 -0
- data/spec/support/certs/client_key.pem +30 -0
- data/spec/support/certs/rogue_client.p12 +0 -0
- data/spec/support/config.ru +7 -0
- data/spec/support/raw_request_capture.rb +93 -0
- data/spec/support/test_server.rb +6 -3
- data/spec/support/valgrind.supp +2 -0
- metadata +13 -4
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDSzCCAjOgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBPMQswCQYDVQQGEwJVUzEg
|
|
3
|
+
MB4GA1UECgwXUGF0cm9uIFRlc3QgQ2VydGlmaWNhdGUxHjAcBgNVBAMMFVBhdHJv
|
|
4
|
+
biBUZXN0IENsaWVudCBDQTAeFw0yNTAxMDEwMDAwMDBaFw00NTAxMDEwMDAwMDBa
|
|
5
|
+
MEwxCzAJBgNVBAYTAlVTMSAwHgYDVQQKDBdQYXRyb24gVGVzdCBDZXJ0aWZpY2F0
|
|
6
|
+
ZTEbMBkGA1UEAwwScGF0cm9uLXRlc3QtY2xpZW50MIIBIjANBgkqhkiG9w0BAQEF
|
|
7
|
+
AAOCAQ8AMIIBCgKCAQEAnp7kL8f5Yk/T6B+IRoLWvCTHeORa4ilp7ixTE4EJOwTB
|
|
8
|
+
atAfnoue4U+xMBAX91oIiGT8u5yKcC+EbvmvcOuOYQPCVXvx3bHBzeTzjBkH37k1
|
|
9
|
+
rBeXVxfhEnkXAANJOccTpPJRH2m+1X/FcXuUNKZOnSrc4G/Gpraj2Opygz8ntxbW
|
|
10
|
+
JR94mz1nkkAILlkCie4VFfSdnxE5ghQSnt8B7W9SdqSKD22NKpBmOff2x5136IEo
|
|
11
|
+
YNK7zYintgJfX6Pa7TR8peEVtAkl3/EjSEoSmfFQVZz2OB7IK3xFT2RyqoC9MjO6
|
|
12
|
+
2mItbD0LgZzTD3f91g00a7z6EjSW8g8QYc5pXSdC1wIDAQABozUwMzAMBgNVHRMB
|
|
13
|
+
Af8EAjAAMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkq
|
|
14
|
+
hkiG9w0BAQsFAAOCAQEAU/u+MOKWMXQqkPSQx2RjrOale3KKdkHX6LlGfZYjqMhn
|
|
15
|
+
IQjYbEfGtIKyD2MChXrhKMLwuwvMUaCMQWq0r9vZb2cz+AGDS/OzHui/YXgoUU/D
|
|
16
|
+
JOqmT17ClM1JIBZjfbLaogGGY//l4NA48MjhTDHw9MGOnnjzVYUy7CC6qiFu0onC
|
|
17
|
+
Ko+ZE0Ebb3KyjUeJLSCXEhTVW0myGc+/jNRJP0YW/TvxKRZ5EAhaJrLXvKm7tjil
|
|
18
|
+
SZpXBETiXq6eZZsD+XNfQvytBIFO4eBhv3sipQ8Ga30L3SqkMlsb4ptWpa5+z1Vk
|
|
19
|
+
BlkuYdKpPhD/nmx/OfRdfTItDVstm8Q13+MGS5Rokw==
|
|
20
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
2
|
+
Proc-Type: 4,ENCRYPTED
|
|
3
|
+
DEK-Info: AES-256-CBC,73A5C7F43CBFB22BDECED16E3F6D4014
|
|
4
|
+
|
|
5
|
+
s2DHo+hKzlo9ZFcql4V84w5c8OADkBHNYp3DJJSII+LSufiNLJ1JVcACEZdr/JsF
|
|
6
|
+
vLKTlCIjExGY1b85GPZ25PArB5jrZvWCcOFfs5PGzr3/ykRTfTMHSXo+WfFwkZ2x
|
|
7
|
+
2k/Zt8yVJ7f69G7EevTtVbkmx/wLMnlTSjMKfzWfs/f57GP+dWM5rHOiMBKK2KhC
|
|
8
|
+
OlD6yhKV4frmF9YTQlslrg5Nmd+3UQsSubT6s4qLByYrLm7pJHB9mx2LNLLQnoxv
|
|
9
|
+
IBIL+FcQRQvLtGGbzV42BHEqr6Kq7aqDf9RT6JohOMdGMYMtABZP9/iOqCUMT2QB
|
|
10
|
+
2/cio1jjgijHqpVBsPg9D493t0fm+1FXLK21wfvQgP6/v0wDB3YKlGlHzWWiVc2f
|
|
11
|
+
4EF/VAyPR8bJwpnZvQpsQ16HEftyyHTbishVbiqzvTNi4HnmSX6WQI2pwFHlF22j
|
|
12
|
+
UWJzSfLqp8NW340zo/EYDAm/dPaqVGR+pVtUEta+oGSI7OQWB3hGt7znSpdgFrah
|
|
13
|
+
i1IOkcu3fie15FzSlBtFhByigtz6iedrTCvYZhfswU4XxJfPJtlVnDubQ9xKDXp6
|
|
14
|
+
3xuy8R4ekMysSOGHq2NdOaYvOoMK8LAJ++KZwEe6UJfBDvEkC23V3mkoCKiUANDY
|
|
15
|
+
GuJ5SOqw6DF6cO/i+StvOuB+2mxMOhR4x893N/ntQrITYldvRh+QHNFoyKc5NUu4
|
|
16
|
+
035ezDVRDR4kzJzzTFfO9yshGFrqb7oyfUegzZMGah1sNn7AWG7AtBSbxSzmRk5s
|
|
17
|
+
0uBrBX7ASfMUGkpUV7BFsvDPi819k5iWLYLFPiFB/CJ5Wq0HHU4j9XDIa/dEuSe+
|
|
18
|
+
0HvQpYf/2IiYocxKxnZOXfdO06cigip1OjxbIXgx1uqrCyzB7c7XMLEUXETpdj1p
|
|
19
|
+
XPCvZ/9BaRNjWvvTrNNB2nMSarFdtjY6pB8bZtP4xObiQ59L3mLoL9/MD87Cnhzn
|
|
20
|
+
Gr8Yr3V5TJk8o60rWNNpaXeCbIlJONFN128uAP8vgjaYviHNb15ViDW1a57fO/OW
|
|
21
|
+
4XOt5jqVDC/NfQXADku7bcs1SPUOKbDgVeYiiq0na3t5yePNRZVcA6FEHuc9hDLi
|
|
22
|
+
T+Fp7bqxXa7pZMn9FOCJ0cRhtoD6LaIqNbtzCIU9u6HBbJkg36Nz7ysh3riYPKBI
|
|
23
|
+
ShLhSb+unI2owdWBi6QwDRus2FtcvPwPNrqtnZ1V5/YyFY6k7ScLR6R67A4ESKNJ
|
|
24
|
+
qIWQ0iCN9nCjbCGpO62e5BzwoVGXiTQK7QK1WPT95+E3Qlv8q6hGi32d5gMUNR1h
|
|
25
|
+
r8OmL+HXMZE2azKJB1mKldYznOYiSIJDjvSp/Jm4A+6zYB6wwiQWwZUUrmW7zbg0
|
|
26
|
+
o7PHImDvRj8hkJlpNRCqLKkyKF+/PaqkFx2dJxJT3xmVJ+H7wqmwhii1Y0RE9YHu
|
|
27
|
+
yzrL7vZpC2a2cjyJnlDG7sX4ZLPNg1vOEXcnKO/6+HHDLkmPQj85+mqHInrTvuca
|
|
28
|
+
GqC/JZeD8FMnpsWQC51ZwThbwlzq91kvFPhSxOqEequ0HP/1K3DdNhxQNKwQseV2
|
|
29
|
+
0jK0rCgibSroD8bE7kghtUPK/fts6XGRqgXmPmx9i3VJGbJw14GvKGp3oyosVEna
|
|
30
|
+
-----END RSA PRIVATE KEY-----
|
|
Binary file
|
data/spec/support/config.ru
CHANGED
|
@@ -127,6 +127,12 @@ LargeServlet = Proc.new {|env|
|
|
|
127
127
|
[200, {'Content-Type' => 'binary/octet-stream', 'Content-Length' => len.to_s}, body]
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
# Echoes the subject of the client certificate Puma verified, if any
|
|
131
|
+
PeerCertServlet = Proc.new {|env|
|
|
132
|
+
subject = env['puma.peercert'] ? env['puma.peercert'].subject.to_s : 'none'
|
|
133
|
+
[200, {'Content-Type' => 'text/plain', 'Content-Length' => subject.bytesize.to_s}, [subject]]
|
|
134
|
+
}
|
|
135
|
+
|
|
130
136
|
run Rack::URLMap.new({
|
|
131
137
|
"/" => Proc.new {|env| [200, {'Content-Length' => '2'}, ['Welcome']]},
|
|
132
138
|
"/test" => Readback,
|
|
@@ -143,4 +149,5 @@ run Rack::URLMap.new({
|
|
|
143
149
|
"/repetitiveheader" => RepetitiveHeaderServlet,
|
|
144
150
|
"/wrongcontentlength" => WrongContentLengthServlet,
|
|
145
151
|
"/gzip-compressed" => GzipServlet,
|
|
152
|
+
"/peercert" => PeerCertServlet,
|
|
146
153
|
})
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'stringio'
|
|
4
|
+
|
|
5
|
+
# Captures the raw HTTP request a client puts on the wire, independent of any
|
|
6
|
+
# Rack/HTTP server. This lets us assert on hop-by-hop details such as
|
|
7
|
+
# Transfer-Encoding which a conformant server (webrick, puma, ...) decodes and
|
|
8
|
+
# never exposes to the application.
|
|
9
|
+
module RawRequestCapture
|
|
10
|
+
CapturedRequest = Struct.new(:request_line, :headers, :body)
|
|
11
|
+
|
|
12
|
+
CERT_DIR = File.join(__dir__, 'certs')
|
|
13
|
+
|
|
14
|
+
# Boots a one-shot capture server on an ephemeral port, yields its base URL to
|
|
15
|
+
# the block (which must perform a single request against it) and returns the
|
|
16
|
+
# CapturedRequest. Pass ssl: true to terminate TLS using the test certificates.
|
|
17
|
+
def capture_request(ssl: false)
|
|
18
|
+
tcp = TCPServer.new('127.0.0.1', 0)
|
|
19
|
+
port = tcp.addr[1]
|
|
20
|
+
server = ssl ? wrap_tls(tcp) : tcp
|
|
21
|
+
captured = nil
|
|
22
|
+
acceptor = Thread.new do
|
|
23
|
+
conn = server.accept
|
|
24
|
+
captured = read_request(conn)
|
|
25
|
+
conn.write("HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok")
|
|
26
|
+
conn.close
|
|
27
|
+
end
|
|
28
|
+
yield "#{ssl ? 'https' : 'http'}://127.0.0.1:#{port}"
|
|
29
|
+
acceptor.join(5)
|
|
30
|
+
captured
|
|
31
|
+
ensure
|
|
32
|
+
tcp.close if tcp
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def wrap_tls(tcp)
|
|
38
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
|
39
|
+
ctx.cert = OpenSSL::X509::Certificate.new(File.read(File.join(CERT_DIR, 'cacert.pem')))
|
|
40
|
+
ctx.key = OpenSSL::PKey.read(File.read(File.join(CERT_DIR, 'privkey.pem')))
|
|
41
|
+
OpenSSL::SSL::SSLServer.new(tcp, ctx)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def read_request(conn)
|
|
45
|
+
buf = +''
|
|
46
|
+
buf << conn.readpartial(4096) until buf.include?("\r\n\r\n")
|
|
47
|
+
head, rest = buf.split("\r\n\r\n", 2)
|
|
48
|
+
request_line, *header_lines = head.split("\r\n")
|
|
49
|
+
headers = header_lines.each_with_object({}) do |line, acc|
|
|
50
|
+
name, value = line.split(': ', 2)
|
|
51
|
+
acc[name.downcase] = value
|
|
52
|
+
end
|
|
53
|
+
# Honour Expect: 100-continue so the client proceeds to send the body.
|
|
54
|
+
conn.write("HTTP/1.1 100 Continue\r\n\r\n") if headers['expect'].to_s =~ /100-continue/i
|
|
55
|
+
CapturedRequest.new(request_line, headers, read_body(conn, headers, rest))
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def read_body(conn, headers, rest)
|
|
59
|
+
if headers['transfer-encoding'] == 'chunked'
|
|
60
|
+
dechunk(drain_chunked(conn, rest))
|
|
61
|
+
elsif (len = headers['content-length'])
|
|
62
|
+
read_exactly(conn, len.to_i, rest)
|
|
63
|
+
else
|
|
64
|
+
rest
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def drain_chunked(conn, rest)
|
|
69
|
+
buf = rest.dup
|
|
70
|
+
buf << conn.readpartial(4096) until buf.include?("0\r\n\r\n")
|
|
71
|
+
buf
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def read_exactly(conn, n, rest)
|
|
75
|
+
buf = rest.dup
|
|
76
|
+
buf << conn.readpartial(4096) while buf.bytesize < n
|
|
77
|
+
buf.byteslice(0, n)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def dechunk(buf)
|
|
81
|
+
io = StringIO.new(buf)
|
|
82
|
+
out = +''
|
|
83
|
+
while (size_line = io.gets("\r\n"))
|
|
84
|
+
size = size_line.strip.split(';', 2).first.to_i(16)
|
|
85
|
+
break if size.zero?
|
|
86
|
+
out << io.read(size).to_s
|
|
87
|
+
io.read(2) # trailing CRLF
|
|
88
|
+
end
|
|
89
|
+
out
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
RSpec.configure { |c| c.include RawRequestCapture }
|
data/spec/support/test_server.rb
CHANGED
|
@@ -5,7 +5,7 @@ require 'rack/handler/puma'
|
|
|
5
5
|
class PatronTestServer
|
|
6
6
|
APP = Rack::Builder.new { eval(File.read(File.join(__dir__, 'config.ru'))) }
|
|
7
7
|
|
|
8
|
-
def self.start(ssl = false, port = 9001 )
|
|
8
|
+
def self.start(ssl = false, port = 9001, client_ca: nil)
|
|
9
9
|
# Reset the RSpec's SIGINT handler that does not really terminate after
|
|
10
10
|
# the first Ctrl+C pressed.
|
|
11
11
|
# Useful to terminate the forked process before Puma is actually started:
|
|
@@ -16,11 +16,14 @@ class PatronTestServer
|
|
|
16
16
|
keypath = File.join(__dir__, 'certs', 'privkey.pem')
|
|
17
17
|
certpath = File.join(__dir__, 'certs', 'cacert.pem')
|
|
18
18
|
|
|
19
|
-
host = if ssl
|
|
19
|
+
host = if ssl && client_ca
|
|
20
|
+
# force_peer makes Puma drop the handshake when no valid client certificate is presented
|
|
21
|
+
'ssl://0.0.0.0:%d?key=%s&cert=%s&ca=%s&verify_mode=force_peer' % [port, keypath, certpath, client_ca]
|
|
22
|
+
elsif ssl
|
|
20
23
|
'ssl://0.0.0.0:%d?key=%s&cert=%s' % [port, keypath, certpath]
|
|
21
24
|
else
|
|
22
25
|
'0.0.0.0'
|
|
23
26
|
end
|
|
24
|
-
Rack::Handler::Puma.run(APP,
|
|
27
|
+
Rack::Handler::Puma.run(APP, :Port => port.to_i, :Verbose => true, :Host => host)
|
|
25
28
|
end
|
|
26
29
|
end
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: patron
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Aeryn Riley Dowling-Toland
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date:
|
|
10
|
+
date: 2026-10-27 00:00:00.000000000 Z
|
|
11
11
|
dependencies: []
|
|
12
12
|
description: Ruby HTTP client library based on libcurl
|
|
13
13
|
email:
|
|
@@ -18,6 +18,7 @@ extensions:
|
|
|
18
18
|
extra_rdoc_files: []
|
|
19
19
|
files:
|
|
20
20
|
- ".autotest"
|
|
21
|
+
- ".github/dependabot.yml"
|
|
21
22
|
- ".github/workflows/ci.yml"
|
|
22
23
|
- ".gitignore"
|
|
23
24
|
- ".rspec"
|
|
@@ -47,6 +48,7 @@ files:
|
|
|
47
48
|
- patron.gemspec
|
|
48
49
|
- pic.png
|
|
49
50
|
- script/console
|
|
51
|
+
- script/generate_client_certs
|
|
50
52
|
- script/test_server
|
|
51
53
|
- spec/header_parser_spec.rb
|
|
52
54
|
- spec/patron_spec.rb
|
|
@@ -62,10 +64,17 @@ files:
|
|
|
62
64
|
- spec/session_ssl_spec.rb
|
|
63
65
|
- spec/spec_helper.rb
|
|
64
66
|
- spec/support/certs/cacert.pem
|
|
67
|
+
- spec/support/certs/client.p12
|
|
68
|
+
- spec/support/certs/client_ca.pem
|
|
69
|
+
- spec/support/certs/client_cert.pem
|
|
70
|
+
- spec/support/certs/client_key.pem
|
|
65
71
|
- spec/support/certs/privkey.pem
|
|
72
|
+
- spec/support/certs/rogue_client.p12
|
|
66
73
|
- spec/support/config.ru
|
|
67
74
|
- spec/support/fork.rb
|
|
75
|
+
- spec/support/raw_request_capture.rb
|
|
68
76
|
- spec/support/test_server.rb
|
|
77
|
+
- spec/support/valgrind.supp
|
|
69
78
|
- spec/util_spec.rb
|
|
70
79
|
homepage: https://github.com/toland/patron
|
|
71
80
|
licenses:
|
|
@@ -86,12 +95,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
86
95
|
requirements:
|
|
87
96
|
- - ">="
|
|
88
97
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
98
|
+
version: '2.3'
|
|
90
99
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
91
100
|
requirements:
|
|
92
101
|
- - ">="
|
|
93
102
|
- !ruby/object:Gem::Version
|
|
94
|
-
version:
|
|
103
|
+
version: 2.5.0
|
|
95
104
|
requirements: []
|
|
96
105
|
rubygems_version: 3.6.2
|
|
97
106
|
specification_version: 4
|