canvas-lms/config/initializers/safe_yaml.rb

104 lines
2.9 KiB
Ruby

# frozen_string_literal: true
#
# Copyright (C) 2015 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
# SafeYAML-like interface, but vanilla Psych
module SafeYAML
class << self
attr_accessor :permitted_classes
def whitelist_class!(*klasses)
permitted_classes.concat(klasses).uniq!
end
end
self.permitted_classes = []
whitelist_class!(
ActiveSupport::HashWithIndifferentAccess,
ActiveSupport::TimeWithZone,
ActiveSupport::TimeZone,
ActionController::Parameters,
BigDecimal,
Date,
DateTime,
Mime::Type,
Mime::NullType,
OpenObject,
OpenStruct,
Symbol,
Time,
URI::HTTP,
URI::HTTPS
)
module Psych
if ::Psych::VERSION < "4"
# load defaults to safe
def load(*args, safe: true, **kwargs)
return super(*args, **kwargs) unless safe
safe_load(*args, **kwargs)
end
def unsafe_load(*args, **kwargs)
load(*args, safe: false, **kwargs)
end
else
def load(*args, safe: true, **kwargs)
return unsafe_load(*args, **kwargs) unless safe
super(*args, aliases: true, **kwargs)
end
end
def safe_load(yaml, permitted_classes: [], **kwargs)
super(yaml, permitted_classes: permitted_classes + SafeYAML.permitted_classes, aliases: true, **kwargs)
end
end
end
Psych.singleton_class.prepend(SafeYAML::Psych)
module ScalarScannerFix
# in rubies < 2.7, Psych uses a regex to identify an integer, then strips commas and underscores,
# then checks _again_ against the regex. In 2.7, the second check was eliminated because the
# stripping was inlined in the name of optimization to avoid a string allocation. unfortunately
# this means something like 0x_ which passes the first check is not a valid number, and will
# throw an exception. this is the simplest way to catch that case without completely reverting
# the optimization
def parse_int(string)
super
rescue ArgumentError
string
end
end
Psych::ScalarScanner.prepend(ScalarScannerFix)
module YAMLSingletonFix
def revive(klass, node)
if klass < Singleton
klass.instance
elsif klass == Set
super.tap { |s| s.instance_variable_get(:@hash).default = false }
else
super
end
end
end
Psych::Visitors::ToRuby.prepend(YAMLSingletonFix)