puma 6.0.2 → 6.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|