puma 4.1.1 → 4.3.6

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.

@@ -0,0 +1,76 @@
1
+ module Puma
2
+ module MiniSSL
3
+ class ContextBuilder
4
+ def initialize(params, events)
5
+ require 'puma/minissl'
6
+ MiniSSL.check
7
+
8
+ @params = params
9
+ @events = events
10
+ end
11
+
12
+ def context
13
+ ctx = MiniSSL::Context.new
14
+
15
+ if defined?(JRUBY_VERSION)
16
+ unless params['keystore']
17
+ events.error "Please specify the Java keystore via 'keystore='"
18
+ end
19
+
20
+ ctx.keystore = params['keystore']
21
+
22
+ unless params['keystore-pass']
23
+ events.error "Please specify the Java keystore password via 'keystore-pass='"
24
+ end
25
+
26
+ ctx.keystore_pass = params['keystore-pass']
27
+ ctx.ssl_cipher_list = params['ssl_cipher_list'] if params['ssl_cipher_list']
28
+ else
29
+ unless params['key']
30
+ events.error "Please specify the SSL key via 'key='"
31
+ end
32
+
33
+ ctx.key = params['key']
34
+
35
+ unless params['cert']
36
+ events.error "Please specify the SSL cert via 'cert='"
37
+ end
38
+
39
+ ctx.cert = params['cert']
40
+
41
+ if ['peer', 'force_peer'].include?(params['verify_mode'])
42
+ unless params['ca']
43
+ events.error "Please specify the SSL ca via 'ca='"
44
+ end
45
+ end
46
+
47
+ ctx.ca = params['ca'] if params['ca']
48
+ ctx.ssl_cipher_filter = params['ssl_cipher_filter'] if params['ssl_cipher_filter']
49
+ end
50
+
51
+ ctx.no_tlsv1 = true if params['no_tlsv1'] == 'true'
52
+ ctx.no_tlsv1_1 = true if params['no_tlsv1_1'] == 'true'
53
+
54
+ if params['verify_mode']
55
+ ctx.verify_mode = case params['verify_mode']
56
+ when "peer"
57
+ MiniSSL::VERIFY_PEER
58
+ when "force_peer"
59
+ MiniSSL::VERIFY_PEER | MiniSSL::VERIFY_FAIL_IF_NO_PEER_CERT
60
+ when "none"
61
+ MiniSSL::VERIFY_NONE
62
+ else
63
+ events.error "Please specify a valid verify_mode="
64
+ MiniSSL::VERIFY_NONE
65
+ end
66
+ end
67
+
68
+ ctx
69
+ end
70
+
71
+ private
72
+
73
+ attr_reader :params, :events
74
+ end
75
+ end
76
+ end
@@ -62,8 +62,11 @@ module Puma
62
62
  end
63
63
 
64
64
  def fire_background
65
- @background.each do |b|
66
- Thread.new(&b)
65
+ @background.each_with_index do |b, i|
66
+ Thread.new do
67
+ Puma.set_thread_name "plugin background #{i}"
68
+ b.call
69
+ end
67
70
  end
68
71
  end
69
72
  end
@@ -225,7 +225,7 @@ module Puma
225
225
  # will be flooding them with errors when persistent connections
226
226
  # are closed.
227
227
  rescue ConnectionError
228
- c.write_500
228
+ c.write_error(500)
229
229
  c.close
230
230
 
231
231
  clear_monitor mon
@@ -237,7 +237,8 @@ module Puma
237
237
  ssl_socket = c.io
238
238
  begin
239
239
  addr = ssl_socket.peeraddr.last
240
- rescue IOError
240
+ # EINVAL can happen when browser closes socket w/security exception
241
+ rescue IOError, Errno::EINVAL
241
242
  addr = "<unknown>"
242
243
  end
243
244
 
@@ -252,7 +253,7 @@ module Puma
252
253
  rescue HttpParserError => e
253
254
  @server.lowlevel_error(e, c.env)
254
255
 
255
- c.write_400
256
+ c.write_error(400)
256
257
  c.close
257
258
 
258
259
  clear_monitor mon
