activesupport 6.1.7.2 → 7.0.6

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (183) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +272 -501
  3. data/README.rdoc +2 -2
  4. data/lib/active_support/actionable_error.rb +1 -1
  5. data/lib/active_support/array_inquirer.rb +0 -2
  6. data/lib/active_support/backtrace_cleaner.rb +2 -2
  7. data/lib/active_support/benchmarkable.rb +2 -2
  8. data/lib/active_support/cache/file_store.rb +15 -9
  9. data/lib/active_support/cache/mem_cache_store.rb +148 -37
  10. data/lib/active_support/cache/memory_store.rb +24 -16
  11. data/lib/active_support/cache/null_store.rb +10 -2
  12. data/lib/active_support/cache/redis_cache_store.rb +59 -78
  13. data/lib/active_support/cache/strategy/local_cache.rb +38 -61
  14. data/lib/active_support/cache.rb +299 -147
  15. data/lib/active_support/callbacks.rb +184 -85
  16. data/lib/active_support/code_generator.rb +65 -0
  17. data/lib/active_support/concern.rb +5 -5
  18. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  19. data/lib/active_support/concurrency/share_lock.rb +2 -2
  20. data/lib/active_support/configurable.rb +8 -5
  21. data/lib/active_support/configuration_file.rb +1 -1
  22. data/lib/active_support/core_ext/array/access.rb +1 -5
  23. data/lib/active_support/core_ext/array/conversions.rb +13 -12
  24. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  25. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  26. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  27. data/lib/active_support/core_ext/array.rb +1 -0
  28. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  29. data/lib/active_support/core_ext/class/subclasses.rb +25 -17
  30. data/lib/active_support/core_ext/date/blank.rb +1 -1
  31. data/lib/active_support/core_ext/date/calculations.rb +24 -9
  32. data/lib/active_support/core_ext/date/conversions.rb +14 -14
  33. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  34. data/lib/active_support/core_ext/date.rb +1 -0
  35. data/lib/active_support/core_ext/date_and_time/calculations.rb +4 -4
  36. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  37. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  38. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  39. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  40. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  41. data/lib/active_support/core_ext/date_time.rb +1 -0
  42. data/lib/active_support/core_ext/digest/uuid.rb +39 -14
  43. data/lib/active_support/core_ext/enumerable.rb +106 -37
  44. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  45. data/lib/active_support/core_ext/hash/conversions.rb +0 -1
  46. data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
  47. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  48. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  49. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  50. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  51. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  52. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
  53. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
  54. data/lib/active_support/core_ext/module/delegation.rb +2 -8
  55. data/lib/active_support/core_ext/name_error.rb +2 -8
  56. data/lib/active_support/core_ext/numeric/conversions.rb +80 -77
  57. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  58. data/lib/active_support/core_ext/numeric.rb +1 -0
  59. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  60. data/lib/active_support/core_ext/object/blank.rb +2 -2
  61. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  62. data/lib/active_support/core_ext/object/duplicable.rb +15 -4
  63. data/lib/active_support/core_ext/object/json.rb +30 -25
  64. data/lib/active_support/core_ext/object/to_query.rb +2 -4
  65. data/lib/active_support/core_ext/object/try.rb +20 -20
  66. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  67. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  68. data/lib/active_support/core_ext/pathname.rb +3 -0
  69. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  70. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  71. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  72. data/lib/active_support/core_ext/range/each.rb +1 -1
  73. data/lib/active_support/core_ext/range/include_time_with_zone.rb +3 -26
  74. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  75. data/lib/active_support/core_ext/range.rb +1 -1
  76. data/lib/active_support/core_ext/securerandom.rb +1 -1
  77. data/lib/active_support/core_ext/string/conversions.rb +2 -2
  78. data/lib/active_support/core_ext/string/filters.rb +1 -1
  79. data/lib/active_support/core_ext/string/inflections.rb +1 -5
  80. data/lib/active_support/core_ext/string/inquiry.rb +1 -1
  81. data/lib/active_support/core_ext/string/output_safety.rb +66 -38
  82. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  83. data/lib/active_support/core_ext/time/calculations.rb +11 -8
  84. data/lib/active_support/core_ext/time/conversions.rb +13 -12
  85. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  86. data/lib/active_support/core_ext/time/zones.rb +10 -26
  87. data/lib/active_support/core_ext/time.rb +1 -0
  88. data/lib/active_support/core_ext/uri.rb +3 -27
  89. data/lib/active_support/core_ext.rb +1 -0
  90. data/lib/active_support/current_attributes.rb +31 -15
  91. data/lib/active_support/dependencies/interlock.rb +10 -18
  92. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  93. data/lib/active_support/dependencies.rb +58 -788
  94. data/lib/active_support/deprecation/behaviors.rb +8 -5
  95. data/lib/active_support/deprecation/disallowed.rb +3 -3
  96. data/lib/active_support/deprecation/method_wrappers.rb +3 -3
  97. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  98. data/lib/active_support/deprecation.rb +2 -2
  99. data/lib/active_support/descendants_tracker.rb +174 -68
  100. data/lib/active_support/digest.rb +4 -4
  101. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  102. data/lib/active_support/duration/iso8601_serializer.rb +9 -1
  103. data/lib/active_support/duration.rb +77 -48
  104. data/lib/active_support/encrypted_configuration.rb +45 -3
  105. data/lib/active_support/encrypted_file.rb +13 -1
  106. data/lib/active_support/environment_inquirer.rb +1 -1
  107. data/lib/active_support/error_reporter.rb +117 -0
  108. data/lib/active_support/evented_file_update_checker.rb +17 -6
  109. data/lib/active_support/execution_context/test_helper.rb +13 -0
  110. data/lib/active_support/execution_context.rb +53 -0
  111. data/lib/active_support/execution_wrapper.rb +30 -11
  112. data/lib/active_support/executor/test_helper.rb +7 -0
  113. data/lib/active_support/fork_tracker.rb +19 -12
  114. data/lib/active_support/gem_version.rb +5 -5
  115. data/lib/active_support/hash_with_indifferent_access.rb +3 -1
  116. data/lib/active_support/html_safe_translation.rb +43 -0
  117. data/lib/active_support/i18n.rb +1 -0
  118. data/lib/active_support/i18n_railtie.rb +1 -1
  119. data/lib/active_support/inflector/inflections.rb +23 -7
  120. data/lib/active_support/inflector/methods.rb +28 -53
  121. data/lib/active_support/inflector/transliterate.rb +1 -1
  122. data/lib/active_support/isolated_execution_state.rb +72 -0
  123. data/lib/active_support/json/encoding.rb +3 -3
  124. data/lib/active_support/key_generator.rb +22 -5
  125. data/lib/active_support/lazy_load_hooks.rb +28 -4
  126. data/lib/active_support/locale/en.yml +1 -1
  127. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  128. data/lib/active_support/log_subscriber.rb +15 -5
  129. data/lib/active_support/logger_thread_safe_level.rb +4 -13
  130. data/lib/active_support/message_encryptor.rb +12 -6
  131. data/lib/active_support/message_verifier.rb +46 -14
  132. data/lib/active_support/messages/metadata.rb +2 -2
  133. data/lib/active_support/multibyte/chars.rb +10 -11
  134. data/lib/active_support/multibyte/unicode.rb +0 -12
  135. data/lib/active_support/multibyte.rb +1 -1
  136. data/lib/active_support/notifications/fanout.rb +91 -65
  137. data/lib/active_support/notifications/instrumenter.rb +32 -15
  138. data/lib/active_support/notifications.rb +23 -23
  139. data/lib/active_support/number_helper/number_converter.rb +1 -3
  140. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  141. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  142. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  143. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  144. data/lib/active_support/number_helper/rounding_helper.rb +1 -5
  145. data/lib/active_support/number_helper.rb +4 -5
  146. data/lib/active_support/option_merger.rb +10 -18
  147. data/lib/active_support/ordered_hash.rb +1 -1
  148. data/lib/active_support/ordered_options.rb +1 -1
  149. data/lib/active_support/parameter_filter.rb +20 -11
  150. data/lib/active_support/per_thread_registry.rb +5 -1
  151. data/lib/active_support/railtie.rb +69 -19
  152. data/lib/active_support/rescuable.rb +12 -12
  153. data/lib/active_support/ruby_features.rb +7 -0
  154. data/lib/active_support/secure_compare_rotator.rb +2 -2
  155. data/lib/active_support/string_inquirer.rb +0 -2
  156. data/lib/active_support/subscriber.rb +7 -18
  157. data/lib/active_support/tagged_logging.rb +1 -1
  158. data/lib/active_support/test_case.rb +13 -21
  159. data/lib/active_support/testing/assertions.rb +35 -5
  160. data/lib/active_support/testing/deprecation.rb +52 -1
  161. data/lib/active_support/testing/isolation.rb +30 -29
  162. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  163. data/lib/active_support/testing/parallelization/server.rb +4 -0
  164. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  165. data/lib/active_support/testing/parallelization.rb +4 -0
  166. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  167. data/lib/active_support/testing/stream.rb +3 -5
  168. data/lib/active_support/testing/tagged_logging.rb +1 -1
  169. data/lib/active_support/testing/time_helpers.rb +13 -2
  170. data/lib/active_support/time_with_zone.rb +62 -22
  171. data/lib/active_support/values/time_zone.rb +33 -14
  172. data/lib/active_support/version.rb +1 -1
  173. data/lib/active_support/xml_mini/jdom.rb +1 -1
  174. data/lib/active_support/xml_mini/libxml.rb +5 -5
  175. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  176. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  177. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  178. data/lib/active_support/xml_mini/rexml.rb +1 -1
  179. data/lib/active_support/xml_mini.rb +5 -4
  180. data/lib/active_support.rb +16 -0
  181. metadata +28 -26
  182. data/lib/active_support/core_ext/marshal.rb +0 -26
  183. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -120
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveSupport
4
- # lazy_load_hooks allows Rails to lazily load a lot of components and thus
4
+ # LazyLoadHooks allows Rails to lazily load a lot of components and thus
5
5
  # making the app boot faster. Because of this feature now there is no need to
