37 lines
1 KiB
Ruby
37 lines
1 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
# HtmlSafetyValidator
|
||
|
#
|
||
|
# Validates that a value does not contain HTML
|
||
|
# or other unsafe content that could lead to XSS.
|
||
|
# Relies on Rails HTML Sanitizer:
|
||
|
# https://github.com/rails/rails-html-sanitizer
|
||
|
#
|
||
|
# Example:
|
||
|
#
|
||
|
# class Group < ActiveRecord::Base
|
||
|
# validates :name, presence: true, html_safety: true
|
||
|
# end
|
||
|
#
|
||
|
class HtmlSafetyValidator < ActiveModel::EachValidator
|
||
|
def validate_each(record, attribute, value)
|
||
|
return if value.blank? || safe_value?(value)
|
||
|
|
||
|
record.errors.add(attribute, self.class.error_message)
|
||
|
end
|
||
|
|
||
|
def self.error_message
|
||
|
_("cannot contain HTML/XML tags, including any word between angle brackets (<,>).")
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
# The `FullSanitizer` encodes ampersands as the HTML entity name.
|
||
|
# This isn't particularly necessary for preventing XSS so the ampersand
|
||
|
# is pre-encoded to avoid it being flagged in the comparison.
|
||
|
def safe_value?(text)
|
||
|
pre_encoded_text = text.gsub('&', '&')
|
||
|
Rails::Html::FullSanitizer.new.sanitize(pre_encoded_text) == pre_encoded_text
|
||
|
end
|
||
|
end
|