puma 1.6.3 → 2.0.0.b1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of puma might be problematic. Click here for more details.

@@ -0,0 +1,289 @@
1
+ package org.jruby.puma;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.RubyClass;
5
+ import org.jruby.RubyHash;
6
+ import org.jruby.RubyModule;
7
+ import org.jruby.RubyNumeric;
8
+ import org.jruby.RubyObject;
9
+ import org.jruby.RubyString;
10
+
11
+ import org.jruby.anno.JRubyMethod;
12
+
13
+ import org.jruby.runtime.Block;
14
+ import org.jruby.runtime.ObjectAllocator;
15
+ import org.jruby.runtime.ThreadContext;
16
+ import org.jruby.runtime.builtin.IRubyObject;
17
+
18
+ import org.jruby.exceptions.RaiseException;
19
+
20
+ import org.jruby.util.ByteList;
21
+
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 {
30
+ private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
31
+ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
32
+ return new MiniSSL(runtime, klass);
33
+ }
34
+ };
35
+
36
+ public static void createMiniSSL(Ruby runtime) {
37
+ RubyModule mPuma = runtime.defineModule("Puma");
38
+ RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
39
+
40
+ mPuma.defineClassUnder("SSLError",
41
+ runtime.getClass("IOError"),
42
+ runtime.getClass("IOError").getAllocator());
43
+
44
+ RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
45
+ eng.defineAnnotatedMethods(MiniSSL.class);
46
+ }
47
+
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);
60
+
61
+ this.runtime = runtime;
62
+ }
63
+
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);
70
+
71
+ return newInstance;
72
+ }
73
+
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);
94
+
95
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
96
+ kmf.init(ks, pass);
97
+
98
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
99
+ tmf.init(ts);
100
+
101
+ SSLContext sslCtx = SSLContext.getInstance("TLS");
102
+
103
+ sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
104
+
105
+ sslc = sslCtx;
106
+
107
+ engine = sslc.createSSLEngine();
108
+ engine.setUseClientMode(false);
109
+ // engine.setNeedClientAuth(true);
110
+
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;
126
+ }
127
+
128
+ @JRubyMethod
129
+ public IRubyObject inject(IRubyObject arg) {
130
+ byte[] bytes = arg.convertToString().getBytes();
131
+
132
+ peerNetData.limit(peerNetData.limit() + bytes.length);
133
+
134
+ log("capacity: " + peerNetData.capacity() + " limit: " + peerNetData.limit());
135
+
136
+ peerNetData.put(bytes);
137
+
138
+ log("netData: " + peerNetData.position() + "/" + peerAppData.limit());
139
+ return this;
140
+ }
141
+
142
+ @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();
152
+ }
153
+
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);
159
+
160
+ log("read: ", res);
161
+
162
+ if(peerNetData.hasRemaining()) {
163
+ log("STILL HAD peerNetData!");
164
+ }
165
+
166
+ peerNetData.position(0);
167
+ peerNetData.limit(0);
168
+
169
+ HandshakeStatus hsStatus = runDelegatedTasks(res, engine);
170
+
171
+ if(res.getStatus() == SSLEngineResult.Status.BUFFER_UNDERFLOW) {
172
+ return getRuntime().getNil();
173
+ }
174
+
175
+ if(hsStatus == HandshakeStatus.NEED_WRAP) {
176
+ netData.clear();
177
+ log("netData: " + netData.limit());
178
+ engine.wrap(dummy, netData);
179
+ return getRuntime().getNil();
180
+ }
181
+
182
+ if(hsStatus == HandshakeStatus.NEED_UNWRAP) {
183
+ return getRuntime().getNil();
184
+
185
+ // log("peerNet: " + peerNetData.position() + "/" + peerNetData.limit());
186
+ // log("peerApp: " + peerAppData.position() + "/" + peerAppData.limit());
187
+
188
+ // peerNetData.compact();
189
+
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();
197
+ }
198
+
199
+ // if(peerAppData.position() == 0 &&
200
+ // res.getStatus() == SSLEngineResult.Status.OK &&
201
+ // peerNetData.hasRemaining()) {
202
+ // res = engine.unwrap(peerNetData, peerAppData);
203
+ // }
204
+
205
+ byte[] bss = new byte[peerAppData.limit()];
206
+
207
+ peerAppData.get(bss);
208
+
209
+ RubyString str = getRuntime().newString("");
210
+ str.setValue(new ByteList(bss));
211
+
212
+ return str;
213
+ }
214
+
215
+ private static HandshakeStatus runDelegatedTasks(SSLEngineResult result,
216
+ SSLEngine engine) throws Exception {
217
+
218
+ HandshakeStatus hsStatus = result.getHandshakeStatus();
219
+
220
+ if(hsStatus == HandshakeStatus.NEED_TASK) {
221
+ Runnable runnable;
222
+ while ((runnable = engine.getDelegatedTask()) != null) {
223
+ log("\trunning delegated task...");
224
+ runnable.run();
225
+ }
226
+ hsStatus = engine.getHandshakeStatus();
227
+ if (hsStatus == HandshakeStatus.NEED_TASK) {
228
+ throw new Exception(
229
+ "handshake shouldn't need additional tasks");
230
+ }
231
+ log("\tnew HandshakeStatus: " + hsStatus);
232
+ }
233
+
234
+ return hsStatus;
235
+ }
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");
250
+ }
251
+ }
252
+
253
+ private static void log(String str) {
254
+ System.out.println(str);
255
+ }
256
+
257
+
258
+
259
+ @JRubyMethod
260
+ public IRubyObject write(IRubyObject arg) throws javax.net.ssl.SSLException {
261
+ log("write from: " + netData.position());
262
+
263
+ byte[] bls = arg.convertToString().getBytes();
264
+ ByteBuffer src = ByteBuffer.wrap(bls);
265
+
266
+ SSLEngineResult res = engine.wrap(src, netData);
267
+
268
+ return getRuntime().newFixnum(res.bytesConsumed());
269
+ }
270
+
271
+ @JRubyMethod
272
+ public IRubyObject extract() {
273
+ netData.flip();
274
+
275
+ if(!netData.hasRemaining()) {
276
+ return getRuntime().getNil();
277
+ }
278
+
279
+ byte[] bss = new byte[netData.limit()];
280
+
281
+ netData.get(bss);
282
+ netData.clear();
283
+
284
+ RubyString str = getRuntime().newString("");
285
+ str.setValue(new ByteList(bss));
286
+
287
+ return str;
288
+ }
289
+ }
@@ -456,6 +456,9 @@ VALUE HttpParser_body(VALUE self) {
456
456
  return http->body;
457
457
  }