6
6
  # require <tt>ActiveRecord::Base</tt> at boot time purely to apply
7
7
  # configuration. Instead a hook is registered that applies configuration once
8
8
  # <tt>ActiveRecord::Base</tt> is loaded. Here <tt>ActiveRecord::Base</tt> is
9
9
  # used as example but this feature can be applied elsewhere too.
10
10
  #
11
- # Here is an example where +on_load+ method is called to register a hook.
11
+ # Here is an example where on_load method is called to register a hook.
12
12
  #
13
13
  # initializer 'active_record.initialize_timezone' do
14
14
  # ActiveSupport.on_load(:active_record) do
@@ -18,10 +18,26 @@ module ActiveSupport
18
18
  # end
19
19
  #
20
20
  # When the entirety of +ActiveRecord::Base+ has been
21
- # evaluated then +run_load_hooks+ is invoked. The very last line of
21
+ # evaluated then run_load_hooks is invoked. The very last line of
22
22
  # +ActiveRecord::Base+ is:
23
23
  #
24
24
  # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
25
+ #
26
+ # run_load_hooks will then execute all the hooks that were registered
27
+ # with the on_load method. In the case of the above example, it will
28
+ # execute the block of code that is in the +initializer+.
29
+ #
30
+ # Registering a hook that has already run results in that hook executing
31
+ # immediately. This allows hooks to be nested for code that relies on
32
+ # multiple lazily loaded components:
33
+ #
34
+ # initializer "action_text.renderer" do
35
+ # ActiveSupport.on_load(:action_controller_base) do
36
+ # ActiveSupport.on_load(:action_text_content) do
37
+ # self.default_renderer = Class.new(ActionController::Base).renderer
38
+ # end
39
+ # end
40
+ # end
25
41
  module LazyLoadHooks
