activesupport 7.1.6 → 8.1.1

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 (169) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +256 -1133
  3. data/README.rdoc +1 -1
  4. data/lib/active_support/array_inquirer.rb +1 -1
  5. data/lib/active_support/backtrace_cleaner.rb +81 -3
  6. data/lib/active_support/benchmark.rb +21 -0
  7. data/lib/active_support/benchmarkable.rb +3 -2
  8. data/lib/active_support/broadcast_logger.rb +65 -78
  9. data/lib/active_support/cache/file_store.rb +29 -14
  10. data/lib/active_support/cache/mem_cache_store.rb +42 -102
  11. data/lib/active_support/cache/memory_store.rb +11 -6
  12. data/lib/active_support/cache/null_store.rb +2 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +58 -46
  14. data/lib/active_support/cache/serializer_with_fallback.rb +0 -23
  15. data/lib/active_support/cache/strategy/local_cache.rb +72 -27
  16. data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
  17. data/lib/active_support/cache.rb +146 -86
  18. data/lib/active_support/callbacks.rb +102 -126
  19. data/lib/active_support/class_attribute.rb +33 -0
  20. data/lib/active_support/code_generator.rb +9 -0
  21. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
  22. data/lib/active_support/concurrency/share_lock.rb +0 -1
  23. data/lib/active_support/concurrency/thread_monitor.rb +55 -0
  24. data/lib/active_support/configurable.rb +34 -0
  25. data/lib/active_support/configuration_file.rb +15 -6
  26. data/lib/active_support/continuous_integration.rb +145 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +3 -5
  28. data/lib/active_support/core_ext/array.rb +7 -7
  29. data/lib/active_support/core_ext/benchmark.rb +4 -14
  30. data/lib/active_support/core_ext/big_decimal.rb +1 -1
  31. data/lib/active_support/core_ext/class/attribute.rb +26 -19
  32. data/lib/active_support/core_ext/class/subclasses.rb +15 -35
  33. data/lib/active_support/core_ext/class.rb +2 -2
  34. data/lib/active_support/core_ext/date/blank.rb +4 -0
  35. data/lib/active_support/core_ext/date/conversions.rb +2 -2
  36. data/lib/active_support/core_ext/date.rb +5 -5
  37. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -9
  38. data/lib/active_support/core_ext/date_time/blank.rb +4 -0
  39. data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
  40. data/lib/active_support/core_ext/date_time/conversions.rb +4 -6
  41. data/lib/active_support/core_ext/date_time.rb +5 -5
  42. data/lib/active_support/core_ext/digest/uuid.rb +6 -0
  43. data/lib/active_support/core_ext/digest.rb +1 -1
  44. data/lib/active_support/core_ext/enumerable.rb +25 -8
  45. data/lib/active_support/core_ext/erb/util.rb +10 -5
  46. data/lib/active_support/core_ext/file.rb +1 -1
  47. data/lib/active_support/core_ext/hash/deep_merge.rb +1 -0
  48. data/lib/active_support/core_ext/hash/except.rb +0 -12
  49. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  50. data/lib/active_support/core_ext/hash.rb +8 -8
  51. data/lib/active_support/core_ext/integer.rb +3 -3
  52. data/lib/active_support/core_ext/kernel.rb +3 -3
  53. data/lib/active_support/core_ext/module/attr_internal.rb +16 -6
  54. data/lib/active_support/core_ext/module/delegation.rb +20 -163
  55. data/lib/active_support/core_ext/module/deprecation.rb +1 -4
  56. data/lib/active_support/core_ext/module/introspection.rb +3 -0
  57. data/lib/active_support/core_ext/module.rb +11 -11
  58. data/lib/active_support/core_ext/numeric/conversions.rb +3 -3
  59. data/lib/active_support/core_ext/numeric.rb +3 -3
  60. data/lib/active_support/core_ext/object/blank.rb +45 -1
  61. data/lib/active_support/core_ext/object/instance_variables.rb +11 -19
  62. data/lib/active_support/core_ext/object/json.rb +24 -11
  63. data/lib/active_support/core_ext/object/to_query.rb +7 -1
  64. data/lib/active_support/core_ext/object/try.rb +2 -2
  65. data/lib/active_support/core_ext/object/with.rb +5 -3
  66. data/lib/active_support/core_ext/object.rb +13 -13
  67. data/lib/active_support/core_ext/pathname/blank.rb +4 -0
  68. data/lib/active_support/core_ext/pathname.rb +2 -2
  69. data/lib/active_support/core_ext/range/overlap.rb +4 -4
  70. data/lib/active_support/core_ext/range/sole.rb +17 -0
  71. data/lib/active_support/core_ext/range.rb +4 -4
  72. data/lib/active_support/core_ext/securerandom.rb +4 -4
  73. data/lib/active_support/core_ext/string/conversions.rb +1 -1
  74. data/lib/active_support/core_ext/string/filters.rb +4 -4
  75. data/lib/active_support/core_ext/string/multibyte.rb +13 -4
  76. data/lib/active_support/core_ext/string/output_safety.rb +19 -19
  77. data/lib/active_support/core_ext/string.rb +13 -13
  78. data/lib/active_support/core_ext/symbol.rb +1 -1
  79. data/lib/active_support/core_ext/thread/backtrace/location.rb +2 -7
  80. data/lib/active_support/core_ext/time/calculations.rb +25 -30
  81. data/lib/active_support/core_ext/time/compatibility.rb +2 -3
  82. data/lib/active_support/core_ext/time/conversions.rb +2 -2
  83. data/lib/active_support/core_ext/time/zones.rb +1 -1
  84. data/lib/active_support/core_ext/time.rb +5 -5
  85. data/lib/active_support/core_ext.rb +1 -2
  86. data/lib/active_support/current_attributes/test_helper.rb +2 -2
  87. data/lib/active_support/current_attributes.rb +58 -50
  88. data/lib/active_support/delegation.rb +200 -0
  89. data/lib/active_support/dependencies/autoload.rb +0 -12
  90. data/lib/active_support/dependencies/interlock.rb +11 -5
  91. data/lib/active_support/dependencies.rb +6 -2
  92. data/lib/active_support/deprecation/constant_accessor.rb +47 -26
  93. data/lib/active_support/deprecation/proxy_wrappers.rb +9 -12
  94. data/lib/active_support/deprecation/reporting.rb +5 -17
  95. data/lib/active_support/deprecation.rb +8 -5
  96. data/lib/active_support/descendants_tracker.rb +9 -87
  97. data/lib/active_support/duration/iso8601_parser.rb +2 -2
  98. data/lib/active_support/duration/iso8601_serializer.rb +1 -2
  99. data/lib/active_support/duration.rb +25 -16
  100. data/lib/active_support/editor.rb +70 -0
  101. data/lib/active_support/encrypted_configuration.rb +20 -2
  102. data/lib/active_support/encrypted_file.rb +1 -1
  103. data/lib/active_support/error_reporter.rb +121 -6
  104. data/lib/active_support/event_reporter/test_helper.rb +32 -0
  105. data/lib/active_support/event_reporter.rb +592 -0
  106. data/lib/active_support/evented_file_update_checker.rb +5 -3
  107. data/lib/active_support/execution_context.rb +64 -7
  108. data/lib/active_support/execution_wrapper.rb +1 -2
  109. data/lib/active_support/file_update_checker.rb +9 -7
  110. data/lib/active_support/fork_tracker.rb +2 -38
  111. data/lib/active_support/gem_version.rb +2 -2
  112. data/lib/active_support/gzip.rb +1 -0
  113. data/lib/active_support/hash_with_indifferent_access.rb +66 -45
  114. data/lib/active_support/html_safe_translation.rb +3 -0
  115. data/lib/active_support/i18n_railtie.rb +19 -11
  116. data/lib/active_support/inflector/inflections.rb +31 -15
  117. data/lib/active_support/inflector/transliterate.rb +6 -8
  118. data/lib/active_support/isolated_execution_state.rb +12 -17
  119. data/lib/active_support/json/decoding.rb +6 -4
  120. data/lib/active_support/json/encoding.rb +157 -21
  121. data/lib/active_support/lazy_load_hooks.rb +1 -1
  122. data/lib/active_support/log_subscriber.rb +2 -18
  123. data/lib/active_support/logger.rb +15 -2
  124. data/lib/active_support/logger_thread_safe_level.rb +4 -9
  125. data/lib/active_support/message_encryptors.rb +54 -2
  126. data/lib/active_support/message_pack/extensions.rb +20 -2
  127. data/lib/active_support/message_verifier.rb +21 -0
  128. data/lib/active_support/message_verifiers.rb +57 -3
  129. data/lib/active_support/messages/rotation_coordinator.rb +9 -0
  130. data/lib/active_support/messages/rotator.rb +10 -0
  131. data/lib/active_support/multibyte/chars.rb +14 -4
  132. data/lib/active_support/multibyte.rb +4 -0
  133. data/lib/active_support/notifications/fanout.rb +68 -50
  134. data/lib/active_support/notifications/instrumenter.rb +22 -19
  135. data/lib/active_support/notifications.rb +28 -27
  136. data/lib/active_support/number_helper/number_converter.rb +2 -2
  137. data/lib/active_support/number_helper.rb +22 -0
  138. data/lib/active_support/option_merger.rb +2 -2
  139. data/lib/active_support/ordered_options.rb +53 -15
  140. data/lib/active_support/railtie.rb +36 -20
  141. data/lib/active_support/string_inquirer.rb +1 -1
  142. data/lib/active_support/structured_event_subscriber.rb +99 -0
  143. data/lib/active_support/subscriber.rb +1 -5
  144. data/lib/active_support/syntax_error_proxy.rb +3 -0
  145. data/lib/active_support/tagged_logging.rb +5 -1
  146. data/lib/active_support/test_case.rb +63 -6
  147. data/lib/active_support/testing/assertions.rb +113 -27
  148. data/lib/active_support/testing/constant_stubbing.rb +30 -8
  149. data/lib/active_support/testing/deprecation.rb +5 -12
  150. data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
  151. data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
  152. data/lib/active_support/testing/isolation.rb +19 -9
  153. data/lib/active_support/testing/method_call_assertions.rb +2 -16
  154. data/lib/active_support/testing/notification_assertions.rb +92 -0
  155. data/lib/active_support/testing/parallelization/server.rb +18 -2
  156. data/lib/active_support/testing/parallelization/worker.rb +4 -2
  157. data/lib/active_support/testing/parallelization.rb +25 -1
  158. data/lib/active_support/testing/tests_without_assertions.rb +19 -0
  159. data/lib/active_support/testing/time_helpers.rb +11 -6
  160. data/lib/active_support/time_with_zone.rb +39 -26
  161. data/lib/active_support/values/time_zone.rb +26 -17
  162. data/lib/active_support/xml_mini.rb +14 -4
  163. data/lib/active_support.rb +22 -9
  164. metadata +31 -17
  165. data/lib/active_support/core_ext/range/each.rb +0 -24
  166. data/lib/active_support/deprecation/instance_delegator.rb +0 -65
  167. data/lib/active_support/proxy_object.rb +0 -17
  168. data/lib/active_support/ruby_features.rb +0 -7
  169. data/lib/active_support/testing/strict_warnings.rb +0 -39
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fiber"
4
-
5
3
  module ActiveSupport
