puma 6.0.2 → 6.4.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of puma might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/History.md +213 -7
- data/LICENSE +0 -0
- data/README.md +59 -13
- data/bin/puma-wild +0 -0
- data/docs/architecture.md +0 -0
- data/docs/compile_options.md +0 -0
- data/docs/deployment.md +0 -0
- data/docs/fork_worker.md +0 -0
- data/docs/images/puma-connection-flow-no-reactor.png +0 -0
- data/docs/images/puma-connection-flow.png +0 -0
- data/docs/images/puma-general-arch.png +0 -0
- data/docs/jungle/README.md +0 -0
- data/docs/jungle/rc.d/README.md +0 -0
- data/docs/jungle/rc.d/puma.conf +0 -0
- data/docs/kubernetes.md +12 -0
- data/docs/nginx.md +0 -0
- data/docs/plugins.md +0 -0
- data/docs/rails_dev_mode.md +0 -0
- data/docs/restart.md +1 -0
- data/docs/signals.md +0 -0
- data/docs/stats.md +0 -0
- data/docs/systemd.md +3 -6
- data/docs/testing_benchmarks_local_files.md +0 -0
- data/docs/testing_test_rackup_ci_files.md +0 -0
- data/ext/puma_http11/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/ext_help.h +0 -0
- data/ext/puma_http11/extconf.rb +5 -1
- data/ext/puma_http11/http11_parser.c +0 -0
- data/ext/puma_http11/http11_parser.h +0 -0
- data/ext/puma_http11/http11_parser.java.rl +0 -0
- data/ext/puma_http11/http11_parser.rl +0 -0
- data/ext/puma_http11/http11_parser_common.rl +0 -0
- data/ext/puma_http11/mini_ssl.c +96 -9
- data/ext/puma_http11/no_ssl/PumaHttp11Service.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11.java +0 -0
- data/ext/puma_http11/org/jruby/puma/Http11Parser.java +0 -0
- data/ext/puma_http11/org/jruby/puma/MiniSSL.java +2 -1
- data/ext/puma_http11/puma_http11.c +0 -0
- data/lib/puma/app/status.rb +1 -1
- data/lib/puma/binder.rb +14 -11
- data/lib/puma/cli.rb +5 -1
- data/lib/puma/client.rb +77 -16
- data/lib/puma/cluster/worker.rb +5 -0
- data/lib/puma/cluster/worker_handle.rb +0 -0
- data/lib/puma/cluster.rb +71 -10
- data/lib/puma/commonlogger.rb +21 -14
- data/lib/puma/configuration.rb +6 -4
- data/lib/puma/const.rb +58 -9
- data/lib/puma/control_cli.rb +12 -5
- data/lib/puma/detect.rb +5 -4
- data/lib/puma/dsl.rb +157 -7
- data/lib/puma/error_logger.rb +2 -1
- data/lib/puma/events.rb +0 -0
- data/lib/puma/io_buffer.rb +0 -0
- data/lib/puma/jruby_restart.rb +0 -0
- data/lib/puma/json_serialization.rb +0 -0
- data/lib/puma/launcher/bundle_pruner.rb +0 -0
- data/lib/puma/launcher.rb +9 -22
- data/lib/puma/log_writer.rb +14 -4
- data/lib/puma/minissl/context_builder.rb +3 -0
- data/lib/puma/minissl.rb +22 -0
- data/lib/puma/null_io.rb +16 -2
- data/lib/puma/plugin/systemd.rb +90 -0
- data/lib/puma/plugin/tmp_restart.rb +0 -0
- data/lib/puma/plugin.rb +0 -0
- data/lib/puma/rack/builder.rb +2 -2
- data/lib/puma/rack/urlmap.rb +1 -1
- data/lib/puma/rack_default.rb +18 -3
- data/lib/puma/reactor.rb +16 -7
- data/lib/puma/request.rb +91 -64
- data/lib/puma/runner.rb +13 -2
- data/lib/puma/sd_notify.rb +149 -0
- data/lib/puma/server.rb +91 -27
- data/lib/puma/single.rb +2 -0
- data/lib/puma/state_file.rb +2 -2
- data/lib/puma/thread_pool.rb +41 -3
- data/lib/puma/util.rb +0 -0
- data/lib/puma.rb +0 -0
- data/lib/rack/handler/puma.rb +113 -86
- data/tools/Dockerfile +2 -2
- data/tools/trickletest.rb +0 -0
- metadata +5 -4
- data/lib/puma/systemd.rb +0 -47
data/ext/puma_http11/mini_ssl.c
CHANGED
@@ -36,6 +36,12 @@ void raise_file_error(const char* caller, const char *filename) {
|
|
36
36
|
rb_raise(eError, "%s: error in file '%s': %s", caller, filename, ERR_error_string(ERR_get_error(), NULL));
|
37
37
|
}
|
38
38
|
|
39
|
+
NORETURN(void raise_param_error(const char* caller, const char *param));
|
40
|
+
|
41
|
+
void raise_param_error(const char* caller, const char *param) {
|
42
|
+
rb_raise(eError, "%s: error with parameter '%s': %s", caller, param, ERR_error_string(ERR_get_error(), NULL));
|
43
|
+
}
|
44
|
+
|
39
45
|
void engine_free(void *ptr) {
|
40
46
|
ms_conn *conn = ptr;
|
41
47
|
ms_cert_buf* cert_buf = (ms_cert_buf*)SSL_get_app_data(conn->ssl);
|
@@ -185,6 +191,18 @@ static int engine_verify_callback(int preverify_ok, X509_STORE_CTX* ctx) {
|
|
185
191
|
return preverify_ok;
|
186
192
|
}
|
187
193
|
|
194
|
+
static int password_callback(char *buf, int size, int rwflag, void *userdata) {
|
195
|
+
const char *password = (const char *) userdata;
|
196
|
+
size_t len = strlen(password);
|
197
|
+
|
198
|
+
if (len > (size_t) size) {
|
199
|
+
return 0;
|
200
|
+
}
|
201
|
+
|
202
|
+
memcpy(buf, password, len);
|
203
|
+
return (int) len;
|
204
|
+
}
|
205
|
+
|
188
206
|
static VALUE
|
189
207
|
sslctx_alloc(VALUE klass) {
|
190
208
|
SSL_CTX *ctx;
|
@@ -212,10 +230,12 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
212
230
|
SSL_CTX* ctx;
|
213
231
|
int ssl_options;
|
214
232
|
VALUE key, cert, ca, verify_mode, ssl_cipher_filter, no_tlsv1, no_tlsv1_1,
|
215
|
-
verification_flags, session_id_bytes, cert_pem, key_pem;
|
233
|
+
verification_flags, session_id_bytes, cert_pem, key_pem, key_password_command, key_password;
|
216
234
|
BIO *bio;
|
217
|
-
X509 *x509;
|
235
|
+
X509 *x509 = NULL;
|
218
236
|
EVP_PKEY *pkey;
|
237
|
+
pem_password_cb *password_cb = NULL;
|
238
|
+
const char *password = NULL;
|
219
239
|
#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
|
220
240
|
int min;
|
221
241
|
#endif
|
@@ -235,6 +255,8 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
235
255
|
|
236
256
|
key = rb_funcall(mini_ssl_ctx, rb_intern_const("key"), 0);
|
237
257
|
|
258
|
+
key_password_command = rb_funcall(mini_ssl_ctx, rb_intern_const("key_password_command"), 0);
|
259
|
+
|
238
260
|
cert = rb_funcall(mini_ssl_ctx, rb_intern_const("cert"), 0);
|
239
261
|
|
240
262
|
ca = rb_funcall(mini_ssl_ctx, rb_intern_const("ca"), 0);
|
@@ -261,6 +283,18 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
261
283
|
}
|
262
284
|
}
|
263
285
|
|
286
|
+
if (!NIL_P(key_password_command)) {
|
287
|
+
key_password = rb_funcall(mini_ssl_ctx, rb_intern_const("key_password"), 0);
|
288
|
+
|
289
|
+
if (!NIL_P(key_password)) {
|
290
|
+
StringValue(key_password);
|
291
|
+
password_cb = password_callback;
|
292
|
+
password = RSTRING_PTR(key_password);
|
293
|
+
SSL_CTX_set_default_passwd_cb(ctx, password_cb);
|
294
|
+
SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *) password);
|
295
|
+
}
|
296
|
+
}
|
297
|
+
|
264
298
|
if (!NIL_P(key)) {
|
265
299
|
StringValue(key);
|
266
300
|
|
@@ -270,22 +304,71 @@ sslctx_initialize(VALUE self, VALUE mini_ssl_ctx) {
|
|
270
304
|
}
|
271
305
|
|
272
306
|
if (!NIL_P(cert_pem)) {
|
307
|
+
X509 *ca = NULL;
|
308
|
+
unsigned long err;
|
309
|
+
|
273
310
|
bio = BIO_new(BIO_s_mem());
|
274
311
|
BIO_puts(bio, RSTRING_PTR(cert_pem));
|
312
|
+
|
313
|
+
/**
|
314
|
+
* Much of this pulled as a simplified version of the `use_certificate_chain_file` method
|
315
|
+
* from openssl's `ssl_rsa.c` file.
|
316
|
+
*/
|
317
|
+
|
318
|
+
/* first read the cert as the first item in the pem file */
|
275
319
|
x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
320
|
+
if (NULL == x509) {
|
321
|
+
BIO_free_all(bio);
|
322
|
+
raise_param_error("PEM_read_bio_X509", "cert_pem");
|
323
|
+
}
|
276
324
|
|
277
|
-
|
278
|
-
|
279
|
-
|
325
|
+
/* Add the cert to the context */
|
326
|
+
/* 1 is success - otherwise check the error codes */
|
327
|
+
if (1 != SSL_CTX_use_certificate(ctx, x509)) {
|
328
|
+
BIO_free_all(bio);
|
329
|
+
raise_param_error("SSL_CTX_use_certificate", "cert_pem");
|
330
|
+
}
|
331
|
+
|
332
|
+
X509_free(x509); /* no longer need our reference */
|
333
|
+
|
334
|
+
/* Now lets load up the rest of the certificate chain */
|
335
|
+
/* 1 is success 0 is error */
|
336
|
+
if (0 == SSL_CTX_clear_chain_certs(ctx)) {
|
337
|
+
BIO_free_all(bio);
|
338
|
+
raise_param_error("SSL_CTX_clear_chain_certs","cert_pem");
|
339
|
+
}
|
340
|
+
|
341
|
+
while (1) {
|
342
|
+
ca = PEM_read_bio_X509(bio, NULL, NULL, NULL);
|
343
|
+
|
344
|
+
if (NULL == ca) {
|
345
|
+
break;
|
346
|
+
}
|
347
|
+
|
348
|
+
if (0 == SSL_CTX_add0_chain_cert(ctx, ca)) {
|
349
|
+
BIO_free_all(bio);
|
350
|
+
raise_param_error("SSL_CTX_add0_chain_cert","cert_pem");
|
351
|
+
}
|
352
|
+
/* don't free ca - its now owned by the context */
|
353
|
+
}
|
354
|
+
|
355
|
+
/* ca is NULL - so its either the end of the file or an error */
|
356
|
+
err = ERR_peek_last_error();
|
357
|
+
|
358
|
+
/* If its the end of the file - then we are done, in any case free the bio */
|
359
|
+
BIO_free_all(bio);
|
360
|
+
|
361
|
+
if ((ERR_GET_LIB(err) == ERR_LIB_PEM) && (ERR_GET_REASON(err) == PEM_R_NO_START_LINE)) {
|
362
|
+
ERR_clear_error();
|
363
|
+
} else {
|
364
|
+
raise_param_error("PEM_read_bio_X509","cert_pem");
|
280
365
|
}
|
281
|
-
X509_free(x509);
|
282
|
-
BIO_free(bio);
|
283
366
|
}
|
284
367
|
|
285
368
|
if (!NIL_P(key_pem)) {
|
286
369
|
bio = BIO_new(BIO_s_mem());
|
287
370
|
BIO_puts(bio, RSTRING_PTR(key_pem));
|
288
|
-
pkey = PEM_read_bio_PrivateKey(bio, NULL,
|
371
|
+
pkey = PEM_read_bio_PrivateKey(bio, NULL, password_cb, (void *) password);
|
289
372
|
|
290
373
|
if (SSL_CTX_use_PrivateKey(ctx, pkey) != 1) {
|
291
374
|
BIO_free(bio);
|
@@ -463,7 +546,7 @@ NORETURN(void raise_error(SSL* ssl, int result));
|
|
463
546
|
|
464
547
|
void raise_error(SSL* ssl, int result) {
|
465
548
|
char buf[512];
|
466
|
-
char msg[
|
549
|
+
char msg[768];
|
467
550
|
const char* err_str;
|
468
551
|
int err = errno;
|
469
552
|
int mask = 4095;
|
@@ -721,6 +804,10 @@ void Init_mini_ssl(VALUE puma) {
|
|
721
804
|
|
722
805
|
rb_define_method(eng, "init?", engine_init, 0);
|
723
806
|
|
807
|
+
/* @!attribute [r] peercert
|
808
|
+
* Returns `nil` when `MiniSSL::Context#verify_mode` is set to `VERIFY_NONE`.
|
809
|
+
* @return [String, nil] DER encoded cert
|
810
|
+
*/
|
724
811
|
rb_define_method(eng, "peercert", engine_peercert, 0);
|
725
812
|
|
726
813
|
rb_define_method(eng, "ssl_vers_st", engine_ssl_vers_st, 0);
|
File without changes
|
File without changes
|
File without changes
|
@@ -47,6 +47,7 @@ import static javax.net.ssl.SSLEngineResult.Status;
|
|
47
47
|
import static javax.net.ssl.SSLEngineResult.HandshakeStatus;
|
48
48
|
|
49
49
|
public class MiniSSL extends RubyObject { // MiniSSL::Engine
|
50
|
+
private static final long serialVersionUID = -6903439483039141234L;
|
50
51
|
private static ObjectAllocator ALLOCATOR = new ObjectAllocator() {
|
51
52
|
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
|
52
53
|
return new MiniSSL(runtime, klass);
|
@@ -500,7 +501,7 @@ public class MiniSSL extends RubyObject { // MiniSSL::Engine
|
|
500
501
|
}
|
501
502
|
|
502
503
|
private static RaiseException newError(Ruby runtime, RubyClass errorClass, String message, Throwable cause) {
|
503
|
-
RaiseException ex =
|
504
|
+
RaiseException ex = RaiseException.from(runtime, errorClass, message);
|
504
505
|
ex.initCause(cause);
|
505
506
|
return ex;
|
506
507
|
}
|
File without changes
|
data/lib/puma/app/status.rb
CHANGED
@@ -80,7 +80,7 @@ module Puma
|
|
80
80
|
|
81
81
|
def authenticate(env)
|
82
82
|
return true unless @auth_token
|
83
|
-
env['QUERY_STRING'].to_s.split(
|
83
|
+
env['QUERY_STRING'].to_s.split('&;').include? "token=#{@auth_token}"
|
84
84
|
end
|
85
85
|
|
86
86
|
def rack_response(status, body, content_type='application/json')
|
data/lib/puma/binder.rb
CHANGED
@@ -48,7 +48,6 @@ module Puma
|
|
48
48
|
|
49
49
|
@envs = {}
|
50
50
|
@ios = []
|
51
|
-
localhost_authority
|
52
51
|
end
|
53
52
|
|
54
53
|
attr_reader :ios
|
@@ -158,10 +157,10 @@ module Puma
|
|
158
157
|
ios_len = @ios.length
|
159
158
|
params = Util.parse_query uri.query
|
160
159
|
|
161
|
-
|
160
|
+
low_latency = params.key?('low_latency') && params['low_latency'] != 'false'
|
162
161
|
backlog = params.fetch('backlog', 1024).to_i
|
163
162
|
|
164
|
-
io = add_tcp_listener uri.host, uri.port,
|
163
|
+
io = add_tcp_listener uri.host, uri.port, low_latency, backlog
|
165
164
|
|
166
165
|
@ios[ios_len..-1].each do |i|
|
167
166
|
addr = loc_addr_str i
|
@@ -251,7 +250,8 @@ module Puma
|
|
251
250
|
else
|
252
251
|
ios_len = @ios.length
|
253
252
|
backlog = params.fetch('backlog', 1024).to_i
|
254
|
-
|
253
|
+
low_latency = params['low_latency'] != 'false'
|
254
|
+
io = add_ssl_listener uri.host, uri.port, ctx, low_latency, backlog
|
255
255
|
|
256
256
|
@ios[ios_len..-1].each do |i|
|
257
257
|
addr = loc_addr_str i
|
@@ -330,7 +330,7 @@ module Puma
|
|
330
330
|
return
|
331
331
|
end
|
332
332
|
|
333
|
-
host = host[1..-2] if host
|
333
|
+
host = host[1..-2] if host&.start_with? '['
|
334
334
|
tcp_server = TCPServer.new(host, port)
|
335
335
|
|
336
336
|
if optimize_for_latency
|
@@ -364,7 +364,7 @@ module Puma
|
|
364
364
|
return
|
365
365
|
end
|
366
366
|
|
367
|
-
host = host[1..-2] if host
|
367
|
+
host = host[1..-2] if host&.start_with? '['
|
368
368
|
s = TCPServer.new(host, port)
|
369
369
|
if optimize_for_latency
|
370
370
|
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
@@ -450,11 +450,14 @@ module Puma
|
|
450
450
|
|
451
451
|
def close_listeners
|
452
452
|
@listeners.each do |l, io|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
453
|
+
begin
|
454
|
+
io.close unless io.closed?
|
455
|
+
uri = URI.parse l
|
456
|
+
next unless uri.scheme == 'unix'
|
457
|
+
unix_path = "#{uri.host}#{uri.path}"
|
458
|
+
File.unlink unix_path if @unix_paths.include?(unix_path) && File.exist?(unix_path)
|
459
|
+
rescue Errno::EBADF
|
460
|
+
end
|
458
461
|
end
|
459
462
|
end
|
460
463
|
|
data/lib/puma/cli.rb
CHANGED
@@ -93,7 +93,7 @@ module Puma
|
|
93
93
|
#
|
94
94
|
|
95
95
|
def setup_options
|
96
|
-
@conf = Configuration.new do |user_config, file_config|
|
96
|
+
@conf = Configuration.new({}, {events: @events}) do |user_config, file_config|
|
97
97
|
@parser = OptionParser.new do |o|
|
98
98
|
o.on "-b", "--bind URI", "URI to bind to (tcp://, unix://, ssl://)" do |arg|
|
99
99
|
user_config.bind arg
|
@@ -144,6 +144,10 @@ module Puma
|
|
144
144
|
$LOAD_PATH.unshift(*arg.split(':'))
|
145
145
|
end
|
146
146
|
|
147
|
+
o.on "--idle-timeout SECONDS", "Number of seconds until the next request before automatic shutdown" do |arg|
|
148
|
+
user_config.idle_timeout arg
|
149
|
+
end
|
150
|
+
|
147
151
|
o.on "-p", "--port PORT", "Define the TCP port to bind to",
|
148
152
|
"Use -b for more advanced options" do |arg|
|
149
153
|
user_config.bind "tcp://#{Configuration::DEFAULTS[:tcp_host]}:#{arg}"
|
data/lib/puma/client.rb
CHANGED
@@ -11,7 +11,6 @@ end
|
|
11
11
|
require_relative 'detect'
|
12
12
|
require_relative 'io_buffer'
|
13
13
|
require 'tempfile'
|
14
|
-
require 'forwardable'
|
15
14
|
|
16
15
|
if Puma::IS_JRUBY
|
17
16
|
# We have to work around some OpenSSL buffer/io-readiness bugs
|
@@ -49,7 +48,16 @@ module Puma
|
|
49
48
|
|
50
49
|
# chunked body validation
|
51
50
|
CHUNK_SIZE_INVALID = /[^\h]/.freeze
|
52
|
-
CHUNK_VALID_ENDING =
|
51
|
+
CHUNK_VALID_ENDING = Const::LINE_END
|
52
|
+
CHUNK_VALID_ENDING_SIZE = CHUNK_VALID_ENDING.bytesize
|
53
|
+
|
54
|
+
# The maximum number of bytes we'll buffer looking for a valid
|
55
|
+
# chunk header.
|
56
|
+
MAX_CHUNK_HEADER_SIZE = 4096
|
57
|
+
|
58
|
+
# The maximum amount of excess data the client sends
|
59
|
+
# using chunk size extensions before we abort the connection.
|
60
|
+
MAX_CHUNK_EXCESS = 16 * 1024
|
53
61
|
|
54
62
|
# Content-Length header value validation
|
55
63
|
CONTENT_LENGTH_VALUE_INVALID = /[^\d]/.freeze
|
@@ -61,14 +69,13 @@ module Puma
|
|
61
69
|
EmptyBody = NullIO.new
|
62
70
|
|
63
71
|
include Puma::Const
|
64
|
-
extend Forwardable
|
65
72
|
|
66
73
|
def initialize(io, env=nil)
|
67
74
|
@io = io
|
68
75
|
@to_io = io.to_io
|
69
76
|
@io_buffer = IOBuffer.new
|
70
77
|
@proto_env = env
|
71
|
-
@env = env
|
78
|
+
@env = env&.dup
|
72
79
|
|
73
80
|
@parser = HttpParser.new
|
74
81
|
@parsed_bytes = 0
|
@@ -86,6 +93,9 @@ module Puma
|
|
86
93
|
@requests_served = 0
|
87
94
|
@hijacked = false
|
88
95
|
|
96
|
+
@http_content_length_limit = nil
|
97
|
+
@http_content_length_limit_exceeded = false
|
98
|
+
|
89
99
|
@peerip = nil
|
90
100
|
@peer_family = nil
|
91
101
|
@listener = nil
|
@@ -95,16 +105,22 @@ module Puma
|
|
95
105
|
@body_remain = 0
|
96
106
|
|
97
107
|
@in_last_chunk = false
|
108
|
+
|
109
|
+
# need unfrozen ASCII-8BIT, +'' is UTF-8
|
110
|
+
@read_buffer = String.new # rubocop: disable Performance/UnfreezeString
|
98
111
|
end
|
99
112
|
|
100
113
|
attr_reader :env, :to_io, :body, :io, :timeout_at, :ready, :hijacked,
|
101
|
-
:tempfile, :io_buffer
|
114
|
+
:tempfile, :io_buffer, :http_content_length_limit_exceeded
|
102
115
|
|
103
|
-
attr_writer :peerip
|
116
|
+
attr_writer :peerip, :http_content_length_limit
|
104
117
|
|
105
118
|
attr_accessor :remote_addr_header, :listener
|
106
119
|
|
107
|
-
|
120
|
+
# Remove in Puma 7?
|
121
|
+
def closed?
|
122
|
+
@to_io.closed?
|
123
|
+
end
|
108
124
|
|
109
125
|
# Test to see if io meets a bare minimum of functioning, @to_io needs to be
|
110
126
|
# used for MiniSSL::Socket
|
@@ -151,6 +167,7 @@ module Puma
|
|
151
167
|
@body_remain = 0
|
152
168
|
@peerip = nil if @remote_addr_header
|
153
169
|
@in_last_chunk = false
|
170
|
+
@http_content_length_limit_exceeded = false
|
154
171
|
|
155
172
|
if @buffer
|
156
173
|
return false unless try_to_parse_proxy_protocol
|
@@ -210,6 +227,17 @@ module Puma
|
|
210
227
|
end
|
211
228
|
|
212
229
|
def try_to_finish
|
230
|
+
if env[CONTENT_LENGTH] && above_http_content_limit(env[CONTENT_LENGTH].to_i)
|
231
|
+
@http_content_length_limit_exceeded = true
|
232
|
+
end
|
233
|
+
|
234
|
+
if @http_content_length_limit_exceeded
|
235
|
+
@buffer = nil
|
236
|
+
@body = EmptyBody
|
237
|
+
set_ready
|
238
|
+
return true
|
239
|
+
end
|
240
|
+
|
213
241
|
return read_body if in_data_phase
|
214
242
|
|
215
243
|
begin
|
@@ -239,6 +267,10 @@ module Puma
|
|
239
267
|
|
240
268
|
@parsed_bytes = @parser.execute(@env, @buffer, @parsed_bytes)
|
241
269
|
|
270
|
+
if @parser.finished? && above_http_content_limit(@parser.body.bytesize)
|
271
|
+
@http_content_length_limit_exceeded = true
|
272
|
+
end
|
273
|
+
|
242
274
|
if @parser.finished?
|
243
275
|
return setup_body
|
244
276
|
elsif @parsed_bytes >= MAX_HEADER
|
@@ -360,8 +392,8 @@ module Puma
|
|
360
392
|
cl = @env[CONTENT_LENGTH]
|
361
393
|
|
362
394
|
if cl
|
363
|
-
# cannot contain characters that are not \d
|
364
|
-
if CONTENT_LENGTH_VALUE_INVALID.match? cl
|
395
|
+
# cannot contain characters that are not \d, or be empty
|
396
|
+
if CONTENT_LENGTH_VALUE_INVALID.match?(cl) || cl.empty?
|
365
397
|
raise HttpParserError, "Invalid Content-Length: #{cl.inspect}"
|
366
398
|
end
|
367
399
|
else
|
@@ -414,7 +446,7 @@ module Puma
|
|
414
446
|
end
|
415
447
|
|
416
448
|
begin
|
417
|
-
chunk = @io.read_nonblock(want)
|
449
|
+
chunk = @io.read_nonblock(want, @read_buffer)
|
418
450
|
rescue IO::WaitReadable
|
419
451
|
return false
|
420
452
|
rescue SystemCallError, IOError
|
@@ -446,7 +478,7 @@ module Puma
|
|
446
478
|
def read_chunked_body
|
447
479
|
while true
|
448
480
|
begin
|
449
|
-
chunk = @io.read_nonblock(4096)
|
481
|
+
chunk = @io.read_nonblock(4096, @read_buffer)
|
450
482
|
rescue IO::WaitReadable
|
451
483
|
return false
|
452
484
|
rescue SystemCallError, IOError
|
@@ -472,6 +504,7 @@ module Puma
|
|
472
504
|
@chunked_body = true
|
473
505
|
@partial_part_left = 0
|
474
506
|
@prev_chunk = ""
|
507
|
+
@excess_cr = 0
|
475
508
|
|
476
509
|
@body = Tempfile.new(Const::PUMA_TMP_BASE)
|
477
510
|
@body.unlink
|
@@ -522,7 +555,7 @@ module Puma
|
|
522
555
|
|
523
556
|
while !io.eof?
|
524
557
|
line = io.gets
|
525
|
-
if line.end_with?(
|
558
|
+
if line.end_with?(CHUNK_VALID_ENDING)
|
526
559
|
# Puma doesn't process chunk extensions, but should parse if they're
|
527
560
|
# present, which is the reason for the semicolon regex
|
528
561
|
chunk_hex = line.strip[/\A[^;]+/]
|
@@ -534,19 +567,39 @@ module Puma
|
|
534
567
|
@in_last_chunk = true
|
535
568
|
@body.rewind
|
536
569
|
rest = io.read
|
537
|
-
|
538
|
-
if rest.bytesize < last_crlf_size
|
570
|
+
if rest.bytesize < CHUNK_VALID_ENDING_SIZE
|
539
571
|
@buffer = nil
|
540
|
-
@partial_part_left =
|
572
|
+
@partial_part_left = CHUNK_VALID_ENDING_SIZE - rest.bytesize
|
541
573
|
return false
|
542
574
|
else
|
543
|
-
|
575
|
+
# if the next character is a CRLF, set buffer to everything after that CRLF
|
576
|
+
start_of_rest = if rest.start_with?(CHUNK_VALID_ENDING)
|
577
|
+
CHUNK_VALID_ENDING_SIZE
|
578
|
+
else # we have started a trailer section, which we do not support. skip it!
|
579
|
+
rest.index(CHUNK_VALID_ENDING*2) + CHUNK_VALID_ENDING_SIZE*2
|
580
|
+
end
|
581
|
+
|
582
|
+
@buffer = rest[start_of_rest..-1]
|
544
583
|
@buffer = nil if @buffer.empty?
|
545
584
|
set_ready
|
546
585
|
return true
|
547
586
|
end
|
548
587
|
end
|
549
588
|
|
589
|
+
# Track the excess as a function of the size of the
|
590
|
+
# header vs the size of the actual data. Excess can
|
591
|
+
# go negative (and is expected to) when the body is
|
592
|
+
# significant.
|
593
|
+
# The additional of chunk_hex.size and 2 compensates
|
594
|
+
# for a client sending 1 byte in a chunked body over
|
595
|
+
# a long period of time, making sure that that client
|
596
|
+
# isn't accidentally eventually punished.
|
597
|
+
@excess_cr += (line.size - len - chunk_hex.size - 2)
|
598
|
+
|
599
|
+
if @excess_cr >= MAX_CHUNK_EXCESS
|
600
|
+
raise HttpParserError, "Maximum chunk excess detected"
|
601
|
+
end
|
602
|
+
|
550
603
|
len += 2
|
551
604
|
|
552
605
|
part = io.read(len)
|
@@ -574,6 +627,10 @@ module Puma
|
|
574
627
|
@partial_part_left = len - part.size
|
575
628
|
end
|
576
629
|
else
|
630
|
+
if @prev_chunk.size + chunk.size >= MAX_CHUNK_HEADER_SIZE
|
631
|
+
raise HttpParserError, "maximum size of chunk header exceeded"
|
632
|
+
end
|
633
|
+
|
577
634
|
@prev_chunk = line
|
578
635
|
return false
|
579
636
|
end
|
@@ -594,5 +651,9 @@ module Puma
|
|
594
651
|
@requests_served += 1
|
595
652
|
@ready = true
|
596
653
|
end
|
654
|
+
|
655
|
+
def above_http_content_limit(value)
|
656
|
+
@http_content_length_limit&.< value
|
657
|
+
end
|
597
658
|
end
|
598
659
|
end
|
data/lib/puma/cluster/worker.rb
CHANGED
@@ -115,6 +115,11 @@ module Puma
|
|
115
115
|
|
116
116
|
while restart_server.pop
|
117
117
|
server_thread = server.run
|
118
|
+
|
119
|
+
if @log_writer.debug? && index == 0
|
120
|
+
debug_loaded_extensions "Loaded Extensions - worker 0:"
|
121
|
+
end
|
122
|
+
|
118
123
|
stat_thread ||= Thread.new(@worker_write) do |io|
|
119
124
|
Puma.set_thread_name "stat pld"
|
120
125
|
base_payload = "p#{Process.pid}"
|
File without changes
|