activesupport 6.1.4.1 → 7.0.8.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +325 -395
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_support/actionable_error.rb +1 -1
  6. data/lib/active_support/array_inquirer.rb +0 -2
  7. data/lib/active_support/backtrace_cleaner.rb +2 -2
  8. data/lib/active_support/benchmarkable.rb +2 -2
  9. data/lib/active_support/cache/file_store.rb +15 -9
  10. data/lib/active_support/cache/mem_cache_store.rb +148 -37
  11. data/lib/active_support/cache/memory_store.rb +24 -16
  12. data/lib/active_support/cache/null_store.rb +10 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +68 -85
  14. data/lib/active_support/cache/strategy/local_cache.rb +38 -61
  15. data/lib/active_support/cache.rb +299 -147
  16. data/lib/active_support/callbacks.rb +184 -85
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +5 -5
  19. data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +2 -4
  20. data/lib/active_support/concurrency/share_lock.rb +2 -2
  21. data/lib/active_support/configurable.rb +8 -5
  22. data/lib/active_support/configuration_file.rb +1 -1
  23. data/lib/active_support/core_ext/array/access.rb +1 -5
  24. data/lib/active_support/core_ext/array/conversions.rb +13 -12
  25. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  26. data/lib/active_support/core_ext/array/grouping.rb +6 -6
  27. data/lib/active_support/core_ext/array/inquiry.rb +2 -2
  28. data/lib/active_support/core_ext/array.rb +1 -0
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/subclasses.rb +25 -17
  31. data/lib/active_support/core_ext/date/blank.rb +1 -1
  32. data/lib/active_support/core_ext/date/calculations.rb +24 -9
  33. data/lib/active_support/core_ext/date/conversions.rb +14 -14
  34. data/lib/active_support/core_ext/date/deprecated_conversions.rb +40 -0
  35. data/lib/active_support/core_ext/date.rb +1 -0
  36. data/lib/active_support/core_ext/date_and_time/calculations.rb +4 -4
  37. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  38. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  39. data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
  40. data/lib/active_support/core_ext/date_time/conversions.rb +13 -13
  41. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +36 -0
  42. data/lib/active_support/core_ext/date_time.rb +1 -0
  43. data/lib/active_support/core_ext/digest/uuid.rb +39 -13
  44. data/lib/active_support/core_ext/enumerable.rb +112 -38
  45. data/lib/active_support/core_ext/file/atomic.rb +3 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +0 -1
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
  48. data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
  49. data/lib/active_support/core_ext/hash/keys.rb +4 -4
  50. data/lib/active_support/core_ext/integer/inflections.rb +12 -12
  51. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  52. data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
  53. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
  54. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
  55. data/lib/active_support/core_ext/module/delegation.rb +2 -8
  56. data/lib/active_support/core_ext/name_error.rb +2 -8
  57. data/lib/active_support/core_ext/numeric/conversions.rb +80 -77
  58. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  59. data/lib/active_support/core_ext/numeric.rb +1 -0
  60. data/lib/active_support/core_ext/object/acts_like.rb +29 -5
  61. data/lib/active_support/core_ext/object/blank.rb +2 -2
  62. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  63. data/lib/active_support/core_ext/object/duplicable.rb +15 -4
  64. data/lib/active_support/core_ext/object/json.rb +30 -25
  65. data/lib/active_support/core_ext/object/to_query.rb +2 -4
  66. data/lib/active_support/core_ext/object/try.rb +20 -20
  67. data/lib/active_support/core_ext/object/with_options.rb +21 -2
  68. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  69. data/lib/active_support/core_ext/pathname.rb +3 -0
  70. data/lib/active_support/core_ext/range/compare_range.rb +0 -25
  71. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  72. data/lib/active_support/core_ext/range/deprecated_conversions.rb +36 -0
  73. data/lib/active_support/core_ext/range/each.rb +1 -1
  74. data/lib/active_support/core_ext/range/include_time_with_zone.rb +3 -26
  75. data/lib/active_support/core_ext/range/overlaps.rb +1 -1
  76. data/lib/active_support/core_ext/range.rb +1 -1
  77. data/lib/active_support/core_ext/securerandom.rb +1 -1
  78. data/lib/active_support/core_ext/string/conversions.rb +2 -2
  79. data/lib/active_support/core_ext/string/filters.rb +1 -1
  80. data/lib/active_support/core_ext/string/inflections.rb +1 -5
  81. data/lib/active_support/core_ext/string/inquiry.rb +1 -1
  82. data/lib/active_support/core_ext/string/output_safety.rb +94 -38
  83. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +0 -8
  84. data/lib/active_support/core_ext/time/calculations.rb +13 -8
  85. data/lib/active_support/core_ext/time/conversions.rb +13 -12
  86. data/lib/active_support/core_ext/time/deprecated_conversions.rb +73 -0
  87. data/lib/active_support/core_ext/time/zones.rb +10 -26
  88. data/lib/active_support/core_ext/time.rb +1 -0
  89. data/lib/active_support/core_ext/uri.rb +3 -27
  90. data/lib/active_support/core_ext.rb +1 -0
  91. data/lib/active_support/current_attributes.rb +31 -14
  92. data/lib/active_support/dependencies/interlock.rb +10 -18
  93. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  94. data/lib/active_support/dependencies.rb +58 -788
  95. data/lib/active_support/deprecation/behaviors.rb +8 -5
  96. data/lib/active_support/deprecation/disallowed.rb +3 -3
  97. data/lib/active_support/deprecation/method_wrappers.rb +3 -3
  98. data/lib/active_support/deprecation/proxy_wrappers.rb +2 -2
  99. data/lib/active_support/deprecation.rb +2 -2
  100. data/lib/active_support/descendants_tracker.rb +174 -68
  101. data/lib/active_support/digest.rb +5 -3
  102. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  103. data/lib/active_support/duration/iso8601_serializer.rb +9 -1
  104. data/lib/active_support/duration.rb +81 -51
  105. data/lib/active_support/encrypted_configuration.rb +45 -3
  106. data/lib/active_support/encrypted_file.rb +21 -10
  107. data/lib/active_support/environment_inquirer.rb +1 -1
  108. data/lib/active_support/error_reporter.rb +117 -0
  109. data/lib/active_support/evented_file_update_checker.rb +20 -7
  110. data/lib/active_support/execution_context/test_helper.rb +13 -0
  111. data/lib/active_support/execution_context.rb +53 -0
  112. data/lib/active_support/execution_wrapper.rb +43 -21
  113. data/lib/active_support/executor/test_helper.rb +7 -0
  114. data/lib/active_support/fork_tracker.rb +19 -12
  115. data/lib/active_support/gem_version.rb +5 -5
  116. data/lib/active_support/hash_with_indifferent_access.rb +3 -1
  117. data/lib/active_support/html_safe_translation.rb +43 -0
  118. data/lib/active_support/i18n.rb +1 -0
  119. data/lib/active_support/i18n_railtie.rb +1 -1
  120. data/lib/active_support/inflector/inflections.rb +23 -7
  121. data/lib/active_support/inflector/methods.rb +29 -55
  122. data/lib/active_support/inflector/transliterate.rb +1 -1
  123. data/lib/active_support/isolated_execution_state.rb +72 -0
  124. data/lib/active_support/json/encoding.rb +3 -3
  125. data/lib/active_support/key_generator.rb +22 -5
  126. data/lib/active_support/lazy_load_hooks.rb +28 -4
  127. data/lib/active_support/locale/en.yml +1 -1
  128. data/lib/active_support/log_subscriber/test_helper.rb +2 -2
  129. data/lib/active_support/log_subscriber.rb +15 -5
  130. data/lib/active_support/logger_thread_safe_level.rb +4 -13
  131. data/lib/active_support/message_encryptor.rb +12 -6
  132. data/lib/active_support/message_verifier.rb +46 -14
  133. data/lib/active_support/messages/metadata.rb +2 -2
  134. data/lib/active_support/multibyte/chars.rb +10 -11
  135. data/lib/active_support/multibyte/unicode.rb +0 -12
  136. data/lib/active_support/multibyte.rb +1 -1
  137. data/lib/active_support/notifications/fanout.rb +91 -65
  138. data/lib/active_support/notifications/instrumenter.rb +32 -15
  139. data/lib/active_support/notifications.rb +23 -23
  140. data/lib/active_support/number_helper/number_converter.rb +1 -3
  141. data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
  142. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  143. data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
  144. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  145. data/lib/active_support/number_helper/rounding_helper.rb +1 -5
  146. data/lib/active_support/number_helper.rb +4 -5
  147. data/lib/active_support/option_merger.rb +10 -18
  148. data/lib/active_support/ordered_hash.rb +1 -1
  149. data/lib/active_support/ordered_options.rb +1 -1
  150. data/lib/active_support/parameter_filter.rb +20 -11
  151. data/lib/active_support/per_thread_registry.rb +5 -0
  152. data/lib/active_support/railtie.rb +69 -19
  153. data/lib/active_support/reloader.rb +1 -1
  154. data/lib/active_support/rescuable.rb +12 -12
  155. data/lib/active_support/ruby_features.rb +7 -0
  156. data/lib/active_support/secure_compare_rotator.rb +2 -2
  157. data/lib/active_support/string_inquirer.rb +0 -2
  158. data/lib/active_support/subscriber.rb +7 -18
  159. data/lib/active_support/tagged_logging.rb +2 -2
  160. data/lib/active_support/test_case.rb +13 -21
  161. data/lib/active_support/testing/assertions.rb +36 -6
  162. data/lib/active_support/testing/deprecation.rb +52 -1
  163. data/lib/active_support/testing/isolation.rb +30 -29
  164. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  165. data/lib/active_support/testing/parallelization/server.rb +4 -0
  166. data/lib/active_support/testing/parallelization/worker.rb +3 -0
  167. data/lib/active_support/testing/parallelization.rb +4 -0
  168. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  169. data/lib/active_support/testing/stream.rb +3 -5
  170. data/lib/active_support/testing/tagged_logging.rb +1 -1
  171. data/lib/active_support/testing/time_helpers.rb +13 -2
  172. data/lib/active_support/time_with_zone.rb +43 -22
  173. data/lib/active_support/values/time_zone.rb +35 -14
  174. data/lib/active_support/version.rb +1 -1
  175. data/lib/active_support/xml_mini/jdom.rb +1 -1
  176. data/lib/active_support/xml_mini/libxml.rb +5 -5
  177. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  178. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  179. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  180. data/lib/active_support/xml_mini/rexml.rb +1 -1
  181. data/lib/active_support/xml_mini.rb +5 -4
  182. data/lib/active_support.rb +17 -1
  183. metadata +26 -23
  184. data/lib/active_support/core_ext/marshal.rb +0 -26
  185. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -9,24 +9,41 @@ module ActiveSupport