26
42
  def self.extended(base) # :nodoc:
27
43
  base.class_eval do
@@ -32,7 +48,8 @@ module ActiveSupport
32
48
  end
33
49
 
34
50
  # Declares a block that will be executed when a Rails component is fully
35
- # loaded.
51
+ # loaded. If the component has already loaded, the block is executed
52
+ # immediately.
36
53
  #
37
54
  # Options:
38
55
  #
@@ -46,6 +63,13 @@ module ActiveSupport
46
63
  @load_hooks[name] << [block, options]
47
64
  end
48
65
 
66
+ # Executes all blocks registered to +name+ via on_load, using +base+ as the
67
+ # evaluation context.
68
+ #
69
+ # ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
70
+ #
71
+ # In the case of the above example, it will execute all hooks registered
72
+ # for +:active_record+ within the class +ActiveRecord::Base+.
49
73
  def run_load_hooks(name, base = Object)
50
74
  @loaded[name] << base
51
75
  @load_hooks[name].each do |hook, options|
@@ -55,7 +55,7 @@ en:
55
55
  # Used in NumberHelper.number_to_currency()
56
56
  currency:
57
57
  format:
58
- # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
58
+ # Where is the currency sign? %u is the currency unit, %n is the number (default: $5.00)
59
59
  format: "%u%n"
