diff --git a/README.md b/README.md index 570f2b4..2644b1d 100644 --- a/README.md +++ b/README.md @@ -381,6 +381,18 @@ Names of CSS [at-rules][at-rules] to allow that may have associated blocks containing style rules. At-rules like `media` and `keyframes` fall into this category. Names should be specified in lowercase. +##### :css => :import_url_validator + +This is a `Proc` (or other callable object) that will be called and passed +the URL specified for any `@import` [at-rules][at-rules]. + +You can use this to limit what can be imported, for example something +like the following to limit `@import` to Google Fonts URLs: + +```ruby +Proc.new { |url| url.start_with?("https://fonts.googleapis.com") } +``` + ##### :css => :properties (Array or Set) Whitelist of CSS property names to allow. Names should be specified in diff --git a/lib/sanitize/css.rb b/lib/sanitize/css.rb index a118d0a..30db58c 100644 --- a/lib/sanitize/css.rb +++ b/lib/sanitize/css.rb @@ -80,6 +80,7 @@ def initialize(config = {}) @at_rules = Set.new(@config[:at_rules]) @at_rules_with_properties = Set.new(@config[:at_rules_with_properties]) @at_rules_with_styles = Set.new(@config[:at_rules_with_styles]) + @import_url_validator = @config[:import_url_validator] end # Sanitizes inline CSS style properties. @@ -219,6 +220,7 @@ def at_rule!(rule) rule[:block] = tree!(props) elsif @at_rules.include?(name) + return nil if name == "import" && !import_url_allowed?(rule) return nil if rule.has_key?(:block) else return nil @@ -227,6 +229,19 @@ def at_rule!(rule) rule end + # Passes the URL value of an @import rule to a block to ensure + # it's an allowed URL + def import_url_allowed?(rule) + return true unless @import_url_validator + + url_token = rule[:tokens].detect { |t| t[:node] == :url || t[:node] == :string } + + # don't allow @imports with no URL value + return false unless url_token && (import_url = url_token[:value]) + + @import_url_validator.call(import_url) + end + # Sanitizes a CSS property node. Returns the sanitized node, or `nil` if the # current config doesn't allow this property. def property!(prop) diff --git a/test/test_sanitize_css.rb b/test/test_sanitize_css.rb index 6dbdc01..03db975 100644 --- a/test/test_sanitize_css.rb +++ b/test/test_sanitize_css.rb @@ -325,6 +325,73 @@ ].strip end end + + describe "when validating @import rules" do + + describe "with no validation proc specified" do + before do + @scss = Sanitize::CSS.new(Sanitize::Config.merge(Sanitize::Config::RELAXED[:css], { + :at_rules => ['import'] + })) + end + + it "should allow any URL value" do + css = %[ + @import url('https://somesite.com/something.css'); + ].strip + + @scss.stylesheet(css).strip.must_equal %[ + @import url('https://somesite.com/something.css'); + ].strip + end + end + + describe "with a validation proc specified" do + before do + google_font_validator = Proc.new { |url| url.start_with?("https://fonts.googleapis.com") } + + @scss = Sanitize::CSS.new(Sanitize::Config.merge(Sanitize::Config::RELAXED[:css], { + :at_rules => ['import'], :import_url_validator => google_font_validator + })) + end + + it "should allow a google fonts url" do + css = %[ + @import 'https://fonts.googleapis.com/css?family=Indie+Flower'; + @import url('https://fonts.googleapis.com/css?family=Indie+Flower'); + ].strip + + @scss.stylesheet(css).strip.must_equal %[ + @import 'https://fonts.googleapis.com/css?family=Indie+Flower'; + @import url('https://fonts.googleapis.com/css?family=Indie+Flower'); + ].strip + end + + it "should not allow a nasty url" do + css = %[ + @import 'https://fonts.googleapis.com/css?family=Indie+Flower'; + @import 'https://nastysite.com/nasty_hax0r.css'; + @import url('https://nastysite.com/nasty_hax0r.css'); + ].strip + + @scss.stylesheet(css).strip.must_equal %[ + @import 'https://fonts.googleapis.com/css?family=Indie+Flower'; + ].strip + end + + it "should not allow a blank url" do + css = %[ + @import 'https://fonts.googleapis.com/css?family=Indie+Flower'; + @import ''; + @import url(''); + ].strip + + @scss.stylesheet(css).strip.must_equal %[ + @import 'https://fonts.googleapis.com/css?family=Indie+Flower'; + ].strip + end + end + end end end end