puma-simon 3.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (157) hide show
  1. checksums.yaml +7 -0
  2. data/.github/issue_template.md +20 -0
  3. data/.gitignore +18 -0
  4. data/.hoeignore +12 -0
  5. data/.travis.yml +29 -0
  6. data/DEPLOYMENT.md +91 -0
  7. data/Gemfile +12 -0
  8. data/History.md +1254 -0
  9. data/LICENSE +26 -0
  10. data/Manifest.txt +78 -0
  11. data/README.md +353 -0
  12. data/Rakefile +158 -0
  13. data/Release.md +9 -0
  14. data/bin/puma +10 -0
  15. data/bin/puma-wild +31 -0
  16. data/bin/pumactl +12 -0
  17. data/docs/nginx.md +80 -0
  18. data/docs/signals.md +43 -0
  19. data/docs/systemd.md +197 -0
  20. data/examples/CA/cacert.pem +23 -0
  21. data/examples/CA/newcerts/cert_1.pem +19 -0
  22. data/examples/CA/newcerts/cert_2.pem +19 -0
  23. data/examples/CA/private/cakeypair.pem +30 -0
  24. data/examples/CA/serial +1 -0
  25. data/examples/config.rb +200 -0
  26. data/examples/plugins/redis_stop_puma.rb +46 -0
  27. data/examples/puma/cert_puma.pem +19 -0
  28. data/examples/puma/client-certs/ca.crt +19 -0
  29. data/examples/puma/client-certs/ca.key +27 -0
  30. data/examples/puma/client-certs/client.crt +19 -0
  31. data/examples/puma/client-certs/client.key +27 -0
  32. data/examples/puma/client-certs/client_expired.crt +19 -0
  33. data/examples/puma/client-certs/client_expired.key +27 -0
  34. data/examples/puma/client-certs/client_unknown.crt +19 -0
  35. data/examples/puma/client-certs/client_unknown.key +27 -0
  36. data/examples/puma/client-certs/generate.rb +78 -0
  37. data/examples/puma/client-certs/keystore.jks +0 -0
  38. data/examples/puma/client-certs/server.crt +19 -0
  39. data/examples/puma/client-certs/server.key +27 -0
  40. data/examples/puma/client-certs/server.p12 +0 -0
  41. data/examples/puma/client-certs/unknown_ca.crt +19 -0
  42. data/examples/puma/client-certs/unknown_ca.key +27 -0
  43. data/examples/puma/csr_puma.pem +11 -0
  44. data/examples/puma/keystore.jks +0 -0
  45. data/examples/puma/puma_keypair.pem +15 -0
  46. data/examples/qc_config.rb +13 -0
  47. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  48. data/ext/puma_http11/ext_help.h +15 -0
  49. data/ext/puma_http11/extconf.rb +15 -0
  50. data/ext/puma_http11/http11_parser.c +1069 -0
  51. data/ext/puma_http11/http11_parser.h +65 -0
  52. data/ext/puma_http11/http11_parser.java.rl +161 -0
  53. data/ext/puma_http11/http11_parser.rl +147 -0
  54. data/ext/puma_http11/http11_parser_common.rl +54 -0
  55. data/ext/puma_http11/io_buffer.c +155 -0
  56. data/ext/puma_http11/mini_ssl.c +457 -0
  57. data/ext/puma_http11/org/jruby/puma/Http11.java +234 -0
  58. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +473 -0
  59. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +339 -0
  60. data/ext/puma_http11/puma_http11.c +500 -0
  61. data/gemfiles/2.1-Gemfile +12 -0
  62. data/lib/puma.rb +15 -0
  63. data/lib/puma/accept_nonblock.rb +23 -0
  64. data/lib/puma/app/status.rb +66 -0
  65. data/lib/puma/binder.rb +402 -0
  66. data/lib/puma/cli.rb +220 -0
  67. data/lib/puma/client.rb +434 -0
  68. data/lib/puma/cluster.rb +510 -0
  69. data/lib/puma/commonlogger.rb +106 -0
  70. data/lib/puma/compat.rb +14 -0
  71. data/lib/puma/configuration.rb +364 -0
  72. data/lib/puma/const.rb +224 -0
  73. data/lib/puma/control_cli.rb +259 -0
  74. data/lib/puma/convenient.rb +23 -0
  75. data/lib/puma/daemon_ext.rb +31 -0
  76. data/lib/puma/delegation.rb +11 -0
  77. data/lib/puma/detect.rb +13 -0
  78. data/lib/puma/dsl.rb +486 -0
  79. data/lib/puma/events.rb +152 -0
  80. data/lib/puma/io_buffer.rb +7 -0
  81. data/lib/puma/java_io_buffer.rb +45 -0
  82. data/lib/puma/jruby_restart.rb +83 -0
  83. data/lib/puma/launcher.rb +410 -0
  84. data/lib/puma/minissl.rb +221 -0
  85. data/lib/puma/null_io.rb +42 -0
  86. data/lib/puma/plugin.rb +115 -0
  87. data/lib/puma/plugin/tmp_restart.rb +35 -0
  88. data/lib/puma/rack/backports/uri/common_193.rb +33 -0
  89. data/lib/puma/rack/builder.rb +298 -0
  90. data/lib/puma/rack/urlmap.rb +91 -0
  91. data/lib/puma/rack_default.rb +7 -0
  92. data/lib/puma/reactor.rb +210 -0
  93. data/lib/puma/runner.rb +171 -0
  94. data/lib/puma/server.rb +949 -0
  95. data/lib/puma/single.rb +112 -0
  96. data/lib/puma/state_file.rb +29 -0
  97. data/lib/puma/tcp_logger.rb +39 -0
  98. data/lib/puma/thread_pool.rb +297 -0
  99. data/lib/puma/util.rb +128 -0
  100. data/lib/rack/handler/puma.rb +78 -0
  101. data/puma.gemspec +52 -0
  102. data/test/ab_rs.rb +22 -0
  103. data/test/config.rb +2 -0
  104. data/test/config/app.rb +9 -0
  105. data/test/config/plugin.rb +1 -0
  106. data/test/config/settings.rb +2 -0
  107. data/test/config/state_file_testing_config.rb +14 -0
  108. data/test/hello-bind.ru +2 -0
  109. data/test/hello-delay.ru +3 -0
  110. data/test/hello-map.ru +3 -0
  111. data/test/hello-post.ru +4 -0
  112. data/test/hello-stuck.ru +1 -0
  113. data/test/hello-tcp.ru +5 -0
  114. data/test/hello.ru +1 -0
  115. data/test/hijack.ru +6 -0
  116. data/test/hijack2.ru +5 -0
  117. data/test/lobster.ru +4 -0
  118. data/test/shell/run.sh +24 -0
  119. data/test/shell/t1.rb +19 -0
  120. data/test/shell/t1_conf.rb +3 -0
  121. data/test/shell/t2.rb +17 -0
  122. data/test/shell/t2_conf.rb +6 -0
  123. data/test/shell/t3.rb +25 -0
  124. data/test/shell/t3_conf.rb +5 -0
  125. data/test/slow.ru +4 -0
  126. data/test/ssl_config.rb +4 -0
  127. data/test/test_app_status.rb +93 -0
  128. data/test/test_binder.rb +31 -0
  129. data/test/test_cli.rb +209 -0
  130. data/test/test_config.rb +95 -0
  131. data/test/test_events.rb +161 -0
  132. data/test/test_helper.rb +50 -0
  133. data/test/test_http10.rb +27 -0
  134. data/test/test_http11.rb +186 -0
  135. data/test/test_integration.rb +247 -0
  136. data/test/test_iobuffer.rb +39 -0
  137. data/test/test_minissl.rb +29 -0
  138. data/test/test_null_io.rb +49 -0
  139. data/test/test_persistent.rb +245 -0
  140. data/test/test_puma_server.rb +626 -0
  141. data/test/test_puma_server_ssl.rb +222 -0
  142. data/test/test_rack_handler.rb +57 -0
  143. data/test/test_rack_server.rb +138 -0
  144. data/test/test_tcp_logger.rb +39 -0
  145. data/test/test_tcp_rack.rb +36 -0
  146. data/test/test_thread_pool.rb +250 -0
  147. data/test/test_unix_socket.rb +35 -0
  148. data/test/test_web_server.rb +88 -0
  149. data/tools/jungle/README.md +9 -0
  150. data/tools/jungle/init.d/README.md +59 -0
  151. data/tools/jungle/init.d/puma +421 -0
  152. data/tools/jungle/init.d/run-puma +18 -0
  153. data/tools/jungle/upstart/README.md +61 -0
  154. data/tools/jungle/upstart/puma-manager.conf +31 -0
  155. data/tools/jungle/upstart/puma.conf +69 -0
  156. data/tools/trickletest.rb +45 -0
  157. metadata +297 -0
