lib/metasploit_data_models/base64_serializer.rb
# Provides ActiveRecord 3.1x-friendly serialization for descendants of
# ApplicationRecord. Backwards compatible with older YAML methods and
# will fall back to string decoding in the worst case
#
# @example Using default default of {}
# serialize :foo, MetasploitDataModels::Base64Serializer.new
#
# @example Overriding default to []
# serialize :bar, MetasploitDataModels::Base64Serializer.new(:default => [])
#
class MetasploitDataModels::Base64Serializer
#
# CONSTANTS
#
# The default for {#default}
DEFAULT = {}
# Deserializers for {#load}
# 1. Base64 decoding and then unmarshalling the value.
# 2. Parsing the value as YAML.
# 3. The raw value.
LOADERS = [
lambda { |serialized|
marshaled = serialized.unpack('m').first
# Load the unpacked Marshal object first
Marshal.load(marshaled)
},
lambda { |serialized|
# Support legacy YAML encoding for existing data
YAML.safe_load(serialized, permitted_classes: Rails.application.config.active_record.yaml_column_permitted_classes)
},
lambda { |serialized|
# Fall back to string decoding
serialized
}
]
#
# Methods
#
# Creates a duplicate of default value
#
# @return
def default
@default.dup
end
attr_writer :default
# Serializes the value by marshalling the value and then base64 encodes the marshaled value.
#
# @param value [Object] value to serialize
# @return [String]
def dump(value)
# Always store data back in the Marshal format
marshalled = Marshal.dump(value)
base64_encoded = [ marshalled ].pack('m')
base64_encoded
end
# @param attributes [Hash] attributes
# @option attributes [Object] :default ({}) Value to use for {#default}.
def initialize(attributes={})
attributes.assert_valid_keys(:default)
@default = attributes.fetch(:default, DEFAULT)
end
# Deserializes the value by either
# 1. Base64 decoding and then unmarshalling the value.
# 2. Parsing the value as YAML.
# 3. Returns the raw value.
#
# @param value [String] serialized value
# @return [Object]
#
# @see #default
def load(value)
loaded = nil
if value.blank?
loaded = default
else
LOADERS.each do |loader|
begin
loaded = loader.call(value)
rescue
next
else
break
end
end
end
loaded
end
end