6
4
  module IsolatedExecutionState # :nodoc:
7
5
  @isolation_level = nil
@@ -30,45 +28,42 @@ module ActiveSupport
30
28
  @isolation_level = level
31
29
  end
32
30
 
33
- def unique_id
34
- self[:__id__] ||= Object.new
35
- end
36
-
37
31
  def [](key)
38
- state[key]
32
+ if state = @scope.current.active_support_execution_state
33
+ state[key]
34
+ end
39
35
  end
40
36
 
41
37
  def []=(key, value)
38
+ state = (@scope.current.active_support_execution_state ||= {})
42
39
  state[key] = value
43
40
  end
44
41
 
45
42
  def key?(key)
46
- state.key?(key)
43
+ @scope.current.active_support_execution_state&.key?(key)
47
44
  end
48
45
 
49
46
  def delete(key)
50
- state.delete(key)
47
+ @scope.current.active_support_execution_state&.delete(key)
51
48
  end
52
49
 
53
50
  def clear
54
- state.clear
51
+ @scope.current.active_support_execution_state&.clear
55
52
  end
56
53
 
57
54
  def context
58
55
  scope.current
59
56
  end
60
57
 
61
- def share_with(other)
58
+ def share_with(other, &block)
62
59
  # Action Controller streaming spawns a new thread and copy thread locals.
