activesupport 6.0.3.7 → 7.0.0

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 (204) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +220 -533
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/lib/active_support/actionable_error.rb +1 -1
  6. data/lib/active_support/array_inquirer.rb +2 -2
  7. data/lib/active_support/backtrace_cleaner.rb +3 -3
  8. data/lib/active_support/benchmarkable.rb +3 -3
  9. data/lib/active_support/cache/file_store.rb +18 -11
  10. data/lib/active_support/cache/mem_cache_store.rb +143 -37
  11. data/lib/active_support/cache/memory_store.rb +56 -28
  12. data/lib/active_support/cache/null_store.rb +10 -2
  13. data/lib/active_support/cache/redis_cache_store.rb +63 -88
  14. data/lib/active_support/cache/strategy/local_cache.rb +46 -57
  15. data/lib/active_support/cache.rb +273 -82
  16. data/lib/active_support/callbacks.rb +226 -118
  17. data/lib/active_support/code_generator.rb +65 -0
  18. data/lib/active_support/concern.rb +49 -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 +9 -6
  22. data/lib/active_support/configuration_file.rb +51 -0
  23. data/lib/active_support/core_ext/array/access.rb +1 -5
  24. data/lib/active_support/core_ext/array/conversions.rb +9 -7
  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.rb +1 -0
  28. data/lib/active_support/core_ext/benchmark.rb +2 -2
  29. data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
  30. data/lib/active_support/core_ext/class/attribute.rb +34 -44
  31. data/lib/active_support/core_ext/class/subclasses.rb +21 -40
  32. data/lib/active_support/core_ext/date/blank.rb +1 -1
  33. data/lib/active_support/core_ext/date/calculations.rb +4 -4
  34. data/lib/active_support/core_ext/date/conversions.rb +5 -4
  35. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  36. data/lib/active_support/core_ext/date.rb +1 -0
  37. data/lib/active_support/core_ext/date_and_time/calculations.rb +13 -0
  38. data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
  39. data/lib/active_support/core_ext/date_time/blank.rb +1 -1
  40. data/lib/active_support/core_ext/date_time/conversions.rb +5 -5
  41. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -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 +139 -15
  45. data/lib/active_support/core_ext/file/atomic.rb +1 -1
  46. data/lib/active_support/core_ext/hash/conversions.rb +2 -2
  47. data/lib/active_support/core_ext/hash/deep_transform_values.rb +1 -1
  48. data/lib/active_support/core_ext/hash/except.rb +1 -1
  49. data/lib/active_support/core_ext/hash/keys.rb +2 -2
  50. data/lib/active_support/core_ext/hash/slice.rb +3 -2
  51. data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
  52. data/lib/active_support/core_ext/load_error.rb +1 -1
  53. data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
  54. data/lib/active_support/core_ext/module/attribute_accessors.rb +25 -29
  55. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +26 -13
  56. data/lib/active_support/core_ext/module/concerning.rb +8 -2
  57. data/lib/active_support/core_ext/module/delegation.rb +40 -36
  58. data/lib/active_support/core_ext/module/introspection.rb +1 -25
  59. data/lib/active_support/core_ext/name_error.rb +23 -2
  60. data/lib/active_support/core_ext/numeric/conversions.rb +79 -72
  61. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  62. data/lib/active_support/core_ext/numeric.rb +1 -0
  63. data/lib/active_support/core_ext/object/blank.rb +2 -2
  64. data/lib/active_support/core_ext/object/deep_dup.rb +1 -1
  65. data/lib/active_support/core_ext/object/duplicable.rb +11 -0
  66. data/lib/active_support/core_ext/object/json.rb +42 -26
  67. data/lib/active_support/core_ext/object/to_query.rb +2 -2
  68. data/lib/active_support/core_ext/object/try.rb +20 -20
  69. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  70. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  71. data/lib/active_support/core_ext/pathname.rb +3 -0
  72. data/lib/active_support/core_ext/range/compare_range.rb +6 -25
  73. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  74. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  75. data/lib/active_support/core_ext/range/each.rb +1 -1
  76. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -20
  77. data/lib/active_support/core_ext/range.rb +1 -1
  78. data/lib/active_support/core_ext/regexp.rb +8 -1
  79. data/lib/active_support/core_ext/string/access.rb +5 -24
  80. data/lib/active_support/core_ext/string/conversions.rb +1 -0
  81. data/lib/active_support/core_ext/string/filters.rb +1 -1
  82. data/lib/active_support/core_ext/string/inflections.rb +39 -5
  83. data/lib/active_support/core_ext/string/inquiry.rb +1 -0
  84. data/lib/active_support/core_ext/string/multibyte.rb +2 -2
  85. data/lib/active_support/core_ext/string/output_safety.rb +69 -45
  86. data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
  87. data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
  88. data/lib/active_support/core_ext/symbol.rb +3 -0
  89. data/lib/active_support/core_ext/time/calculations.rb +26 -6
  90. data/lib/active_support/core_ext/time/conversions.rb +6 -3
  91. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  92. data/lib/active_support/core_ext/time/zones.rb +4 -19
  93. data/lib/active_support/core_ext/time.rb +1 -0
  94. data/lib/active_support/core_ext/uri.rb +3 -23
  95. data/lib/active_support/core_ext.rb +2 -1
  96. data/lib/active_support/current_attributes/test_helper.rb +13 -0
  97. data/lib/active_support/current_attributes.rb +39 -16
  98. data/lib/active_support/dependencies/interlock.rb +10 -18
  99. data/lib/active_support/dependencies/require_dependency.rb +28 -0
  100. data/lib/active_support/dependencies.rb +58 -764
  101. data/lib/active_support/deprecation/behaviors.rb +19 -3
  102. data/lib/active_support/deprecation/disallowed.rb +56 -0
  103. data/lib/active_support/deprecation/instance_delegator.rb +0 -1
  104. data/lib/active_support/deprecation/method_wrappers.rb +6 -5
  105. data/lib/active_support/deprecation/proxy_wrappers.rb +4 -4
  106. data/lib/active_support/deprecation/reporting.rb +50 -7
  107. data/lib/active_support/deprecation.rb +6 -1
  108. data/lib/active_support/descendants_tracker.rb +177 -64
  109. data/lib/active_support/digest.rb +5 -3
  110. data/lib/active_support/duration/iso8601_parser.rb +3 -3
  111. data/lib/active_support/duration/iso8601_serializer.rb +24 -10
  112. data/lib/active_support/duration.rb +134 -55
  113. data/lib/active_support/encrypted_configuration.rb +11 -1
  114. data/lib/active_support/encrypted_file.rb +20 -3
  115. data/lib/active_support/environment_inquirer.rb +20 -0
  116. data/lib/active_support/error_reporter.rb +117 -0
  117. data/lib/active_support/evented_file_update_checker.rb +70 -134
  118. data/lib/active_support/execution_context/test_helper.rb +13 -0
  119. data/lib/active_support/execution_context.rb +53 -0
  120. data/lib/active_support/execution_wrapper.rb +30 -4
  121. data/lib/active_support/executor/test_helper.rb +7 -0
  122. data/lib/active_support/fork_tracker.rb +71 -0
  123. data/lib/active_support/gem_version.rb +3 -3
  124. data/lib/active_support/hash_with_indifferent_access.rb +51 -25
  125. data/lib/active_support/html_safe_translation.rb +43 -0
  126. data/lib/active_support/i18n.rb +1 -0
  127. data/lib/active_support/i18n_railtie.rb +14 -19
  128. data/lib/active_support/inflector/inflections.rb +24 -9
  129. data/lib/active_support/inflector/methods.rb +29 -49
  130. data/lib/active_support/inflector/transliterate.rb +4 -4
  131. data/lib/active_support/isolated_execution_state.rb +56 -0
  132. data/lib/active_support/json/decoding.rb +4 -4
  133. data/lib/active_support/json/encoding.rb +8 -4
  134. data/lib/active_support/key_generator.rb +19 -2
  135. data/lib/active_support/locale/en.yml +8 -4
  136. data/lib/active_support/log_subscriber.rb +21 -3
  137. data/lib/active_support/logger.rb +1 -1
  138. data/lib/active_support/logger_silence.rb +2 -26
  139. data/lib/active_support/logger_thread_safe_level.rb +34 -21
  140. data/lib/active_support/message_encryptor.rb +12 -10
  141. data/lib/active_support/message_verifier.rb +50 -18
  142. data/lib/active_support/messages/metadata.rb +11 -3
  143. data/lib/active_support/messages/rotation_configuration.rb +2 -1
  144. data/lib/active_support/messages/rotator.rb +6 -5
  145. data/lib/active_support/multibyte/chars.rb +13 -52
  146. data/lib/active_support/multibyte/unicode.rb +1 -87
  147. data/lib/active_support/multibyte.rb +1 -1
  148. data/lib/active_support/notifications/fanout.rb +110 -69
  149. data/lib/active_support/notifications/instrumenter.rb +37 -29
  150. data/lib/active_support/notifications.rb +47 -26
  151. data/lib/active_support/number_helper/number_converter.rb +2 -4
  152. data/lib/active_support/number_helper/number_to_currency_converter.rb +10 -9
  153. data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
  154. data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
  155. data/lib/active_support/number_helper/number_to_human_size_converter.rb +2 -2
  156. data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -1
  157. data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
  158. data/lib/active_support/number_helper/rounding_helper.rb +12 -32
  159. data/lib/active_support/number_helper.rb +29 -16
  160. data/lib/active_support/option_merger.rb +9 -16
  161. data/lib/active_support/ordered_hash.rb +1 -1
  162. data/lib/active_support/ordered_options.rb +8 -2
  163. data/lib/active_support/parameter_filter.rb +21 -11
  164. data/lib/active_support/per_thread_registry.rb +6 -1
  165. data/lib/active_support/rails.rb +1 -4
  166. data/lib/active_support/railtie.rb +77 -5
  167. data/lib/active_support/rescuable.rb +6 -6
  168. data/lib/active_support/ruby_features.rb +7 -0
  169. data/lib/active_support/secure_compare_rotator.rb +51 -0
  170. data/lib/active_support/security_utils.rb +19 -12
  171. data/lib/active_support/string_inquirer.rb +2 -2
  172. data/lib/active_support/subscriber.rb +19 -25
  173. data/lib/active_support/tagged_logging.rb +31 -6
  174. data/lib/active_support/test_case.rb +9 -21
  175. data/lib/active_support/testing/assertions.rb +49 -12
  176. data/lib/active_support/testing/deprecation.rb +52 -1
  177. data/lib/active_support/testing/isolation.rb +2 -2
  178. data/lib/active_support/testing/method_call_assertions.rb +5 -5
  179. data/lib/active_support/testing/parallelization/server.rb +82 -0
  180. data/lib/active_support/testing/parallelization/worker.rb +103 -0
  181. data/lib/active_support/testing/parallelization.rb +16 -95
  182. data/lib/active_support/testing/parallelize_executor.rb +76 -0
  183. data/lib/active_support/testing/stream.rb +3 -5
  184. data/lib/active_support/testing/tagged_logging.rb +1 -1
  185. data/lib/active_support/testing/time_helpers.rb +53 -5
  186. data/lib/active_support/time_with_zone.rb +120 -55
  187. data/lib/active_support/values/time_zone.rb +49 -18
  188. data/lib/active_support/xml_mini/jdom.rb +1 -1
  189. data/lib/active_support/xml_mini/libxml.rb +5 -5
  190. data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
  191. data/lib/active_support/xml_mini/nokogiri.rb +4 -4
  192. data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
  193. data/lib/active_support/xml_mini/rexml.rb +9 -2
  194. data/lib/active_support/xml_mini.rb +5 -4
  195. data/lib/active_support.rb +29 -1
  196. metadata +46 -45
  197. data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
  198. data/lib/active_support/core_ext/hash/compact.rb +0 -5
  199. data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
  200. data/lib/active_support/core_ext/marshal.rb +0 -24
  201. data/lib/active_support/core_ext/module/reachable.rb +0 -6
  202. data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
  203. data/lib/active_support/core_ext/range/include_range.rb +0 -9
  204. data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