@@ -0,0 +1,339 @@
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.runtime.Block;
10
+ import org.jruby.runtime.ObjectAllocator;
11
+ import org.jruby.runtime.ThreadContext;
12
+ import org.jruby.runtime.builtin.IRubyObject;
13
+ import org.jruby.util.ByteList;
14
+
15
+ import javax.net.ssl.KeyManagerFactory;
16
+ import javax.net.ssl.TrustManagerFactory;
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
+ public static void createMiniSSL(Ruby runtime) {
43
+ RubyModule mPuma = runtime.defineModule("Puma");
44
+ RubyModule ssl = mPuma.defineModuleUnder("MiniSSL");
45
+
46
+ mPuma.defineClassUnder("SSLError",
47
+ runtime.getClass("IOError"),
48
+ runtime.getClass("IOError").getAllocator());
49
+
50
+ RubyClass eng = ssl.defineClassUnder("Engine",runtime.getObject(),ALLOCATOR);
51
+ eng.defineAnnotatedMethods(MiniSSL.class);
52
+ }
53
+
54
+ /**
55
+ * Fairly transparent wrapper around {@link java.nio.ByteBuffer} which adds the enhancements we need
56
+ */
57
+ private static class MiniSSLBuffer {
58
+ ByteBuffer buffer;
59
+
60
+ private MiniSSLBuffer(int capacity) { buffer = ByteBuffer.allocate(capacity); }
61
+ private MiniSSLBuffer(byte[] initialContents) { buffer = ByteBuffer.wrap(initialContents); }
62
+
63
+ public void clear() { buffer.clear(); }
64
+ public void compact() { buffer.compact(); }
65
+ public void flip() { buffer.flip(); }
66
+ public boolean hasRemaining() { return buffer.hasRemaining(); }
67
+ public int position() { return buffer.position(); }
68
+
69
+ public ByteBuffer getRawBuffer() {
70
+ return buffer;
71
+ }
72
+
73
+ /**
74
+ * Writes bytes to the buffer after ensuring there's room
75
+ */
76
+ public void put(byte[] bytes) {
77
+ if (buffer.remaining() < bytes.length) {
78
+ resize(buffer.limit() + bytes.length);
79
+ }
80
+ buffer.put(bytes);
81
+ }
82
+
83
+ /**
84
+ * Ensures that newCapacity bytes can be written to this buffer, only re-allocating if necessary
85
+ */
86
+ public void resize(int newCapacity) {
87
+ if (newCapacity > buffer.capacity()) {
88
+ ByteBuffer dstTmp = ByteBuffer.allocate(newCapacity);
89
+ buffer.flip();
90
+ dstTmp.put(buffer);
91
+ buffer = dstTmp;
92
+ } else {
93
+ buffer.limit(newCapacity);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Drains the buffer to a ByteList, or returns null for an empty buffer
99
+ */
100
+ public ByteList asByteList() {
101
+ buffer.flip();
102
+ if (!buffer.hasRemaining()) {
103
+ buffer.clear();
104
+ return null;
105
+ }
106
+
107
+ byte[] bss = new byte[buffer.limit()];
108
+
109
+ buffer.get(bss);
110
+ buffer.clear();
111
+ return new ByteList(bss);
112
+ }
113
+
114
+ @Override
115
+ public String toString() { return buffer.toString(); }
116
+ }
117
+
118
+ private SSLEngine engine;
119
+ private MiniSSLBuffer inboundNetData;
120
+ private MiniSSLBuffer outboundAppData;
121
+ private MiniSSLBuffer outboundNetData;
122
+
123
+ public MiniSSL(Ruby runtime, RubyClass klass) {
124
+ super(runtime, klass);
125
+ }
126
+
127
+ @JRubyMethod(meta = true)
128
+ public static IRubyObject server(ThreadContext context, IRubyObject recv, IRubyObject miniSSLContext) {
129
+ RubyClass klass = (RubyClass) recv;
130
+
131
+ return klass.newInstance(context,
132
+ new IRubyObject[] { miniSSLContext },
133
+ Block.NULL_BLOCK);
134
+ }
135
+
136
+ @JRubyMethod
137
+ public IRubyObject initialize(ThreadContext threadContext, IRubyObject miniSSLContext)
138
+ throws KeyStoreException, IOException, CertificateException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyManagementException {
139
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
140
+ KeyStore ts = KeyStore.getInstance(KeyStore.getDefaultType());
141
+
142
+ char[] password = miniSSLContext.callMethod(threadContext, "keystore_pass").convertToString().asJavaString().toCharArray();
143
+ String keystoreFile = miniSSLContext.callMethod(threadContext, "keystore").convertToString().asJavaString();
144
+ ks.load(new FileInputStream(keystoreFile), password);
145
+ ts.load(new FileInputStream(keystoreFile), password);
146
+
147
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
148
+ kmf.init(ks, password);
149
+
150
+ TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
151
+ tmf.init(ts);
152
+
153
+ SSLContext sslCtx = SSLContext.getInstance("TLS");
154
+
155
+ sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
156
+ engine = sslCtx.createSSLEngine();
157
+
158
+ String[] protocols = new String[] { "TLSv1", "TLSv1.1", "TLSv1.2" };
159
+ engine.setEnabledProtocols(protocols);
160
+ engine.setUseClientMode(false);
161
+
162
+ long verify_mode = miniSSLContext.callMethod(threadContext, "verify_mode").convertToInteger().getLongValue();
163
+ if ((verify_mode & 0x1) != 0) { // 'peer'
164
+ engine.setWantClientAuth(true);
165
+ }
166
+ if ((verify_mode & 0x2) != 0) { // 'force_peer'
167
+ engine.setNeedClientAuth(true);
168
+ }
169
+
170
+ SSLSession session = engine.getSession();
171
+ inboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
172
+ outboundAppData = new MiniSSLBuffer(session.getApplicationBufferSize());
173
+ outboundAppData.flip();
174
+ outboundNetData = new MiniSSLBuffer(session.getPacketBufferSize());
175
+
176
+ return this;
177
+ }
178
+
179
+ @JRubyMethod
180
+ public IRubyObject inject(IRubyObject arg) {
181
+ try {
182
+ byte[] bytes = arg.convertToString().getBytes();
183
+ inboundNetData.put(bytes);
184
+ return this;
185
+ } catch (Exception e) {
186
+ e.printStackTrace();
187
+ throw new RuntimeException(e);
188
+ }
189
+ }
190
+
191
+ private enum SSLOperation {
192
+ WRAP,
193
+ UNWRAP
194
+ }
195
+
196
+ private SSLEngineResult doOp(SSLOperation sslOp, MiniSSLBuffer src, MiniSSLBuffer dst) throws SSLException {
197
+ SSLEngineResult res = null;
198
+ boolean retryOp = true;
199
+ while (retryOp) {
200
+ switch (sslOp) {
201
+ case WRAP:
202
+ res = engine.wrap(src.getRawBuffer(), dst.getRawBuffer());
203
+ break;
204
+ case UNWRAP:
205
+ res = engine.unwrap(src.getRawBuffer(), dst.getRawBuffer());
206
+ break;
207
+ default:
208
+ throw new IllegalStateException("Unknown SSLOperation: " + sslOp);
209
+ }
210
+
211
+ switch (res.getStatus()) {
212
+ case BUFFER_OVERFLOW:
213
+ // increase the buffer size to accommodate the overflowing data
214
+ int newSize = Math.max(engine.getSession().getPacketBufferSize(), engine.getSession().getApplicationBufferSize());
215
+ dst.resize(newSize + dst.position());
216
+ // retry the operation
217
+ retryOp = true;
218
+ break;
219
+ case BUFFER_UNDERFLOW:
220
+ // need to wait for more data to come in before we retry
221
+ retryOp = false;
222
+ break;
223
+ default:
224
+ // other cases are OK and CLOSED. We're done here.
225
+ retryOp = false;
226
+ }
227
+ }
228
+
229
+ // after each op, run any delegated tasks if needed
230
+ if(engine.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
231
+ Runnable runnable;
232
+ while ((runnable = engine.getDelegatedTask()) != null) {
233
+ runnable.run();
234
+ }
235
+ }
236
+
237
+ return res;
238
+ }
239
+
240
+ @JRubyMethod
241
+ public IRubyObject read() throws Exception {
242
+ try {
243
+ inboundNetData.flip();
244
+
245
+ if(!inboundNetData.hasRemaining()) {
246
+ return getRuntime().getNil();
247
+ }
248
+
249
+ MiniSSLBuffer inboundAppData = new MiniSSLBuffer(engine.getSession().getApplicationBufferSize());
250
+ doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
251
+
252
+ HandshakeStatus handshakeStatus = engine.getHandshakeStatus();
253
+ boolean done = false;
254
+ while (!done) {
255
+ switch (handshakeStatus) {
256
+ case NEED_WRAP:
257
+ doOp(SSLOperation.WRAP, inboundAppData, outboundNetData);
258
+ break;
259
+ case NEED_UNWRAP:
260
+ SSLEngineResult res = doOp(SSLOperation.UNWRAP, inboundNetData, inboundAppData);
261
+ if (res.getStatus() == Status.BUFFER_UNDERFLOW) {
262
+ // need more data before we can shake more hands
263
+ done = true;
264
+ }
265
+ break;
266
+ default:
267
+ done = true;
268
+ }
269
+ handshakeStatus = engine.getHandshakeStatus();
270
+ }
271
+
272
+ if (inboundNetData.hasRemaining()) {
273
+ inboundNetData.compact();
274
+ } else {
275
+ inboundNetData.clear();
276
+ }
277
+
278
+ ByteList appDataByteList = inboundAppData.asByteList();
279
+ if (appDataByteList == null) {
280
+ return getRuntime().getNil();
281
+ }
282
+
283
+ RubyString str = getRuntime().newString("");
284
+ str.setValue(appDataByteList);
285
+ return str;
286
+ } catch (Exception e) {
287
+ throw getRuntime().newEOFError(e.getMessage());
288
+ }
289
+ }
290
+
291
+ @JRubyMethod
292
+ public IRubyObject write(IRubyObject arg) {
293
+ try {
294
+ byte[] bls = arg.convertToString().getBytes();
295
+ outboundAppData = new MiniSSLBuffer(bls);
296
+
297
+ return getRuntime().newFixnum(bls.length);
298
+ } catch (Exception e) {
299
+ e.printStackTrace();
300
+ throw new RuntimeException(e);
301
+ }
302
+ }
303
+
304
+ @JRubyMethod
305
+ public IRubyObject extract() throws SSLException {
306
+ try {
307
+ ByteList dataByteList = outboundNetData.asByteList();
308
+ if (dataByteList != null) {
309
+ RubyString str = getRuntime().newString("");
310
+ str.setValue(dataByteList);
311
+ return str;
312
+ }
313
+
314
+ if (!outboundAppData.hasRemaining()) {
315
+ return getRuntime().getNil();
316
+ }
317
+
318
+ outboundNetData.clear();
319
+ doOp(SSLOperation.WRAP, outboundAppData, outboundNetData);
320
+ dataByteList = outboundNetData.asByteList();
321
+ if (dataByteList == null) {
322
+ return getRuntime().getNil();
323
+ }
324
+
325
+ RubyString str = getRuntime().newString("");
326
+ str.setValue(dataByteList);
327
+
328
+ return str;
329
+ } catch (Exception e) {
330
+ e.printStackTrace();
331
+ throw new RuntimeException(e);
332
+ }
333
+ }
334
+
335
+ @JRubyMethod
336
+ public IRubyObject peercert() {
337
+ return getRuntime().getNil();
338
+ }
339
+ }
@@ -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
+ }