rails-profiler 0.34.4 → 0.34.5

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d4912dd474a748e657c35111e3224d83500e75960ef2592e5cd652155dc62617
4
- data.tar.gz: d0d4cb0f34f25d98f5be08875bdc59ec4cc84894f7cbe0b512fd1ecd153dda4b
3
+ metadata.gz: 0b498ec1274af5787fe96b70e64613b009298b578a7b084d65d450ec86a431bc
4
+ data.tar.gz: b575d9afef090eee5539cfca2ffd536887d90b4c08a3b5099504a8e31ca69eab
5
5
  SHA512:
6
- metadata.gz: 3ca18ad3dd582e68c2bb935a5ace8628dc466a4999fa655e54117879548d543c5a291ec98765eb25456b0ff8fa774ee0f98f939544a346aa9b3368d37c421f72
7
- data.tar.gz: e144011d2fd93d37aa3166748bf2d3671d8d246325a475cf13d756b188dd99999ef6f95b232fd36eca444c91e24e2715c1902695697ea1930407bc932b2e952d
6
+ metadata.gz: f7da413fad8fd79420dddc04242b1ce30c05aac1106073219b7c0dcda26dada8c59a1b8e0d6a6c3c403d92ee6cd519271285d5f99fe6670b0e970179f79be119
7
+ data.tar.gz: d36516568229d7fa228dad51b70a30e8ca2753cc07fa114279f322200d32bd9a4f5e02891ff0ea96b0c904dc1594d528f677129b840fe0acadd2f106f29ea9cc
data/CHANGELOG.md CHANGED
@@ -19,6 +19,22 @@ every commit of every tag interval is accounted for one way or the other.
19
19
 
20
20
  ## [Unreleased]
21
21
 
22
+ ## [0.34.5] - 2026-10-16
23
+
24
+ <!-- stamped -->
25
+
26
+ ### Fixed
27
+
28
+ - **Request capture:** the parameters of a request are now read once the application has returned, so
29
+ route parameters are present and `param_encoding` is applied, and they are converted to UTF-8. They
30
+ used to be read in the outermost middleware, before the router ran.
31
+ - **Request capture:** a text body keeps its accents. It is now read in the `charset` of its
32
+ `Content-Type`, UTF-8 when it declares none, and invalid bytes are replaced, instead of every
33
+ accent being turned into a replacement character.
34
+ - **Request capture:** a request whose `Content-Type` declares a charset Ruby cannot convert from
35
+ (`charset=utf-7`, `charset=internal`) no longer loses its profile; the body is read as UTF-8 with
36
+ invalid bytes replaced.
37
+
22
38
  ## [0.34.4] - 2026-10-16
23
39
 
24
40
  <!-- stamped -->
@@ -28,7 +28,7 @@ module Profiler
28
28
  kept = nil
29
29
  begin
30
30
  begin
31
- profile = Models::Profile.new(build_request(env))
31
+ profile = Models::Profile.new(build_request(env), params: false)
32
32
  profile.gem_version = Profiler::VERSION
33
33
  Profiler::CurrentContext.token = profile.token
34
34
 
@@ -57,10 +57,12 @@ module Profiler
57
57
  begin
58
58
  response = @app.call(env)
59
59
  rescue Exception => e # rubocop:disable Lint/RescueException
60
+ capture_params(env, profile) if failed_request?(e)
60
61
  record_failed_request(env, profile, collectors, request_body, allocations_before, e) if failed_request?(e)
61
62
  raise
62
63
  end
63
64
 
65
+ capture_params(env, profile)
64
66
  response, kept = complete_profile(env, profile, collectors, request_body, allocations_before, response)
65
67
  response
66
68
  ensure
@@ -74,6 +76,14 @@ module Profiler
74
76
 
75
77
  private
76
78
 
79
+ # Read after the application has run, so after the routing: the route parameters are there,
80
+ # and the params carry the encoding the route declared, as the controller saw them.
81
+ def capture_params(env, profile)
82
+ profile.capture_params(build_request(env))
83
+ rescue => e
84
+ Profiler.log_error("ProfilerMiddleware: could not read the request params", e)
85
+ end
86
+
77
87
  def complete_profile(env, profile, collectors, request_body, allocations_before, response)
78
88
  status, headers, body = response
79
89
 
@@ -22,7 +22,9 @@ module Profiler
22
22
  :collectors_released_after_seconds, :params_truncated,