63
60
  # We do the same here for backward compatibility, but this is very much a hack
64
61
  # and streaming should be rethought.
65
- context.active_support_execution_state = other.active_support_execution_state.dup
62
+ old_state, context.active_support_execution_state = context.active_support_execution_state, other.active_support_execution_state.dup
63
+ block.call
64
+ ensure
65
+ context.active_support_execution_state = old_state
66
66
  end
67
-
68
- private
69
- def state
70
- context.active_support_execution_state ||= {}
71
- end
72
67
  end
73
68
 
74
69
  self.isolation_level = :thread
@@ -14,13 +14,15 @@ module ActiveSupport
14
14
  DATETIME_REGEX = /\A(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)\z/
15
15
 
16
16
  class << self
17
- # Parses a JSON string (JavaScript Object Notation) into a hash.
17
+ # Parses a JSON string (JavaScript Object Notation) into a Ruby object.
18
18
  # See http://www.json.org for more info.
19
19
  #
20
20
  # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
21
- # => {"team" => "rails", "players" => "36"}
22
- def decode(json)
23
- data = ::JSON.parse(json, quirks_mode: true)
21
+ # # => {"team" => "rails", "players" => "36"}
22
+ # ActiveSupport::JSON.decode("2.39")
23
+ # # => 2.39
24
+ def decode(json, options = {})
25
+ data = ::JSON.parse(json, options)
24
26
 
25
27
  if ActiveSupport.parse_json_times
26
28
  convert_dates_from(data)
