require 'resolv' require 'ipaddr' require 'uri' module Preview class UrlSafety class UnsafeUrl < StandardError; end FORBIDDEN_NETWORKS = [ '0.0.0.0/8', '10.0.0.0/8', '100.64.0.0/10', '127.0.0.0/8', '169.254.0.0/16', '172.16.0.0/12', '192.0.0.0/24', '192.0.2.0/24', '192.168.0.0/16', '198.18.0.0/15', '198.51.100.0/24', '203.0.113.0/24', '224.0.0.0/4', '240.0.0.0/4', '::/128', '::1/128', 'fc00::/7', 'fe80::/10', 'ff00::/8', '2001:db8::/32', '::ffff:0:0/96' ].map { IPAddr.new(_1) }.freeze def self.validate(raw_url) value = raw_url.to_s.strip if value.match?(/\A[a-z][a-z0-9+\-.]*:/i) unless value.match?(/\Ahttps?:\/\//i) raise UnsafeUrl, 'http または https の URL を指定してください.' end else value = "http://#{ value }" end uri = URI.parse(value) unless ['http', 'https'].include?(uri.scheme&.downcase) && uri.host.present? raise UnsafeUrl, 'http または https の URL を指定してください.' end raise UnsafeUrl, 'userinfo つき URL は使用できません.' if uri.userinfo.present? addresses = Resolv.getaddresses(uri.host) raise UnsafeUrl, 'URL のホストを解決できません.' if addresses.empty? parsed_addresses = addresses.map { IPAddr.new(_1) } if parsed_addresses.any? { |address| forbidden?(address) } raise UnsafeUrl, '安全でない接続先は使用できません.' end [uri, parsed_addresses.map(&:to_s)] rescue Resolv::ResolvError raise UnsafeUrl, 'URL のホストを解決できません.' rescue URI::InvalidURIError, IPAddr::InvalidAddressError raise UnsafeUrl, 'URL が不正です.' end def self.forbidden?(address) FORBIDDEN_NETWORKS.any? { _1.include?(address) } end private_class_method :forbidden? end end