60
60
  unit: "$"
61
61
  # These six are to override number.format and are optional
@@ -27,13 +27,13 @@ module ActiveSupport
27
27
  #
28
28
  # All you need to do is to ensure that your log subscriber is added to
29
29
  # Rails::Subscriber, as in the second line of the code above. The test
30
- # helpers are responsible for setting up the queue, subscriptions and
30
+ # helpers are responsible for setting up the queue and subscriptions, and
31
31
  # turning colors in logs off.
32
32
  #
33
33
  # The messages are available in the @logger instance, which is a logger with
34
34
  # limited powers (it actually does not send anything to your output), and
35
35
  # you can collect them doing @logger.logged(level), where level is the level
36
- # used in logging, like info, debug, warn and so on.
36
+ # used in logging, like info, debug, warn, and so on.
37
37
  module TestHelper
38
38
  def setup # :nodoc:
39
39
  @logger = MockLogger.new
@@ -6,7 +6,7 @@ require "active_support/subscriber"
6
6
 
7
7
  module ActiveSupport
8
8
  # <tt>ActiveSupport::LogSubscriber</tt> is an object set to consume
9
- # <tt>ActiveSupport::Notifications</tt> with the sole purpose of logging them.
9
+ # ActiveSupport::Notifications with the sole purpose of logging them.
10
10
  # The log subscriber dispatches notifications to a registered object based
11
11
  # on its given namespace.
12
12
  #
@@ -36,7 +36,7 @@ module ActiveSupport
36
36
  # it will properly dispatch the event
37
37
  # (<tt>ActiveSupport::Notifications::Event</tt>) to the sql method.
38
38
  #
39
- # Being an <tt>ActiveSupport::Notifications</tt> consumer,
39
+ # Being an ActiveSupport::Notifications consumer,
40
40
  # <tt>ActiveSupport::LogSubscriber</tt> exposes a simple interface to check if
41
41
  # instrumented code raises an exception. It is common to log a different
42
42
  # message in case of an error, and this can be achieved by extending
@@ -114,9 +114,13 @@ module ActiveSupport
114
114
  def finish(name, id, payload)
115
115
  super if logger
116
116
  rescue => e
117
- if logger
118
- logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
119
- end
117
+ log_exception(name, e)
118
+ end
119
+
120
+ def publish_event(event)
121
+ super if logger
122
+ rescue => e
123
+ log_exception(event.name, e)
120
124
  end
121
125
 
122
126
  private
@@ -138,5 +142,11 @@ module ActiveSupport
138
142
  bold = bold ? BOLD : ""
139
143
  "#{bold}#{color}#{text}#{CLEAR}"
140
144
  end
145
+
146
+ def log_exception(name, e)
147
+ if logger
148
+ logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
149
+ end
150
+ end
141
151
  end
142
152
  end
@@ -9,10 +9,6 @@ module ActiveSupport
9
9
  module LoggerThreadSafeLevel # :nodoc:
10
10
  extend ActiveSupport::Concern
11
11
 
12
- included do
13
- cattr_accessor :local_levels, default: Concurrent::Map.new(initial_capacity: 2), instance_accessor: false
14
- end
15
-
16
12
  Logger::Severity.constants.each do |severity|
17
13
  class_eval(<<-EOT, __FILE__, __LINE__ + 1)
18
14
  def #{severity.downcase}? # def debug?
@@ -21,25 +17,20 @@ module ActiveSupport
21
17
  EOT
22
18
  end
23
19
 
24
- def local_log_id
25
- Fiber.current.__id__
26
- end
27
-
28
20
  def local_level
29
- self.class.local_levels[local_log_id]
21
+ IsolatedExecutionState[:logger_thread_safe_level]
30
22
  end
31
23
 
32
24
  def local_level=(level)
33
25
  case level
34
26
  when Integer
35
- self.class.local_levels[local_log_id] = level
36
27
  when Symbol
37
- self.class.local_levels[local_log_id] = Logger::Severity.const_get(level.to_s.upcase)
28
+ level = Logger::Severity.const_get(level.to_s.upcase)
38
29
  when nil