@@ -8,24 +8,70 @@ module ActiveSupport
8
8
  delegate :use_standard_json_time_format, :use_standard_json_time_format=,
9
9
  :time_precision, :time_precision=,
10
10
  :escape_html_entities_in_json, :escape_html_entities_in_json=,
11
+ :escape_js_separators_in_json, :escape_js_separators_in_json=,
11
12
  :json_encoder, :json_encoder=,
12
13
  to: :'ActiveSupport::JSON::Encoding'
13
14
  end
14
15
 
15
16
  module JSON
16
- # Dumps objects in JSON (JavaScript Object Notation).
17
- # See http://www.json.org for more info.
18
- #
19
- # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
20
- # # => "{\"team\":\"rails\",\"players\":\"36\"}"
21
17
  class << self
18
+ # Dumps objects in JSON (JavaScript Object Notation).
19
+ # See http://www.json.org for more info.
20
+ #
21
+ # ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
22
+ # # => "{\"team\":\"rails\",\"players\":\"36\"}"
23
+ #
24
+ # By default, it generates JSON that is safe to include in JavaScript, as
25
+ # it escapes U+2028 (Line Separator) and U+2029 (Paragraph Separator):
26
+ #
27
+ # ActiveSupport::JSON.encode({ key: "\u2028" })
28
+ # # => "{\"key\":\"\\u2028\"}"
29
+ #
30
+ # By default, it also generates JSON that is safe to include in HTML, as
31
+ # it escapes <tt><</tt>, <tt>></tt>, and <tt>&</tt>:
32
+ #
33
+ # ActiveSupport::JSON.encode({ key: "<>&" })
34
+ # # => "{\"key\":\"\\u003c\\u003e\\u0026\"}"
35
+ #
36
+ # This behavior can be changed with the +escape_html_entities+ option, or the
37
+ # global escape_html_entities_in_json configuration option.
38
+ #
39
+ # ActiveSupport::JSON.encode({ key: "<>&" }, escape_html_entities: false)
40
+ # # => "{\"key\":\"<>&\"}"
41
+ #
42
+ # For performance reasons, you can set the +escape+ option to false,
43
+ # which will skip all escaping:
44
+ #
45
+ # ActiveSupport::JSON.encode({ key: "\u2028<>&" }, escape: false)
46
+ # # => "{\"key\":\"\u2028<>&\"}"
22
47
  def encode(value, options = nil)
23
- Encoding.json_encoder.new(options).encode(value)
48
+ if options.nil? || options.empty?
49
+ Encoding.encode_without_options(value)
50
+ elsif options == { escape: false }.freeze
51
+ Encoding.encode_without_escape(value)
52
+ else
53
+ Encoding.json_encoder.new(options).encode(value)
54
+ end
24
55
  end
25
56
  alias_method :dump, :encode
26
57
  end
27
58
 
28
59
  module Encoding # :nodoc:
60
+ U2028 = -"\u2028".b
61
+ U2029 = -"\u2029".b
62
+
63
+ ESCAPED_CHARS = {
64
+ U2028 => '\u2028'.b,
65
+ U2029 => '\u2029'.b,
66
+ ">".b => '\u003e'.b,
67
+ "<".b => '\u003c'.b,
68
+ "&".b => '\u0026'.b,
69
+ }
70
+
71
+ HTML_ENTITIES_REGEX = Regexp.union(*(ESCAPED_CHARS.keys - [U2028, U2029]))
72
+ FULL_ESCAPE_REGEX = Regexp.union(*ESCAPED_CHARS.keys)
73
+ JS_SEPARATORS_REGEX = Regexp.union(U2028, U2029)
74
+
29
75
  class JSONGemEncoder # :nodoc:
30
76
  attr_reader :options
31
77
 
@@ -36,21 +82,23 @@ module ActiveSupport
36
82
  # Encode the given object into a JSON string
37
83
  def encode(value)
38
84
  unless options.empty?
39
- value = value.as_json(options.dup)
85
+ value = value.as_json(options.dup.freeze)
40
86
  end
41
87
  json = stringify(jsonify(value))
42
88
 
43
- # Rails does more escaping than the JSON gem natively does (we
44
- # escape \u2028 and \u2029 and optionally >, <, & to work around
45
- # certain browser problems).
46
- if Encoding.escape_html_entities_in_json
47
- json.gsub!(">", '\u003e')
48
- json.gsub!("<", '\u003c')
49
- json.gsub!("&", '\u0026')
89
+ return json unless @options.fetch(:escape, true)
90
+
91
+ json.force_encoding(::Encoding::BINARY)
92
+ if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
93
+ if Encoding.escape_js_separators_in_json
94
+ json.gsub!(FULL_ESCAPE_REGEX, ESCAPED_CHARS)
95
+ else
96
+ json.gsub!(HTML_ENTITIES_REGEX, ESCAPED_CHARS)
97
+ end
98
+ elsif Encoding.escape_js_separators_in_json
99
+ json.gsub!(JS_SEPARATORS_REGEX, ESCAPED_CHARS)
50
100
  end