9
9
  # This lets Rails applications have a single secure secret, but avoid reusing that
10
10
  # key in multiple incompatible contexts.
11
11
  class KeyGenerator
12
+ class << self
13
+ def hash_digest_class=(klass)
14
+ if klass.kind_of?(Class) && klass < OpenSSL::Digest
15
+ @hash_digest_class = klass
16
+ else
17
+ raise ArgumentError, "#{klass} is expected to be an OpenSSL::Digest subclass"
18
+ end
19
+ end
20
+
21
+ def hash_digest_class
22
+ @hash_digest_class ||= OpenSSL::Digest::SHA1
23
+ end
24
+ end
25
+
12
26
  def initialize(secret, options = {})
13
27
  @secret = secret
14
28
  # The default iterations are higher than required for our key derivation uses
15
29
  # on the off chance someone uses this for password storage
16
30
  @iterations = options[:iterations] || 2**16
31
+ # Also allow configuration here so people can use this to build a rotation
32
+ # scheme when switching the digest class.
33
+ @hash_digest_class = options[:hash_digest_class] || self.class.hash_digest_class
17
34
  end
18
35
 
19
- # Returns a derived key suitable for use. The default key_size is chosen
36
+ # Returns a derived key suitable for use. The default +key_size+ is chosen
20
37
  # to be compatible with the default settings of ActiveSupport::MessageVerifier.
21
- # i.e. OpenSSL::Digest::SHA1#block_length
38
+ # i.e. <tt>OpenSSL::Digest::SHA1#block_length</tt>
22
39
  def generate_key(salt, key_size = 64)
23
- OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size)
40
+ OpenSSL::PKCS5.pbkdf2_hmac(@secret, salt, @iterations, key_size, @hash_digest_class.new)
24
41
  end
25
42
  end
26
43
 
27
44
  # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
28
- # re-executing the key generation process when it's called using the same salt and
29
- # key_size.
45
+ # re-executing the key generation process when it's called using the same +salt+ and
46
+ # +key_size+.
30
47
  class CachingKeyGenerator
31
48
  def initialize(key_generator)
32
49
  @key_generator = key_generator
@@ -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 6.2."
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 6.2."
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"