39
- self.class.local_levels.delete(local_log_id)
40
30
  else
41
31
  raise ArgumentError, "Invalid log level: #{level.inspect}"
42
32
  end
33
+ IsolatedExecutionState[:logger_thread_safe_level] = level
43
34
  end
44
35
 
45
36
  def level
@@ -56,7 +47,7 @@ module ActiveSupport
56
47
 
57
48
  # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
58
49
  # FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
59
- def add(severity, message = nil, progname = nil, &block) #:nodoc:
50
+ def add(severity, message = nil, progname = nil, &block) # :nodoc:
60
51
  severity ||= UNKNOWN
61
52
  progname ||= @progname
62
53
 
@@ -13,7 +13,7 @@ module ActiveSupport
13
13
  # The cipher text and initialization vector are base64 encoded and returned
14
14
  # to you.
15
15
  #
16
- # This can be used in situations similar to the <tt>MessageVerifier</tt>, but
16
+ # This can be used in situations similar to the MessageVerifier, but
17
17
  # where you don't want users to be able to determine the value of the payload.
18
18
  #
19
19
  # len = ActiveSupport::MessageEncryptor.key_len
@@ -23,6 +23,12 @@ module ActiveSupport
23
23
  # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
24
24
  # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
25
25
  #
26
+ # The +decrypt_and_verify+ method will raise an
27
+ # <tt>ActiveSupport::MessageEncryptor::InvalidMessage</tt> exception if the data
28
+ # provided cannot be decrypted or verified.
29
+ #
30
+ # crypt.decrypt_and_verify('not encrypted data') # => ActiveSupport::MessageEncryptor::InvalidMessage
31
+ #
26
32
  # === Confining messages to a specific purpose
27
33
  #
28
34
  # By default any message can be used throughout your app. But they can also be
@@ -84,7 +90,7 @@ module ActiveSupport
84
90
  cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
85
91
 
86
92
  class << self
87
- def default_cipher #:nodoc:
93
+ def default_cipher # :nodoc:
88
94
  if use_authenticated_message_encryption
89
95
  "aes-256-gcm"
90
96
  else
@@ -93,7 +99,7 @@ module ActiveSupport
93
99
  end
94
100
  end
95
101
 
96
- module NullSerializer #:nodoc:
102
+ module NullSerializer # :nodoc:
97
103
  def self.load(value)
98
104
  value
99
105
  end
@@ -103,7 +109,7 @@ module ActiveSupport
103
109
  end
104
110
  end
105
111
 
106
- module NullVerifier #:nodoc:
112
+ module NullVerifier # :nodoc:
107
113
  def self.verify(value)
108
114
  value
109
115
  end
@@ -119,10 +125,10 @@ module ActiveSupport
119
125
  # Initialize a new MessageEncryptor. +secret+ must be at least as long as
120
126
  # the cipher key size. For the default 'aes-256-gcm' cipher, this is 256
121
127
  # bits. If you are using a user-entered secret, you can generate a suitable
122
- # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
128
+ # key by using ActiveSupport::KeyGenerator or a similar key
123
129
  # derivation function.
124
130
  #
125
- # First additional parameter is used as the signature key for +MessageVerifier+.
131
+ # First additional parameter is used as the signature key for MessageVerifier.
126
132
  # This allows you to specify keys to encrypt and sign data.
127
133
  #
128
134
  # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "openssl"
3
4
  require "base64"
4
5
  require "active_support/core_ext/object/blank"
5
6
  require "active_support/security_utils"
@@ -68,8 +69,8 @@ module ActiveSupport
68
69
  # return the original value. But messages can be set to expire at a given
69
70
  # time with +:expires_in+ or +:expires_at+.
70
71
  #
71
- # @verifier.generate(parcel, expires_in: 1.month)
72
- # @verifier.generate(doowad, expires_at: Time.now.end_of_year)
72
+ # @verifier.generate("parcel", expires_in: 1.month)
73
+ # @verifier.generate("doowad", expires_at: Time.now.end_of_year)
73
74
  #
74
75
  # Then the messages can be verified and returned up to the expire time.
75
76
  # Thereafter, the +verified+ method returns +nil+ while +verify+ raises
@@ -78,8 +79,8 @@ module ActiveSupport
78
79
  # === Rotating keys
79
80
  #
80
81
  # MessageVerifier also supports rotating out old configurations by falling