51
- json.gsub!("\u2028", '\u2028')
52
- json.gsub!("\u2029", '\u2029')
53
- json
101
+ json.force_encoding(::Encoding::UTF_8)
54
102
  end
55
103
 
56
104
  private
@@ -83,14 +131,75 @@ module ActiveSupport
83
131
  when Array
84
132
  value.map { |v| jsonify(v) }
85
133
  else
86
- jsonify value.as_json
134
+ if defined?(::JSON::Fragment) && ::JSON::Fragment === value
135
+ value
136
+ else
137
+ jsonify value.as_json
138
+ end
87
139
  end
88
140
  end
89
141
 
90
142
  # Encode a "jsonified" Ruby data structure using the JSON gem
91
143
  def stringify(jsonified)
92
- ::JSON.generate(jsonified, quirks_mode: true, max_nesting: false)
144
+ ::JSON.generate(jsonified)
145
+ end
146
+ end
147
+
148
+ # ruby/json 2.14.x yields non-String keys but doesn't let us know it's a key
149
+ if defined?(::JSON::Coder) && Gem::Version.new(::JSON::VERSION) >= Gem::Version.new("2.15.2")
150
+ class JSONGemCoderEncoder # :nodoc:
151
+ JSON_NATIVE_TYPES = [Hash, Array, Float, String, Symbol, Integer, NilClass, TrueClass, FalseClass, ::JSON::Fragment].freeze
152
+ CODER = ::JSON::Coder.new do |value, is_key|
153
+ json_value = value.as_json
154
+ # Keep compatibility by calling to_s on non-String keys
155
+ next json_value.to_s if is_key
156
+ # Handle objects returning self from as_json
157
+ if json_value.equal?(value)
158
+ next ::JSON::Fragment.new(::JSON.generate(json_value))
159
+ end
160
+ # Handle objects not returning JSON-native types from as_json
161
+ count = 5
162
+ until JSON_NATIVE_TYPES.include?(json_value.class)
163
+ raise SystemStackError if count == 0
164
+ json_value = json_value.as_json
165
+ count -= 1
166
+ end
167
+ json_value
168
+ end
169
+
170
+
171
+ def initialize(options = nil)
172
+ if options
173
+ options = options.dup
174
+ @escape = options.delete(:escape) { true }
175
+ @options = options.freeze
176
+ else
177
+ @escape = true
178
+ @options = {}.freeze
179
+ end
93
180
  end
181
+
182
+ # Encode the given object into a JSON string
183
+ def encode(value)
184
+ value = value.as_json(@options) unless @options.empty?
185
+
186
+ json = CODER.dump(value)
187
+
188
+ return json unless @escape
189
+
190
+ json.force_encoding(::Encoding::BINARY)
191
+ if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
192
+ if Encoding.escape_js_separators_in_json
193
+ json.gsub!(FULL_ESCAPE_REGEX, ESCAPED_CHARS)
194
+ else
195
+ json.gsub!(HTML_ENTITIES_REGEX, ESCAPED_CHARS)
196
+ end
197
+ elsif Encoding.escape_js_separators_in_json
198
+ json.gsub!(JS_SEPARATORS_REGEX, ESCAPED_CHARS)
199
+ end
200
+ json.force_encoding(::Encoding::UTF_8)
201
+ end
202
+ end
94
203
  end
95
204
 
96
205
  class << self
@@ -102,18 +211,45 @@ module ActiveSupport
102
211
  # as a safety measure.
103
212
  attr_accessor :escape_html_entities_in_json
104
213
 
214
+ # If true, encode LINE SEPARATOR (U+2028) and PARAGRAPH SEPARATOR (U+2029)
215
+ # as escaped unicode sequences ('\u2028' and '\u2029').
216
+ # Historically these characters were not valid inside JavaScript strings
217
+ # but that changed in ECMAScript 2019. As such it's no longer a concern in
218
+ # modern browsers: https://caniuse.com/mdn-javascript_builtins_json_json_superset.
219
+ attr_accessor :escape_js_separators_in_json
220
+
105
221
  # Sets the precision of encoded time values.
106
222
  # Defaults to 3 (equivalent to millisecond precision)
107
223
  attr_accessor :time_precision
108
224
 
109
225
  # Sets the encoder used by \Rails to encode Ruby objects into JSON strings
110
226
  # in +Object#to_json+ and +ActiveSupport::JSON.encode+.
