ed-precompiled_puma 7.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +7 -0
  2. data/History.md +3172 -0
  3. data/LICENSE +29 -0
  4. data/README.md +477 -0
  5. data/bin/puma +10 -0
  6. data/bin/puma-wild +25 -0
  7. data/bin/pumactl +12 -0
  8. data/docs/architecture.md +74 -0
  9. data/docs/compile_options.md +55 -0
  10. data/docs/deployment.md +102 -0
  11. data/docs/fork_worker.md +41 -0
  12. data/docs/images/puma-connection-flow-no-reactor.png +0 -0
  13. data/docs/images/puma-connection-flow.png +0 -0
  14. data/docs/images/puma-general-arch.png +0 -0
  15. data/docs/java_options.md +54 -0
  16. data/docs/jungle/README.md +9 -0
  17. data/docs/jungle/rc.d/README.md +74 -0
  18. data/docs/jungle/rc.d/puma +61 -0
  19. data/docs/jungle/rc.d/puma.conf +10 -0
  20. data/docs/kubernetes.md +80 -0
  21. data/docs/nginx.md +80 -0
  22. data/docs/plugins.md +42 -0
  23. data/docs/rails_dev_mode.md +28 -0
  24. data/docs/restart.md +65 -0
  25. data/docs/signals.md +98 -0
  26. data/docs/stats.md +148 -0
  27. data/docs/systemd.md +253 -0
  28. data/docs/testing_benchmarks_local_files.md +150 -0
  29. data/docs/testing_test_rackup_ci_files.md +36 -0
  30. data/ext/puma_http11/PumaHttp11Service.java +17 -0
  31. data/ext/puma_http11/ext_help.h +15 -0
  32. data/ext/puma_http11/extconf.rb +65 -0
  33. data/ext/puma_http11/http11_parser.c +1057 -0
  34. data/ext/puma_http11/http11_parser.h +65 -0
  35. data/ext/puma_http11/http11_parser.java.rl +145 -0
  36. data/ext/puma_http11/http11_parser.rl +149 -0
  37. data/ext/puma_http11/http11_parser_common.rl +54 -0
  38. data/ext/puma_http11/mini_ssl.c +852 -0
  39. data/ext/puma_http11/no_ssl/PumaHttp11Service.java +15 -0
  40. data/ext/puma_http11/org/jruby/puma/Http11.java +257 -0
  41. data/ext/puma_http11/org/jruby/puma/Http11Parser.java +455 -0
  42. data/ext/puma_http11/org/jruby/puma/MiniSSL.java +509 -0
  43. data/ext/puma_http11/puma_http11.c +507 -0
  44. data/lib/puma/app/status.rb +96 -0
  45. data/lib/puma/binder.rb +511 -0
  46. data/lib/puma/cli.rb +245 -0
  47. data/lib/puma/client.rb +720 -0
  48. data/lib/puma/cluster/worker.rb +182 -0
  49. data/lib/puma/cluster/worker_handle.rb +127 -0
  50. data/lib/puma/cluster.rb +635 -0
  51. data/lib/puma/cluster_accept_loop_delay.rb +91 -0
  52. data/lib/puma/commonlogger.rb +115 -0
  53. data/lib/puma/configuration.rb +452 -0
  54. data/lib/puma/const.rb +307 -0
  55. data/lib/puma/control_cli.rb +320 -0
  56. data/lib/puma/detect.rb +47 -0
  57. data/lib/puma/dsl.rb +1480 -0
  58. data/lib/puma/error_logger.rb +115 -0
  59. data/lib/puma/events.rb +72 -0
  60. data/lib/puma/io_buffer.rb +50 -0
  61. data/lib/puma/jruby_restart.rb +11 -0
  62. data/lib/puma/json_serialization.rb +96 -0
  63. data/lib/puma/launcher/bundle_pruner.rb +104 -0
  64. data/lib/puma/launcher.rb +496 -0
  65. data/lib/puma/log_writer.rb +147 -0
  66. data/lib/puma/minissl/context_builder.rb +96 -0
  67. data/lib/puma/minissl.rb +463 -0
  68. data/lib/puma/null_io.rb +101 -0
  69. data/lib/puma/plugin/systemd.rb +90 -0
  70. data/lib/puma/plugin/tmp_restart.rb +36 -0
  71. data/lib/puma/plugin.rb +111 -0
  72. data/lib/puma/rack/builder.rb +297 -0
  73. data/lib/puma/rack/urlmap.rb +93 -0
  74. data/lib/puma/rack_default.rb +24 -0
  75. data/lib/puma/reactor.rb +140 -0
  76. data/lib/puma/request.rb +701 -0
  77. data/lib/puma/runner.rb +211 -0
  78. data/lib/puma/sd_notify.rb +146 -0
  79. data/lib/puma/server.rb +734 -0
  80. data/lib/puma/single.rb +72 -0
  81. data/lib/puma/state_file.rb +69 -0
  82. data/lib/puma/thread_pool.rb +402 -0
  83. data/lib/puma/util.rb +134 -0
  84. data/lib/puma.rb +93 -0
  85. data/lib/rack/handler/puma.rb +144 -0
  86. data/tools/Dockerfile +18 -0
  87. data/tools/trickletest.rb +44 -0
  88. metadata +152 -0
