class Sqreen::SafeJSON

Safely dump datastructure in json (more resilient to encoding errors)

Public Class Methods

dump(data) click to toggle source
# File lib/sqreen/safe_json.rb, line 13
def self.dump(data)
  JSON.generate(data)
rescue JSON::GeneratorError, Encoding::UndefinedConversionError
  Sqreen.log.debug('Payload could not be encoded enforcing recode')
  JSON.generate(rencode_payload(data))
end
enforce_encoding(str) click to toggle source
# File lib/sqreen/safe_json.rb, line 47
def self.enforce_encoding(str)
  return str unless str.is_a?(String)
  return str if str.ascii_only?
  encoded8bit = str.encoding.name == 'ASCII-8BIT'
  return str if !encoded8bit && str.valid_encoding?
  r = str.chars.map do |v|
    if !v.valid_encoding? || (encoded8bit && !v.ascii_only?)
      v.bytes.map { |c| "\\x#{c.to_s(16).upcase}" }.join
    else
      v
    end
  end.join
  "SqBytes[#{r}]"
end
rencode_array(array, max_depth) click to toggle source
# File lib/sqreen/safe_json.rb, line 42
def self.rencode_array(array, max_depth)
  array.map! { |e| rencode_payload(e, max_depth - 1) }
  array
end
rencode_payload(obj, max_depth = 20) click to toggle source
# File lib/sqreen/safe_json.rb, line 20
def self.rencode_payload(obj, max_depth = 20)
  max_depth -= 1
  return obj if max_depth < 0
  return rencode_array(obj, max_depth) if obj.is_a?(Array)
  return enforce_encoding(obj) unless obj.is_a?(Hash)
  nobj = {}
  obj.each do |k, v|
    safe_k = rencode_payload(k, max_depth)
    nobj[safe_k] = case v
                   when Array
                     rencode_array(v, max_depth)
                   when Hash
                     rencode_payload(v, max_depth)
                   when String
                     enforce_encoding(v)
                   else # for example integers
                     v
                   end
  end
  nobj
end