diff --git a/.vscode/settings.json b/.vscode/settings.json index d258b46..536d64f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,4 +3,7 @@ "[ruby]": { "editor.defaultFormatter": "Shopify.ruby-lsp" }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, } diff --git a/README.md b/README.md index 0728e38..0fe05df 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ attributes: { # URL handling protocols to allow in specific attributes. By default, no # protocols are allowed. Use :relative in place of a protocol if you want -# to allow relative URLs sans protocol. +# to allow relative URLs sans protocol. Set to `:all` to allow any protocol. protocols: { "a" => { "href" => ["http", "https", "mailto", :relative] }, "img" => { "href" => ["http", "https"] }, diff --git a/ext/selma/src/sanitizer.rs b/ext/selma/src/sanitizer.rs index 2ec89f1..2f67c90 100644 --- a/ext/selma/src/sanitizer.rs +++ b/ext/selma/src/sanitizer.rs @@ -211,20 +211,23 @@ impl SelmaSanitizer { } Some(protocol_list) => protocol_list.push(allowed_protocol.to_string()), } - } else if allowed_protocol.is_kind_of(class::symbol()) - && allowed_protocol.inspect() == ":relative" - { - match protocol_list { - None => { - protocol_sanitizers.insert( - attr_name.to_string(), - vec!["#".to_string(), "/".to_string()], - ); - } - Some(protocol_list) => { - protocol_list.push("#".to_string()); - protocol_list.push("/".to_string()); + } else if allowed_protocol.is_kind_of(class::symbol()) { + let protocol_config = allowed_protocol.inspect(); + if protocol_config == ":relative" { + match protocol_list { + None => { + protocol_sanitizers.insert( + attr_name.to_string(), + vec!["#".to_string(), "/".to_string()], + ); + } + Some(protocol_list) => { + protocol_list.push("#".to_string()); + protocol_list.push("/".to_string()); + } } + } else if protocol_config == ":all" { + protocol_sanitizers.insert(attr_name.to_string(), vec!["all".to_string()]); } } } @@ -388,6 +391,10 @@ impl SelmaSanitizer { } fn has_allowed_protocol(protocols_allowed: &[String], attr_val: &String) -> bool { + if protocols_allowed.contains(&"all".to_string()) { + return true; + } + // FIXME: is there a more idiomatic way to do this? let mut pos: usize = 0; let mut chars = attr_val.chars(); diff --git a/lib/selma/sanitizer.rb b/lib/selma/sanitizer.rb index d802ae7..db53984 100644 --- a/lib/selma/sanitizer.rb +++ b/lib/selma/sanitizer.rb @@ -66,7 +66,12 @@ def allow_class(element, *klass) end def allow_protocol(element, attr, protos) - protos = [protos] unless protos.is_a?(Array) + if protos.is_a?(Array) + raise ArgumentError, "`:all` must be passed outside of an array" if protos.include?(:all) + else + protos = [protos] + end + set_allowed_protocols(element, attr, protos) end diff --git a/lib/selma/sanitizer/config/default.rb b/lib/selma/sanitizer/config/default.rb index 9b55bfa..b8cacd9 100644 --- a/lib/selma/sanitizer/config/default.rb +++ b/lib/selma/sanitizer/config/default.rb @@ -28,7 +28,7 @@ module Config # URL handling protocols to allow in specific attributes. By default, no # protocols are allowed. Use :relative in place of a protocol if you want - # to allow relative URLs sans protocol. + # to allow relative URLs sans protocol. Set to `:all` to allow any protocol. protocols: {}, # An Array of element names whose contents will be removed. The contents diff --git a/lib/selma/version.rb b/lib/selma/version.rb index 7f1e0d1..9028ce5 100644 --- a/lib/selma/version.rb +++ b/lib/selma/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Selma - VERSION = "0.2.2" + VERSION = "0.3.0" end diff --git a/test/selma_maliciousness_test.rb b/test/selma_maliciousness_test.rb index e4bb58f..fb74206 100644 --- a/test/selma_maliciousness_test.rb +++ b/test/selma_maliciousness_test.rb @@ -154,4 +154,17 @@ def test_that_it_raises_on_handle_text_returning_non_string Selma::Rewriter.new(sanitizer: nil, handlers: [GarbageTextOptions.new]).rewrite(frag) end end + + def test_sanitizer_expects_all_as_symbol + html = "wow!" + sanitizer = Selma::Sanitizer.new({ + elements: ["a"], + attributes: { "a" => ["href"] }, + protocols: { "a" => { "href" => [:all] } }, + }) + + assert_raises(ArgumentError) do + Selma::Rewriter.new(sanitizer: sanitizer).rewrite(html) + end + end end diff --git a/test/selma_sanitizer_elements_test.rb b/test/selma_sanitizer_elements_test.rb index d5878a7..34e0a2c 100644 --- a/test/selma_sanitizer_elements_test.rb +++ b/test/selma_sanitizer_elements_test.rb @@ -252,6 +252,24 @@ def test_should_allow_relative_urls_containing_colons_when_the_colon_is_part_of_ assert_equal("Footnote 1", Selma::Rewriter.new(sanitizer: sanitizer).rewrite(input)) end + def test_should_allow_all_protocols_if_asked + input = <<~HTML + Link + Link + Link + Link + Link + HTML + + sanitizer = Selma::Sanitizer.new({ + elements: ["a"], + attributes: { "a" => ["href"] }, + protocols: { "a" => { "href" => :all } }, + }) + + assert_equal(input, Selma::Rewriter.new(sanitizer: sanitizer).rewrite(input)) + end + def test_should_remove_the_contents_of_filtered_nodes_when_remove_contents_is_true sanitizer = Selma::Sanitizer.new({ remove_contents: true })