111
- attr_accessor :json_encoder
227
+ attr_reader :json_encoder
228
+
229
+ def json_encoder=(encoder)
230
+ @json_encoder = encoder
231
+ @encoder_without_options = encoder.new
232
+ @encoder_without_escape = encoder.new(escape: false)
233
+ end
234
+
235
+ def encode_without_options(value) # :nodoc:
236
+ @encoder_without_options.encode(value)
237
+ end
238
+
239
+ def encode_without_escape(value) # :nodoc:
240
+ @encoder_without_escape.encode(value)
241
+ end
112
242
  end
113
243
 
114
244
  self.use_standard_json_time_format = true
115
245
  self.escape_html_entities_in_json = true
116
- self.json_encoder = JSONGemEncoder
246
+ self.escape_js_separators_in_json = true
247
+ self.json_encoder =
248
+ if defined?(JSONGemCoderEncoder)
249
+ JSONGemCoderEncoder
250
+ else
251
+ JSONGemEncoder
252
+ end
117
253
  self.time_precision = 3
118
254
  end
119
255
  end
@@ -53,7 +53,7 @@ module ActiveSupport
53
53
  # loaded. If the component has already loaded, the block is executed
54
54
  # immediately.
55
55
  #
56
- # Options:
56
+ # ==== Options
57
57
  #
58
58
  # * <tt>:yield</tt> - Yields the object that run_load_hooks to +block+.
59
59
  # * <tt>:run_once</tt> - Given +block+ will run only once.
@@ -62,10 +62,6 @@ module ActiveSupport
62
62
  # that all logs are flushed, and it is called in Rails::Rack::Logger after a
63
63
  # request finishes.
64
64
  class LogSubscriber < Subscriber
65
- # Embed in a String to clear all previous ANSI sequences.
66
- CLEAR = ActiveSupport::Deprecation::DeprecatedObjectProxy.new("\e[0m", "CLEAR is deprecated! Use MODES[:clear] instead.", ActiveSupport.deprecator)
67
- BOLD = ActiveSupport::Deprecation::DeprecatedObjectProxy.new("\e[1m", "BOLD is deprecated! Use MODES[:bold] instead.", ActiveSupport.deprecator)
68
-
69
65
  # ANSI sequence modes
