puma 2.2.0 → 8.0.2

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 (136) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +3334 -0
  3. data/LICENSE +23 -20
  4. data/README.md +387 -100
  5. data/bin/puma-wild +25 -0
  6. data/bin/pumactl +1 -1
  7. data/docs/5.0-Upgrade.md +98 -0
  8. data/docs/6.0-Upgrade.md +56 -0
  9. data/docs/7.0-Upgrade.md +52 -0
  10. data/docs/8.0-Upgrade.md +100 -0
  11. data/docs/architecture.md +74 -0
  12. data/docs/compile_options.md +55 -0
  13. data/docs/deployment.md +137 -0
  14. data/docs/fork_worker.md +41 -0
  15. data/docs/grpc.md +62 -0
  16. data/docs/images/favicon.svg +1 -0
  17. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  18. data/docs/images/puma-connection-flow.png +0 -0
  19. data/docs/images/puma-general-arch.png +0 -0
  20. data/docs/images/running-puma.svg +1 -0
  21. data/docs/images/standard-logo.svg +1 -0
  22. data/docs/java_options.md +54 -0
  23. data/docs/jungle/README.md +9 -0
  24. data/docs/jungle/rc.d/README.md +74 -0
  25. data/docs/jungle/rc.d/puma +61 -0
  26. data/docs/jungle/rc.d/puma.conf +10 -0
  27. data/docs/kubernetes.md +73 -0
  28. data/docs/nginx.md +5 -5
  29. data/docs/plugins.md +42 -0
  30. data/docs/rails_dev_mode.md +28 -0
  31. data/docs/restart.md +65 -0
  32. data/docs/signals.md +98 -0
  33. data/docs/stats.md +148 -0
  34. data/docs/systemd.md +253 -0
  35. data/docs/testing_benchmarks_local_files.md +150 -0
  36. data/docs/testing_test_rackup_ci_files.md +36 -0
  37. data/ext/puma_http11/PumaHttp11Service.java +2 -2
  38. data/ext/puma_http11/extconf.rb +59 -2
  39. data/ext/puma_http11/http11_parser.c +319 -487
  40. data/ext/puma_http11/http11_parser.h +15 -13
  41. data/ext/puma_http11/http11_parser.java.rl +64 -94
  42. data/ext/puma_http11/http11_parser.rl +27 -24
  43. data/ext/puma_http11/http11_parser_common.rl +8 -8
  44. data/ext/puma_http11/mini_ssl.c +696 -38
  45. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  46. data/ext/puma_http11/org/jruby/puma/EnvKey.java +241 -0
  47. data/ext/puma_http11/org/jruby/puma/Http11.java +241 -145
  48. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +174 -221
  49. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +413 -193
  50. data/ext/puma_http11/puma_http11.c +183 -175
  51. data/lib/puma/app/status.rb +77 -29
  52. data/lib/puma/binder.rb +349 -119
  53. data/lib/puma/cli.rb +163 -801
  54. data/lib/puma/client.rb +627 -144
  55. data/lib/puma/client_env.rb +171 -0
  56. data/lib/puma/cluster/worker.rb +183 -0
  57. data/lib/puma/cluster/worker_handle.rb +127 -0
  58. data/lib/puma/cluster.rb +634 -0
  59. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  60. data/lib/puma/commonlogger.rb +115 -0
  61. data/lib/puma/configuration.rb +420 -198
  62. data/lib/puma/const.rb +266 -115
  63. data/lib/puma/control_cli.rb +219 -120
  64. data/lib/puma/detect.rb +56 -2
  65. data/lib/puma/dsl.rb +1562 -0
  66. data/lib/puma/error_logger.rb +115 -0
  67. data/lib/puma/events.rb +44 -60
  68. data/lib/puma/io_buffer.rb +48 -5
  69. data/lib/puma/jruby_restart.rb +2 -51
  70. data/lib/puma/json_serialization.rb +96 -0
  71. data/lib/puma/launcher/bundle_pruner.rb +102 -0
  72. data/lib/puma/launcher.rb +501 -0
  73. data/lib/puma/log_writer.rb +153 -0
  74. data/lib/puma/minissl/context_builder.rb +96 -0
  75. data/lib/puma/minissl.rb +355 -37
  76. data/lib/puma/null_io.rb +79 -12
  77. data/lib/puma/plugin/systemd.rb +90 -0
  78. data/lib/puma/plugin/tmp_restart.rb +36 -0
  79. data/lib/puma/plugin.rb +111 -0
  80. data/lib/puma/rack/builder.rb +297 -0
  81. data/lib/puma/rack/urlmap.rb +93 -0
  82. data/lib/puma/rack_default.rb +21 -4
  83. data/lib/puma/reactor.rb +102 -133
  84. data/lib/puma/response.rb +532 -0
  85. data/lib/puma/runner.rb +211 -0
  86. data/lib/puma/sd_notify.rb +146 -0
  87. data/lib/puma/server.rb +582 -445
  88. data/lib/puma/server_plugin_control.rb +32 -0
  89. data/lib/puma/single.rb +72 -0
  90. data/lib/puma/state_file.rb +69 -0
  91. data/lib/puma/thread_pool.rb +389 -57
  92. data/lib/puma/util.rb +125 -0
  93. data/lib/puma.rb +80 -6
  94. data/lib/rack/handler/puma.rb +125 -47
  95. data/tools/Dockerfile +26 -0
  96. data/tools/trickletest.rb +44 -0
  97. metadata +88 -148
  98. data/COPYING +0 -55
  99. data/Gemfile +0 -10
  100. data/History.txt +0 -302
  101. data/Manifest.txt +0 -60
  102. data/Rakefile +0 -165
  103. data/TODO +0 -5
  104. data/docs/config.md +0 -0
  105. data/ext/puma_http11/ext_help.h +0 -15
  106. data/ext/puma_http11/io_buffer.c +0 -154
  107. data/lib/puma/accept_nonblock.rb +0 -23
  108. data/lib/puma/capistrano.rb +0 -34
  109. data/lib/puma/compat.rb +0 -11
  110. data/lib/puma/daemon_ext.rb +0 -20
  111. data/lib/puma/delegation.rb +0 -11
  112. data/lib/puma/java_io_buffer.rb +0 -45
  113. data/lib/puma/rack_patch.rb +0 -25
  114. data/puma.gemspec +0 -45
  115. data/test/test_app_status.rb +0 -88
  116. data/test/test_cli.rb +0 -171
  117. data/test/test_config.rb +0 -16
  118. data/test/test_http10.rb +0 -27
  119. data/test/test_http11.rb +0 -126
  120. data/test/test_integration.rb +0 -154
  121. data/test/test_iobuffer.rb +0 -38
  122. data/test/test_minissl.rb +0 -25
  123. data/test/test_null_io.rb +0 -31
  124. data/test/test_persistent.rb +0 -238
  125. data/test/test_puma_server.rb +0 -224
  126. data/test/test_rack_handler.rb +0 -10
  127. data/test/test_rack_server.rb +0 -141
  128. data/test/test_thread_pool.rb +0 -146
  129. data/test/test_unix_socket.rb +0 -39
  130. data/test/test_ws.rb +0 -89
  131. data/tools/jungle/init.d/README.md +0 -54
  132. data/tools/jungle/init.d/puma +0 -332
  133. data/tools/jungle/init.d/run-puma +0 -3
  134. data/tools/jungle/upstart/README.md +0 -61
  135. data/tools/jungle/upstart/puma-manager.conf +0 -31
  136. data/tools/jungle/upstart/puma.conf +0 -52