@@ -4,19 +4,6 @@ require "active_support/concern"
4
4
  require "active_support/core_ext/module/attribute_accessors"
5
5
  require "active_support/logger_thread_safe_level"
6
6
 
7
- module LoggerSilence
8
- extend ActiveSupport::Concern
9
-
10
- included do
11
- ActiveSupport::Deprecation.warn(
12
- "Including LoggerSilence is deprecated and will be removed in Rails 6.1. " \
13
- "Please use `ActiveSupport::LoggerSilence` instead"
14
- )
15
-
16
- include ActiveSupport::LoggerSilence
17
- end
18
- end
19
-
20
7
  module ActiveSupport
21
8
  module LoggerSilence
22
9
  extend ActiveSupport::Concern
@@ -27,19 +14,8 @@ module ActiveSupport
27
14
  end
28
15
 
29
16
  # Silences the logger for the duration of the block.
30
- def silence(temporary_level = Logger::ERROR)
31
- if silencer
32
- begin
33
- old_local_level = local_level
34
- self.local_level = temporary_level
35
-
36
- yield self
37
- ensure
38
- self.local_level = old_local_level
39
- end
40
- else
41
- yield self
42
- end
17
+ def silence(severity = Logger::ERROR)
18
+ silencer ? log_at(severity) { yield self } : yield(self)
43
19
  end
