wendell-puma 2.9.2

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