@@ -0,0 +1,701 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Puma
4
+ #———————————————————————— DO NOT USE — this class is for internal use only ———
5
+
6
+
7
+ # The methods here are included in Server, but are separated into this file.
8
+ # All the methods here pertain to passing the request to the app, then
9
+ # writing the response back to the client.
10
+ #
11
+ # None of the methods here are called externally, with the exception of
12
+ # #handle_request, which is called in Server#process_client.
13
+ # @version 5.0.3
14
+ #
15
+ module Request # :nodoc:
16
+
17
+ # Single element array body: smaller bodies are written to io_buffer first,
18
+ # then a single write from io_buffer. Larger sizes are written separately.
19
+ # Also fixes max size of chunked file body read.
20
+ BODY_LEN_MAX = 1_024 * 256
21
+
22
+ # File body: smaller bodies are combined with io_buffer, then written to
23
+ # socket. Larger bodies are written separately using `copy_stream`
24
+ IO_BODY_MAX = 1_024 * 64
25
+
26
+ # Array body: elements are collected in io_buffer. When io_buffer's size
27
+ # exceeds value, they are written to the socket.
28
+ IO_BUFFER_LEN_MAX = 1_024 * 512
29
+
30
+ SOCKET_WRITE_ERR_MSG = "Socket timeout writing data"
31
+
32
+ CUSTOM_STAT = 'CUSTOM'
33
+
34
+ include Puma::Const
35
+
36
+ # Takes the request contained in +client+, invokes the Rack application to construct
37
+ # the response and writes it back to +client.io+.
38
+ #
39
+ # It'll return +false+ when the connection is closed, this doesn't mean
40
+ # that the response wasn't successful.
41
+ #
42
+ # It'll return +:async+ if the connection remains open but will be handled
43
+ # elsewhere, i.e. the connection has been hijacked by the Rack application.
44
+ #
45
+ # Finally, it'll return +true+ on keep-alive connections.
46
+ # @param client [Puma::Client]
47
+ # @param requests [Integer]
48
+ # @return [Boolean,:async]
49
+ #
50
+ def handle_request(client, requests)
51
+ env = client.env
52
+ io_buffer = client.io_buffer
53
+ socket = client.io # io may be a MiniSSL::Socket
54
+ app_body = nil
55
+ error = nil
56
+
57
+ return false if closed_socket?(socket)
58
+
59
+ if client.http_content_length_limit_exceeded
60
+ return prepare_response(413, {}, ["Payload Too Large"], requests, client)
61
+ end
62
+
63
+ normalize_env env, client
64
+
65
+ env[PUMA_SOCKET] = socket
66
+
67
+ if env[HTTPS_KEY] && socket.peercert
68
+ env[PUMA_PEERCERT] = socket.peercert
69
+ end
70
+
71
+ env[HIJACK_P] = true
72
+ env[HIJACK] = client.method :full_hijack
73
+
74
+ env[RACK_INPUT] = client.body
75
+ env[RACK_URL_SCHEME] ||= default_server_port(env) == PORT_443 ? HTTPS : HTTP
76
+
77
+ if @early_hints
78
+ env[EARLY_HINTS] = lambda { |headers|
79
+ begin
80
+ unless (str = str_early_hints headers).empty?
81
+ fast_write_str socket, "HTTP/1.1 103 Early Hints\r\n#{str}\r\n"
82
+ end
83
+ rescue ConnectionError => e
84
+ @log_writer.debug_error e
85
+ # noop, if we lost the socket we just won't send the early hints
86
+ end
87
+ }
88
+ end
89
+
90
+ req_env_post_parse env
91
+
92
+ # A rack extension. If the app writes #call'ables to this
93
+ # array, we will invoke them when the request is done.
94
+ #
95
+ env[RACK_AFTER_REPLY] ||= []
96
+ env[RACK_RESPONSE_FINISHED] ||= []
97
+
98
+ begin
99
+ if @supported_http_methods == :any || @supported_http_methods.key?(env[REQUEST_METHOD])
100
+ status, headers, app_body = @thread_pool.with_force_shutdown do
101
+ @app.call(env)
102
+ end
103
+ else
104
+ @log_writer.log "Unsupported HTTP method used: #{env[REQUEST_METHOD]}"
105
+ status, headers, app_body = [501, {}, ["#{env[REQUEST_METHOD]} method is not supported"]]
106
+ end
107
+
108
+ # app_body needs to always be closed, hold value in case lowlevel_error
109
+ # is called
110
+ res_body = app_body
111
+
112
+ # full hijack, app called env['rack.hijack']
113
+ return :async if client.hijacked
114
+
115
+ status = status.to_i
116
+
117
+ if status == -1
118
+ unless headers.empty? and res_body == []
119
+ raise "async response must have empty headers and body"
120
+ end
121
+
122
+ return :async
123
+ end
124
+ rescue ThreadPool::ForceShutdown => error
125
+ @log_writer.unknown_error error, client, "Rack app"
126
+ @log_writer.log "Detected force shutdown of a thread"
127
+
128
+ status, headers, res_body = lowlevel_error(error, env, 503)
129
+ rescue Exception => error
130
+ @log_writer.unknown_error error, client, "Rack app"
131
+
132
+ status, headers, res_body = lowlevel_error(error, env, 500)
133
+ end
134
+ prepare_response(status, headers, res_body, requests, client)
135
+ ensure
136
+ io_buffer.reset
137
+ uncork_socket client.io
138
+ app_body.close if app_body.respond_to? :close
139
+ client&.tempfile_close
140
+ if after_reply = env[RACK_AFTER_REPLY]
141
+ after_reply.each do |o|
142
+ begin
143
+ o.call
144
+ rescue StandardError => e
145
+ @log_writer.debug_error e
146
+ end
147
+ end
148
+ end
149
+
150
+ if response_finished = env[RACK_RESPONSE_FINISHED]
151
+ response_finished.reverse_each do |o|
152
+ begin
153
+ o.call(env, status, headers, error)
154
+ rescue StandardError => e
155
+ @log_writer.debug_error e
156
+ end
157
+ end
158
+ end
159
+ end
160
+
161
+ # Assembles the headers and prepares the body for actually sending the
162
+ # response via `#fast_write_response`.
163
+ #
164
+ # @param status [Integer] the status returned by the Rack application
165
+ # @param headers [Hash] the headers returned by the Rack application
166
+ # @param res_body [Array] the body returned by the Rack application or
167
+ # a call to `Server#lowlevel_error`
168
+ # @param requests [Integer] number of inline requests handled
169
+ # @param client [Puma::Client]
170
+ # @return [Boolean,:async] keep-alive status or `:async`
171
+ def prepare_response(status, headers, res_body, requests, client)
172
+ env = client.env
173
+ socket = client.io
174
+ io_buffer = client.io_buffer
175
+
176
+ return false if closed_socket?(socket)
177
+
178
+ # Close the connection after a reasonable number of inline requests
179
+ force_keep_alive = @enable_keep_alives && client.requests_served < @max_keep_alive
180
+
181
+ resp_info = str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
182
+
183
+ close_body = false
184
+ response_hijack = nil
185
+ content_length = resp_info[:content_length]
186
+ keep_alive = resp_info[:keep_alive]
187
+
188
+ if res_body.respond_to?(:each) && !resp_info[:response_hijack]
189
+ # below converts app_body into body, dependent on app_body's characteristics, and
190
+ # content_length will be set if it can be determined
191
+ if !content_length && !resp_info[:transfer_encoding] && status != 204
192
+ if res_body.respond_to?(:to_ary) && (array_body = res_body.to_ary) &&
193
+ array_body.is_a?(Array)
194
+ body = array_body.compact
195
+ content_length = body.sum(&:bytesize)
196
+ elsif res_body.is_a?(File) && res_body.respond_to?(:size)
197
+ body = res_body
198
+ content_length = body.size
199
+ elsif res_body.respond_to?(:to_path) && (fn = res_body.to_path) &&
200
+ File.readable?(fn)
201
+ body = File.open fn, 'rb'
202
+ content_length = body.size
203
+ close_body = true
204
+ else
205
+ body = res_body
206
+ end
207
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:to_path) &&
208
+ (fn = res_body.to_path) && File.readable?(fn = res_body.to_path)
209
+ body = File.open fn, 'rb'
210
+ content_length = body.size
211
+ close_body = true
212
+ elsif !res_body.is_a?(::File) && res_body.respond_to?(:filename) &&
213
+ res_body.respond_to?(:bytesize) && File.readable?(fn = res_body.filename)
214
+ # Sprockets::Asset
215
+ content_length = res_body.bytesize unless content_length
216
+ if (body_str = res_body.to_hash[:source])
217
+ body = [body_str]
218
+ else # avoid each and use a File object
219
+ body = File.open fn, 'rb'
220
+ close_body = true
221
+ end
222
+ else
223
+ body = res_body
224
+ end
225
+ else
226
+ # partial hijack, from Rack spec:
227
+ # Servers must ignore the body part of the response tuple when the
228
+ # rack.hijack response header is present.
229
+ response_hijack = resp_info[:response_hijack] || res_body
230
+ end
231
+
232
+ line_ending = LINE_END
233
+
234
+ cork_socket socket
235
+
236
+ if resp_info[:no_body]
237
+ # 101 (Switching Protocols) doesn't return here or have content_length,
238
+ # it should be using `response_hijack`
239
+ unless status == 101
240
+ if content_length && status != 204
241
+ io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
242
+ end
243
+
244
+ io_buffer << LINE_END
245
+ fast_write_str socket, io_buffer.read_and_reset
246
+ socket.flush
247
+ return keep_alive
248
+ end
249
+ else
250
+ if content_length
251
+ io_buffer.append CONTENT_LENGTH_S, content_length.to_s, line_ending
252
+ chunked = false
253
+ elsif !response_hijack && resp_info[:allow_chunked]
254
+ io_buffer << TRANSFER_ENCODING_CHUNKED
255
+ chunked = true
256
+ end
257
+ end
258
+
259
+ io_buffer << line_ending
260
+
261
+ # partial hijack, we write headers, then hand the socket to the app via
262
+ # response_hijack.call
263
+ if response_hijack
264
+ fast_write_str socket, io_buffer.read_and_reset
265
+ uncork_socket socket
266
+ response_hijack.call socket
267
+ return :async
268
+ end
269
+
270
+ fast_write_response socket, body, io_buffer, chunked, content_length.to_i
271
+ body.close if close_body
272
+ # if we're shutting down, close keep_alive connections
273
+ !shutting_down? && keep_alive
274
+ end
275
+
276
+ HTTP_ON_VALUES = { "on" => true, HTTPS => true }
277
+ private_constant :HTTP_ON_VALUES
278
+
279
+ # @param env [Hash] see Puma::Client#env, from request
280
+ # @return [Puma::Const::PORT_443,Puma::Const::PORT_80]
281
+ #
282
+ def default_server_port(env)
283
+ if HTTP_ON_VALUES[env[HTTPS_KEY]] ||
284
+ env[HTTP_X_FORWARDED_PROTO]&.start_with?(HTTPS) ||
285
+ env[HTTP_X_FORWARDED_SCHEME] == HTTPS ||
286
+ env[HTTP_X_FORWARDED_SSL] == "on"
287
+ PORT_443
288
+ else
289
+ PORT_80
290
+ end
291
+ end
292
+
293
+ # Used to write 'early hints', 'no body' responses, 'hijacked' responses,
294
+ # and body segments (called by `fast_write_response`).
295
+ # Writes a string to a socket (normally `Client#io`) using `write_nonblock`.
296
+ # Large strings may not be written in one pass, especially if `io` is a
297
+ # `MiniSSL::Socket`.
298
+ # @param socket [#write_nonblock] the request/response socket
299
+ # @param str [String] the string written to the io
300
+ # @raise [ConnectionError]
301
+ #
302
+ def fast_write_str(socket, str)
303
+ n = 0
304
+ byte_size = str.bytesize
305
+ while n < byte_size
306
+ begin
307
+ n += socket.write_nonblock(n.zero? ? str : str.byteslice(n..-1))
308
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
309
+ unless socket.wait_writable WRITE_TIMEOUT
310
+ raise ConnectionError, SOCKET_WRITE_ERR_MSG
311
+ end
312
+ retry
313
+ rescue Errno::EPIPE, SystemCallError, IOError
314
+ raise ConnectionError, SOCKET_WRITE_ERR_MSG
315
+ end
316
+ end
317
+ end
318
+
319
+ # Used to write headers and body.
320
+ # Writes to a socket (normally `Client#io`) using `#fast_write_str`.
321
+ # Accumulates `body` items into `io_buffer`, then writes to socket.
322
+ # @param socket [#write] the response socket
323
+ # @param body [Enumerable, File] the body object
324
+ # @param io_buffer [Puma::IOBuffer] contains headers
325
+ # @param chunked [Boolean]
326
+ # @paramn content_length [Integer
327
+ # @raise [ConnectionError]
328
+ #
329
+ def fast_write_response(socket, body, io_buffer, chunked, content_length)
330
+ if body.is_a?(::File) && body.respond_to?(:read)
331
+ if chunked # would this ever happen?
332
+ while chunk = body.read(BODY_LEN_MAX)
333
+ io_buffer.append chunk.bytesize.to_s(16), LINE_END, chunk, LINE_END
334
+ end
335
+ fast_write_str socket, CLOSE_CHUNKED
336
+ else
337
+ if content_length <= IO_BODY_MAX
338
+ io_buffer.write body.read(content_length)
339
+ fast_write_str socket, io_buffer.read_and_reset
340
+ else
341
+ fast_write_str socket, io_buffer.read_and_reset
342
+ IO.copy_stream body, socket
343
+ end
344
+ end
345
+ elsif body.is_a?(::Array) && body.length == 1
346
+ body_first = nil
347
+ # using body_first = body.first causes issues?
348
+ body.each { |str| body_first ||= str }
349
+
350
+ if body_first.is_a?(::String) && body_first.bytesize < BODY_LEN_MAX
351
+ # smaller body, write to io_buffer first
352
+ io_buffer.write body_first
353
+ fast_write_str socket, io_buffer.read_and_reset
354
+ else
355
+ # large body, write both header & body to socket
356
+ fast_write_str socket, io_buffer.read_and_reset
357
+ fast_write_str socket, body_first
358
+ end
359
+ elsif body.is_a?(::Array)
360
+ # for array bodies, flush io_buffer to socket when size is greater than
361
+ # IO_BUFFER_LEN_MAX
362
+ if chunked
363
+ body.each do |part|
364
+ next if (byte_size = part.bytesize).zero?
365
+ io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
366
+ if io_buffer.length > IO_BUFFER_LEN_MAX
367
+ fast_write_str socket, io_buffer.read_and_reset
368
+ end
369
+ end
370
+ io_buffer.write CLOSE_CHUNKED
371
+ else
372
+ body.each do |part|
373
+ next if part.bytesize.zero?
374
+ io_buffer.write part
375
+ if io_buffer.length > IO_BUFFER_LEN_MAX
376
+ fast_write_str socket, io_buffer.read_and_reset
377
+ end
378
+ end
379
+ end
380
+ # may write last body part for non-chunked, also headers if array is empty
381
+ fast_write_str(socket, io_buffer.read_and_reset) unless io_buffer.length.zero?
382
+ else
383
+ # for enum bodies
384
+ if chunked
385
+ empty_body = true
386
+ body.each do |part|
387
+ next if part.nil? || (byte_size = part.bytesize).zero?
388
+ empty_body = false
389
+ io_buffer.append byte_size.to_s(16), LINE_END, part, LINE_END
390
+ fast_write_str socket, io_buffer.read_and_reset
391
+ end
392
+ if empty_body
393
+ io_buffer << CLOSE_CHUNKED
394
+ fast_write_str socket, io_buffer.read_and_reset
395
+ else
396
+ fast_write_str socket, CLOSE_CHUNKED
397
+ end
398
+ else
399
+ fast_write_str socket, io_buffer.read_and_reset
400
+ body.each do |part|
401
+ next if part.bytesize.zero?
402
+ fast_write_str socket, part
403
+ end
404
+ end
405
+ end
406
+ socket.flush
407
+ rescue Errno::EAGAIN, Errno::EWOULDBLOCK
408
+ raise ConnectionError, SOCKET_WRITE_ERR_MSG
409
+ rescue Errno::EPIPE, SystemCallError, IOError
410
+ raise ConnectionError, SOCKET_WRITE_ERR_MSG
411
+ end
412
+
413
+ private :fast_write_str, :fast_write_response
414
+
415
+ # Given a Hash +env+ for the request read from +client+, add
416
+ # and fixup keys to comply with Rack's env guidelines.
417
+ # @param env [Hash] see Puma::Client#env, from request
418
+ # @param client [Puma::Client] only needed for Client#peerip
419
+ #
420
+ def normalize_env(env, client)
421
+ if host = env[HTTP_HOST]
422
+ # host can be a hostname, ipv4 or bracketed ipv6. Followed by an optional port.
423
+ if colon = host.rindex("]:") # IPV6 with port
424
+ env[SERVER_NAME] = host[0, colon+1]
425
+ env[SERVER_PORT] = host[colon+2, host.bytesize]
426
+ elsif !host.start_with?("[") && colon = host.index(":") # not hostname or IPV4 with port
427
+ env[SERVER_NAME] = host[0, colon]
428
+ env[SERVER_PORT] = host[colon+1, host.bytesize]
429
+ else
430
+ env[SERVER_NAME] = host
431
+ env[SERVER_PORT] = default_server_port(env)
432
+ end
433
+ else
434
+ env[SERVER_NAME] = LOCALHOST
435
+ env[SERVER_PORT] = default_server_port(env)
436
+ end
437
+
438
+ unless env[REQUEST_PATH]
439
+ # it might be a dumbass full host request header
440
+ uri = begin
441
+ URI.parse(env[REQUEST_URI])
442
+ rescue URI::InvalidURIError
443
+ raise Puma::HttpParserError
444
+ end
445
+ env[REQUEST_PATH] = uri.path
446
+
447
+ # A nil env value will cause a LintError (and fatal errors elsewhere),
448
+ # so only set the env value if there actually is a value.
449
+ env[QUERY_STRING] = uri.query if uri.query
450
+ end
451
+
452
+ env[PATH_INFO] = env[REQUEST_PATH].to_s # #to_s in case it's nil
453
+
454
+ # From https://www.ietf.org/rfc/rfc3875 :
455
+ # "Script authors should be aware that the REMOTE_ADDR and
456
+ # REMOTE_HOST meta-variables (see sections 4.1.8 and 4.1.9)
457
+ # may not identify the ultimate source of the request.
458
+ # They identify the client for the immediate request to the
459
+ # server; that client may be a proxy, gateway, or other
460
+ # intermediary acting on behalf of the actual source client."
461
+ #
462
+
463
+ unless env.key?(REMOTE_ADDR)
464
+ begin
465
+ addr = client.peerip
466
+ rescue Errno::ENOTCONN
467
+ # Client disconnects can result in an inability to get the
468
+ # peeraddr from the socket; default to unspec.
469
+ if client.peer_family == Socket::AF_INET6
470
+ addr = UNSPECIFIED_IPV6
471
+ else
472
+ addr = UNSPECIFIED_IPV4
473
+ end
474
+ end
475
+
476
+ # Set unix socket addrs to localhost
477
+ if addr.empty?
478
+ if client.peer_family == Socket::AF_INET6
479
+ addr = LOCALHOST_IPV6
480
+ else
481
+ addr = LOCALHOST_IPV4
482
+ end
483
+ end
484
+
485
+ env[REMOTE_ADDR] = addr
486
+ end
487
+
488
+ # The legacy HTTP_VERSION header can be sent as a client header.
489
+ # Rack v4 may remove using HTTP_VERSION. If so, remove this line.
490
+ env[HTTP_VERSION] = env[SERVER_PROTOCOL] if @env_set_http_version
491
+ end
492
+ private :normalize_env
493
+
494
+ # @param header_key [#to_s]
495
+ # @return [Boolean]
496
+ #
497
+ def illegal_header_key?(header_key)
498
+ !!(ILLEGAL_HEADER_KEY_REGEX =~ header_key.to_s)
499
+ end
500
+
501
+ # @param header_value [#to_s]
502
+ # @return [Boolean]
503
+ #
504
+ def illegal_header_value?(header_value)
505
+ !!(ILLEGAL_HEADER_VALUE_REGEX =~ header_value.to_s)
506
+ end
507
+ private :illegal_header_key?, :illegal_header_value?
508
+
509
+ # Fixup any headers with `,` in the name to have `_` now. We emit
510
+ # headers with `,` in them during the parse phase to avoid ambiguity
511
+ # with the `-` to `_` conversion for critical headers. But here for
512
+ # compatibility, we'll convert them back. This code is written to
513
+ # avoid allocation in the common case (ie there are no headers
514
+ # with `,` in their names), that's why it has the extra conditionals.
515
+ #
516
+ # @note If a normalized version of a `,` header already exists, we ignore
517
+ # the `,` version. This prevents clobbering headers managed by proxies
518
+ # but not by clients (Like X-Forwarded-For).
519
+ #
520
+ # @param env [Hash] see Puma::Client#env, from request, modifies in place
521
+ # @version 5.0.3
522
+ #
523
+ def req_env_post_parse(env)
524
+ to_delete = nil
525
+ to_add = nil
526
+
527
+ env.each do |k,v|
528
+ if k.start_with?("HTTP_") && k.include?(",") && !UNMASKABLE_HEADERS.key?(k)
529
+ if to_delete
530
+ to_delete << k
531
+ else
532
+ to_delete = [k]
533
+ end
534
+
535
+ new_k = k.tr(",", "_")
536
+ if env.key?(new_k)
537
+ next
538
+ end
539
+
540
+ unless to_add
541
+ to_add = {}
542
+ end
543
+
544
+ to_add[new_k] = v
545
+ end
546
+ end
547
+
548
+ if to_delete # rubocop:disable Style/SafeNavigation
549
+ to_delete.each { |k| env.delete(k) }
550
+ end
551
+
552
+ if to_add
553
+ env.merge! to_add
554
+ end
555
+ end
556
+ private :req_env_post_parse
557
+
558
+ # Used in the lambda for env[ `Puma::Const::EARLY_HINTS` ]
559
+ # @param headers [Hash] the headers returned by the Rack application
560
+ # @return [String]
561
+ # @version 5.0.3
562
+ #
563
+ def str_early_hints(headers)
564
+ eh_str = +""
565
+ headers.each_pair do |k, vs|
566
+ next if illegal_header_key?(k)
567
+
568
+ if vs.respond_to?(:to_s) && !vs.to_s.empty?
569
+ vs.to_s.split(NEWLINE).each do |v|
570
+ next if illegal_header_value?(v)
571
+ eh_str << "#{k}: #{v}\r\n"
572
+ end
573
+ elsif !(vs.to_s.empty? || !illegal_header_value?(vs))
574
+ eh_str << "#{k}: #{vs}\r\n"
575
+ end
576
+ end
577
+ eh_str.freeze
578
+ end
579
+ private :str_early_hints
580
+
581
+ # @param status [Integer] status from the app
582
+ # @return [String] the text description from Puma::HTTP_STATUS_CODES
583
+ #
584
+ def fetch_status_code(status)
585
+ HTTP_STATUS_CODES.fetch(status) { CUSTOM_STAT }
586
+ end
587
+ private :fetch_status_code
588
+
589
+ # Processes and write headers to the IOBuffer.
590
+ # @param env [Hash] see Puma::Client#env, from request
591
+ # @param status [Integer] the status returned by the Rack application
592
+ # @param headers [Hash] the headers returned by the Rack application
593
+ # @param content_length [Integer,nil] content length if it can be determined from the
594
+ # response body
595
+ # @param io_buffer [Puma::IOBuffer] modified inn place
596
+ # @param force_keep_alive [Boolean] 'anded' with keep_alive, based on system
597
+ # status and `@max_keep_alive`
598
+ # @return [Hash] resp_info
599
+ # @version 5.0.3
600
+ #
601
+ def str_headers(env, status, headers, res_body, io_buffer, force_keep_alive)
602
+
603
+ line_ending = LINE_END
604
+ colon = COLON
605
+
606
+ resp_info = {}
607
+ resp_info[:no_body] = env[REQUEST_METHOD] == HEAD
608
+
609
+ http_11 = env[SERVER_PROTOCOL] == HTTP_11
610
+ if http_11
611
+ resp_info[:allow_chunked] = true
612
+ resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase != CLOSE
613
+
614
+ # An optimization. The most common response is 200, so we can
615
+ # reply with the proper 200 status without having to compute
616
+ # the response header.
617
+ #
618
+ if status == 200
619
+ io_buffer << HTTP_11_200
620
+ else
621
+ io_buffer.append "#{HTTP_11} #{status} ", fetch_status_code(status), line_ending
622
+
623
+ resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
624
+ end
625
+ else
626
+ resp_info[:allow_chunked] = false
627
+ resp_info[:keep_alive] = env.fetch(HTTP_CONNECTION, "").downcase == KEEP_ALIVE
628
+
629
+ # Same optimization as above for HTTP/1.1
630
+ #
631
+ if status == 200
632
+ io_buffer << HTTP_10_200
633
+ else
634
+ io_buffer.append "HTTP/1.0 #{status} ",
635
+ fetch_status_code(status), line_ending
636
+
637
+ resp_info[:no_body] ||= status < 200 || STATUS_WITH_NO_ENTITY_BODY[status]
638
+ end
639
+ end
640
+
641
+ # regardless of what the client wants, we always close the connection
642
+ # if running without request queueing
643
+ resp_info[:keep_alive] &&= @queue_requests
644
+
645
+ # see prepare_response
646
+ resp_info[:keep_alive] &&= force_keep_alive
647
+
648
+ resp_info[:response_hijack] = nil
649
+
650
+ headers.each do |k, vs|
651
+ next if illegal_header_key?(k)
652
+
653
+ case k.downcase
654
+ when CONTENT_LENGTH2
655
+ next if illegal_header_value?(vs)
656
+ # nil.to_i is 0, nil&.to_i is nil
657
+ resp_info[:content_length] = vs&.to_i
658
+ next
659
+ when TRANSFER_ENCODING
660
+ resp_info[:allow_chunked] = false
661
+ resp_info[:content_length] = nil
662
+ resp_info[:transfer_encoding] = vs
663
+ when HIJACK
664
+ resp_info[:response_hijack] = vs
665
+ next
666
+ when BANNED_HEADER_KEY
667
+ next
668
+ end
669
+
670
+ ary = if vs.is_a?(::Array) && !vs.empty?
671
+ vs
672
+ elsif vs.respond_to?(:to_s) && !vs.to_s.empty?
673
+ vs.to_s.split NEWLINE
674
+ else
675
+ nil
676
+ end
677
+ if ary
678
+ ary.each do |v|
679
+ next if illegal_header_value?(v)
680
+ io_buffer.append k.downcase, colon, v, line_ending
681
+ end
682
+ else
683
+ io_buffer.append k.downcase, colon, line_ending
684
+ end
685
+ end
686
+
687
+ # HTTP/1.1 & 1.0 assume different defaults:
688
+ # - HTTP 1.0 assumes the connection will be closed if not specified
689
+ # - HTTP 1.1 assumes the connection will be kept alive if not specified.
690
+ # Only set the header if we're doing something which is not the default
691
+ # for this protocol version
692
+ if http_11
693
+ io_buffer << CONNECTION_CLOSE if !resp_info[:keep_alive]
694
+ else
695
+ io_buffer << CONNECTION_KEEP_ALIVE if resp_info[:keep_alive]
696
+ end
697
+ resp_info
698
+ end
699
+ private :str_headers
700
+ end
701
+ end