23
23
  :gem_version
24
24
 
25
- def initialize(request = nil)
25
+ # +params+: false leaves the params to capture_params, for a caller that builds the profile
26
+ # before the router has run.
27
+ def initialize(request = nil, params: true)
26
28
  @token = SecureRandom.hex(16)
27
29
  @started_at = Time.now
28
30
  @collectors_data = {}
@@ -34,12 +36,19 @@ module Profiler
34
36
  if request
35
37
  @path = request.path
36
38
  @method = request.request_method
37
- @params, @params_truncated = cap_params(sanitize_params(request.params),
38
- Profiler.configuration.max_captured_body_bytes)
39
+ capture_params(request) if params
39
40
  @headers = extract_headers(request.env)
40
41
  end
41
42
  end
42
43
 
44
+ # The request's params as the controller sees them. Read once the router has run: Rails
45
+ # adds the route parameters (:id...) and applies the routes' param_encoding only then, so
46
+ # a read before it keeps neither.
47
+ def capture_params(request)
48
+ @params, @params_truncated = cap_params(sanitize_params(utf8_params(request.params)),
49
+ Profiler.configuration.max_captured_body_bytes)
50
+ end
51
+
43
52
  # The sizes are those of the whole bodies, when a body was cut at max_captured_body_bytes;
44
53
  # *_size_is_minimum when the whole size is not known (no Content-Length, a stream that
45
54
  # stopped), only that it is at least that.
@@ -252,7 +261,7 @@ module Profiler
252
261
  # compressed format (zip, png, gzip...) does not hold the secret as it is.
253
262
  { body: Base64.strict_encode64(Redaction.hide_credentials(raw.b)), encoding: "base64" }
254
263
  else
255
- text = Redaction.filter_body(raw, content_type).encode("UTF-8", invalid: :replace, undef: :replace)
264
+ text = utf8_text(Redaction.filter_body(raw, content_type), content_type)
256
265
  if compress_body?(text)
257
266
  # The fastest level: paid on the request, a quarter larger than the default level.
258
267
  { body: Base64.strict_encode64(Zlib::Deflate.deflate(text, Zlib::BEST_SPEED)), encoding: "gzip+base64" }
@@ -262,6 +271,39 @@ module Profiler
262
271
  end
263
272
  end
264
273
 
274
+ # The text as UTF-8: a body read from the wire is binary, and is taken for the charset its
275
+ # Content-Type declares (UTF-8 when it declares none, or one Ruby does not know). Bytes
276
+ # that do not belong to it are replaced, never raised on.
277
+ def utf8_text(text, content_type)
278
+ return text.encode("UTF-8", invalid: :replace, undef: :replace) unless text.encoding == Encoding::BINARY
279
+
280
+ charset = content_type.to_s[/charset\s*=\s*"?([^\s";]+)/i, 1]
281
+ source = begin
282
+ charset ? Encoding.find(charset) : Encoding::UTF_8
283
+ rescue ArgumentError
284
+ Encoding::UTF_8
285
+ end
286
+ # "internal" is a name Ruby knows but resolves to nil, "utf-7" is a dummy encoding with no converter.
287
+ source ||= Encoding::UTF_8
288
+ begin
289
+ text.dup.force_encoding(source).encode("UTF-8", invalid: :replace, undef: :replace)
290
+ rescue EncodingError
291
+ text.dup.force_encoding(Encoding::UTF_8).encode("UTF-8", invalid: :replace, undef: :replace)
292
+ end
293
+ end
294
+
295
+ # The params with their strings in UTF-8: a route's param_encoding gives some another
296
+ # encoding, which the profile must not carry into its JSON.
297
+ def utf8_params(value)
298
+ case value
299
+ when Hash then value.to_h { |k, v| [utf8_params(k), utf8_params(v)] }
300
+ when Array then value.map { |v| utf8_params(v) }
301
+ when String
302
+ value.encoding == Encoding::UTF_8 ? value : value.encode("UTF-8", invalid: :replace, undef: :replace)
303
+ else value
304
+ end
305
+ end
306
+
265
307
  def body_size(captured, total)
266
308
  captured_size = captured.to_s.bytesize
267
309
  total ||= captured_size
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Profiler
4
- VERSION = "0.34.4"
4
+ VERSION = "0.34.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.34.4
4
+ version: 0.34.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sébastien Duplessy