@@ -1,32 +1,53 @@
1
1
  package org.jruby.puma;
2
2
 
3
3
  import org.jruby.Ruby;
4
+ import org.jruby.RubyArray;
4
5
  import org.jruby.RubyClass;
5
- import org.jruby.RubyHash;
6
6
  import org.jruby.RubyModule;
7
- import org.jruby.RubyNumeric;
8
7
  import org.jruby.RubyObject;
9
8
  import org.jruby.RubyString;
10
-
11
9
  import org.jruby.anno.JRubyMethod;
12
-
10
+ import org.jruby.exceptions.RaiseException;
11
+ import org.jruby.javasupport.JavaEmbedUtils;
13
12
  import org.jruby.runtime.Block;
14
13
  import org.jruby.runtime.ObjectAllocator;
15
14
  import org.jruby.runtime.ThreadContext;
16
15
  import org.jruby.runtime.builtin.IRubyObject;
17
-
18
- import org.jruby.exceptions.RaiseException;
19
-
20
16
  import org.jruby.util.ByteList;
21
17
 
22
-
23
- import javax.net.ssl.*;
24
- import javax.net.ssl.SSLEngineResult.*;
25
- import java.io.*;
26
- import java.security.*;
27
- import java.nio.*;
28
-
29
- public class MiniSSL extends RubyObject {
18
+ import javax.net.ssl.KeyManagerFactory;
19
+ import javax.net.ssl.TrustManager;
20
+ import javax.net.ssl.TrustManagerFactory;
21
+ import javax.net.ssl.SSLContext;
22
+ import javax.net.ssl.SSLEngine;
23
+ import javax.net.ssl.SSLEngineResult;
24
+ import javax.net.ssl.SSLException;
25
+ import javax.net.ssl.SSLPeerUnverifiedException;
26
+ import javax.net.ssl.SSLSession;
27
+ import javax.net.ssl.X509TrustManager;
28
+ import java.io.FileInputStream;
29
+ import java.io.InputStream;
30
+ import java.io.IOException;
31
+ import java.nio.Buffer;
32
+ import java.nio.ByteBuffer;
33
+ import java.security.KeyManagementException;
34
+ import java.security.KeyStore;
35
+ import java.security.KeyStoreException;
36
+ import java.security.NoSuchAlgorithmException;
37
+ import java.security.UnrecoverableKeyException;
38
+ import java.security.cert.Certificate;
39
+ import java.security.cert.CertificateEncodingException;
40
+ import java.security.cert.CertificateException;
41
+ import java.security.cert.X509Certificate;
42
+ import java.util.concurrent.ConcurrentHashMap;
43
+ import java.util.Map;
44
+ import java.util.function.Supplier;
45
+
46
+ import static javax.net.ssl.SSLEngineResult.Status;
47
+ import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
48
+
49
+ public class MiniSSL extends RubyObject { // MiniSSL::Engine
50
+ private static final long serialVersionUID = -6903439483039141234L;
30
51
  private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
31
52
  public IRubyObject allocate(Ruby runtime, RubyClass klass) {
32
53
  return new MiniSSL(runtime, klass);
@@ -35,255 +56,454 @@ public class MiniSSL extends RubyObject {
35
56
 
36
57
  public static void createMiniSSL(Ruby runtime) {
37
58
  RubyModule mPuma = runtime.defineModule("Puma");
38
- RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
59
+ RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
39
60
 
40
- mPuma.defineClassUnder("SSLError",
41
- runtime.getClass("IOError"),
42
- runtime.getClass("IOError").getAllocator());
61
+ // Puma::MiniSSL::SSLError
62
+ ssl.defineClassUnder("SSLError", runtime.getStandardError(), runtime.getStandardError().getAllocator());
43
63
 
44
- RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
64
+ RubyClass eng = ssl.defineClassUnder("Engine", runtime.getObject(), ALLOCATOR);
45
65
  eng.defineAnnotatedMethods(MiniSSL.class);
46
66
  }
47
67
 
48
- private Ruby runtime;
49
- private SSLContext sslc;
50
-
51
- private SSLEngine engine;
52
-
53
- private ByteBuffer peerAppData;
54
- private ByteBuffer peerNetData;
55
- private ByteBuffer netData;
56
- private ByteBuffer dummy;
57
-
58
- public MiniSSL(Ruby runtime, RubyClass klass) {
59
- super(runtime, klass);
68
+ /**
69
+ * Fairly transparent wrapper around {@link java.nio.ByteBuffer} which adds the enhancements we need
70
+ */
71
+ private static class MiniSSLBuffer {
72
+ ByteBuffer buffer;
60
73
 
61
- this.runtime = runtime;
62
- }
74
+ private MiniSSLBuffer(int capacity) { buffer = ByteBuffer.allocate(capacity); }
75
+ private MiniSSLBuffer(byte[] initialContents) { buffer = ByteBuffer.wrap(initialContents); }
63
76
 
64
- @JRubyMethod(meta = true)
65
- public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject key, IRubyObject cert) {
66
- RubyClass klass = (RubyClass) recv;
67
- IRubyObject newInstance = klass.newInstance(context,
68
- new IRubyObject[] { key, cert },
69
- Block.NULL_BLOCK);
77
+ public void clear() { buffer.clear(); }
78
+ public void compact() { buffer.compact(); }
79
+ public void flip() { ((Buffer) buffer).flip(); }
80
+ public boolean hasRemaining() { return buffer.hasRemaining(); }
81
+ public int position() { return buffer.position(); }
70
82
 
71
- return newInstance;
72
- }
83
+ public ByteBuffer getRawBuffer() {
84
+ return buffer;
85
+ }
73
86
 
74
- @JRubyMethod
75
- public IRubyObject initialize(IRubyObject key, IRubyObject cert)
76
- throws java.security.KeyStoreException,
77
- java.io.FileNotFoundException,
78
- java.io.IOException,
79
- java.io.FileNotFoundException,
80
- java.security.NoSuchAlgorithmException,
81
- java.security.KeyManagementException,
82
- java.security.cert.CertificateException,
83
- java.security.UnrecoverableKeyException
84
- {
85
- KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
86
- KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
87
-
88
- char[] pass = "blahblah".toCharArray();
89
-
90
- ks.load(new FileInputStream(key.convertToString().asJavaString()),
91
- pass);
92
- ts.load(new FileInputStream(cert.convertToString().asJavaString()),
93
- pass);
87
+ /**
88
+ * Writes bytes to the buffer after ensuring there's room
89
+ */
90
+ private void put(byte[] bytes, final int offset, final int length) {
91
+ if (buffer.remaining() < length) {
92
+ resize(buffer.limit() + length);
93
+ }
94
+ buffer.put(bytes, offset, length);
95
+ }
94
96
 
95
- KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
96
- kmf.init(ks, pass);
97
+ /**
98
+ * Ensures that newCapacity bytes can be written to this buffer, only re-allocating if necessary
99
+ */
100
+ public void resize(int newCapacity) {
101
+ if (newCapacity > buffer.capacity()) {
102
+ ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
103
+ flip();
104
+ dstTmp.put(buffer);
105
+ buffer = dstTmp;
106
+ } else {
107
+ buffer.limit(newCapacity);
108
+ }
109
+ }
97
110
 
98
- TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
99
- tmf.init(ts);
111
+ /**
112
+ * Drains the buffer to a ByteList, or returns null for an empty buffer
113
+ */
114
+ public ByteList asByteList() {
115
+ flip();
116
+ if (!buffer.hasRemaining()) {
117
+ buffer.clear();
118
+ return null;
119
+ }
100
120
 
101
- SSLContext sslCtx = SSLContext.getInstance("TLS");
121
+ byte[] bss = new byte[buffer.limit()];
102
122
 
103
- sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
123
+ buffer.get(bss);
124
+ buffer.clear();
125
+ return new ByteList(bss, false);
126
+ }
104
127
 
105
- sslc = sslCtx;
128
+ @Override
129
+ public String toString() { return buffer.toString(); }
130
+ }
106
131
 
107
- engine = sslc.createSSLEngine();
108
- engine.setUseClientMode(false);
109
- // engine.setNeedClientAuth(true);
132
+ private SSLEngine engine;
133
+ private boolean closed;
134
+ private boolean handshake;
135
+ private MiniSSLBuffer inboundNetData;
136
+ private MiniSSLBuffer outboundAppData;
137
+ private MiniSSLBuffer outboundNetData;
110
138
 
111
- SSLSession session = engine.getSession();
112
- peerNetData = ByteBuffer.allocate(session.getPacketBufferSize());
113
- peerAppData = ByteBuffer.allocate(session.getApplicationBufferSize());
114
- netData = ByteBuffer.allocate(session.getPacketBufferSize());
115
- peerNetData.limit(0);
116
- peerAppData.limit(0);
117
- netData.limit(0);
118
-
119
- peerNetData.clear();
120
- peerAppData.clear();
121
- netData.clear();
122
-
123
- dummy = ByteBuffer.allocate(0);
124
-
125
- return this;
139
+ public MiniSSL(Ruby runtime, RubyClass klass) {
140
+ super(runtime, klass);
126
141
  }
127
142
 
128
- @JRubyMethod
129
- public IRubyObject inject(IRubyObject arg) {
130
- byte[] bytes = arg.convertToString().getBytes();
143
+ private static Map<String, KeyManagerFactory> keyManagerFactoryMap = new ConcurrentHashMap<String, KeyManagerFactory>();
144
+ private static Map<String, TrustManagerFactory> trustManagerFactoryMap = new ConcurrentHashMap<String, TrustManagerFactory>();
145
+
146
+ @JRubyMethod(meta = true) // Engine.server
147
+ public static synchronized IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext)
148
+ throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException {
149
+ // Create the KeyManagerFactory and TrustManagerFactory for this server
150
+ String keystoreFile = asStringValue(miniSSLContext.callMethod(context, "keystore"), null);
151
+ char[] keystorePass = asStringValue(miniSSLContext.callMethod(context, "keystore_pass"), null).toCharArray();
152
+ String keystoreType = asStringValue(miniSSLContext.callMethod(context, "keystore_type"), KeyStore::getDefaultType);
153
+
154
+ String truststoreFile;
155
+ char[] truststorePass;
156
+ String truststoreType;
157
+ IRubyObject truststore = miniSSLContext.callMethod(context, "truststore");
158
+ if (truststore.isNil()) {
159
+ truststoreFile = keystoreFile;
160
+ truststorePass = keystorePass;
161
+ truststoreType = keystoreType;
162
+ } else if (!isDefaultSymbol(context, truststore)) {
163
+ truststoreFile = truststore.convertToString().asJavaString();
164
+ IRubyObject pass = miniSSLContext.callMethod(context, "truststore_pass");
165
+ if (pass.isNil()) {
166
+ truststorePass = null;
167
+ } else {
168
+ truststorePass = asStringValue(pass, null).toCharArray();
169
+ }
170
+ truststoreType = asStringValue(miniSSLContext.callMethod(context, "truststore_type"), KeyStore::getDefaultType);
171
+ } else { // self.truststore = :default
172
+ truststoreFile = null;
173
+ truststorePass = null;
174
+ truststoreType = null;
175
+ }
131
176
 
132
- peerNetData.limit(peerNetData.limit() + bytes.length);
177
+ KeyStore ks = KeyStore.getInstance(keystoreType);
178
+ InputStream is = new FileInputStream(keystoreFile);
179
+ try {
180
+ ks.load(is, keystorePass);
181
+ } finally {
182
+ is.close();
183
+ }
184
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
185
+ kmf.init(ks, keystorePass);
186
+ keyManagerFactoryMap.put(keystoreFile, kmf);
187
+
188
+ if (truststoreFile != null) {
189
+ KeyStore ts = KeyStore.getInstance(truststoreType);
190
+ is = new FileInputStream(truststoreFile);
191
+ try {
192
+ ts.load(is, truststorePass);
193
+ } finally {
194
+ is.close();
195
+ }
196
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
197
+ tmf.init(ts);
198
+ trustManagerFactoryMap.put(truststoreFile, tmf);
199
+ }
133
200
 
134
- log("capacity: " + peerNetData.capacity() + " limit: " + peerNetData.limit());
201
+ RubyClass klass = (RubyClass) recv;
202
+ return klass.newInstance(context, miniSSLContext, Block.NULL_BLOCK);
203
+ }
135
204
 
136
- peerNetData.put(bytes);
205
+ private static String asStringValue(IRubyObject value, Supplier<String> defaultValue) {
206
+ if (defaultValue != null && value.isNil()) return defaultValue.get();
207
+ return value.convertToString().asJavaString();
208
+ }
137
209
 
138
- log("netData: " + peerNetData.position() + "/" + peerAppData.limit());
139
- return this;
210
+ private static boolean isDefaultSymbol(ThreadContext context, IRubyObject truststore) {
211
+ return context.runtime.newSymbol("default").equals(truststore);
140
212
  }
141
213
 
142
214
  @JRubyMethod
143
- public IRubyObject read() throws javax.net.ssl.SSLException, Exception {
144
- peerAppData.clear();
145
- peerNetData.flip();
146
- SSLEngineResult res;
147
-
148
- log("available read: " + peerNetData.position() + "/ " + peerNetData.limit());
149
-
150
- if(!peerNetData.hasRemaining()) {
151
- return getRuntime().getNil();
215
+ public IRubyObject initialize(ThreadContext context, IRubyObject miniSSLContext)
216
+ throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
217
+
218
+ String keystoreFile = miniSSLContext.callMethod(context, "keystore").convertToString().asJavaString();
219
+ KeyManagerFactory kmf = keyManagerFactoryMap.get(keystoreFile);
220
+ IRubyObject truststore = miniSSLContext.callMethod(context, "truststore");
221
+ String truststoreFile = isDefaultSymbol(context, truststore) ? "" : asStringValue(truststore, () -> keystoreFile);
222
+ TrustManagerFactory tmf = trustManagerFactoryMap.get(truststoreFile); // null if self.truststore = :default
223
+ if (kmf == null) {
224
+ throw new KeyStoreException("Could not find KeyManagerFactory for keystore: " + keystoreFile + " truststore: " + truststoreFile);
152
225
  }
153
226
 
154
- do {
155
- res = engine.unwrap(peerNetData, peerAppData);
156
- } while(res.getStatus() == SSLEngineResult.Status.OK &&
157
- res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
158
- res.bytesProduced() == 0);
227
+ SSLContext sslCtx = SSLContext.getInstance("TLS");
159
228
 
160
- log("read: ", res);
229
+ sslCtx.init(kmf.getKeyManagers(), getTrustManagers(tmf), null);
230
+ closed = false;
231
+ handshake = false;
232
+ engine = sslCtx.createSSLEngine();
233
+
234
+ String[] enabledProtocols;
235
+ IRubyObject protocols = miniSSLContext.callMethod(context, "protocols");
236
+ if (protocols.isNil()) {
237
+ if (miniSSLContext.callMethod(context, "no_tlsv1").isTrue()) {
238
+ enabledProtocols = new String[] { "TLSv1.1", "TLSv1.2", "TLSv1.3" };
239
+ } else {
240
+ enabledProtocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3" };
241
+ }
161
242
 
162
- if(peerNetData.hasRemaining()) {
163
- log("STILL HAD peerNetData!");
243
+ if (miniSSLContext.callMethod(context, "no_tlsv1_1").isTrue()) {
244
+ enabledProtocols = new String[] { "TLSv1.2", "TLSv1.3" };
245
+ }
246
+ } else if (protocols instanceof RubyArray) {
247
+ enabledProtocols = (String[]) ((RubyArray) protocols).toArray(new String[0]);
248
+ } else {
249
+ throw context.runtime.newTypeError(protocols, context.runtime.getArray());
164
250
  }
251
+ engine.setEnabledProtocols(enabledProtocols);
165
252
 
166
- peerNetData.position(0);
167
- peerNetData.limit(0);
253
+ engine.setUseClientMode(false);
168
254
 
169
- HandshakeStatus hsStatus = runDelegatedTasks(res, engine);
255
+ long verify_mode = miniSSLContext.callMethod(context, "verify_mode").convertToInteger("to_i").getLongValue();
256
+ if ((verify_mode & 0x1) != 0) { // 'peer'
257
+ engine.setWantClientAuth(true);
258
+ }
259
+ if ((verify_mode & 0x2) != 0) { // 'force_peer'
260
+ engine.setNeedClientAuth(true);
261
+ }
170
262
 
171
- if(res.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
172
- return getRuntime().getNil();
263
+ IRubyObject cipher_suites = miniSSLContext.callMethod(context, "cipher_suites");
264
+ if (cipher_suites instanceof RubyArray) {
265
+ engine.setEnabledCipherSuites((String[]) ((RubyArray) cipher_suites).toArray(new String[0]));
266
+ } else if (!cipher_suites.isNil()) {
267
+ throw context.runtime.newTypeError(cipher_suites, context.runtime.getArray());
173
268
  }
174
269
 
175
- if(hsStatus == HandshakeStatus.NEED_WRAP) {
176
- netData.clear();
177
- log("netData: " + netData.limit());
178
- engine.wrap(dummy, netData);
179
- return getRuntime().getNil();
270
+ SSLSession session = engine.getSession();
271
+ inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
272
+ outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
273
+ outboundAppData.flip();
274
+ outboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
275
+
276
+ return this;
277
+ }
278
+
279
+ private TrustManager[] getTrustManagers(TrustManagerFactory factory) {
280
+ if (factory == null) return null; // use JDK trust defaults
281
+ final TrustManager[] tms = factory.getTrustManagers();
282
+ if (tms != null) {
283
+ for (int i=0; i<tms.length; i++) {
284
+ final TrustManager tm = tms[i];
285
+ if (tm instanceof X509TrustManager) {
286
+ tms[i] = new TrustManagerWrapper((X509TrustManager) tm);
287
+ }
288
+ }
180
289
  }
290
+ return tms;
291
+ }
181
292
 
182
- if(hsStatus == HandshakeStatus.NEED_UNWRAP) {
183
- return getRuntime().getNil();
293
+ private volatile transient X509Certificate lastCheckedCert0;
184
294
 
185
- // log("peerNet: " + peerNetData.position() + "/" + peerNetData.limit());
186
- // log("peerApp: " + peerAppData.position() + "/" + peerAppData.limit());
295
+ private class TrustManagerWrapper implements X509TrustManager {
187
296
 
188
- // peerNetData.compact();
297
+ private final X509TrustManager delegate;
189
298
 
190
- // log("peerNet: " + peerNetData.position() + "/" + peerNetData.limit());
191
- // do {
192
- // res = engine.unwrap(peerNetData, peerAppData);
193
- // } while(res.getStatus() == SSLEngineResult.Status.OK &&
194
- // res.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP &&
195
- // res.bytesProduced() == 0);
196
- // return getRuntime().getNil();
299
+ TrustManagerWrapper(X509TrustManager delegate) {
300
+ this.delegate = delegate;
197
301
  }
198
302
 
199
- // if(peerAppData.position() == 0 &&
200
- // res.getStatus() == SSLEngineResult.Status.OK &&
201
- // peerNetData.hasRemaining()) {
202
- // res = engine.unwrap(peerNetData, peerAppData);
203
- // }
303
+ @Override
304
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
305
+ lastCheckedCert0 = chain.length > 0 ? chain[0] : null;
306
+ delegate.checkClientTrusted(chain, authType);
307
+ }
204
308
 
205
- byte[] bss = new byte[peerAppData.limit()];
309
+ @Override
310
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
311
+ delegate.checkServerTrusted(chain, authType);
312
+ }
206
313
 
207
- peerAppData.get(bss);
314
+ @Override
315
+ public X509Certificate[] getAcceptedIssuers() {
316
+ return delegate.getAcceptedIssuers();
317
+ }
208
318
 
209
- RubyString str = getRuntime().newString("");
210
- str.setValue(new ByteList(bss));
319
+ }
211
320
 
212
- return str;
321
+ @JRubyMethod
322
+ public IRubyObject inject(IRubyObject arg) {
323
+ ByteList bytes = arg.convertToString().getByteList();
324
+ inboundNetData.put(bytes.unsafeBytes(), bytes.getBegin(), bytes.getRealSize());
325
+ return this;
213
326
  }
214
327
 
215
- private static HandshakeStatus runDelegatedTasks(SSLEngineResult result,
216
- SSLEngine engine) throws Exception {
328
+ private enum SSLOperation {
329
+ WRAP,
330
+ UNWRAP
331
+ }
217
332
 
218
- HandshakeStatus hsStatus = result.getHandshakeStatus();
333
+ private SSLEngineResult doOp(SSLOperation sslOp, MiniSSLBuffer src, MiniSSLBuffer dst) throws SSLException {
334
+ SSLEngineResult res = null;
335
+ boolean retryOp = true;
336
+ while (retryOp) {
337
+ switch (sslOp) {
338
+ case WRAP:
339
+ res = engine.wrap(src.getRawBuffer(), dst.getRawBuffer());
340
+ break;
341
+ case UNWRAP:
342
+ res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer());
343
+ break;
344
+ default:
345
+ throw new AssertionError("Unknown SSLOperation: " + sslOp);
346
+ }
219
347
 
220
- if(hsStatus == HandshakeStatus.NEED_TASK) {
221
- Runnable runnable;
222
- while ((runnable = engine.getDelegatedTask()) != null) {
223
- log("\trunning delegated task...");
224
- runnable.run();
348
+ switch (res.getStatus()) {
349
+ case BUFFER_OVERFLOW:
350
+ // increase the buffer size to accommodate the overflowing data
351
+ int newSize = Math.max(engine.getSession().getPacketBufferSize(), engine.getSession().getApplicationBufferSize());
352
+ dst.resize(newSize + dst.position());
353
+ // retry the operation
354
+ retryOp = true;
355
+ break;
356
+ case BUFFER_UNDERFLOW:
357
+ // need to wait for more data to come in before we retry
358
+ retryOp = false;
359
+ break;
360
+ case CLOSED:
361
+ closed = true;
362
+ retryOp = false;
363
+ break;
364
+ default:
365
+ // other case is OK. We're done here.
366
+ retryOp = false;
225
367
  }
226
- hsStatus = engine.getHandshakeStatus();
227
- if (hsStatus == HandshakeStatus.NEED_TASK) {
228
- throw new Exception(
229
- "handshake shouldn't need additional tasks");
368
+ if (res.getHandshakeStatus() == HandshakeStatus.FINISHED) {
369
+ handshake = true;
230
370
  }
231
- log("\tnew HandshakeStatus: " + hsStatus);
232
371
  }
233
372
 
234
- return hsStatus;
373
+ return res;
235
374
  }
236
-
237
-
238
- private static void log(String str, SSLEngineResult result) {
239
- System.out.println("The format of the SSLEngineResult is: \n" +
240
- "\t\"getStatus() / getHandshakeStatus()\" +\n" +
241
- "\t\"bytesConsumed() / bytesProduced()\"\n");
242
-
243
- HandshakeStatus hsStatus = result.getHandshakeStatus();
244
- log(str +
245
- result.getStatus() + "/" + hsStatus + ", " +
246
- result.bytesConsumed() + "/" + result.bytesProduced() +
247
- " bytes");
248
- if (hsStatus == HandshakeStatus.FINISHED) {
249
- log("\t...ready for application data");
375
+
376
+ @JRubyMethod
377
+ public IRubyObject read() {
378
+ try {
379
+ inboundNetData.flip();
380
+
381
+ if(!inboundNetData.hasRemaining()) {
382
+ return getRuntime().getNil();
383
+ }
384
+
385
+ MiniSSLBuffer inboundAppData = new MiniSSLBuffer(engine.getSession().getApplicationBufferSize());
386
+ doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
387
+
388
+ HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
389
+ boolean done = false;
390
+ while (!done) {
391
+ SSLEngineResult res;
392
+ switch (handshakeStatus) {
393
+ case NEED_WRAP:
394
+ res = doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
395
+ handshakeStatus = res.getHandshakeStatus();
396
+ break;
397
+ case NEED_UNWRAP:
398
+ res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
399
+ if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
400
+ // need more data before we can shake more hands
401
+ done = true;
402
+ }
403
+ handshakeStatus = res.getHandshakeStatus();
404
+ break;
405
+ case NEED_TASK:
406
+ Runnable runnable;
407
+ while ((runnable = engine.getDelegatedTask()) != null) {
408
+ runnable.run();
409
+ }
410
+ handshakeStatus = engine.getHandshakeStatus();
411
+ break;
412
+ default:
413
+ done = true;
414
+ }
415
+ }
416
+
417
+ if (inboundNetData.hasRemaining()) {
418
+ inboundNetData.compact();
419
+ } else {
420
+ inboundNetData.clear();
421
+ }
422
+
423
+ ByteList appDataByteList = inboundAppData.asByteList();
424
+ if (appDataByteList == null) {
425
+ return getRuntime().getNil();
426
+ }
427
+
428
+ return RubyString.newString(getRuntime(), appDataByteList);
429
+ } catch (SSLException e) {
430
+ throw newSSLError(getRuntime(), e);
250
431
  }
251
432
  }
252
433
 
253
- private static void log(String str) {
254
- System.out.println(str);
434
+ @JRubyMethod
435
+ public IRubyObject write(IRubyObject arg) {
436
+ byte[] bls = arg.convertToString().getBytes();
437
+ outboundAppData = new MiniSSLBuffer(bls);
438
+
439
+ return getRuntime().newFixnum(bls.length);
255
440
  }
256
-
257
-
258
441
 
259
442
  @JRubyMethod
260
- public IRubyObject write(IRubyObject arg) throws javax.net.ssl.SSLException {
261
- log("write from: " + netData.position());
443
+ public IRubyObject extract(ThreadContext context) {
444
+ try {
445
+ ByteList dataByteList = outboundNetData.asByteList();
446
+ if (dataByteList != null) {
447
+ return RubyString.newString(context.runtime, dataByteList);
448
+ }
262
449
 
263
- byte[] bls = arg.convertToString().getBytes();
264
- ByteBuffer src = ByteBuffer.wrap(bls);
450
+ if (!outboundAppData.hasRemaining()) {
451
+ return context.nil;
452
+ }
265
453
 
266
- SSLEngineResult res = engine.wrap(src, netData);
454
+ outboundNetData.clear();
455
+ doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
456
+ dataByteList = outboundNetData.asByteList();
457
+ if (dataByteList == null) {
458
+ return context.nil;
459
+ }
267
460
 
268
- return getRuntime().newFixnum(res.bytesConsumed());
461
+ return RubyString.newString(context.runtime, dataByteList);
462
+ } catch (SSLException e) {
463
+ throw newSSLError(getRuntime(), e);
464
+ }
269
465
  }
270
466
 
271
467
  @JRubyMethod
272
- public IRubyObject extract() {
273
- netData.flip();
274
-
275
- if(!netData.hasRemaining()) {
276
- return getRuntime().getNil();
468
+ public IRubyObject peercert(ThreadContext context) throws CertificateEncodingException {
469
+ Certificate peerCert;
470
+ try {
471
+ peerCert = engine.getSession().getPeerCertificates()[0];
472
+ } catch (SSLPeerUnverifiedException e) {
473
+ peerCert = lastCheckedCert0; // null if trust check did not happen
277
474
  }
475
+ return peerCert == null ? context.nil : JavaEmbedUtils.javaToRuby(context.runtime, peerCert.getEncoded());
476
+ }
278
477
 
279
- byte[] bss = new byte[netData.limit()];
478
+ @JRubyMethod(name = "init?")
479
+ public IRubyObject isInit(ThreadContext context) {
480
+ return handshake ? getRuntime().getFalse() : getRuntime().getTrue();
481
+ }
280
482
 
281
- netData.get(bss);
282
- netData.clear();
483
+ @JRubyMethod
484
+ public IRubyObject shutdown() {
485
+ if (closed || engine.isInboundDone() && engine.isOutboundDone()) {
486
+ if (engine.isOutboundDone()) {
487
+ engine.closeOutbound();
488
+ }
489
+ return getRuntime().getTrue();
490
+ } else {
491
+ return getRuntime().getFalse();
492
+ }
493
+ }
283
494
 
284
- RubyString str = getRuntime().newString("");
285
- str.setValue(new ByteList(bss));
495
+ private static RubyClass getSSLError(Ruby runtime) {
496
+ return (RubyClass) ((RubyModule) runtime.getModule("Puma").getConstantAt("MiniSSL")).getConstantAt("SSLError");
497
+ }
286
498
 
287
- return str;
499
+ private static RaiseException newSSLError(Ruby runtime, SSLException cause) {
500
+ return newError(runtime, getSSLError(runtime), cause.toString(), cause);
288
501
  }
502
+
503
+ private static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, Throwable cause) {
504
+ RaiseException ex = RaiseException.from(runtime, errorClass, message);
505
+ ex.initCause(cause);
506
+ return ex;
507
+ }
508
+
289
509
  }