44
20
  end
45
21
  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,36 +17,53 @@ module ActiveSupport
21
17
  EOT
22
18
  end
23
19
 
24
- def after_initialize
25
- ActiveSupport::Deprecation.warn(
26
- "Logger don't need to call #after_initialize directly anymore. It will be deprecated without replacement in " \
27
- "Rails 6.1."
28
- )
29
- end
30
-
31
- def local_log_id
32
- Fiber.current.__id__
33
- end
34
-
35
20
  def local_level
36
- self.class.local_levels[local_log_id]
21
+ IsolatedExecutionState[:logger_thread_safe_level]
37
22
  end
38
23
 
39
24
  def local_level=(level)
40
- if level
41
- self.class.local_levels[local_log_id] = level
25
+ case level
26
+ when Integer
27
+ when Symbol
28
+ level = Logger::Severity.const_get(level.to_s.upcase)
29
+ when nil
42
30
  else
43
- self.class.local_levels.delete(local_log_id)
31
+ raise ArgumentError, "Invalid log level: #{level.inspect}"
44
32
  end
33
+ IsolatedExecutionState[:logger_thread_safe_level] = level
45
34
  end
46
35
 
47
36
  def level
48
37
  local_level || super
49
38
  end
50
39
 
40
+ # Change the thread-local level for the duration of the given block.
41
+ def log_at(level)
42
+ old_local_level, self.local_level = local_level, level
43
+ yield
44
+ ensure
45
+ self.local_level = old_local_level
46
+ end
47
+
48
+ # Redefined to check severity against #level, and thus the thread-local level, rather than +@level+.
49
+ # FIXME: Remove when the minimum Ruby version supports overriding Logger#level.
51
50
  def add(severity, message = nil, progname = nil, &block) # :nodoc:
52
- return true if @logdev.nil? || (severity || UNKNOWN) < level
53
- super
51
+ severity ||= UNKNOWN
52
+ progname ||= @progname
53
+
54
+ return true if @logdev.nil? || severity < level
55
+
56
+ if message.nil?
57
+ if block_given?
58
+ message = yield
59
+ else
60
+ message = progname
61
+ progname = @progname
62
+ end
63
+ end
64
+
65
+ @logdev.write \
66
+ format_message(format_severity(severity), Time.now, progname, message)
54
67
  end
55
68
  end
56
69
  end
@@ -2,7 +2,6 @@
2
2
 
3
3
  require "openssl"
4
4
  require "base64"
5
- require "active_support/core_ext/array/extract_options"
6
5
  require "active_support/core_ext/module/attribute_accessors"
7
6
  require "active_support/message_verifier"
8
7
  require "active_support/messages/metadata"
@@ -23,6 +22,11 @@ module ActiveSupport
23
22
  # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
24
23
  # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
25
24
  # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
25
+ # The +decrypt_and_verify+ method will raise an
26
+ # <tt>ActiveSupport::MessageEncryptor::InvalidMessage</tt> exception if the data
27
+ # provided cannot be decrypted or verified.
28
+ #
29
+ # crypt.decrypt_and_verify('not encrypted data') # => ActiveSupport::MessageEncryptor::InvalidMessage
26
30
  #