458
458
 
459
+ void Init_io_buffer(VALUE puma);
460
+ void Init_mini_ssl(VALUE mod);
461
+
459
462
  void Init_puma_http11()
460
463
  {
461
464
 
@@ -482,4 +485,7 @@ void Init_puma_http11()
482
485
  rb_define_method(cHttpParser, "nread", HttpParser_nread, 0);
483
486
  rb_define_method(cHttpParser, "body", HttpParser_body, 0);
484
487
  init_common_fields();
488
+
489
+ Init_io_buffer(mPuma);
490
+ Init_mini_ssl(mPuma);
485
491
  }
@@ -0,0 +1,23 @@
1
+ require 'openssl'
2
+
3
+ module OpenSSL
4
+ module SSL
5
+ class SSLServer
6
+ unless public_method_defined? :accept_nonblock
7
+ def accept_nonblock
8
+ sock = @svr.accept_nonblock
9
+
10
+ begin
11
+ ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
12
+ ssl.sync_close = true
13
+ ssl.accept if @start_immediately
14
+ ssl
15
+ rescue SSLError => ex
16
+ sock.close
17
+ raise ex
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,253 @@
1
+ require 'puma/const'
2
+
3
+ module Puma
4
+ class Binder
5
+ include Puma::Const
6
+
7
+ def initialize(events)
8
+ @events = events
9
+ @listeners = []
10
+ @inherited_fds = {}
11
+ @unix_paths = []
12
+
13
+ @proto_env = {
14
+ "rack.version".freeze => Rack::VERSION,
15
+ "rack.errors".freeze => events.stderr,
16
+ "rack.multithread".freeze => true,
17
+ "rack.multiprocess".freeze => false,
18
+ "rack.run_once".freeze => true,
19
+ "SCRIPT_NAME".freeze => ENV['SCRIPT_NAME'] || "",
20
+
21
+ # Rack blows up if this is an empty string, and Rack::Lint
22
+ # blows up if it's nil. So 'text/plain' seems like the most
23
+ # sensible default value.
24
+ "CONTENT_TYPE".freeze => "text/plain",
25
+
26
+ "QUERY_STRING".freeze => "",
27
+ SERVER_PROTOCOL => HTTP_11,
28
+ SERVER_SOFTWARE => PUMA_VERSION,
29
+ GATEWAY_INTERFACE => CGI_VER
30
+ }
31
+
32
+ @envs = {}
33
+ @ios = []
34
+ end
35
+
36
+ attr_reader :listeners, :ios
37
+
38
+ def env(sock)
39
+ @envs.fetch(sock, @proto_env)
40
+ end
41
+
42
+ def close
43
+ @ios.each { |i| i.close }
44
+ @unix_paths.each { |i| File.unlink i }
45
+ end
46
+
47
+ def import_from_env
48
+ remove = []
49
+
50
+ ENV.each do |k,v|
51
+ if k =~ /PUMA_INHERIT_\d+/
52
+ fd, url = v.split(":", 2)
53
+ @inherited_fds[url] = fd.to_i
54
+ remove << k
55
+ end
56
+ end
57
+
58
+ remove.each do |k|
59
+ ENV.delete k
60
+ end
61
+ end
62
+
63
+ def parse(binds, logger)
64
+ binds.each do |str|
65
+ uri = URI.parse str
66
+ case uri.scheme
67
+ when "tcp"
68
+ if fd = @inherited_fds.delete(str)
69
+ logger.log "* Inherited #{str}"
70
+ io = inherit_tcp_listener uri.host, uri.port, fd
71
+ else
72
+ logger.log "* Listening on #{str}"
73
+ io = add_tcp_listener uri.host, uri.port
74
+ end
75
+
76
+ @listeners << [str, io]
77
+ when "unix"
78
+ path = "#{uri.host}#{uri.path}"
79
+
80
+ if fd = @inherited_fds.delete(str)
81
+ logger.log "* Inherited #{str}"
82
+ io = inherit_unix_listener path, fd
83
+ else
84
+ logger.log "* Listening on #{str}"
85
+
86
+ umask = nil
87
+
88
+ if uri.query
89
+ params = Rack::Utils.parse_query uri.query
90
+ if u = params['umask']
91
+ # Use Integer() to respect the 0 prefix as octal
92
+ umask = Integer(u)
93
+ end
94
+ end
95
+
96
+ io = add_unix_listener path, umask
97
+ end
98
+
99
+ @listeners << [str, io]
100
+ when "ssl"
101
+ if IS_JRUBY
102
+ @events.error "SSL not supported on JRuby"
103
+ raise UnsupportedOption
104
+ end
105
+
106
+ params = Rack::Utils.parse_query uri.query
107
+ require 'puma/minissl'
108
+
109
+ ctx = MiniSSL::Context.new
110
+ unless params['key']
111
+ @events.error "Please specify the SSL key via 'key='"
112
+ end
113
+
114
+ ctx.key = params['key']
115
+
116
+ unless params['cert']
117
+ @events.error "Please specify the SSL cert via 'cert='"
118
+ end
119
+
120
+ ctx.cert = params['cert']
121
+
122
+ ctx.verify_mode = MiniSSL::VERIFY_NONE
123
+
124
+ if fd = @inherited_fds.delete(str)
125
+ logger.log "* Inherited #{str}"
126
+ io = inherited_ssl_listener fd, ctx
127
+ else
128
+ logger.log "* Listening on #{str}"
129
+ io = add_ssl_listener uri.host, uri.port, ctx
130
+ end
131
+
132
+ @listeners << [str, io]
133
+ else
134
+ logger.error "Invalid URI: #{str}"
135
+ end
136
+ end
137
+
138
+ # If we inherited fds but didn't use them (because of a
139
+ # configuration change), then be sure to close them.
140
+ @inherited_fds.each do |str, fd|
141
+ logger.log "* Closing unused inherited connection: #{str}"
142
+
143
+ begin
144
+ IO.for_fd(fd).close
145
+ rescue SystemCallError
146
+ end
147
+
148
+ # We have to unlink a unix socket path that's not being used
149
+ uri = URI.parse str
150
+ if uri.scheme == "unix"
151
+ path = "#{uri.host}#{uri.path}"
152
+ File.unlink path
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ # Tell the server to listen on host +host+, port +port+.
159
+ # If +optimize_for_latency+ is true (the default) then clients connecting
160
+ # will be optimized for latency over throughput.
161
+ #
162
+ # +backlog+ indicates how many unaccepted connections the kernel should
163
+ # allow to accumulate before returning connection refused.
164
+ #
165
+ def add_tcp_listener(host, port, optimize_for_latency=true, backlog=1024)
166
+ s = TCPServer.new(host, port)
167
+ if optimize_for_latency
168
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
169
+ end
170
+ s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
171
+ s.listen backlog
172
+ @ios << s
173
+ s
174
+ end
175
+
176
+ def inherit_tcp_listener(host, port, fd)
177
+ if fd.kind_of? TCPServer
178
+ s = fd
179
+ else
180
+ s = TCPServer.for_fd(fd)
181
+ end
182
+
183
+ @ios << s
184
+ s
185
+ end
186
+
187
+ def add_ssl_listener(host, port, ctx,
188
+ optimize_for_latency=true, backlog=1024)
189
+ if IS_JRUBY
190
+ @events.error "SSL not supported on JRuby"
191
+ raise UnsupportedOption
192
+ end
193
+
194
+ require 'puma/minissl'
195
+
196
+ s = TCPServer.new(host, port)
197
+ if optimize_for_latency
198
+ s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
199
+ end
200
+ s.setsockopt(Socket::SOL_SOCKET,Socket::SO_REUSEADDR, true)
201
+ s.listen backlog
202
+
203
+ ssl = MiniSSL::Server.new s, ctx
204
+ env = @proto_env.dup
205
+ env[HTTPS_KEY] = HTTPS
206
+ @envs[ssl] = env
207
+
208
+ @ios << ssl
209
+ s
210
+ end
211
+
212
+ def inherited_ssl_listener(fd, ctx)
213
+ if IS_JRUBY
214
+ @events.error "SSL not supported on JRuby"
215
+ raise UnsupportedOption
216
+ end
217
+
218
+ require 'puma/minissl'
219
+ s = TCPServer.for_fd(fd)
220
+ @ios << MiniSSL::Server.new(s, ctx)
221
+ s
222
+ end
223
+
224
+ # Tell the server to listen on +path+ as a UNIX domain socket.
225
+ #
226
+ def add_unix_listener(path, umask=nil)
227
+ @unix_paths << path
228
+
229
+ # Let anyone connect by default
230
+ umask ||= 0
231
+
232
+ begin
233
+ old_mask = File.umask(umask)
234
+ s = UNIXServer.new(path)
235
+ @ios << s
236
+ ensure
237
+ File.umask old_mask
238
+ end
239
+
240
+ s
241
+ end
242
+
243
+ def inherit_unix_listener(path, fd)
244
+ @unix_paths << path
245
+
246
+ s = UNIXServer.for_fd fd
247
+ @ios << s
248
+
249
+ s
250
+ end
251
+
252
+ end
253
+ end