@@ -261,7 +262,7 @@ module Puma
261
262
  rescue StandardError => e
262
263
  @server.lowlevel_error(e, c.env)
263
264
 
264
- c.write_500
265
+ c.write_error(500)
265
266
  c.close
266
267
 
267
268
  clear_monitor mon
@@ -277,7 +278,7 @@ module Puma
277
278
  while @timeouts.first.value.timeout_at < now
278
279
  mon = @timeouts.shift
279
280
  c = mon.value
280
- c.write_408 if c.in_data_phase
281
+ c.write_error(408) if c.in_data_phase
281
282
  c.close
282
283
 
283
284
  clear_monitor mon
@@ -307,6 +308,7 @@ module Puma
307
308
 
308
309
  def run_in_thread
309
310
  @thread = Thread.new do
311
+ Puma.set_thread_name "reactor"
310
312
  begin
311
313
  run_internal
312
314
  rescue StandardError => e
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'puma/server'
4
4
  require 'puma/const'
5
+ require 'puma/minissl/context_builder'
5
6
 
6
7
  module Puma
7
8
  # Generic class that is used by `Puma::Cluster` and `Puma::Single` to
@@ -53,17 +54,23 @@ module Puma
53
54
 
54
55
  uri = URI.parse str
55
56
 
56
- app = Puma::App::Status.new @launcher
57
-
58
57
  if token = @options[:control_auth_token]
59
- app.auth_token = token unless token.empty? || token == 'none'
58
+ token = nil if token.empty? || token == 'none'
60
59
  end
61
60
 
61
+ app = Puma::App::Status.new @launcher, token
62
+
62
63
  control = Puma::Server.new app, @launcher.events
63
64
  control.min_threads = 0
64
65
  control.max_threads = 1
65
66
 
66
67
  case uri.scheme
68
+ when "ssl"
69
+ log "* Starting control server on #{str}"
70
+ params = Util.parse_query uri.query
71
+ ctx = MiniSSL::ContextBuilder.new(params, @events).context
72
+
73
+ control.add_ssl_listener uri.host, uri.port, ctx
67
74
  when "tcp"
68
75
  log "* Starting control server on #{str}"
69
76
  control.add_tcp_listener uri.host, uri.port
@@ -9,13 +9,13 @@ require 'puma/null_io'
9
9
  require 'puma/reactor'
10
10
  require 'puma/client'
11
11
  require 'puma/binder'
12
- require 'puma/delegation'
13
12
  require 'puma/accept_nonblock'
14
13
  require 'puma/util'
15
14
 
16
15
  require 'puma/puma_http11'
17
16
 
18
17
  require 'socket'
18
+ require 'forwardable'
19
19
 
20
20
  module Puma
21
21
 
@@ -32,7 +32,7 @@ module Puma
32
32
  class Server
33
33
 
34
34
  include Puma::Const
35
- extend Puma::Delegation
35
+ extend Forwardable
36
36
 
37
37
  attr_reader :thread
38
38
  attr_reader :events
@@ -89,10 +89,7 @@ module Puma
89
89
 
90
90
  attr_accessor :binder, :leak_stack_on_error, :early_hints
91
91
 
92
- forward :add_tcp_listener, :@binder
93
- forward :add_ssl_listener, :@binder
94
- forward :add_unix_listener, :@binder
95
- forward :connected_port, :@binder
92
+ def_delegators :@binder, :add_tcp_listener, :add_ssl_listener, :add_unix_listener, :connected_port
96
93
 
97
94
  def inherit_binder(bind)
98
95
  @binder = bind
@@ -207,7 +204,10 @@ module Puma
207
204
  @events.fire :state, :running
208
205
 
209
206
  if background
210
- @thread = Thread.new { handle_servers_lopez_mode }
207
+ @thread = Thread.new do
208
+ Puma.set_thread_name "server"
209
+ handle_servers_lopez_mode
210
+ end
211
211
  return @thread
212
212
  else
213
213
  handle_servers_lopez_mode
@@ -317,7 +317,7 @@ module Puma
317
317
 
318
318
  @events.ssl_error self, addr, cert, e