27
31
  # === Confining messages to a specific purpose
28
32
  #
@@ -85,7 +89,7 @@ module ActiveSupport
85
89
  cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false
86
90
 
87
91
  class << self
88
- def default_cipher #:nodoc:
92
+ def default_cipher # :nodoc:
89
93
  if use_authenticated_message_encryption
90
94
  "aes-256-gcm"
91
95
  else
@@ -94,7 +98,7 @@ module ActiveSupport
94
98
  end
95
99
  end
96
100
 
97
- module NullSerializer #:nodoc:
101
+ module NullSerializer # :nodoc:
98
102
  def self.load(value)
99
103
  value
100
104
  end
@@ -104,7 +108,7 @@ module ActiveSupport
104
108
  end
105
109
  end
106
110
 
107
- module NullVerifier #:nodoc:
111
+ module NullVerifier # :nodoc:
108
112
  def self.verify(value)
109
113
  value
110
114
  end
@@ -134,15 +138,13 @@ module ActiveSupport
134
138
  # * <tt>:digest</tt> - String of digest to use for signing. Default is
135
139
  # +SHA1+. Ignored when using an AEAD cipher like 'aes-256-gcm'.
136
140
  # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
137
- def initialize(secret, *signature_key_or_options)
138
- options = signature_key_or_options.extract_options!
139
- sign_secret = signature_key_or_options.first
141
+ def initialize(secret, sign_secret = nil, cipher: nil, digest: nil, serializer: nil)
140
142
  @secret = secret
141
143
  @sign_secret = sign_secret
142
- @cipher = options[:cipher] || self.class.default_cipher
143
- @digest = options[:digest] || "SHA1" unless aead_mode?
144
+ @cipher = cipher || self.class.default_cipher
145
+ @digest = digest || "SHA1" unless aead_mode?
144
146
  @verifier = resolve_verifier
145
- @serializer = options[:serializer] || Marshal
147
+ @serializer = serializer || Marshal
146
148
  end
147
149
 
148
150
  # Encrypt and sign a message. We need to sign the message in order to avoid
@@ -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,11 +104,14 @@ module ActiveSupport
103
104
 
104
105
  class InvalidSignature < StandardError; end
105
106
 
106
- def initialize(secret, options = {})
107
+ SEPARATOR = "--" # :nodoc:
108
+ SEPARATOR_LENGTH = SEPARATOR.length # :nodoc:
109
+
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 = options[:digest] || "SHA1"
110
- @serializer = options[:serializer] || Marshal
113
+ @digest = digest&.to_s || "SHA1"
114
+ @serializer = serializer || Marshal
111
115
  end
112
116
 
113
117
  # Checks if a signed message could have been generated by signing an object
@@ -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
@@ -178,14 +180,14 @@ module ActiveSupport
178
180
 
179
181
  # Generates a signed message for the provided value.
180
182
  #
181
- # The message is signed with the +MessageVerifier+'s secret. Without knowing
182
- # the secret, the original value cannot be extracted from the message.
183
+ # The message is signed with the +MessageVerifier+'s secret.
184
+ # Returns Base64-encoded message joined with the generated signature.
183
185
  #
184
186
  # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
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,11 +3,11 @@
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
- @expires_at = expires_at.is_a?(String) ? Time.iso8601(expires_at) : expires_at
10
+ @expires_at = expires_at.is_a?(String) ? parse_expires_at(expires_at) : expires_at
11
11
  end
12
12
 
13
13
  def as_json(options = {})
@@ -67,6 +67,14 @@ module ActiveSupport
67
67
  def fresh?
68
68
  @expires_at.nil? || Time.now.utc < @expires_at