81
- # back to a stack of verifiers. Call +rotate+ to build and add a verifier to
82
- # so either +verified+ or +verify+ will also try verifying with the fallback.
82
+ # back to a stack of verifiers. Call +rotate+ to build and add a verifier so
83
+ # either +verified+ or +verify+ will also try verifying with the fallback.
83
84
  #
84
85
  # By default any rotated verifiers use the values of the primary
85
86
  # verifier unless specified otherwise.
@@ -103,10 +104,13 @@ module ActiveSupport
103
104
 
104
105
  class InvalidSignature < StandardError; end
105
106
 
107
+ SEPARATOR = "--" # :nodoc:
108
+ SEPARATOR_LENGTH = SEPARATOR.length # :nodoc:
109
+
106
110
  def initialize(secret, digest: nil, serializer: nil)
107
111
  raise ArgumentError, "Secret should not be nil." unless secret
108
112
  @secret = secret
109
- @digest = digest || "SHA1"
113
+ @digest = digest&.to_s || "SHA1"
110
114
  @serializer = serializer || Marshal
111
115
  end
112
116
 
@@ -120,10 +124,8 @@ module ActiveSupport
120
124
  # tampered_message = signed_message.chop # editing the message invalidates the signature
121
125
  # verifier.valid_message?(tampered_message) # => false
122
126
  def valid_message?(signed_message)
123
- return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
124
-
125
- data, digest = signed_message.split("--")
126
- data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
127
+ data, digest = get_data_and_digest_from(signed_message)
128
+ digest_matches_data?(digest, data)
127
129
  end
128
130
 
129
131
  # Decodes the signed message using the +MessageVerifier+'s secret.
@@ -148,9 +150,9 @@ module ActiveSupport
148
150
  # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
149
151
  # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
150
152
  def verified(signed_message, purpose: nil, **)
151
- if valid_message?(signed_message)
153
+ data, digest = get_data_and_digest_from(signed_message)
154
+ if digest_matches_data?(digest, data)
152
155
  begin
153
- data = signed_message.split("--")[0]
154
156
  message = Messages::Metadata.verify(decode(data), purpose)
155
157
  @serializer.load(message) if message
156
158
  rescue ArgumentError => argument_error
@@ -185,7 +187,7 @@ module ActiveSupport
185
187
  # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
186
188
  def generate(value, expires_at: nil, expires_in: nil, purpose: nil)
187
189
  data = encode(Messages::Metadata.wrap(@serializer.dump(value), expires_at: expires_at, expires_in: expires_in, purpose: purpose))
188
- "#{data}--#{generate_digest(data)}"
190
+ "#{data}#{SEPARATOR}#{generate_digest(data)}"
189
191
  end
190
192
 
191
193
  private
@@ -198,8 +200,38 @@ module ActiveSupport
198
200
  end
199
201
 
200
202
  def generate_digest(data)
201
- require "openssl" unless defined?(OpenSSL)
202
- OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
203
+ OpenSSL::HMAC.hexdigest(@digest, @secret, data)
204
+ end
205
+
206
+ def digest_length_in_hex
207
+ # In hexadecimal (AKA base16) it takes 4 bits to represent a character,
208
+ # hence we multiply the digest's length (in bytes) by 8 to get it in
209
+ # bits and divide by 4 to get its number of characters it hex. Well, 8
210
+ # divided by 4 is 2.
211
+ @digest_length_in_hex ||= OpenSSL::Digest.new(@digest).digest_length * 2
212
+ end
213
+
214
+ def separator_index_for(signed_message)
215
+ index = signed_message.length - digest_length_in_hex - SEPARATOR_LENGTH
216
+ return if index.negative? || signed_message[index, SEPARATOR_LENGTH] != SEPARATOR
217
+
218
+ index
219
+ end
220
+
221
+ def get_data_and_digest_from(signed_message)
222
+ return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.empty?
223
+
224
+ separator_index = separator_index_for(signed_message)
225
+ return if separator_index.nil?
226
+
227
+ data = signed_message[0...separator_index]
228
+ digest = signed_message[separator_index + SEPARATOR_LENGTH..-1]
229
+
230
+ [data, digest]
231
+ end
232
+
233
+ def digest_matches_data?(digest, data)
234
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
203
235
  end
204
236
  end
205
237
  end
@@ -3,8 +3,8 @@
3
3
  require "time"
4
4
 
