piesync-puma 3.12.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +1429 -0
  3. data/LICENSE +26 -0
  4. data/README.md +280 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +31 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +36 -0
  9. data/docs/deployment.md +91 -0
  10. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  11. data/docs/images/puma-connection-flow.png +0 -0
  12. data/docs/images/puma-general-arch.png +0 -0
  13. data/docs/nginx.md +80 -0
  14. data/docs/plugins.md +28 -0
  15. data/docs/restart.md +39 -0
  16. data/docs/signals.md +96 -0
  17. data/docs/systemd.md +272 -0
  18. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  19. data/ext/puma_http11/ext_help.h +15 -0
  20. data/ext/puma_http11/extconf.rb +15 -0
  21. data/ext/puma_http11/http11_parser.c +1071 -0
  22. data/ext/puma_http11/http11_parser.h +65 -0
  23. data/ext/puma_http11/http11_parser.java.rl +161 -0
  24. data/ext/puma_http11/http11_parser.rl +149 -0
  25. data/ext/puma_http11/http11_parser_common.rl +54 -0
  26. data/ext/puma_http11/io_buffer.c +155 -0
  27. data/ext/puma_http11/mini_ssl.c +494 -0
  28. data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
  29. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +470 -0
  30. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +352 -0
  31. data/ext/puma_http11/puma_http11.c +500 -0
  32. data/lib/puma.rb +23 -0
  33. data/lib/puma/accept_nonblock.rb +23 -0
  34. data/lib/puma/app/status.rb +74 -0
  35. data/lib/puma/binder.rb +413 -0
  36. data/lib/puma/cli.rb +235 -0
  37. data/lib/puma/client.rb +480 -0
  38. data/lib/puma/cluster.rb +531 -0
  39. data/lib/puma/commonlogger.rb +108 -0
  40. data/lib/puma/compat.rb +14 -0
  41. data/lib/puma/configuration.rb +361 -0
  42. data/lib/puma/const.rb +239 -0
  43. data/lib/puma/control_cli.rb +264 -0
  44. data/lib/puma/convenient.rb +25 -0
  45. data/lib/puma/daemon_ext.rb +33 -0
  46. data/lib/puma/delegation.rb +13 -0
  47. data/lib/puma/detect.rb +15 -0
  48. data/lib/puma/dsl.rb +518 -0
  49. data/lib/puma/events.rb +153 -0
  50. data/lib/puma/io_buffer.rb +9 -0
  51. data/lib/puma/java_io_buffer.rb +47 -0
  52. data/lib/puma/jruby_restart.rb +84 -0
  53. data/lib/puma/launcher.rb +433 -0
  54. data/lib/puma/minissl.rb +285 -0
  55. data/lib/puma/null_io.rb +44 -0
  56. data/lib/puma/plugin.rb +117 -0
  57. data/lib/puma/plugin/tmp_restart.rb +34 -0
  58. data/lib/puma/rack/backports/uri/common_193.rb +33 -0
  59. data/lib/puma/rack/builder.rb +299 -0
  60. data/lib/puma/rack/urlmap.rb +91 -0
  61. data/lib/puma/rack_default.rb +7 -0
  62. data/lib/puma/reactor.rb +347 -0
  63. data/lib/puma/runner.rb +184 -0
  64. data/lib/puma/server.rb +1072 -0
  65. data/lib/puma/single.rb +123 -0
  66. data/lib/puma/state_file.rb +31 -0
  67. data/lib/puma/tcp_logger.rb +41 -0
  68. data/lib/puma/thread_pool.rb +346 -0
  69. data/lib/puma/util.rb +129 -0
  70. data/lib/rack/handler/puma.rb +115 -0
  71. data/tools/jungle/README.md +19 -0
  72. data/tools/jungle/init.d/README.md +61 -0
  73. data/tools/jungle/init.d/puma +421 -0
  74. data/tools/jungle/init.d/run-puma +18 -0
  75. data/tools/jungle/rc.d/README.md +74 -0
  76. data/tools/jungle/rc.d/puma +61 -0
  77. data/tools/jungle/rc.d/puma.conf +10 -0
  78. data/tools/jungle/upstart/README.md +61 -0
  79. data/tools/jungle/upstart/puma-manager.conf +31 -0
  80. data/tools/jungle/upstart/puma.conf +69 -0
  81. data/tools/trickletest.rb +45 -0
  82. 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
+ }