69
69
  end
70
+
71
+ def parse_expires_at(expires_at)
72
+ if ActiveSupport.use_standard_json_time_format
73
+ Time.iso8601(expires_at)
74
+ else
75
+ Time.parse(expires_at)
76
+ end
77
+ end
70
78
  end
71
79
  end
72
80
  end
@@ -9,7 +9,8 @@ module ActiveSupport
9
9
  @signed, @encrypted = [], []
10
10
  end
11
11
 
12
- def rotate(kind, *args)
12
+ def rotate(kind, *args, **options)
13
+ args << options unless options.empty?
13
14
  case kind
14
15
  when :signed
15
16
  @signed << args
@@ -3,11 +3,12 @@
3
3
  module ActiveSupport
4
4
  module Messages
5
5
  module Rotator # :nodoc:
6
- def initialize(*, **options)
7
- super
6
+ def initialize(*secrets, on_rotation: nil, **options)
7
+ super(*secrets, **options)
8
8
 
9
9
  @options = options
10
10
  @rotations = []
11
+ @on_rotation = on_rotation
11
12
  end
12
13
 
13
14
  def rotate(*secrets, **options)
@@ -17,7 +18,7 @@ module ActiveSupport
17
18
  module Encryptor
18
19
  include Rotator
19
20
 
20
- def decrypt_and_verify(*args, on_rotation: nil, **options)
21
+ def decrypt_and_verify(*args, on_rotation: @on_rotation, **options)
21
22
  super
22
23
  rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature
23
24
  run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, **options) } || raise
@@ -32,7 +33,7 @@ module ActiveSupport
32
33
  module Verifier
33
34
  include Rotator
34
35
 
35
- def verified(*args, on_rotation: nil, **options)
36
+ def verified(*args, on_rotation: @on_rotation, **options)
36
37
  super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, **options) }
37
38
  end
38
39
 
@@ -46,7 +47,7 @@ module ActiveSupport
46
47
  def run_rotations(on_rotation)
47
48
  @rotations.find do |rotation|
48
49
  if message = yield(rotation) rescue next
49
- on_rotation.call if on_rotation
50
+ on_rotation&.call
50
51
  return message
51
52
  end
52
53
  end
@@ -5,8 +5,8 @@ require "active_support/core_ext/string/access"
5
5
  require "active_support/core_ext/string/behavior"
6
6
  require "active_support/core_ext/module/delegation"
7
7
 
8
- module ActiveSupport #:nodoc:
9
- module Multibyte #:nodoc:
8
+ module ActiveSupport # :nodoc:
9
+ module Multibyte # :nodoc:
10
10
  # Chars enables you to work transparently with UTF-8 encoding in the Ruby
11
11
  # String class without having extensive knowledge about the encoding. A
12
12
  # Chars object accepts a string upon initialization and proxies String
@@ -48,7 +48,7 @@ module ActiveSupport #:nodoc:
48
48
  alias to_s wrapped_string
49
49
  alias to_str wrapped_string
50
50
 
51
- delegate :<=>, :=~, :acts_like_string?, to: :wrapped_string
51
+ delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string
52
52
 
53
53
  # Creates a new Chars instance by wrapping _string_.
54
54
  def initialize(string)
@@ -59,7 +59,7 @@ module ActiveSupport #:nodoc:
59
59
  # Forward all undefined methods to the wrapped string.
60
60
  def method_missing(method, *args, &block)
61
61
  result = @wrapped_string.__send__(method, *args, &block)
62
- if /!$/.match?(method)
62
+ if method.end_with?("!")
63
63
  self if result
64
64
  else
65
65
  result.kind_of?(String) ? chars(result) : result
@@ -73,17 +73,6 @@ module ActiveSupport #:nodoc:
73
73
  @wrapped_string.respond_to?(method, include_private)
74
74
  end
75
75
 
