activesupport 5.0.7.1

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 (236) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1013 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +39 -0
  5. data/lib/active_support.rb +99 -0
  6. data/lib/active_support/all.rb +3 -0
  7. data/lib/active_support/array_inquirer.rb +44 -0
  8. data/lib/active_support/backtrace_cleaner.rb +103 -0
  9. data/lib/active_support/benchmarkable.rb +49 -0
  10. data/lib/active_support/builder.rb +6 -0
  11. data/lib/active_support/cache.rb +701 -0
  12. data/lib/active_support/cache/file_store.rb +204 -0
  13. data/lib/active_support/cache/mem_cache_store.rb +207 -0
  14. data/lib/active_support/cache/memory_store.rb +167 -0
  15. data/lib/active_support/cache/null_store.rb +41 -0
  16. data/lib/active_support/cache/strategy/local_cache.rb +172 -0
  17. data/lib/active_support/cache/strategy/local_cache_middleware.rb +44 -0
  18. data/lib/active_support/callbacks.rb +791 -0
  19. data/lib/active_support/concern.rb +142 -0
  20. data/lib/active_support/concurrency/latch.rb +26 -0
  21. data/lib/active_support/concurrency/share_lock.rb +226 -0
  22. data/lib/active_support/configurable.rb +148 -0
  23. data/lib/active_support/core_ext.rb +4 -0
  24. data/lib/active_support/core_ext/array.rb +7 -0
  25. data/lib/active_support/core_ext/array/access.rb +90 -0
  26. data/lib/active_support/core_ext/array/conversions.rb +211 -0
  27. data/lib/active_support/core_ext/array/extract_options.rb +29 -0
  28. data/lib/active_support/core_ext/array/grouping.rb +107 -0
  29. data/lib/active_support/core_ext/array/inquiry.rb +17 -0
  30. data/lib/active_support/core_ext/array/prepend_and_append.rb +7 -0
  31. data/lib/active_support/core_ext/array/wrap.rb +46 -0
  32. data/lib/active_support/core_ext/benchmark.rb +14 -0
  33. data/lib/active_support/core_ext/big_decimal.rb +1 -0
  34. data/lib/active_support/core_ext/big_decimal/conversions.rb +14 -0
  35. data/lib/active_support/core_ext/class.rb +2 -0
  36. data/lib/active_support/core_ext/class/attribute.rb +128 -0
  37. data/lib/active_support/core_ext/class/attribute_accessors.rb +4 -0
  38. data/lib/active_support/core_ext/class/subclasses.rb +41 -0
  39. data/lib/active_support/core_ext/date.rb +5 -0
  40. data/lib/active_support/core_ext/date/acts_like.rb +8 -0
  41. data/lib/active_support/core_ext/date/blank.rb +12 -0
  42. data/lib/active_support/core_ext/date/calculations.rb +143 -0
  43. data/lib/active_support/core_ext/date/conversions.rb +95 -0
  44. data/lib/active_support/core_ext/date/zones.rb +6 -0
  45. data/lib/active_support/core_ext/date_and_time/calculations.rb +335 -0
  46. data/lib/active_support/core_ext/date_and_time/compatibility.rb +14 -0
  47. data/lib/active_support/core_ext/date_and_time/zones.rb +40 -0
  48. data/lib/active_support/core_ext/date_time.rb +5 -0
  49. data/lib/active_support/core_ext/date_time/acts_like.rb +14 -0
  50. data/lib/active_support/core_ext/date_time/blank.rb +12 -0
  51. data/lib/active_support/core_ext/date_time/calculations.rb +199 -0
  52. data/lib/active_support/core_ext/date_time/compatibility.rb +16 -0
  53. data/lib/active_support/core_ext/date_time/conversions.rb +105 -0
  54. data/lib/active_support/core_ext/digest/uuid.rb +51 -0
  55. data/lib/active_support/core_ext/enumerable.rb +146 -0
  56. data/lib/active_support/core_ext/file.rb +1 -0
  57. data/lib/active_support/core_ext/file/atomic.rb +68 -0
  58. data/lib/active_support/core_ext/hash.rb +9 -0
  59. data/lib/active_support/core_ext/hash/compact.rb +24 -0
  60. data/lib/active_support/core_ext/hash/conversions.rb +262 -0
  61. data/lib/active_support/core_ext/hash/deep_merge.rb +38 -0
  62. data/lib/active_support/core_ext/hash/except.rb +22 -0
  63. data/lib/active_support/core_ext/hash/indifferent_access.rb +23 -0
  64. data/lib/active_support/core_ext/hash/keys.rb +170 -0
  65. data/lib/active_support/core_ext/hash/reverse_merge.rb +22 -0
  66. data/lib/active_support/core_ext/hash/slice.rb +48 -0
  67. data/lib/active_support/core_ext/hash/transform_values.rb +29 -0
  68. data/lib/active_support/core_ext/integer.rb +3 -0
  69. data/lib/active_support/core_ext/integer/inflections.rb +29 -0
  70. data/lib/active_support/core_ext/integer/multiple.rb +10 -0
  71. data/lib/active_support/core_ext/integer/time.rb +29 -0
  72. data/lib/active_support/core_ext/kernel.rb +4 -0
  73. data/lib/active_support/core_ext/kernel/agnostics.rb +11 -0
  74. data/lib/active_support/core_ext/kernel/concern.rb +12 -0
  75. data/lib/active_support/core_ext/kernel/debugger.rb +3 -0
  76. data/lib/active_support/core_ext/kernel/reporting.rb +43 -0
  77. data/lib/active_support/core_ext/kernel/singleton_class.rb +6 -0
  78. data/lib/active_support/core_ext/load_error.rb +31 -0
  79. data/lib/active_support/core_ext/marshal.rb +22 -0
  80. data/lib/active_support/core_ext/module.rb +12 -0
  81. data/lib/active_support/core_ext/module/aliasing.rb +74 -0
  82. data/lib/active_support/core_ext/module/anonymous.rb +28 -0
  83. data/lib/active_support/core_ext/module/attr_internal.rb +36 -0
  84. data/lib/active_support/core_ext/module/attribute_accessors.rb +212 -0
  85. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +141 -0
  86. data/lib/active_support/core_ext/module/concerning.rb +135 -0
  87. data/lib/active_support/core_ext/module/delegation.rb +216 -0
  88. data/lib/active_support/core_ext/module/deprecation.rb +23 -0
  89. data/lib/active_support/core_ext/module/introspection.rb +68 -0
  90. data/lib/active_support/core_ext/module/method_transplanting.rb +3 -0
  91. data/lib/active_support/core_ext/module/qualified_const.rb +70 -0
  92. data/lib/active_support/core_ext/module/reachable.rb +8 -0
  93. data/lib/active_support/core_ext/module/remove_method.rb +35 -0
  94. data/lib/active_support/core_ext/name_error.rb +31 -0
  95. data/lib/active_support/core_ext/numeric.rb +4 -0
  96. data/lib/active_support/core_ext/numeric/bytes.rb +64 -0
  97. data/lib/active_support/core_ext/numeric/conversions.rb +144 -0
  98. data/lib/active_support/core_ext/numeric/inquiry.rb +26 -0
  99. data/lib/active_support/core_ext/numeric/time.rb +74 -0
  100. data/lib/active_support/core_ext/object.rb +14 -0
  101. data/lib/active_support/core_ext/object/acts_like.rb +10 -0
  102. data/lib/active_support/core_ext/object/blank.rb +143 -0
  103. data/lib/active_support/core_ext/object/conversions.rb +4 -0
  104. data/lib/active_support/core_ext/object/deep_dup.rb +53 -0
  105. data/lib/active_support/core_ext/object/duplicable.rb +124 -0
  106. data/lib/active_support/core_ext/object/inclusion.rb +27 -0
  107. data/lib/active_support/core_ext/object/instance_variables.rb +28 -0
  108. data/lib/active_support/core_ext/object/json.rb +205 -0
  109. data/lib/active_support/core_ext/object/to_param.rb +1 -0
  110. data/lib/active_support/core_ext/object/to_query.rb +84 -0
  111. data/lib/active_support/core_ext/object/try.rb +146 -0
  112. data/lib/active_support/core_ext/object/with_options.rb +69 -0
  113. data/lib/active_support/core_ext/range.rb +4 -0
  114. data/lib/active_support/core_ext/range/conversions.rb +31 -0
  115. data/lib/active_support/core_ext/range/each.rb +21 -0
  116. data/lib/active_support/core_ext/range/include_range.rb +23 -0
  117. data/lib/active_support/core_ext/range/overlaps.rb +8 -0
  118. data/lib/active_support/core_ext/regexp.rb +5 -0
  119. data/lib/active_support/core_ext/securerandom.rb +23 -0
  120. data/lib/active_support/core_ext/string.rb +13 -0
  121. data/lib/active_support/core_ext/string/access.rb +104 -0
  122. data/lib/active_support/core_ext/string/behavior.rb +6 -0
  123. data/lib/active_support/core_ext/string/conversions.rb +57 -0
  124. data/lib/active_support/core_ext/string/exclude.rb +11 -0
  125. data/lib/active_support/core_ext/string/filters.rb +102 -0
  126. data/lib/active_support/core_ext/string/indent.rb +43 -0
  127. data/lib/active_support/core_ext/string/inflections.rb +244 -0
  128. data/lib/active_support/core_ext/string/inquiry.rb +13 -0
  129. data/lib/active_support/core_ext/string/multibyte.rb +53 -0
  130. data/lib/active_support/core_ext/string/output_safety.rb +260 -0
  131. data/lib/active_support/core_ext/string/starts_ends_with.rb +4 -0
  132. data/lib/active_support/core_ext/string/strip.rb +23 -0
  133. data/lib/active_support/core_ext/string/zones.rb +14 -0
  134. data/lib/active_support/core_ext/struct.rb +3 -0
  135. data/lib/active_support/core_ext/time.rb +5 -0
  136. data/lib/active_support/core_ext/time/acts_like.rb +8 -0
  137. data/lib/active_support/core_ext/time/calculations.rb +290 -0
  138. data/lib/active_support/core_ext/time/compatibility.rb +14 -0
  139. data/lib/active_support/core_ext/time/conversions.rb +67 -0
  140. data/lib/active_support/core_ext/time/marshal.rb +3 -0
  141. data/lib/active_support/core_ext/time/zones.rb +111 -0
  142. data/lib/active_support/core_ext/uri.rb +24 -0
  143. data/lib/active_support/dependencies.rb +755 -0
  144. data/lib/active_support/dependencies/autoload.rb +77 -0
  145. data/lib/active_support/dependencies/interlock.rb +55 -0
  146. data/lib/active_support/deprecation.rb +43 -0
  147. data/lib/active_support/deprecation/behaviors.rb +90 -0
  148. data/lib/active_support/deprecation/instance_delegator.rb +37 -0
  149. data/lib/active_support/deprecation/method_wrappers.rb +70 -0
  150. data/lib/active_support/deprecation/proxy_wrappers.rb +149 -0
  151. data/lib/active_support/deprecation/reporting.rb +112 -0
  152. data/lib/active_support/descendants_tracker.rb +60 -0
  153. data/lib/active_support/duration.rb +235 -0
  154. data/lib/active_support/duration/iso8601_parser.rb +122 -0
  155. data/lib/active_support/duration/iso8601_serializer.rb +51 -0
  156. data/lib/active_support/evented_file_update_checker.rb +199 -0
  157. data/lib/active_support/execution_wrapper.rb +126 -0
  158. data/lib/active_support/executor.rb +6 -0
  159. data/lib/active_support/file_update_checker.rb +157 -0
  160. data/lib/active_support/gem_version.rb +15 -0
  161. data/lib/active_support/gzip.rb +36 -0
  162. data/lib/active_support/hash_with_indifferent_access.rb +329 -0
  163. data/lib/active_support/i18n.rb +13 -0
  164. data/lib/active_support/i18n_railtie.rb +115 -0
  165. data/lib/active_support/inflections.rb +70 -0
  166. data/lib/active_support/inflector.rb +7 -0
  167. data/lib/active_support/inflector/inflections.rb +242 -0
  168. data/lib/active_support/inflector/methods.rb +390 -0
  169. data/lib/active_support/inflector/transliterate.rb +112 -0
  170. data/lib/active_support/json.rb +2 -0
  171. data/lib/active_support/json/decoding.rb +74 -0
  172. data/lib/active_support/json/encoding.rb +127 -0
  173. data/lib/active_support/key_generator.rb +71 -0
  174. data/lib/active_support/lazy_load_hooks.rb +76 -0
  175. data/lib/active_support/locale/en.yml +135 -0
  176. data/lib/active_support/log_subscriber.rb +109 -0
  177. data/lib/active_support/log_subscriber/test_helper.rb +104 -0
  178. data/lib/active_support/logger.rb +106 -0
  179. data/lib/active_support/logger_silence.rb +28 -0
  180. data/lib/active_support/logger_thread_safe_level.rb +31 -0
  181. data/lib/active_support/message_encryptor.rb +114 -0
  182. data/lib/active_support/message_verifier.rb +134 -0
  183. data/lib/active_support/multibyte.rb +21 -0
  184. data/lib/active_support/multibyte/chars.rb +231 -0
  185. data/lib/active_support/multibyte/unicode.rb +413 -0
  186. data/lib/active_support/notifications.rb +212 -0
  187. data/lib/active_support/notifications/fanout.rb +157 -0
  188. data/lib/active_support/notifications/instrumenter.rb +91 -0
  189. data/lib/active_support/number_helper.rb +368 -0
  190. data/lib/active_support/number_helper/number_converter.rb +182 -0
  191. data/lib/active_support/number_helper/number_to_currency_converter.rb +44 -0
  192. data/lib/active_support/number_helper/number_to_delimited_converter.rb +28 -0
  193. data/lib/active_support/number_helper/number_to_human_converter.rb +68 -0
  194. data/lib/active_support/number_helper/number_to_human_size_converter.rb +62 -0
  195. data/lib/active_support/number_helper/number_to_percentage_converter.rb +12 -0
  196. data/lib/active_support/number_helper/number_to_phone_converter.rb +58 -0
  197. data/lib/active_support/number_helper/number_to_rounded_converter.rb +92 -0
  198. data/lib/active_support/option_merger.rb +25 -0
  199. data/lib/active_support/ordered_hash.rb +48 -0
  200. data/lib/active_support/ordered_options.rb +81 -0
  201. data/lib/active_support/per_thread_registry.rb +58 -0
  202. data/lib/active_support/proxy_object.rb +13 -0
  203. data/lib/active_support/rails.rb +27 -0
  204. data/lib/active_support/railtie.rb +51 -0
  205. data/lib/active_support/reloader.rb +129 -0
  206. data/lib/active_support/rescuable.rb +173 -0
  207. data/lib/active_support/security_utils.rb +27 -0
  208. data/lib/active_support/string_inquirer.rb +26 -0
  209. data/lib/active_support/subscriber.rb +120 -0
  210. data/lib/active_support/tagged_logging.rb +77 -0
  211. data/lib/active_support/test_case.rb +88 -0
  212. data/lib/active_support/testing/assertions.rb +99 -0
  213. data/lib/active_support/testing/autorun.rb +5 -0
  214. data/lib/active_support/testing/constant_lookup.rb +50 -0
  215. data/lib/active_support/testing/declarative.rb +26 -0
  216. data/lib/active_support/testing/deprecation.rb +36 -0
  217. data/lib/active_support/testing/file_fixtures.rb +34 -0
  218. data/lib/active_support/testing/isolation.rb +115 -0
  219. data/lib/active_support/testing/method_call_assertions.rb +41 -0
  220. data/lib/active_support/testing/setup_and_teardown.rb +50 -0
  221. data/lib/active_support/testing/stream.rb +42 -0
  222. data/lib/active_support/testing/tagged_logging.rb +25 -0
  223. data/lib/active_support/testing/time_helpers.rb +136 -0
  224. data/lib/active_support/time.rb +18 -0
  225. data/lib/active_support/time_with_zone.rb +511 -0
  226. data/lib/active_support/values/time_zone.rb +484 -0
  227. data/lib/active_support/values/unicode_tables.dat +0 -0
  228. data/lib/active_support/version.rb +8 -0
  229. data/lib/active_support/xml_mini.rb +209 -0
  230. data/lib/active_support/xml_mini/jdom.rb +181 -0
  231. data/lib/active_support/xml_mini/libxml.rb +77 -0
  232. data/lib/active_support/xml_mini/libxmlsax.rb +82 -0
  233. data/lib/active_support/xml_mini/nokogiri.rb +81 -0
  234. data/lib/active_support/xml_mini/nokogirisax.rb +85 -0
  235. data/lib/active_support/xml_mini/rexml.rb +128 -0
  236. metadata +350 -0
