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.
@@ -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
@@ -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 }
@@ -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, {:Port => port.to_i, :Verbose => true, :Host => host})
27
+ Rack::Handler::Puma.run(APP, :Port => port.to_i, :Verbose => true, :Host => host)
25
28
  end
26
29
  end
@@ -0,0 +1,2 @@
1
+ # Suppressions for the valgrind CI job. Keep this to things that are demonstrably
2
+ # not Patron's: nothing from session_ext.c or membuffer.c may be suppressed here.
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.13.4
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: 2025-02-27 00:00:00.000000000 Z
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: '0'
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: 1.2.0
103
+ version: 2.5.0
95
104
  requirements: []
96
105
  rubygems_version: 3.6.2
97
106
  specification_version: 4