diff --git a/app/serializers/api/product_serializer.rb b/app/serializers/api/product_serializer.rb index 9223a797d8..d97f9967b2 100644 --- a/app/serializers/api/product_serializer.rb +++ b/app/serializers/api/product_serializer.rb @@ -1,8 +1,6 @@ require "open_food_network/scope_variant_to_hub" class Api::ProductSerializer < ActiveModel::Serializer - include ActionView::Helpers::SanitizeHelper - attributes :id, :name, :permalink, :meta_keywords attributes :group_buy, :notes, :description, :description_html attributes :properties_with_values, :price @@ -18,14 +16,12 @@ class Api::ProductSerializer < ActiveModel::Serializer # return an unformatted descripton def description - strip_tags object.description&.strip + sanitizer.strip_content(object.description) end # return a sanitized html description def description_html - d = sanitize(object.description, tags: ["p", "b", "strong", "em", "i", "a", "u"], - attributes: ["href", "target"]) - d.to_s.html_safe + sanitizer.sanitize_content(object.description)&.html_safe end def properties_with_values @@ -47,4 +43,10 @@ class Api::ProductSerializer < ActiveModel::Serializer object.master.price_with_fees(options[:current_distributor], options[:current_order_cycle]) end end + + private + + def sanitizer + @sanitizer ||= ContentSanitizer.new + end end diff --git a/app/services/content_sanitizer.rb b/app/services/content_sanitizer.rb new file mode 100644 index 0000000000..66db2879fd --- /dev/null +++ b/app/services/content_sanitizer.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +# Sanitizes and cleans up user-provided content that may contain tags, special characters, etc. + +class ContentSanitizer + include ActionView::Helpers::SanitizeHelper + + ALLOWED_TAGS = ["p", "b", "strong", "em", "i", "a", "u"].freeze + ALLOWED_ATTRIBUTES = ["href", "target"].freeze + FILTERED_CHARACTERS = { + "&amp;" => "&", + "&" => "&", + " " => " " + }.freeze + + def strip_content(content) + return unless content.present? + + content = strip_tags(content.to_s.strip) + + filter_characters(content) + end + + def sanitize_content(content) + return unless content.present? + + content = sanitize(content.to_s, tags: ALLOWED_TAGS, attributes: ALLOWED_ATTRIBUTES) + + filter_characters(content) + end + + private + + def filter_characters(content) + FILTERED_CHARACTERS.each do |character, sub| + content = content.gsub(character, sub) + end + content + end +end diff --git a/spec/services/content_sanitizer_spec.rb b/spec/services/content_sanitizer_spec.rb new file mode 100644 index 0000000000..8e2117c1e8 --- /dev/null +++ b/spec/services/content_sanitizer_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe ContentSanitizer do + let(:service) { described_class.new } + + context "#strip_content" do + it "strips disallowed tags" do + expect(service.strip_content("I'm friendly!")).to eq("I'm friendly!") + end + + it "replaces spaces" do + expect(service.strip_content("swiss chard")).to eq("swiss chard") + end + + it "replaces ampersands" do + expect(service.strip_content("pb & j")).to eq("pb & j") + end + + it "replaces double escaped ampersands" do + expect(service.strip_content("pb &amp; j")).to eq("pb & j") + end + + it "echos nil if given nil" do + expect(service.strip_content(nil)).to be(nil) + end + end + + context "#sanitize_content" do + it "leaves bold tags" do + bold = "I'm bold" + expect(service.sanitize_content(bold)).to eq(bold) + end + + it "leaves links intact" do + link = "Bar" + expect(service.sanitize_content(link)).to eq(link) + end + + it "replaces spaces" do + expect(service.sanitize_content("swiss chard")).to eq("swiss chard") + end + + it "replaces ampersands" do + expect(service.sanitize_content("pb & j")).to eq("pb & j") + end + + it "replaces double escaped ampersands" do + expect(service.sanitize_content("pb &amp; j")).to eq("pb & j") + end + + it "echos nil if given nil" do + expect(service.sanitize_content(nil)).to be(nil) + end + end +end