70
66
  MODES = {
71
67
  clear: 0,
@@ -153,12 +149,6 @@ module ActiveSupport
153
149
  log_exception(event.name, e)
154
150
  end
155
151
 
156
- def publish_event(event)
157
- super if logger
158
- rescue => e
159
- log_exception(event.name, e)
160
- end
161
-
162
152
  attr_writer :event_levels # :nodoc:
163
153
 
164
154
  private
@@ -182,20 +172,14 @@ module ActiveSupport
182
172
  end
183
173
 
184
174
  def mode_from(options)
185
- if options.is_a?(TrueClass) || options.is_a?(FalseClass)
186
- ActiveSupport.deprecator.warn(<<~MSG.squish)
187
- Bolding log text with a positional boolean is deprecated and will be removed
188
- in Rails 7.2. Use an option hash instead (eg. `color("my text", :red, bold: true)`).
189
- MSG
190
- options = { bold: options }
191
- end
192
-
193
175
  modes = MODES.values_at(*options.compact_blank.keys)
194
176
 
195
177
  "\e[#{modes.join(";")}m" if modes.any?
196
178
  end
197
179
 
198
180
  def log_exception(name, e)
181
+ ActiveSupport.error_reporter.report(e, source: name)
182
+
199
183
  if logger
200
184
  logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
201
185
  end
@@ -13,6 +13,10 @@ module ActiveSupport
13
13
  # logger = Logger.new(STDOUT)
14
14
  # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
15
15
  # # => true
16
+ #
17
+ # logger = Logger.new('/var/log/rails.log')
18
+ # ActiveSupport::Logger.logger_outputs_to?(logger, '/var/log/rails.log')
19
+ # # => true
16
20
  def self.logger_outputs_to?(logger, *sources)
17
21
  loggers = if logger.is_a?(BroadcastLogger)
18
22
  logger.broadcasts
@@ -21,9 +25,9 @@ module ActiveSupport
21
25
  end
22
26
 
23
27
  logdevs = loggers.map { |logger| logger.instance_variable_get(:@logdev) }
24
- logger_sources = logdevs.filter_map { |logdev| logdev.dev if logdev.respond_to?(:dev) }
28
+ logger_sources = logdevs.filter_map { |logdev| logdev.try(:filename) || logdev.try(:dev) }
25
29
 
26
- (sources & logger_sources).any?
30
+ normalize_sources(sources).intersect?(normalize_sources(logger_sources))
27
31
  end
28
32
 
29
33
  def initialize(*args, **kwargs)
@@ -38,5 +42,14 @@ module ActiveSupport
38
42
  "#{String === msg ? msg : msg.inspect}\n"
39
43
  end
40
44
  end
45
+
46
+ private
47
+ def self.normalize_sources(sources)
48
+ sources.map do |source|
49
+ source = source.path if source.respond_to?(:path)
50
+ source = File.realpath(source) if source.is_a?(String) && File.exist?(source)
51
+ source
52
+ end
53
+ end
41
54
  end
42
55
  end
@@ -7,12 +7,9 @@ module ActiveSupport
7
7
  module LoggerThreadSafeLevel # :nodoc:
8
8
  extend ActiveSupport::Concern
9
9
 
10
- Logger::Severity.constants.each do |severity|
11
- class_eval(<<-EOT, __FILE__, __LINE__ + 1)
12
- def #{severity.downcase}? # def debug?
13
- Logger::#{severity} >= level # DEBUG >= level
14
- end # end
15
- EOT
10
+ def initialize(...)
11
+ super
12
+ @local_level_key = :"logger_thread_safe_level_#{object_id}"
16
13
  end
17
14
 
18
15
  def local_level
@@ -48,8 +45,6 @@ module ActiveSupport
48
45
  end
49
46
 
50
47
  private
51
- def local_level_key
52
- @local_level_key ||= :"logger_thread_safe_level_#{object_id}"
53
- end
48
+ attr_reader :local_level_key
54
49
  end
55
50
  end
@@ -26,10 +26,13 @@ module ActiveSupport
26
26
  # as the first rotation and <tt>transitional = true</tt>. Then, after all
27
27
  # servers have been updated, perform a second rolling deploy with
28
28
  # <tt>transitional = false</tt>.
29
+ #
30
+ #--
31
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#transitional
29
32
 
30
33
  ##
31
- # :method: initialize
32
- # :call-seq: initialize(&secret_generator)
34
+ # :singleton-method: new
35
+ # :call-seq: new(&secret_generator)
33
36
  #
34
37
  # Initializes a new instance. +secret_generator+ must accept a salt and a
35
38
  # +secret_length+ kwarg, and return a suitable secret (string) or secrets
@@ -43,6 +46,9 @@ module ActiveSupport
43
46
  # end
44
47
  #
45
48
  # encryptors.rotate(base: "...")
49
+ #
50
+ #--
51
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#initialize
46
52
 
47
53
  ##
48
54
  # :method: []
@@ -51,12 +57,18 @@ module ActiveSupport
51
57
  # Returns a MessageEncryptor configured with a secret derived from the
52
58
  # given +salt+, and options from #rotate. MessageEncryptor instances will
53
59
  # be memoized, so the same +salt+ will return the same instance.
60
+ #
61
+ #--
62
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]
54
63
 
55
64
  ##
56
65
  # :method: []=
57
66
  # :call-seq: []=(salt, encryptor)
58
67
  #
59
68
  # Overrides a MessageEncryptor instance associated with a given +salt+.
69
+ #
70
+ #--
71
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#[]=
60
72
 
61
73
  ##
62
74
  # :method: rotate
@@ -106,18 +118,55 @@ module ActiveSupport
106
118
  #
107
119
  # # Uses `serializer: Marshal, url_safe: false`.
108
120
  # encryptors[:baz]
121
+ #
122
+ #--
123
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate
124
+
125
+ ##
126
+ # :method: prepend
127
+ # :call-seq:
128
+ # prepend(**options)
129
+ # prepend(&block)
130
+ #
131
+ # Just like #rotate, but prepends the given options or block to the list of
132
+ # option sets.
133
+ #
134
+ # This can be useful when you have an already-configured +MessageEncryptors+
135
+ # instance, but you want to override the way messages are encrypted.
136
+ #
137
+ # module ThirdParty
138
+ # ENCRYPTORS = ActiveSupport::MessageEncryptors.new { ... }.
139
+ # rotate(serializer: Marshal, url_safe: true).
140
+ # rotate(serializer: Marshal, url_safe: false)
141
+ # end
142
+ #
143
+ # ThirdParty.ENCRYPTORS.prepend(serializer: JSON, url_safe: true)
144
+ #
145
+ # # Uses `serializer: JSON, url_safe: true`.
146
+ # # Falls back to `serializer: Marshal, url_safe: true` or
147
+ # # `serializer: Marshal, url_safe: false`.
148
+ # ThirdParty.ENCRYPTORS[:foo]
149
+ #
150
+ #--
151
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#prepend
109
152
 
110
153
  ##
111
154
  # :method: rotate_defaults
112
155
  # :call-seq: rotate_defaults
113
156
  #
114
157
  # Invokes #rotate with the default options.
158
+ #
159
+ #--
160
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#rotate_defaults
115
161
 
116
162
  ##
117
163
  # :method: clear_rotations
118
164
  # :call-seq: clear_rotations