@@ -0,0 +1,28 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/module/attribute_accessors'
3
+ require 'concurrent'
4
+
5
+ module LoggerSilence
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ cattr_accessor :silencer
10
+ self.silencer = true
11
+ end
12
+
13
+ # Silences the logger for the duration of the block.
14
+ def silence(temporary_level = Logger::ERROR)
15
+ if silencer
16
+ begin
17
+ old_local_level = local_level
18
+ self.local_level = temporary_level
19
+
20
+ yield self
21
+ ensure
22
+ self.local_level = old_local_level
23
+ end
24
+ else
25
+ yield self
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,31 @@
1
+ require 'active_support/concern'
2
+
3
+ module ActiveSupport
4
+ module LoggerThreadSafeLevel # :nodoc:
5
+ extend ActiveSupport::Concern
6
+
7
+ def after_initialize
8
+ @local_levels = Concurrent::Map.new(initial_capacity: 2)
9
+ end
10
+
11
+ def local_log_id
12
+ Thread.current.__id__
13
+ end
14
+
15
+ def local_level
16
+ @local_levels[local_log_id]
17
+ end
18
+
19
+ def local_level=(level)
20
+ if level
21
+ @local_levels[local_log_id] = level
22
+ else
23
+ @local_levels.delete(local_log_id)
24
+ end
25
+ end
26
+
27
+ def level
28
+ local_level || super
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,114 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+ require 'active_support/core_ext/array/extract_options'
4
+
5
+ module ActiveSupport
6
+ # MessageEncryptor is a simple way to encrypt values which get stored
7
+ # somewhere you don't trust.
8
+ #
9
+ # The cipher text and initialization vector are base64 encoded and returned
10
+ # to you.
11
+ #
12
+ # This can be used in situations similar to the <tt>MessageVerifier</tt>, but
13
+ # where you don't want users to be able to determine the value of the payload.
14
+ #
15
+ # salt = SecureRandom.random_bytes(64)
16
+ # key = ActiveSupport::KeyGenerator.new('password').generate_key(salt, 32) # => "\x89\xE0\x156\xAC..."
17
+ # crypt = ActiveSupport::MessageEncryptor.new(key) # => #<ActiveSupport::MessageEncryptor ...>
18
+ # encrypted_data = crypt.encrypt_and_sign('my secret data') # => "NlFBTTMwOUV5UlA1QlNEN2xkY2d6eThYWWh..."
19
+ # crypt.decrypt_and_verify(encrypted_data) # => "my secret data"
20
+ class MessageEncryptor
21
+ DEFAULT_CIPHER = "aes-256-cbc"
22
+
23
+ module NullSerializer #:nodoc:
24
+ def self.load(value)
25
+ value
26
+ end
27
+
28
+ def self.dump(value)
29
+ value
30
+ end
31
+ end
32
+
33
+ class InvalidMessage < StandardError; end
34
+ OpenSSLCipherError = OpenSSL::Cipher::CipherError
35
+
36
+ # Initialize a new MessageEncryptor. +secret+ must be at least as long as
37
+ # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
38
+ # bits. If you are using a user-entered secret, you can generate a suitable
39
+ # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
40
+ # derivation function.
41
+ #
42
+ # Options:
43
+ # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
44
+ # <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
45
+ # * <tt>:digest</tt> - String of digest to use for signing. Default is +SHA1+.
46
+ # * <tt>:serializer</tt> - Object serializer to use. Default is +Marshal+.
47
+ def initialize(secret, *signature_key_or_options)
48
+ options = signature_key_or_options.extract_options!
49
+ sign_secret = signature_key_or_options.first
50
+ @secret = secret
51
+ @sign_secret = sign_secret
52
+ @cipher = options[:cipher] || 'aes-256-cbc'
53
+ @verifier = MessageVerifier.new(@sign_secret || @secret, digest: options[:digest] || 'SHA1', serializer: NullSerializer)
54
+ @serializer = options[:serializer] || Marshal
55
+ end
56
+
57
+ # Encrypt and sign a message. We need to sign the message in order to avoid
58
+ # padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
59
+ def encrypt_and_sign(value)
60
+ verifier.generate(_encrypt(value))
61
+ end
62
+
63
+ # Decrypt and verify a message. We need to verify the message in order to
64
+ # avoid padding attacks. Reference: http://www.limited-entropy.com/padding-oracle-attacks.
65
+ def decrypt_and_verify(value)
66
+ _decrypt(verifier.verify(value))
67
+ end
68
+
69
+ # Given a cipher, returns the key length of the cipher to help generate the key of desired size
70
+ def self.key_len(cipher = DEFAULT_CIPHER)
71
+ OpenSSL::Cipher.new(cipher).key_len
72
+ end
73
+
74
+ private
75
+
76
+ def _encrypt(value)
77
+ cipher = new_cipher
78
+ cipher.encrypt
79
+ cipher.key = @secret
80
+
81
+ # Rely on OpenSSL for the initialization vector
82
+ iv = cipher.random_iv
83
+
84
+ encrypted_data = cipher.update(@serializer.dump(value))
85
+ encrypted_data << cipher.final
86
+
87
+ "#{::Base64.strict_encode64 encrypted_data}--#{::Base64.strict_encode64 iv}"
88
+ end
89
+
90
+ def _decrypt(encrypted_message)
91
+ cipher = new_cipher
92
+ encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)}
93
+
94
+ cipher.decrypt
95
+ cipher.key = @secret
96
+ cipher.iv = iv
97
+
98
+ decrypted_data = cipher.update(encrypted_data)
99
+ decrypted_data << cipher.final
100
+
101
+ @serializer.load(decrypted_data)
102
+ rescue OpenSSLCipherError, TypeError, ArgumentError
103
+ raise InvalidMessage
104
+ end
105
+
106
+ def new_cipher
107
+ OpenSSL::Cipher.new(@cipher)
108
+ end
109
+
110
+ def verifier
111
+ @verifier
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,134 @@
1
+ require 'base64'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/security_utils'
4
+
5
+ module ActiveSupport
6
+ # +MessageVerifier+ makes it easy to generate and verify messages which are
7
+ # signed to prevent tampering.
8
+ #
9
+ # This is useful for cases like remember-me tokens and auto-unsubscribe links
10
+ # where the session store isn't suitable or available.
11
+ #
12
+ # Remember Me:
13
+ # cookies[:remember_me] = @verifier.generate([@user.id, 2.weeks.from_now])
14
+ #
15
+ # In the authentication filter:
16
+ #
17
+ # id, time = @verifier.verify(cookies[:remember_me])
18
+ # if Time.now < time
19
+ # self.current_user = User.find(id)
20
+ # end
21
+ #
22
+ # By default it uses Marshal to serialize the message. If you want to use
23
+ # another serialization method, you can set the serializer in the options
24
+ # hash upon initialization:
25
+ #
26
+ # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
27
+ #
28
+ # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
29
+ # If you want to use a different hash algorithm, you can change it by providing
30
+ # `:digest` key as an option while initializing the verifier:
31
+ #
32
+ # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
33
+ class MessageVerifier
34
+ class InvalidSignature < StandardError; end
35
+
36
+ def initialize(secret, options = {})
37
+ raise ArgumentError, 'Secret should not be nil.' unless secret
38
+ @secret = secret
39
+ @digest = options[:digest] || 'SHA1'
40
+ @serializer = options[:serializer] || Marshal
41
+ end
42
+
43
+ # Checks if a signed message could have been generated by signing an object
44
+ # with the +MessageVerifier+'s secret.
45
+ #
46
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
47
+ # signed_message = verifier.generate 'a private message'
48
+ # verifier.valid_message?(signed_message) # => true
49
+ #
50
+ # tampered_message = signed_message.chop # editing the message invalidates the signature
51
+ # verifier.valid_message?(tampered_message) # => false
52
+ def valid_message?(signed_message)
53
+ return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
54
+
55
+ data, digest = signed_message.split("--".freeze)
56
+ data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
57
+ end
58
+
59
+ # Decodes the signed message using the +MessageVerifier+'s secret.
60
+ #
61
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
62
+ #
63
+ # signed_message = verifier.generate 'a private message'
64
+ # verifier.verified(signed_message) # => 'a private message'
65
+ #
66
+ # Returns +nil+ if the message was not signed with the same secret.
67
+ #
68
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
69
+ # other_verifier.verified(signed_message) # => nil
70
+ #
71
+ # Returns +nil+ if the message is not Base64-encoded.
72
+ #
73
+ # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d"
74
+ # verifier.verified(invalid_message) # => nil
75
+ #
76
+ # Raises any error raised while decoding the signed message.
77
+ #
78
+ # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff"
79
+ # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format
80
+ def verified(signed_message)
81
+ if valid_message?(signed_message)
82
+ begin
83
+ data = signed_message.split("--".freeze)[0]
84
+ @serializer.load(decode(data))
85
+ rescue ArgumentError => argument_error
86
+ return if argument_error.message =~ %r{invalid base64}
87
+ raise
88
+ end
89
+ end
90
+ end
91
+
92
+ # Decodes the signed message using the +MessageVerifier+'s secret.
93
+ #
94
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
95
+ # signed_message = verifier.generate 'a private message'
96
+ #
97
+ # verifier.verify(signed_message) # => 'a private message'
98
+ #
99
+ # Raises +InvalidSignature+ if the message was not signed with the same
100
+ # secret or was not Base64-encoded.
101
+ #
102
+ # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit'
103
+ # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature
104
+ def verify(signed_message)
105
+ verified(signed_message) || raise(InvalidSignature)
106
+ end
107
+
108
+ # Generates a signed message for the provided value.
109
+ #
110
+ # The message is signed with the +MessageVerifier+'s secret. Without knowing
111
+ # the secret, the original value cannot be extracted from the message.
112
+ #
113
+ # verifier = ActiveSupport::MessageVerifier.new 's3Krit'
114
+ # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772"
115
+ def generate(value)
116
+ data = encode(@serializer.dump(value))
117
+ "#{data}--#{generate_digest(data)}"
118
+ end
119
+
120
+ private
121
+ def encode(data)
122
+ ::Base64.strict_encode64(data)
123
+ end
124
+
125
+ def decode(data)
126
+ ::Base64.strict_decode64(data)
127
+ end
128
+
129
+ def generate_digest(data)
130
+ require 'openssl' unless defined?(OpenSSL)
131
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data)
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveSupport #:nodoc:
2
+ module Multibyte
3
+ autoload :Chars, 'active_support/multibyte/chars'
4
+ autoload :Unicode, 'active_support/multibyte/unicode'
5
+
6
+ # The proxy class returned when calling mb_chars. You can use this accessor
7
+ # to configure your own proxy class so you can support other encodings. See
8
+ # the ActiveSupport::Multibyte::Chars implementation for an example how to
9
+ # do this.
10
+ #
11
+ # ActiveSupport::Multibyte.proxy_class = CharsForUTF32
12
+ def self.proxy_class=(klass)
13
+ @proxy_class = klass
14
+ end
15
+
16
+ # Returns the current proxy class.
17
+ def self.proxy_class
18
+ @proxy_class ||= ActiveSupport::Multibyte::Chars
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,231 @@
1
+ require 'active_support/json'
2
+ require 'active_support/core_ext/string/access'
3
+ require 'active_support/core_ext/string/behavior'
4
+ require 'active_support/core_ext/module/delegation'
5
+
6
+ module ActiveSupport #:nodoc:
7
+ module Multibyte #:nodoc:
8
+ # Chars enables you to work transparently with UTF-8 encoding in the Ruby
9
+ # String class without having extensive knowledge about the encoding. A
10
+ # Chars object accepts a string upon initialization and proxies String
11
+ # methods in an encoding safe manner. All the normal String methods are also
12
+ # implemented on the proxy.
13
+ #
14
+ # String methods are proxied through the Chars object, and can be accessed
15
+ # through the +mb_chars+ method. Methods which would normally return a
16
+ # String object now return a Chars object so methods can be chained.
17
+ #
18
+ # 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
19
+ #
20
+ # Chars objects are perfectly interchangeable with String objects as long as
21
+ # no explicit class checks are made. If certain methods do explicitly check
22
+ # the class, call +to_s+ before you pass chars objects to them.
23
+ #
24
+ # bad.explicit_checking_method 'T'.mb_chars.downcase.to_s
25
+ #
26
+ # The default Chars implementation assumes that the encoding of the string
27
+ # is UTF-8, if you want to handle different encodings you can write your own
28
+ # multibyte string handler and configure it through
29
+ # ActiveSupport::Multibyte.proxy_class.
30
+ #
31
+ # class CharsForUTF32
32
+ # def size
33
+ # @wrapped_string.size / 4
34
+ # end
35
+ #
36
+ # def self.accepts?(string)
37
+ # string.length % 4 == 0
38
+ # end
39
+ # end
40
+ #
41
+ # ActiveSupport::Multibyte.proxy_class = CharsForUTF32
42
+ class Chars
43
+ include Comparable
44
+ attr_reader :wrapped_string
45
+ alias to_s wrapped_string
46
+ alias to_str wrapped_string
47
+
48
+ delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string
49
+
50
+ # Creates a new Chars instance by wrapping _string_.
51
+ def initialize(string)
52
+ @wrapped_string = string
53
+ @wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
54
+ end
55
+
56
+ # Forward all undefined methods to the wrapped string.
57
+ def method_missing(method, *args, &block)
58
+ result = @wrapped_string.__send__(method, *args, &block)
59
+ if method.to_s =~ /!$/
60
+ self if result
61
+ else
62
+ result.kind_of?(String) ? chars(result) : result
63
+ end
64
+ end
65
+
66
+ # Returns +true+ if _obj_ responds to the given method. Private methods
67
+ # are included in the search only if the optional second parameter
68
+ # evaluates to +true+.
69
+ def respond_to_missing?(method, include_private)
70
+ @wrapped_string.respond_to?(method, include_private)
71
+ end
72
+
73
+ # Returns +true+ when the proxy class can handle the string. Returns
74
+ # +false+ otherwise.
75
+ def self.consumes?(string)
76
+ string.encoding == Encoding::UTF_8
77
+ end
78
+
79
+ # Works just like <tt>String#split</tt>, with the exception that the items
80
+ # in the resulting list are Chars instances instead of String. This makes
81
+ # chaining methods easier.
82
+ #
83
+ # 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
84
+ def split(*args)
85
+ @wrapped_string.split(*args).map { |i| self.class.new(i) }
86
+ end
87
+
88
+ # Works like <tt>String#slice!</tt>, but returns an instance of
89
+ # Chars, or nil if the string was not modified. The string will not be
90
+ # modified if the range given is out of bounds
91
+ #
92
+ # string = 'Welcome'
93
+ # string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c">
94
+ # string # => 'Welome'
95
+ # string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo">
96
+ # string # => 'me'
97
+ def slice!(*args)
98
+ string_sliced = @wrapped_string.slice!(*args)
99
+ if string_sliced
100
+ chars(string_sliced)
101
+ end
102
+ end
103
+
104
+ # Reverses all characters in the string.
105
+ #
106
+ # 'Café'.mb_chars.reverse.to_s # => 'éfaC'
107
+ def reverse
108
+ chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
109
+ end
110
+
111
+ # Limits the byte size of the string to a number of bytes without breaking
112
+ # characters. Usable when the storage for a string is limited for some
113
+ # reason.
114
+ #
115
+ # 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
116
+ def limit(limit)
117
+ slice(0...translate_offset(limit))
118
+ end
119
+
120
+ # Converts characters in the string to uppercase.
121
+ #
122
+ # 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
123
+ def upcase
124
+ chars Unicode.upcase(@wrapped_string)
125
+ end
126
+
127
+ # Converts characters in the string to lowercase.
128
+ #
129
+ # 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
130
+ def downcase
131
+ chars Unicode.downcase(@wrapped_string)
132
+ end
133
+
134
+ # Converts characters in the string to the opposite case.
135
+ #
136
+ # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
137
+ def swapcase
138
+ chars Unicode.swapcase(@wrapped_string)
139
+ end
140
+
141
+ # Converts the first character to uppercase and the remainder to lowercase.
142
+ #
143
+ # 'über'.mb_chars.capitalize.to_s # => "Über"
144
+ def capitalize
145
+ (slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
146
+ end
147
+
148
+ # Capitalizes the first letter of every word, when possible.
149
+ #
150
+ # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
151
+ # "日本語".mb_chars.titleize # => "日本語"
152
+ def titleize
153
+ chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
154
+ end
155
+ alias_method :titlecase, :titleize
156
+
157
+ # Returns the KC normalization of the string by default. NFKC is
158
+ # considered the best normalization form for passing strings to databases
159
+ # and validations.
160
+ #
161
+ # * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
162
+ # <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
163
+ # ActiveSupport::Multibyte::Unicode.default_normalization_form
164
+ def normalize(form = nil)
165
+ chars(Unicode.normalize(@wrapped_string, form))
166
+ end
167
+
168
+ # Performs canonical decomposition on all the characters.
169
+ #
170
+ # 'é'.length # => 2
171
+ # 'é'.mb_chars.decompose.to_s.length # => 3
172
+ def decompose
173
+ chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*'))
174
+ end
175
+
176
+ # Performs composition on all the characters.
177
+ #
178
+ # 'é'.length # => 3
179
+ # 'é'.mb_chars.compose.to_s.length # => 2
180
+ def compose
181
+ chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*'))
182
+ end
183
+
184
+ # Returns the number of grapheme clusters in the string.
185
+ #
186
+ # 'क्षि'.mb_chars.length # => 4
187
+ # 'क्षि'.mb_chars.grapheme_length # => 3
188
+ def grapheme_length
189
+ Unicode.unpack_graphemes(@wrapped_string).length
190
+ end
191
+
192
+ # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
193
+ # resulting in a valid UTF-8 string.
194
+ #
195
+ # Passing +true+ will forcibly tidy all bytes, assuming that the string's
196
+ # encoding is entirely CP1252 or ISO-8859-1.
197
+ def tidy_bytes(force = false)
198
+ chars(Unicode.tidy_bytes(@wrapped_string, force))
199
+ end
200
+
201
+ def as_json(options = nil) #:nodoc:
202
+ to_s.as_json(options)
203
+ end
204
+
205
+ %w(capitalize downcase reverse tidy_bytes upcase).each do |method|
206
+ define_method("#{method}!") do |*args|
207
+ @wrapped_string = send(method, *args).to_s
208
+ self
209
+ end
210
+ end
211
+
212
+ protected
213
+
214
+ def translate_offset(byte_offset) #:nodoc:
215
+ return nil if byte_offset.nil?
216
+ return 0 if @wrapped_string == ''
217
+
218
+ begin
219
+ @wrapped_string.byteslice(0...byte_offset).unpack('U*').length
220
+ rescue ArgumentError
221
+ byte_offset -= 1
222
+ retry
223
+ end
224
+ end
225
+
226
+ def chars(string) #:nodoc:
227
+ self.class.new(string)
228
+ end
229
+ end
230
+ end
231
+ end