76
- # Returns +true+ when the proxy class can handle the string. Returns
77
- # +false+ otherwise.
78
- def self.consumes?(string)
79
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
80
- ActiveSupport::Multibyte::Chars.consumes? is deprecated and will be
81
- removed from Rails 6.1. Use string.is_utf8? instead.
82
- MSG
83
-
84
- string.encoding == Encoding::UTF_8
85
- end
86
-
87
76
  # Works just like <tt>String#split</tt>, with the exception that the items
88
77
  # in the resulting list are Chars instances instead of String. This makes
89
78
  # chaining methods easier.
@@ -113,7 +102,7 @@ module ActiveSupport #:nodoc:
113
102
  #
114
103
  # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
115
104
  def reverse
116
- chars(@wrapped_string.scan(/\X/).reverse.join)
105
+ chars(@wrapped_string.grapheme_clusters.reverse.join)
117
106
  end
118
107
 
119
108
  # Limits the byte size of the string to a number of bytes without breaking
@@ -134,46 +123,18 @@ module ActiveSupport #:nodoc:
134
123
  end
135
124
  alias_method :titlecase, :titleize
136
125
 
137
- # Returns the KC normalization of the string by default. NFKC is
138
- # considered the best normalization form for passing strings to databases
139
- # and validations.
140
- #
141
- # * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
142
- # <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
143
- # ActiveSupport::Multibyte::Unicode.default_normalization_form
144
- def normalize(form = nil)
145
- form ||= Unicode.default_normalization_form
146
-
147
- # See https://www.unicode.org/reports/tr15, Table 1
148
- if alias_form = Unicode::NORMALIZATION_FORM_ALIASES[form]
149
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
150
- ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
151
- removed from Rails 6.1. Use #unicode_normalize(:#{alias_form}) instead.
152
- MSG
153
-
154
- send(:unicode_normalize, alias_form)
155
- else
156
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
157
- ActiveSupport::Multibyte::Chars#normalize is deprecated and will be
158
- removed from Rails 6.1. Use #unicode_normalize instead.
159
- MSG
160
-
161
- raise ArgumentError, "#{form} is not a valid normalization variant", caller
162
- end
163
- end
164
-
165
126
  # Performs canonical decomposition on all the characters.
166
127
  #
167
- # 'é'.length # => 2
168
- # 'é'.mb_chars.decompose.to_s.length # => 3
128
+ # 'é'.length # => 1
129
+ # 'é'.mb_chars.decompose.to_s.length # => 2
169
130
  def decompose
170
131
  chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack("U*"))
171
132
  end
172
133
 
173
134
  # Performs composition on all the characters.
174
135
  #
175
- # 'é'.length # => 3
176
- # 'é'.mb_chars.compose.to_s.length # => 2
136
+ # 'é'.length # => 1
137
+ # 'é'.mb_chars.compose.to_s.length # => 1
177
138
  def compose
178
139
  chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack("U*"))
179
140
  end
@@ -181,9 +142,9 @@ module ActiveSupport #:nodoc:
181
142
  # Returns the number of grapheme clusters in the string.
182
143
  #
183
144
  # 'क्षि'.mb_chars.length # => 4
184
- # 'क्षि'.mb_chars.grapheme_length # => 3
145
+ # 'क्षि'.mb_chars.grapheme_length # => 2
185
146
  def grapheme_length
186
- @wrapped_string.scan(/\X/).length
147
+ @wrapped_string.grapheme_clusters.length
187
148
  end
188
149
 
189
150
  # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
@@ -195,13 +156,13 @@ module ActiveSupport #:nodoc:
195
156
  chars(Unicode.tidy_bytes(@wrapped_string, force))
196
157
  end
197
158
 
198
- def as_json(options = nil) #:nodoc:
159
+ def as_json(options = nil) # :nodoc:
199
160
  to_s.as_json(options)
200
161
  end
201
162
 
202
163
  %w(reverse tidy_bytes).each do |method|
203
164
  define_method("#{method}!") do |*args|
204
- @wrapped_string = send(method, *args).to_s
165
+ @wrapped_string = public_send(method, *args).to_s
205
166
  self
206
167
  end
207
168
  end
@@ -5,55 +5,9 @@ module ActiveSupport
5
5
  module Unicode
6
6
  extend self
7
7
 