119
165
  #
120
166
  # Clears the list of option sets.
167
+ #
168
+ #--
169
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#clear_rotations
121
170
 
122
171
  ##
123
172
  # :method: on_rotation
@@ -129,6 +178,9 @@ module ActiveSupport
129
178
  # For example, this callback could log each time it is called, and thus
130
179
  # indicate whether old option sets are still in use or can be removed from
131
180
  # rotation.
181
+ #
182
+ #--
183
+ # Implemented by ActiveSupport::Messages::RotationCoordinator#on_rotation
132
184
 
133
185
  ##
134
186
  private
@@ -7,6 +7,7 @@ require "pathname"
7
7
  require "uri/generic"
8
8
  require "msgpack/bigint"
9
9
  require "active_support/hash_with_indifferent_access"
10
+ require "active_support/core_ext/string/output_safety"
10
11
  require "active_support/time"
11
12
 
12
13
  module ActiveSupport
@@ -86,8 +87,9 @@ module ActiveSupport
86
87
  unpacker: URI.method(:parse)
87
88
 
88
89
  registry.register_type 14, IPAddr,
89
- packer: :to_s,
90
- unpacker: :new
90
+ packer: method(:write_ipaddr),
91
+ unpacker: method(:read_ipaddr),
92
+ recursive: true
91
93
 
92
94
  registry.register_type 15, Pathname,
93
95
  packer: :to_s,
@@ -101,6 +103,10 @@ module ActiveSupport
101
103
  packer: method(:write_hash_with_indifferent_access),
102
104
  unpacker: method(:read_hash_with_indifferent_access),
103
105
  recursive: true
106
+
107
+ registry.register_type 18, ActiveSupport::SafeBuffer,
108
+ packer: :to_s,
109
+ unpacker: :new
104
110
  end
105
111
 
106
112
  def install_unregistered_type_error(registry)
@@ -221,6 +227,18 @@ module ActiveSupport
221
227
  Set.new(unpacker.read)
222
228
  end
223
229
 
230
+ def write_ipaddr(ipaddr, packer)
231
+ if ipaddr.prefix < 32 || (ipaddr.ipv6? && ipaddr.prefix < 128)
232
+ packer.write("#{ipaddr}/#{ipaddr.prefix}")
233
+ else
234
+ packer.write(ipaddr.to_s)
235
+ end
236
+ end
237
+
238
+ def read_ipaddr(unpacker)
239
+ IPAddr.new(unpacker.read)
240
+ end
241
+
224
242
  def write_hash_with_indifferent_access(hwia, packer)
225
243
  packer.write(hwia.to_h)
226
244
  end
@@ -30,6 +30,18 @@ module ActiveSupport
30
30
  # self.current_user = User.find(id)
31
31
  # end
32
32
  #
33
+ # === Signing is not encryption
34
+ #
35
+ # The signed messages are not encrypted. The payload is merely encoded (Base64 by default) and can be decoded by
36
+ # anyone. The signature is just assuring that the message wasn't tampered with. For example:
37
+ #
38
+ # message = Rails.application.message_verifier('my_purpose').generate('never put secrets here')
39
+ # # => "BAhJIhtuZXZlciBwdXQgc2VjcmV0cyBoZXJlBjoGRVQ=--a0c1c0827919da5e949e989c971249355735e140"
40
+ # Base64.decode64(message.split("--").first) # no key needed
41
+ # # => 'never put secrets here'
42
+ #
43
+ # If you also need to encrypt the contents, you must use ActiveSupport::MessageEncryptor instead.
44
+ #
33
45
  # === Confine messages to a specific purpose
34
46
  #
35
47
  # It's not recommended to use the same verifier for different purposes in your application.
@@ -142,6 +154,8 @@ module ActiveSupport
142
154
  # not URL-safe. In other words, they can contain "+" and "/". If you want to
143
155
  # generate URL-safe strings (in compliance with "Base 64 Encoding with URL
144
156
  # and Filename Safe Alphabet" in RFC 4648), you can pass +true+.
157
+ # Note that MessageVerifier will always accept both URL-safe and URL-unsafe
158
+ # encoded messages, to allow a smooth transition between the two settings.
145
159
  #
146
160
  # [+:force_legacy_metadata_serializer+]
147
161
  # Whether to use the legacy metadata serializer, which serializes the
@@ -306,6 +320,13 @@ module ActiveSupport
306
320
  end
307
321
 
308
322
  private
323
+ def decode(encoded, url_safe: @url_safe)
324
+ catch :invalid_message_format do
325
+ return super
326
+ end
327
+ super(encoded, url_safe: !url_safe)
328
+ end
329
+
309
330
  def sign_encoded(encoded)
310
331
  digest = generate_digest(encoded)
311
332
  encoded << SEPARATOR << digest