319
319
  rescue HttpParserError => e
320
- client.write_400
320
+ client.write_error(400)
321
321
  client.close
322
322
 
323
323
  @events.parse_error self, client.env, e
@@ -351,7 +351,10 @@ module Puma
351
351
  @events.fire :state, :running
352
352
 
353
353
  if background
354
- @thread = Thread.new { handle_servers }
354
+ @thread = Thread.new do
355
+ Puma.set_thread_name "server"
356
+ handle_servers
357
+ end
355
358
  return @thread
356
359
  else
357
360
  handle_servers
@@ -463,6 +466,8 @@ module Puma
463
466
  clean_thread_locals = @options[:clean_thread_locals]
464
467
  close_socket = true
465
468
 
469
+ requests = 0
470
+
466
471
  while true
467
472
  case handle_request(client, buffer)
468
473
  when false
@@ -476,7 +481,19 @@ module Puma
476
481
 
477
482
  ThreadPool.clean_thread_locals if clean_thread_locals
478
483
 
479
- unless client.reset(@status == :run)
484
+ requests += 1
485
+
486
+ check_for_more_data = @status == :run
487
+
488
+ if requests >= MAX_FAST_INLINE
489
+ # This will mean that reset will only try to use the data it already
490
+ # has buffered and won't try to read more data. What this means is that
491
+ # every client, independent of their request speed, gets treated like a slow
492
+ # one once every MAX_FAST_INLINE requests.
493
+ check_for_more_data = false
494
+ end
495
+
496
+ unless client.reset(check_for_more_data)
480
497
  close_socket = false
481
498
  client.set_timeout @persistent_timeout
482
499
  @reactor.add client
@@ -505,7 +522,7 @@ module Puma
505
522
  rescue HttpParserError => e
506
523
  lowlevel_error(e, client.env)
507
524
 
508
- client.write_400
525
+ client.write_error(400)
509
526
 
510
527
  @events.parse_error self, client.env, e
511
528
 
@@ -513,7 +530,7 @@ module Puma
513
530
  rescue StandardError => e
514
531
  lowlevel_error(e, client.env)
515
532
 
516
- client.write_500
533
+ client.write_error(500)
517
534
 
518
535
  @events.unknown_error self, e, "Read"
519
536
 
@@ -640,6 +657,7 @@ module Puma
640
657
  headers.each_pair do |k, vs|
641
658
  if vs.respond_to?(:to_s) && !vs.to_s.empty?
642
659
  vs.to_s.split(NEWLINE).each do |v|
660
+ next if possible_header_injection?(v)
643
661
  fast_write client, "#{k}: #{v}\r\n"
644
662
  end
645
663
  else
@@ -654,6 +672,37 @@ module Puma
654
672
  }
655
673
  end
656
674
 
675
+ # Fixup any headers with , in the name to have _ now. We emit
676
+ # headers with , in them during the parse phase to avoid ambiguity
677
+ # with the - to _ conversion for critical headers. But here for
678
+ # compatibility, we'll convert them back. This code is written to
679
+ # avoid allocation in the common case (ie there are no headers
680
+ # with , in their names), that's why it has the extra conditionals.
681
+
682
+ to_delete = nil
683
+ to_add = nil
684
+
685
+ env.each do |k,v|
686
+ if k.start_with?("HTTP_") and k.include?(",") and k != "HTTP_TRANSFER,ENCODING"
687
+ if to_delete
688
+ to_delete << k
689
+ else
690
+ to_delete = [k]
691
+ end
692
+
693
+ unless to_add
694
+ to_add = {}
695
+ end
696
+
697
+ to_add[k.gsub(",", "_")] = v
698
+ end
699
+ end
700
+
701
+ if to_delete
702
+ to_delete.each { |k| env.delete(k) }
703
+ env.merge! to_add
704
+ end
705
+
657
706
  # A rack extension. If the app writes #call'ables to this
658
707
  # array, we will invoke them when the request is done.
659
708
  #
@@ -741,6 +790,7 @@ module Puma
741
790
  headers.each do |k, vs|
742
791
  case k.downcase
