piesync-puma 3.12.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/History.md +1429 -0
- data/LICENSE +26 -0
- data/README.md +280 -0
- data/bin/puma +10 -0
- data/bin/puma-wild +31 -0
- data/bin/pumactl +12 -0
- data/docs/architecture.md +36 -0
- data/docs/deployment.md +91 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/nginx.md +80 -0
- data/docs/plugins.md +28 -0
- data/docs/restart.md +39 -0
- data/docs/signals.md +96 -0
- data/docs/systemd.md +272 -0
- data/ext/puma_http11/PumaHttp11Service.java +17 -0
- data/ext/puma_http11/ext_help.h +15 -0
- data/ext/puma_http11/extconf.rb +15 -0
- data/ext/puma_http11/http11_parser.c +1071 -0
- data/ext/puma_http11/http11_parser.h +65 -0
- data/ext/puma_http11/http11_parser.java.rl +161 -0
- data/ext/puma_http11/http11_parser.rl +149 -0
- data/ext/puma_http11/http11_parser_common.rl +54 -0
- data/ext/puma_http11/io_buffer.c +155 -0
- data/ext/puma_http11/mini_ssl.c +494 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +470 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +352 -0
- data/ext/puma_http11/puma_http11.c +500 -0
- data/lib/puma.rb +23 -0
- data/lib/puma/accept_nonblock.rb +23 -0
- data/lib/puma/app/status.rb +74 -0
- data/lib/puma/binder.rb +413 -0
- data/lib/puma/cli.rb +235 -0
- data/lib/puma/client.rb +480 -0
- data/lib/puma/cluster.rb +531 -0
- data/lib/puma/commonlogger.rb +108 -0
- data/lib/puma/compat.rb +14 -0
- data/lib/puma/configuration.rb +361 -0
- data/lib/puma/const.rb +239 -0
- data/lib/puma/control_cli.rb +264 -0
- data/lib/puma/convenient.rb +25 -0
- data/lib/puma/daemon_ext.rb +33 -0
- data/lib/puma/delegation.rb +13 -0
- data/lib/puma/detect.rb +15 -0
- data/lib/puma/dsl.rb +518 -0
- data/lib/puma/events.rb +153 -0
- data/lib/puma/io_buffer.rb +9 -0
- data/lib/puma/java_io_buffer.rb +47 -0
- data/lib/puma/jruby_restart.rb +84 -0
- data/lib/puma/launcher.rb +433 -0
- data/lib/puma/minissl.rb +285 -0
- data/lib/puma/null_io.rb +44 -0
- data/lib/puma/plugin.rb +117 -0
- data/lib/puma/plugin/tmp_restart.rb +34 -0
- data/lib/puma/rack/backports/uri/common_193.rb +33 -0
- data/lib/puma/rack/builder.rb +299 -0
- data/lib/puma/rack/urlmap.rb +91 -0
- data/lib/puma/rack_default.rb +7 -0
- data/lib/puma/reactor.rb +347 -0
- data/lib/puma/runner.rb +184 -0
- data/lib/puma/server.rb +1072 -0
- data/lib/puma/single.rb +123 -0
- data/lib/puma/state_file.rb +31 -0
- data/lib/puma/tcp_logger.rb +41 -0
- data/lib/puma/thread_pool.rb +346 -0
- data/lib/puma/util.rb +129 -0
- data/lib/rack/handler/puma.rb +115 -0
- data/tools/jungle/README.md +19 -0
- data/tools/jungle/init.d/README.md +61 -0
- data/tools/jungle/init.d/puma +421 -0
- data/tools/jungle/init.d/run-puma +18 -0
- data/tools/jungle/rc.d/README.md +74 -0
- data/tools/jungle/rc.d/puma +61 -0
- data/tools/jungle/rc.d/puma.conf +10 -0
- data/tools/jungle/upstart/README.md +61 -0
- data/tools/jungle/upstart/puma-manager.conf +31 -0
- data/tools/jungle/upstart/puma.conf +69 -0
- data/tools/trickletest.rb +45 -0
- metadata +131 -0
@@ -0,0 +1,352 @@
|
|
1
|
+
package org.jruby.puma;
|
2
|
+
|
3
|
+
import org.jruby.Ruby;
|
4
|
+
import org.jruby.RubyClass;
|
5
|
+
import org.jruby.RubyModule;
|
6
|
+
import org.jruby.RubyObject;
|
7
|
+
import org.jruby.RubyString;
|
8
|
+
import org.jruby.anno.JRubyMethod;
|
9
|
+
import org.jruby.javasupport.JavaEmbedUtils;
|
10
|
+
import org.jruby.runtime.Block;
|
11
|
+
import org.jruby.runtime.ObjectAllocator;
|
12
|
+
import org.jruby.runtime.ThreadContext;
|
13
|
+
import org.jruby.runtime.builtin.IRubyObject;
|
14
|
+
import org.jruby.util.ByteList;
|
15
|
+
|
16
|
+
import javax.net.ssl.KeyManagerFactory;
|
17
|
+
import javax.net.ssl.TrustManagerFactory;
|
18
|
+
import javax.net.ssl.SSLContext;
|
19
|
+
import javax.net.ssl.SSLEngine;
|
20
|
+
import javax.net.ssl.SSLEngineResult;
|
21
|
+
import javax.net.ssl.SSLException;
|
22
|
+
import javax.net.ssl.SSLPeerUnverifiedException;
|
23
|
+
import javax.net.ssl.SSLSession;
|
24
|
+
import java.io.FileInputStream;
|
25
|
+
import java.io.IOException;
|
26
|
+
import java.nio.ByteBuffer;
|
27
|
+
import java.security.KeyManagementException;
|
28
|
+
import java.security.KeyStore;
|
29
|
+
import java.security.KeyStoreException;
|
30
|
+
import java.security.NoSuchAlgorithmException;
|
31
|
+
import java.security.UnrecoverableKeyException;
|
32
|
+
import java.security.cert.CertificateEncodingException;
|
33
|
+
import java.security.cert.CertificateException;
|
34
|
+
|
35
|
+
import static javax.net.ssl.SSLEngineResult.Status;
|
36
|
+
import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
37
|
+
|
38
|
+
public class MiniSSL extends RubyObject {
|
39
|
+
private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
40
|
+
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
41
|
+
return new MiniSSL(runtime, klass);
|
42
|
+
}
|
43
|
+
};
|
44
|
+
|
45
|
+
public static void createMiniSSL(Ruby runtime) {
|
46
|
+
RubyModule mPuma = runtime.defineModule("Puma");
|
47
|
+
RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
|
48
|
+
|
49
|
+
mPuma.defineClassUnder("SSLError",
|
50
|
+
runtime.getClass("IOError"),
|
51
|
+
runtime.getClass("IOError").getAllocator());
|
52
|
+
|
53
|
+
RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
|
54
|
+
eng.defineAnnotatedMethods(MiniSSL.class);
|
55
|
+
}
|
56
|
+
|
57
|
+
/**
|
58
|
+
* Fairly transparent wrapper around {@link java.nio.ByteBuffer} which adds the enhancements we need
|
59
|
+
*/
|
60
|
+
private static class MiniSSLBuffer {
|
61
|
+
ByteBuffer buffer;
|
62
|
+
|
63
|
+
private MiniSSLBuffer(int capacity) { buffer = ByteBuffer.allocate(capacity); }
|
64
|
+
private MiniSSLBuffer(byte[] initialContents) { buffer = ByteBuffer.wrap(initialContents); }
|
65
|
+
|
66
|
+
public void clear() { buffer.clear(); }
|
67
|
+
public void compact() { buffer.compact(); }
|
68
|
+
public void flip() { buffer.flip(); }
|
69
|
+
public boolean hasRemaining() { return buffer.hasRemaining(); }
|
70
|
+
public int position() { return buffer.position(); }
|
71
|
+
|
72
|
+
public ByteBuffer getRawBuffer() {
|
73
|
+
return buffer;
|
74
|
+
}
|
75
|
+
|
76
|
+
/**
|
77
|
+
* Writes bytes to the buffer after ensuring there's room
|
78
|
+
*/
|
79
|
+
public void put(byte[] bytes) {
|
80
|
+
if (buffer.remaining() < bytes.length) {
|
81
|
+
resize(buffer.limit() + bytes.length);
|
82
|
+
}
|
83
|
+
buffer.put(bytes);
|
84
|
+
}
|
85
|
+
|
86
|
+
/**
|
87
|
+
* Ensures that newCapacity bytes can be written to this buffer, only re-allocating if necessary
|
88
|
+
*/
|
89
|
+
public void resize(int newCapacity) {
|
90
|
+
if (newCapacity > buffer.capacity()) {
|
91
|
+
ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
|
92
|
+
buffer.flip();
|
93
|
+
dstTmp.put(buffer);
|
94
|
+
buffer = dstTmp;
|
95
|
+
} else {
|
96
|
+
buffer.limit(newCapacity);
|
97
|
+
}
|
98
|
+
}
|
99
|
+
|
100
|
+
/**
|
101
|
+
* Drains the buffer to a ByteList, or returns null for an empty buffer
|
102
|
+
*/
|
103
|
+
public ByteList asByteList() {
|
104
|
+
buffer.flip();
|
105
|
+
if (!buffer.hasRemaining()) {
|
106
|
+
buffer.clear();
|
107
|
+
return null;
|
108
|
+
}
|
109
|
+
|
110
|
+
byte[] bss = new byte[buffer.limit()];
|
111
|
+
|
112
|
+
buffer.get(bss);
|
113
|
+
buffer.clear();
|
114
|
+
return new ByteList(bss);
|
115
|
+
}
|
116
|
+
|
117
|
+
@Override
|
118
|
+
public String toString() { return buffer.toString(); }
|
119
|
+
}
|
120
|
+
|
121
|
+
private SSLEngine engine;
|
122
|
+
private MiniSSLBuffer inboundNetData;
|
123
|
+
private MiniSSLBuffer outboundAppData;
|
124
|
+
private MiniSSLBuffer outboundNetData;
|
125
|
+
|
126
|
+
public MiniSSL(Ruby runtime, RubyClass klass) {
|
127
|
+
super(runtime, klass);
|
128
|
+
}
|
129
|
+
|
130
|
+
@JRubyMethod(meta = true)
|
131
|
+
public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext) {
|
132
|
+
RubyClass klass = (RubyClass) recv;
|
133
|
+
|
134
|
+
return klass.newInstance(context,
|
135
|
+
new IRubyObject[] { miniSSLContext },
|
136
|
+
Block.NULL_BLOCK);
|
137
|
+
}
|
138
|
+
|
139
|
+
@JRubyMethod
|
140
|
+
public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
|
141
|
+
throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
|
142
|
+
KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
|
143
|
+
KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
|
144
|
+
|
145
|
+
char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray();
|
146
|
+
String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
|
147
|
+
ks.load(new FileInputStream(keystoreFile), password);
|
148
|
+
ts.load(new FileInputStream(keystoreFile), password);
|
149
|
+
|
150
|
+
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
|
151
|
+
kmf.init(ks, password);
|
152
|
+
|
153
|
+
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
|
154
|
+
tmf.init(ts);
|
155
|
+
|
156
|
+
SSLContext sslCtx = SSLContext.getInstance("TLS");
|
157
|
+
|
158
|
+
sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
159
|
+
engine = sslCtx.createSSLEngine();
|
160
|
+
|
161
|
+
String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
|
162
|
+
engine.setEnabledProtocols(protocols);
|
163
|
+
engine.setUseClientMode(false);
|
164
|
+
|
165
|
+
long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
|
166
|
+
if ((verify_mode & 0x1) != 0) { // 'peer'
|
167
|
+
engine.setWantClientAuth(true);
|
168
|
+
}
|
169
|
+
if ((verify_mode & 0x2) != 0) { // 'force_peer'
|
170
|
+
engine.setNeedClientAuth(true);
|
171
|
+
}
|
172
|
+
|
173
|
+
IRubyObject sslCipherListObject = miniSSLContext.callMethod(threadContext, "ssl_cipher_list");
|
174
|
+
if (!sslCipherListObject.isNil()) {
|
175
|
+
String[] sslCipherList = sslCipherListObject.convertToString().asJavaString().split(",");
|
176
|
+
engine.setEnabledCipherSuites(sslCipherList);
|
177
|
+
}
|
178
|
+
|
179
|
+
SSLSession session = engine.getSession();
|
180
|
+
inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
|
181
|
+
outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
|
182
|
+
outboundAppData.flip();
|
183
|
+
outboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
|
184
|
+
|
185
|
+
return this;
|
186
|
+
}
|
187
|
+
|
188
|
+
@JRubyMethod
|
189
|
+
public IRubyObject inject(IRubyObject arg) {
|
190
|
+
try {
|
191
|
+
byte[] bytes = arg.convertToString().getBytes();
|
192
|
+
inboundNetData.put(bytes);
|
193
|
+
return this;
|
194
|
+
} catch (Exception e) {
|
195
|
+
e.printStackTrace();
|
196
|
+
throw new RuntimeException(e);
|
197
|
+
}
|
198
|
+
}
|
199
|
+
|
200
|
+
private enum SSLOperation {
|
201
|
+
WRAP,
|
202
|
+
UNWRAP
|
203
|
+
}
|
204
|
+
|
205
|
+
private SSLEngineResult doOp(SSLOperation sslOp, MiniSSLBuffer src, MiniSSLBuffer dst) throws SSLException {
|
206
|
+
SSLEngineResult res = null;
|
207
|
+
boolean retryOp = true;
|
208
|
+
while (retryOp) {
|
209
|
+
switch (sslOp) {
|
210
|
+
case WRAP:
|
211
|
+
res = engine.wrap(src.getRawBuffer(), dst.getRawBuffer());
|
212
|
+
break;
|
213
|
+
case UNWRAP:
|
214
|
+
res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer());
|
215
|
+
break;
|
216
|
+
default:
|
217
|
+
throw new IllegalStateException("Unknown SSLOperation: " + sslOp);
|
218
|
+
}
|
219
|
+
|
220
|
+
switch (res.getStatus()) {
|
221
|
+
case BUFFER_OVERFLOW:
|
222
|
+
// increase the buffer size to accommodate the overflowing data
|
223
|
+
int newSize = Math.max(engine.getSession().getPacketBufferSize(), engine.getSession().getApplicationBufferSize());
|
224
|
+
dst.resize(newSize + dst.position());
|
225
|
+
// retry the operation
|
226
|
+
retryOp = true;
|
227
|
+
break;
|
228
|
+
case BUFFER_UNDERFLOW:
|
229
|
+
// need to wait for more data to come in before we retry
|
230
|
+
retryOp = false;
|
231
|
+
break;
|
232
|
+
default:
|
233
|
+
// other cases are OK and CLOSED. We're done here.
|
234
|
+
retryOp = false;
|
235
|
+
}
|
236
|
+
}
|
237
|
+
|
238
|
+
// after each op, run any delegated tasks if needed
|
239
|
+
if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
|
240
|
+
Runnable runnable;
|
241
|
+
while ((runnable = engine.getDelegatedTask()) != null) {
|
242
|
+
runnable.run();
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
return res;
|
247
|
+
}
|
248
|
+
|
249
|
+
@JRubyMethod
|
250
|
+
public IRubyObject read() throws Exception {
|
251
|
+
try {
|
252
|
+
inboundNetData.flip();
|
253
|
+
|
254
|
+
if(!inboundNetData.hasRemaining()) {
|
255
|
+
return getRuntime().getNil();
|
256
|
+
}
|
257
|
+
|
258
|
+
MiniSSLBuffer inboundAppData = new MiniSSLBuffer(engine.getSession().getApplicationBufferSize());
|
259
|
+
doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
260
|
+
|
261
|
+
HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
|
262
|
+
boolean done = false;
|
263
|
+
while (!done) {
|
264
|
+
switch (handshakeStatus) {
|
265
|
+
case NEED_WRAP:
|
266
|
+
doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
|
267
|
+
break;
|
268
|
+
case NEED_UNWRAP:
|
269
|
+
SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
|
270
|
+
if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
|
271
|
+
// need more data before we can shake more hands
|
272
|
+
done = true;
|
273
|
+
}
|
274
|
+
break;
|
275
|
+
default:
|
276
|
+
done = true;
|
277
|
+
}
|
278
|
+
handshakeStatus = engine.getHandshakeStatus();
|
279
|
+
}
|
280
|
+
|
281
|
+
if (inboundNetData.hasRemaining()) {
|
282
|
+
inboundNetData.compact();
|
283
|
+
} else {
|
284
|
+
inboundNetData.clear();
|
285
|
+
}
|
286
|
+
|
287
|
+
ByteList appDataByteList = inboundAppData.asByteList();
|
288
|
+
if (appDataByteList == null) {
|
289
|
+
return getRuntime().getNil();
|
290
|
+
}
|
291
|
+
|
292
|
+
RubyString str = getRuntime().newString("");
|
293
|
+
str.setValue(appDataByteList);
|
294
|
+
return str;
|
295
|
+
} catch (Exception e) {
|
296
|
+
throw getRuntime().newEOFError(e.getMessage());
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
@JRubyMethod
|
301
|
+
public IRubyObject write(IRubyObject arg) {
|
302
|
+
try {
|
303
|
+
byte[] bls = arg.convertToString().getBytes();
|
304
|
+
outboundAppData = new MiniSSLBuffer(bls);
|
305
|
+
|
306
|
+
return getRuntime().newFixnum(bls.length);
|
307
|
+
} catch (Exception e) {
|
308
|
+
e.printStackTrace();
|
309
|
+
throw new RuntimeException(e);
|
310
|
+
}
|
311
|
+
}
|
312
|
+
|
313
|
+
@JRubyMethod
|
314
|
+
public IRubyObject extract() throws SSLException {
|
315
|
+
try {
|
316
|
+
ByteList dataByteList = outboundNetData.asByteList();
|
317
|
+
if (dataByteList != null) {
|
318
|
+
RubyString str = getRuntime().newString("");
|
319
|
+
str.setValue(dataByteList);
|
320
|
+
return str;
|
321
|
+
}
|
322
|
+
|
323
|
+
if (!outboundAppData.hasRemaining()) {
|
324
|
+
return getRuntime().getNil();
|
325
|
+
}
|
326
|
+
|
327
|
+
outboundNetData.clear();
|
328
|
+
doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
|
329
|
+
dataByteList = outboundNetData.asByteList();
|
330
|
+
if (dataByteList == null) {
|
331
|
+
return getRuntime().getNil();
|
332
|
+
}
|
333
|
+
|
334
|
+
RubyString str = getRuntime().newString("");
|
335
|
+
str.setValue(dataByteList);
|
336
|
+
|
337
|
+
return str;
|
338
|
+
} catch (Exception e) {
|
339
|
+
e.printStackTrace();
|
340
|
+
throw new RuntimeException(e);
|
341
|
+
}
|
342
|
+
}
|
343
|
+
|
344
|
+
@JRubyMethod
|
345
|
+
public IRubyObject peercert() throws CertificateEncodingException {
|
346
|
+
try {
|
347
|
+
return JavaEmbedUtils.javaToRuby(getRuntime(), engine.getSession().getPeerCertificates()[0].getEncoded());
|
348
|
+
} catch (SSLPeerUnverifiedException ex) {
|
349
|
+
return getRuntime().getNil();
|
350
|
+
}
|
351
|
+
}
|
352
|
+
}
|
@@ -0,0 +1,500 @@
|
|
1
|
+
/**
|
2
|
+
* Copyright (c) 2005 Zed A. Shaw
|
3
|
+
* You can redistribute it and/or modify it under the same terms as Ruby.
|
4
|
+
* License 3-clause BSD
|
5
|
+
*/
|
6
|
+
|
7
|
+
#define RSTRING_NOT_MODIFIED 1
|
8
|
+
|
9
|
+
#include "ruby.h"
|
10
|
+
#include "ext_help.h"
|
11
|
+
#include <assert.h>
|
12
|
+
#include <string.h>
|
13
|
+
#include "http11_parser.h"
|
14
|
+
|
15
|
+
#ifndef MANAGED_STRINGS
|
16
|
+
|
17
|
+
#ifndef RSTRING_PTR
|
18
|
+
#define RSTRING_PTR(s) (RSTRING(s)->ptr)
|
19
|
+
#endif
|
20
|
+
#ifndef RSTRING_LEN
|
21
|
+
#define RSTRING_LEN(s) (RSTRING(s)->len)
|
22
|
+
#endif
|
23
|
+
|
24
|
+
#define rb_extract_chars(e, sz) (*sz = RSTRING_LEN(e), RSTRING_PTR(e))
|
25
|
+
#define rb_free_chars(e) /* nothing */
|
26
|
+
|
27
|
+
#endif
|
28
|
+
|
29
|
+
static VALUE eHttpParserError;
|
30
|
+
|
31
|
+
#define HTTP_PREFIX "HTTP_"
|
32
|
+
#define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1)
|
33
|
+
|
34
|
+
static VALUE global_request_method;
|
35
|
+
static VALUE global_request_uri;
|
36
|
+
static VALUE global_fragment;
|
37
|
+
static VALUE global_query_string;
|
38
|
+
static VALUE global_http_version;
|
39
|
+
static VALUE global_request_path;
|
40
|
+
|
41
|
+
/** Defines common length and error messages for input length validation. */
|
42
|
+
#define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length (was %d)"
|
43
|
+
|
44
|
+
/** Validates the max length of given input and throws an HttpParserError exception if over. */
|
45
|
+
#define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR, len); }
|
46
|
+
|
47
|
+
/** Defines global strings in the init method. */
|
48
|
+
#define DEF_GLOBAL(N, val) global_##N = rb_str_new2(val); rb_global_variable(&global_##N)
|
49
|
+
|
50
|
+
|
51
|
+
/* Defines the maximum allowed lengths for various input elements.*/
|
52
|
+
DEF_MAX_LENGTH(FIELD_NAME, 256);
|
53
|
+
DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
|
54
|
+
DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
|
55
|
+
DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
|
56
|
+
DEF_MAX_LENGTH(REQUEST_PATH, 2048);
|
57
|
+
DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
|
58
|
+
DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
|
59
|
+
|
60
|
+
struct common_field {
|
61
|
+
const size_t len;
|
62
|
+
const char *name;
|
63
|
+
int raw;
|
64
|
+
VALUE value;
|
65
|
+
};
|
66
|
+
|
67
|
+
/*
|
68
|
+
* A list of common HTTP headers we expect to receive.
|
69
|
+
* This allows us to avoid repeatedly creating identical string
|
70
|
+
* objects to be used with rb_hash_aset().
|
71
|
+
*/
|
72
|
+
static struct common_field common_http_fields[] = {
|
73
|
+
# define f(N) { (sizeof(N) - 1), N, 0, Qnil }
|
74
|
+
# define fr(N) { (sizeof(N) - 1), N, 1, Qnil }
|
75
|
+
f("ACCEPT"),
|
76
|
+
f("ACCEPT_CHARSET"),
|
77
|
+
f("ACCEPT_ENCODING"),
|
78
|
+
f("ACCEPT_LANGUAGE"),
|
79
|
+
f("ALLOW"),
|
80
|
+
f("AUTHORIZATION"),
|
81
|
+
f("CACHE_CONTROL"),
|
82
|
+
f("CONNECTION"),
|
83
|
+
f("CONTENT_ENCODING"),
|
84
|
+
fr("CONTENT_LENGTH"),
|
85
|
+
fr("CONTENT_TYPE"),
|
86
|
+
f("COOKIE"),
|
87
|
+
f("DATE"),
|
88
|
+
f("EXPECT"),
|
89
|
+
f("FROM"),
|
90
|
+
f("HOST"),
|
91
|
+
f("IF_MATCH"),
|
92
|
+
f("IF_MODIFIED_SINCE"),
|
93
|
+
f("IF_NONE_MATCH"),
|
94
|
+
f("IF_RANGE"),
|
95
|
+
f("IF_UNMODIFIED_SINCE"),
|
96
|
+
f("KEEP_ALIVE"), /* Firefox sends this */
|
97
|
+
f("MAX_FORWARDS"),
|
98
|
+
f("PRAGMA"),
|
99
|
+
f("PROXY_AUTHORIZATION"),
|
100
|
+
f("RANGE"),
|
101
|
+
f("REFERER"),
|
102
|
+
f("TE"),
|
103
|
+
f("TRAILER"),
|
104
|
+
f("TRANSFER_ENCODING"),
|
105
|
+
f("UPGRADE"),
|
106
|
+
f("USER_AGENT"),
|
107
|
+
f("VIA"),
|
108
|
+
f("X_FORWARDED_FOR"), /* common for proxies */
|
109
|
+
f("X_REAL_IP"), /* common for proxies */
|
110
|
+
f("WARNING")
|
111
|
+
# undef f
|
112
|
+
};
|
113
|
+
|
114
|
+
/*
|
115
|
+
* qsort(3) and bsearch(3) improve average performance slightly, but may
|
116
|
+
* not be worth it for lack of portability to certain platforms...
|
117
|
+
*/
|
118
|
+
#if defined(HAVE_QSORT_BSEARCH)
|
119
|
+
/* sort by length, then by name if there's a tie */
|
120
|
+
static int common_field_cmp(const void *a, const void *b)
|
121
|
+
{
|
122
|
+
struct common_field *cfa = (struct common_field *)a;
|
123
|
+
struct common_field *cfb = (struct common_field *)b;
|
124
|
+
signed long diff = cfa->len - cfb->len;
|
125
|
+
return diff ? diff : memcmp(cfa->name, cfb->name, cfa->len);
|
126
|
+
}
|
127
|
+
#endif /* HAVE_QSORT_BSEARCH */
|
128
|
+
|
129
|
+
static void init_common_fields(void)
|
130
|
+
{
|
131
|
+
unsigned i;
|
132
|
+
struct common_field *cf = common_http_fields;
|
133
|
+
char tmp[256]; /* MAX_FIELD_NAME_LENGTH */
|
134
|
+
memcpy(tmp, HTTP_PREFIX, HTTP_PREFIX_LEN);
|
135
|
+
|
136
|
+
for(i = 0; i < ARRAY_SIZE(common_http_fields); cf++, i++) {
|
137
|
+
if(cf->raw) {
|
138
|
+
cf->value = rb_str_new(cf->name, cf->len);
|
139
|
+
} else {
|
140
|
+
memcpy(tmp + HTTP_PREFIX_LEN, cf->name, cf->len + 1);
|
141
|
+
cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len);
|
142
|
+
}
|
143
|
+
rb_global_variable(&cf->value);
|
144
|
+
}
|
145
|
+
|
146
|
+
#if defined(HAVE_QSORT_BSEARCH)
|
147
|
+
qsort(common_http_fields,
|
148
|
+
ARRAY_SIZE(common_http_fields),
|
149
|
+
sizeof(struct common_field),
|
150
|
+
common_field_cmp);
|
151
|
+
#endif /* HAVE_QSORT_BSEARCH */
|
152
|
+
}
|
153
|
+
|
154
|
+
static VALUE find_common_field_value(const char *field, size_t flen)
|
155
|
+
{
|
156
|
+
#if defined(HAVE_QSORT_BSEARCH)
|
157
|
+
struct common_field key;
|
158
|
+
struct common_field *found;
|
159
|
+
key.name = field;
|
160
|
+
key.len = (signed long)flen;
|
161
|
+
found = (struct common_field *)bsearch(&key, common_http_fields,
|
162
|
+
ARRAY_SIZE(common_http_fields),
|
163
|
+
sizeof(struct common_field),
|
164
|
+
common_field_cmp);
|
165
|
+
return found ? found->value : Qnil;
|
166
|
+
#else /* !HAVE_QSORT_BSEARCH */
|
167
|
+
unsigned i;
|
168
|
+
struct common_field *cf = common_http_fields;
|
169
|
+
for(i = 0; i < ARRAY_SIZE(common_http_fields); i++, cf++) {
|
170
|
+
if (cf->len == flen && !memcmp(cf->name, field, flen))
|
171
|
+
return cf->value;
|
172
|
+
}
|
173
|
+
return Qnil;
|
174
|
+
#endif /* !HAVE_QSORT_BSEARCH */
|
175
|
+
}
|
176
|
+
|
177
|
+
void http_field(puma_parser* hp, const char *field, size_t flen,
|
178
|
+
const char *value, size_t vlen)
|
179
|
+
{
|
180
|
+
VALUE f = Qnil;
|
181
|
+
VALUE v;
|
182
|
+
|
183
|
+
VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
|
184
|
+
VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
|
185
|
+
|
186
|
+
f = find_common_field_value(field, flen);
|
187
|
+
|
188
|
+
if (f == Qnil) {
|
189
|
+
/*
|
190
|
+
* We got a strange header that we don't have a memoized value for.
|
191
|
+
* Fallback to creating a new string to use as a hash key.
|
192
|
+
*/
|
193
|
+
|
194
|
+
size_t new_size = HTTP_PREFIX_LEN + flen;
|
195
|
+
assert(new_size < BUFFER_LEN);
|
196
|
+
|
197
|
+
memcpy(hp->buf, HTTP_PREFIX, HTTP_PREFIX_LEN);
|
198
|
+
memcpy(hp->buf + HTTP_PREFIX_LEN, field, flen);
|
199
|
+
|
200
|
+
f = rb_str_new(hp->buf, new_size);
|
201
|
+
}
|
202
|
+
|
203
|
+
/* check for duplicate header */
|
204
|
+
v = rb_hash_aref(hp->request, f);
|
205
|
+
|
206
|
+
if (v == Qnil) {
|
207
|
+
v = rb_str_new(value, vlen);
|
208
|
+
rb_hash_aset(hp->request, f, v);
|
209
|
+
} else {
|
210
|
+
/* if duplicate header, normalize to comma-separated values */
|
211
|
+
rb_str_cat2(v, ", ");
|
212
|
+
rb_str_cat(v, value, vlen);
|
213
|
+
}
|
214
|
+
}
|
215
|
+
|
216
|
+
void request_method(puma_parser* hp, const char *at, size_t length)
|
217
|
+
{
|
218
|
+
VALUE val = Qnil;
|
219
|
+
|
220
|
+
val = rb_str_new(at, length);
|
221
|
+
rb_hash_aset(hp->request, global_request_method, val);
|
222
|
+
}
|
223
|
+
|
224
|
+
void request_uri(puma_parser* hp, const char *at, size_t length)
|
225
|
+
{
|
226
|
+
VALUE val = Qnil;
|
227
|
+
|
228
|
+
VALIDATE_MAX_LENGTH(length, REQUEST_URI);
|
229
|
+
|
230
|
+
val = rb_str_new(at, length);
|
231
|
+
rb_hash_aset(hp->request, global_request_uri, val);
|
232
|
+
}
|
233
|
+
|
234
|
+
void fragment(puma_parser* hp, const char *at, size_t length)
|
235
|
+
{
|
236
|
+
VALUE val = Qnil;
|
237
|
+
|
238
|
+
VALIDATE_MAX_LENGTH(length, FRAGMENT);
|
239
|
+
|
240
|
+
val = rb_str_new(at, length);
|
241
|
+
rb_hash_aset(hp->request, global_fragment, val);
|
242
|
+
}
|
243
|
+
|
244
|
+
void request_path(puma_parser* hp, const char *at, size_t length)
|
245
|
+
{
|
246
|
+
VALUE val = Qnil;
|
247
|
+
|
248
|
+
VALIDATE_MAX_LENGTH(length, REQUEST_PATH);
|
249
|
+
|
250
|
+
val = rb_str_new(at, length);
|
251
|
+
rb_hash_aset(hp->request, global_request_path, val);
|
252
|
+
}
|
253
|
+
|
254
|
+
void query_string(puma_parser* hp, const char *at, size_t length)
|
255
|
+
{
|
256
|
+
VALUE val = Qnil;
|
257
|
+
|
258
|
+
VALIDATE_MAX_LENGTH(length, QUERY_STRING);
|
259
|
+
|
260
|
+
val = rb_str_new(at, length);
|
261
|
+
rb_hash_aset(hp->request, global_query_string, val);
|
262
|
+
}
|
263
|
+
|
264
|
+
void http_version(puma_parser* hp, const char *at, size_t length)
|
265
|
+
{
|
266
|
+
VALUE val = rb_str_new(at, length);
|
267
|
+
rb_hash_aset(hp->request, global_http_version, val);
|
268
|
+
}
|
269
|
+
|
270
|
+
/** Finalizes the request header to have a bunch of stuff that's
|
271
|
+
needed. */
|
272
|
+
|
273
|
+
void header_done(puma_parser* hp, const char *at, size_t length)
|
274
|
+
{
|
275
|
+
hp->body = rb_str_new(at, length);
|
276
|
+
}
|
277
|
+
|
278
|
+
|
279
|
+
void HttpParser_free(void *data) {
|
280
|
+
TRACE();
|
281
|
+
|
282
|
+
if(data) {
|
283
|
+
xfree(data);
|
284
|
+
}
|
285
|
+
}
|
286
|
+
|
287
|
+
void HttpParser_mark(puma_parser* hp) {
|
288
|
+
if(hp->request) rb_gc_mark(hp->request);
|
289
|
+
if(hp->body) rb_gc_mark(hp->body);
|
290
|
+
}
|
291
|
+
|
292
|
+
VALUE HttpParser_alloc(VALUE klass)
|
293
|
+
{
|
294
|
+
puma_parser *hp = ALLOC_N(puma_parser, 1);
|
295
|
+
TRACE();
|
296
|
+
hp->http_field = http_field;
|
297
|
+
hp->request_method = request_method;
|
298
|
+
hp->request_uri = request_uri;
|
299
|
+
hp->fragment = fragment;
|
300
|
+
hp->request_path = request_path;
|
301
|
+
hp->query_string = query_string;
|
302
|
+
hp->http_version = http_version;
|
303
|
+
hp->header_done = header_done;
|
304
|
+
hp->request = Qnil;
|
305
|
+
|
306
|
+
puma_parser_init(hp);
|
307
|
+
|
308
|
+
return Data_Wrap_Struct(klass, HttpParser_mark, HttpParser_free, hp);
|
309
|
+
}
|
310
|
+
|
311
|
+
/**
|
312
|
+
* call-seq:
|
313
|
+
* parser.new -> parser
|
314
|
+
*
|
315
|
+
* Creates a new parser.
|
316
|
+
*/
|
317
|
+
VALUE HttpParser_init(VALUE self)
|
318
|
+
{
|
319
|
+
puma_parser *http = NULL;
|
320
|
+
DATA_GET(self, puma_parser, http);
|
321
|
+
puma_parser_init(http);
|
322
|
+
|
323
|
+
return self;
|
324
|
+
}
|
325
|
+
|
326
|
+
|
327
|
+
/**
|
328
|
+
* call-seq:
|
329
|
+
* parser.reset -> nil
|
330
|
+
*
|
331
|
+
* Resets the parser to it's initial state so that you can reuse it
|
332
|
+
* rather than making new ones.
|
333
|
+
*/
|
334
|
+
VALUE HttpParser_reset(VALUE self)
|
335
|
+
{
|
336
|
+
puma_parser *http = NULL;
|
337
|
+
DATA_GET(self, puma_parser, http);
|
338
|
+
puma_parser_init(http);
|
339
|
+
|
340
|
+
return Qnil;
|
341
|
+
}
|
342
|
+
|
343
|
+
|
344
|
+
/**
|
345
|
+
* call-seq:
|
346
|
+
* parser.finish -> true/false
|
347
|
+
*
|
348
|
+
* Finishes a parser early which could put in a "good" or bad state.
|
349
|
+
* You should call reset after finish it or bad things will happen.
|
350
|
+
*/
|
351
|
+
VALUE HttpParser_finish(VALUE self)
|
352
|
+
{
|
353
|
+
puma_parser *http = NULL;
|
354
|
+
DATA_GET(self, puma_parser, http);
|
355
|
+
puma_parser_finish(http);
|
356
|
+
|
357
|
+
return puma_parser_is_finished(http) ? Qtrue : Qfalse;
|
358
|
+
}
|
359
|
+
|
360
|
+
|
361
|
+
/**
|
362
|
+
* call-seq:
|
363
|
+
* parser.execute(req_hash, data, start) -> Integer
|
364
|
+
*
|
365
|
+
* Takes a Hash and a String of data, parses the String of data filling in the Hash
|
366
|
+
* returning an Integer to indicate how much of the data has been read. No matter
|
367
|
+
* what the return value, you should call HttpParser#finished? and HttpParser#error?
|
368
|
+
* to figure out if it's done parsing or there was an error.
|
369
|
+
*
|
370
|
+
* This function now throws an exception when there is a parsing error. This makes
|
371
|
+
* the logic for working with the parser much easier. You can still test for an
|
372
|
+
* error, but now you need to wrap the parser with an exception handling block.
|
373
|
+
*
|
374
|
+
* The third argument allows for parsing a partial request and then continuing
|
375
|
+
* the parsing from that position. It needs all of the original data as well
|
376
|
+
* so you have to append to the data buffer as you read.
|
377
|
+
*/
|
378
|
+
VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
|
379
|
+
{
|
380
|
+
puma_parser *http = NULL;
|
381
|
+
int from = 0;
|
382
|
+
char *dptr = NULL;
|
383
|
+
long dlen = 0;
|
384
|
+
|
385
|
+
DATA_GET(self, puma_parser, http);
|
386
|
+
|
387
|
+
from = FIX2INT(start);
|
388
|
+
dptr = rb_extract_chars(data, &dlen);
|
389
|
+
|
390
|
+
if(from >= dlen) {
|
391
|
+
rb_free_chars(dptr);
|
392
|
+
rb_raise(eHttpParserError, "%s", "Requested start is after data buffer end.");
|
393
|
+
} else {
|
394
|
+
http->request = req_hash;
|
395
|
+
puma_parser_execute(http, dptr, dlen, from);
|
396
|
+
|
397
|
+
rb_free_chars(dptr);
|
398
|
+
VALIDATE_MAX_LENGTH(puma_parser_nread(http), HEADER);
|
399
|
+
|
400
|
+
if(puma_parser_has_error(http)) {
|
401
|
+
rb_raise(eHttpParserError, "%s", "Invalid HTTP format, parsing fails.");
|
402
|
+
} else {
|
403
|
+
return INT2FIX(puma_parser_nread(http));
|
404
|
+
}
|
405
|
+
}
|
406
|
+
}
|
407
|
+
|
408
|
+
|
409
|
+
|
410
|
+
/**
|
411
|
+
* call-seq:
|
412
|
+
* parser.error? -> true/false
|
413
|
+
*
|
414
|
+
* Tells you whether the parser is in an error state.
|
415
|
+
*/
|
416
|
+
VALUE HttpParser_has_error(VALUE self)
|
417
|
+
{
|
418
|
+
puma_parser *http = NULL;
|
419
|
+
DATA_GET(self, puma_parser, http);
|
420
|
+
|
421
|
+
return puma_parser_has_error(http) ? Qtrue : Qfalse;
|
422
|
+
}
|
423
|
+
|
424
|
+
|
425
|
+
/**
|
426
|
+
* call-seq:
|
427
|
+
* parser.finished? -> true/false
|
428
|
+
*
|
429
|
+
* Tells you whether the parser is finished or not and in a good state.
|
430
|
+
*/
|
431
|
+
VALUE HttpParser_is_finished(VALUE self)
|
432
|
+
{
|
433
|
+
puma_parser *http = NULL;
|
434
|
+
DATA_GET(self, puma_parser, http);
|
435
|
+
|
436
|
+
return puma_parser_is_finished(http) ? Qtrue : Qfalse;
|
437
|
+
}
|
438
|
+
|
439
|
+
|
440
|
+
/**
|
441
|
+
* call-seq:
|
442
|
+
* parser.nread -> Integer
|
443
|
+
*
|
444
|
+
* Returns the amount of data processed so far during this processing cycle. It is
|
445
|
+
* set to 0 on initialize or reset calls and is incremented each time execute is called.
|
446
|
+
*/
|
447
|
+
VALUE HttpParser_nread(VALUE self)
|
448
|
+
{
|
449
|
+
puma_parser *http = NULL;
|
450
|
+
DATA_GET(self, puma_parser, http);
|
451
|
+
|
452
|
+
return INT2FIX(http->nread);
|
453
|
+
}
|
454
|
+
|
455
|
+
/**
|
456
|
+
* call-seq:
|
457
|
+
* parser.body -> nil or String
|
458
|
+
*
|
459
|
+
* If the request included a body, returns it.
|
460
|
+
*/
|
461
|
+
VALUE HttpParser_body(VALUE self) {
|
462
|
+
puma_parser *http = NULL;
|
463
|
+
DATA_GET(self, puma_parser, http);
|
464
|
+
|
465
|
+
return http->body;
|
466
|
+
}
|
467
|
+
|
468
|
+
void Init_io_buffer(VALUE puma);
|
469
|
+
void Init_mini_ssl(VALUE mod);
|
470
|
+
|
471
|
+
void Init_puma_http11()
|
472
|
+
{
|
473
|
+
|
474
|
+
VALUE mPuma = rb_define_module("Puma");
|
475
|
+
VALUE cHttpParser = rb_define_class_under(mPuma, "HttpParser", rb_cObject);
|
476
|
+
|
477
|
+
DEF_GLOBAL(request_method, "REQUEST_METHOD");
|
478
|
+
DEF_GLOBAL(request_uri, "REQUEST_URI");
|
479
|
+
DEF_GLOBAL(fragment, "FRAGMENT");
|
480
|
+
DEF_GLOBAL(query_string, "QUERY_STRING");
|
481
|
+
DEF_GLOBAL(http_version, "HTTP_VERSION");
|
482
|
+
DEF_GLOBAL(request_path, "REQUEST_PATH");
|
483
|
+
|
484
|
+
eHttpParserError = rb_define_class_under(mPuma, "HttpParserError", rb_eIOError);
|
485
|
+
rb_global_variable(&eHttpParserError);
|
486
|
+
|
487
|
+
rb_define_alloc_func(cHttpParser, HttpParser_alloc);
|
488
|
+
rb_define_method(cHttpParser, "initialize", HttpParser_init, 0);
|
489
|
+
rb_define_method(cHttpParser, "reset", HttpParser_reset, 0);
|
490
|
+
rb_define_method(cHttpParser, "finish", HttpParser_finish, 0);
|
491
|
+
rb_define_method(cHttpParser, "execute", HttpParser_execute, 3);
|
492
|
+
rb_define_method(cHttpParser, "error?", HttpParser_has_error, 0);
|
493
|
+
rb_define_method(cHttpParser, "finished?", HttpParser_is_finished, 0);
|
494
|
+
rb_define_method(cHttpParser, "nread", HttpParser_nread, 0);
|
495
|
+
rb_define_method(cHttpParser, "body", HttpParser_body, 0);
|
496
|
+
init_common_fields();
|
497
|
+
|
498
|
+
Init_io_buffer(mPuma);
|
499
|
+
Init_mini_ssl(mPuma);
|
500
|
+
}
|