5
5
  module ActiveSupport
6
- module Messages #:nodoc:
7
- class Metadata #:nodoc:
6
+ module Messages # :nodoc:
7
+ class Metadata # :nodoc:
8
8
  def initialize(message, expires_at = nil, purpose = nil)
9
9
  @message, @purpose = message, purpose
10
10
  @expires_at = expires_at.is_a?(String) ? parse_expires_at(expires_at) : expires_at
@@ -3,11 +3,10 @@
3
3
  require "active_support/json"
4
4
  require "active_support/core_ext/string/access"
5
5
  require "active_support/core_ext/string/behavior"
6
- require "active_support/core_ext/symbol/starts_ends_with"
7
6
  require "active_support/core_ext/module/delegation"
8
7
 
9
- module ActiveSupport #:nodoc:
10
- module Multibyte #:nodoc:
8
+ module ActiveSupport # :nodoc:
9
+ module Multibyte # :nodoc:
11
10
  # Chars enables you to work transparently with UTF-8 encoding in the Ruby
12
11
  # String class without having extensive knowledge about the encoding. A
13
12
  # Chars object accepts a string upon initialization and proxies String
@@ -103,7 +102,7 @@ module ActiveSupport #:nodoc:
103
102
  #
104
103
  # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
105
104
  def reverse
106
- chars(@wrapped_string.scan(/\X/).reverse.join)
105
+ chars(@wrapped_string.grapheme_clusters.reverse.join)
107
106
  end
108
107
 
109
108
  # Limits the byte size of the string to a number of bytes without breaking
@@ -126,16 +125,16 @@ module ActiveSupport #:nodoc:
126
125
 
127
126
  # Performs canonical decomposition on all the characters.
128
127
  #
129
- # 'é'.length # => 2
130
- # 'é'.mb_chars.decompose.to_s.length # => 3
128
+ # 'é'.length # => 1
129
+ # 'é'.mb_chars.decompose.to_s.length # => 2
131
130
  def decompose
132
131
  chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*"))
133
132
  end
134
133
 
135
134
  # Performs composition on all the characters.
136
135
  #
137
- # 'é'.length # => 3
138
- # 'é'.mb_chars.compose.to_s.length # => 2
136
+ # 'é'.length # => 1
137
+ # 'é'.mb_chars.compose.to_s.length # => 1
139
138
  def compose
140
139
  chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*"))
141
140
  end
@@ -143,9 +142,9 @@ module ActiveSupport #:nodoc:
143
142
  # Returns the number of grapheme clusters in the string.
144
143
  #
145
144
  # 'क्षि'.mb_chars.length # => 4
146
- # 'क्षि'.mb_chars.grapheme_length # => 3
145
+ # 'क्षि'.mb_chars.grapheme_length # => 2
147
146
  def grapheme_length
148
- @wrapped_string.scan(/\X/).length
147
+ @wrapped_string.grapheme_clusters.length
149
148
  end
150
149
 
151
150
  # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
@@ -157,7 +156,7 @@ module ActiveSupport #:nodoc:
157
156
  chars(Unicode.tidy_bytes(@wrapped_string, force))
158
157
  end
159
158
 
160
- def as_json(options = nil) #:nodoc:
159
+ def as_json(options = nil) # :nodoc:
161
160
  to_s.as_json(options)
162
161
  end
163
162
 
@@ -8,18 +8,6 @@ module ActiveSupport
8
8
  # The Unicode version that is supported by the implementation
9
9
  UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
10
10
 
11
- def default_normalization_form
12
- ActiveSupport::Deprecation.warn(
13
- "ActiveSupport::Multibyte::Unicode.default_normalization_form is deprecated and will be removed in Rails 7.0."
14
- )
15
- end
16
-
17
- def default_normalization_form=(_)
18
- ActiveSupport::Deprecation.warn(
19
- "ActiveSupport::Multibyte::Unicode.default_normalization_form= is deprecated and will be removed in Rails 7.0."
20
- )
21
- end
22
-
23
11
  # Decompose composed characters to the decomposed form.
24
12
  def decompose(type, codepoints)
25
13
  if type == :compatibility
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveSupport #:nodoc:
3
+ module ActiveSupport # :nodoc:
4
4
  module Multibyte
5
5
  autoload :Chars, "active_support/multibyte/chars"
6
6
  autoload :Unicode, "active_support/multibyte/unicode"