743
792
  when CONTENT_LENGTH2
793
+ next if possible_header_injection?(vs)
744
794
  content_length = vs
745
795
  next
746
796
  when TRANSFER_ENCODING
@@ -753,6 +803,7 @@ module Puma
753
803
 
754
804
  if vs.respond_to?(:to_s) && !vs.to_s.empty?
755
805
  vs.to_s.split(NEWLINE).each do |v|
806
+ next if possible_header_injection?(v)
756
807
  lines.append k, colon, v, line_ending
757
808
  end
758
809
  else
@@ -1023,5 +1074,10 @@ module Puma
1023
1074
  def shutting_down?
1024
1075
  @status == :stop || @status == :restart
1025
1076
  end
1077
+
1078
+ def possible_header_injection?(header_value)
1079
+ HTTP_INJECTION_REGEX =~ header_value.to_s
1080
+ end
1081
+ private :possible_header_injection?
1026
1082
  end
1027
1083
  end
@@ -87,8 +87,7 @@ module Puma
87
87
  @spawned += 1
88
88
 
89
89
  th = Thread.new(@spawned) do |spawned|
90
- # Thread name is new in Ruby 2.3
91
- Thread.current.name = 'puma %03i' % spawned if Thread.current.respond_to?(:name=)
90
+ Puma.set_thread_name 'threadpool %03i' % spawned
92
91
  todo = @todo
93
92
  block = @block
94
93
  mutex = @mutex
@@ -190,7 +189,7 @@ module Puma
190
189
  # request, it might not be added to the `@todo` array right away.
191
190
  # For example if a slow client has only sent a header, but not a body
192
191
  # then the `@todo` array would stay the same size as the reactor works
193
- # to try to buffer the request. In tha scenario the next call to this
192
+ # to try to buffer the request. In that scenario the next call to this
194
193
  # method would not block and another request would be added into the reactor
195
194
  # by the server. This would continue until a fully bufferend request
196
195
  # makes it through the reactor and can then be processed by the thread pool.
@@ -244,10 +243,12 @@ module Puma
244
243
  end
245
244
  end
246
245
 
247
- class AutoTrim
248
- def initialize(pool, timeout)
246
+ class Automaton
247
+ def initialize(pool, timeout, thread_name, message)
249
248
  @pool = pool
250
249
  @timeout = timeout
250
+ @thread_name = thread_name
251
+ @message = message
251
252
  @running = false
252
253
  end
253
254
 
@@ -255,8 +256,9 @@ module Puma
255
256
  @running = true
256
257
 
257
258
  @thread = Thread.new do
259
+ Puma.set_thread_name @thread_name
258
260
  while @running
259
- @pool.trim
261
+ @pool.public_send(@message)
260
262
  sleep @timeout
261
263
  end
262
264
  end
@@ -269,36 +271,12 @@ module Puma
269
271
  end
270
272
 
271
273
  def auto_trim!(timeout=30)
272
- @auto_trim = AutoTrim.new(self, timeout)
274
+ @auto_trim = Automaton.new(self, timeout, "threadpool trimmer", :trim)
273
275
  @auto_trim.start!
274
276
  end
275
277
 
276
- class Reaper
277
- def initialize(pool, timeout)
278
- @pool = pool
279
- @timeout = timeout
280
- @running = false
281
- end
282
-
283
- def start!
284
- @running = true
285
-
286
- @thread = Thread.new do
287
- while @running
288
- @pool.reap
289
- sleep @timeout
290
- end
291
- end
292
- end
293
-
294
- def stop
295
- @running = false
296
- @thread.wakeup
297
- end
298
- end
299
-
300
278
  def auto_reap!(timeout=5)
301
- @reaper = Reaper.new(self, timeout)
279
+ @reaper = Automaton.new(self, timeout, "threadpool reaper", :reap)
302
280
  @reaper.start!
303
281
  end
304
282
 
@@ -61,8 +61,6 @@ module Rack
61
61
  conf
62
62
  end
63
63
 
64
-
65
-
66
64
  def self.run(app, options = {})
67
65
  conf = self.config(app, options)
68
66