8
- # A list of all available normalization forms.
9
- # See https://www.unicode.org/reports/tr15/tr15-29.html for more
10
- # information about normalization.
11
- NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
12
-
13
- NORMALIZATION_FORM_ALIASES = { # :nodoc:
14
- c: :nfc,
15
- d: :nfd,
16
- kc: :nfkc,
17
- kd: :nfkd
18
- }
19
-
20
8
  # The Unicode version that is supported by the implementation
21
9
  UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"]
22
10
 
23
- # The default normalization used for operations that require
24
- # normalization. It can be set to any of the normalizations
25
- # in NORMALIZATION_FORMS.
26
- #
27
- # ActiveSupport::Multibyte::Unicode.default_normalization_form = :c
28
- attr_accessor :default_normalization_form
29
- @default_normalization_form = :kc
30
-
31
- # Unpack the string at grapheme boundaries. Returns a list of character
32
- # lists.
33
- #
34
- # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]]
35
- # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]]
36
- def unpack_graphemes(string)
37
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
38
- ActiveSupport::Multibyte::Unicode#unpack_graphemes is deprecated and will be
39
- removed from Rails 6.1. Use string.scan(/\X/).map(&:codepoints) instead.
40
- MSG
41
-
42
- string.scan(/\X/).map(&:codepoints)
43
- end
44
-
45
- # Reverse operation of unpack_graphemes.
46
- #
47
- # Unicode.pack_graphemes(Unicode.unpack_graphemes('क्षि')) # => 'क्षि'
48
- def pack_graphemes(unpacked)
49
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
50
- ActiveSupport::Multibyte::Unicode#pack_graphemes is deprecated and will be
51
- removed from Rails 6.1. Use array.flatten.pack("U*") instead.
52
- MSG
53
-
54
- unpacked.flatten.pack("U*")
55
- end
56
-
57
11
  # Decompose composed characters to the decomposed form.
58
12
  def decompose(type, codepoints)
59
13
  if type == :compatibility
@@ -76,7 +30,7 @@ module ActiveSupport
76
30
  # Passing +true+ will forcibly tidy all bytes, assuming that the string's
77
31
  # encoding is entirely CP1252 or ISO-8859-1.
78
32
  def tidy_bytes(string, force = false)
79
- return string if string.empty?
33
+ return string if string.empty? || string.ascii_only?
80
34
  return recode_windows1252_chars(string) if force
81
35
  string.scrub { |bad| recode_windows1252_chars(bad) }
82
36
  end
@@ -107,46 +61,6 @@ module ActiveSupport
107
61
  end
108
62
  end
109
63
 
110
- # Returns the KC normalization of the string by default. NFKC is
111
- # considered the best normalization form for passing strings to databases
112
- # and validations.
113
- #
114
- # * <tt>string</tt> - The string to perform normalization on.
115
- # * <tt>form</tt> - The form you want to normalize in. Should be one of
116
- # the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>.
117
- # Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
118
- def normalize(string, form = nil)
119
- form ||= @default_normalization_form
120
-
121
- # See https://www.unicode.org/reports/tr15, Table 1
122
- if alias_form = NORMALIZATION_FORM_ALIASES[form]
123
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
124
- ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
125
- removed from Rails 6.1. Use String#unicode_normalize(:#{alias_form}) instead.
126
- MSG
127
-
128
- string.unicode_normalize(alias_form)
129
- else
130
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
131
- ActiveSupport::Multibyte::Unicode#normalize is deprecated and will be
132
- removed from Rails 6.1. Use String#unicode_normalize instead.
133
- MSG
134
-
135
- raise ArgumentError, "#{form} is not a valid normalization variant", caller
136
- end
137
- end
138
-
139
- %w(downcase upcase swapcase).each do |method|
140
- define_method(method) do |string|
141
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
142
- ActiveSupport::Multibyte::Unicode##{method} is deprecated and
143
- will be removed from Rails 6.1. Use String methods directly.
144
- MSG
145
-
146
- string.send(method)
147
- end
148
- end
149
-
150
64
  private
151
65
  def recode_windows1252_chars(string)
152
66
  string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace)
@@ -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"