diff --git a/README.md b/README.md index 39e501937..76cfa3c0e 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://api.travis-ci.org/Shopify/liquid.svg?branch=master)](http://travis-ci.org/Shopify/liquid) +[![Build status](https://github.com/Shopify/liquid/actions/workflows/liquid.yml/badge.svg)](https://github.com/Shopify/liquid/actions/workflows/liquid.yml) [![Inline docs](http://inch-ci.org/github/Shopify/liquid.svg?branch=master)](http://inch-ci.org/github/Shopify/liquid) # Liquid template engine diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb index 8e7d771da..842c656da 100644 --- a/lib/liquid/standardfilters.rb +++ b/lib/liquid/standardfilters.rb @@ -29,6 +29,19 @@ module StandardFilters ) STRIP_HTML_TAGS = /<.*?>/m + class << self + def try_coerce_encoding(input, encoding:) + original_encoding = input.encoding + if input.encoding != encoding + input.force_encoding(encoding) + unless input.valid_encoding? + input.force_encoding(original_encoding) + end + end + input + end + end + # @liquid_public_docs # @liquid_type filter # @liquid_category array @@ -150,7 +163,8 @@ def base64_encode(input) # @liquid_syntax string | base64_decode # @liquid_return [string] def base64_decode(input) - Base64.strict_decode64(input.to_s) + input = input.to_s + StandardFilters.try_coerce_encoding(Base64.strict_decode64(input), encoding: input.encoding) rescue ::ArgumentError raise Liquid::ArgumentError, "invalid base64 provided to base64_decode" end @@ -174,7 +188,8 @@ def base64_url_safe_encode(input) # @liquid_syntax string | base64_url_safe_decode # @liquid_return [string] def base64_url_safe_decode(input) - Base64.urlsafe_decode64(input.to_s) + input = input.to_s + StandardFilters.try_coerce_encoding(Base64.urlsafe_decode64(input), encoding: input.encoding) rescue ::ArgumentError raise Liquid::ArgumentError, "invalid base64 provided to base64_url_safe_decode" end @@ -928,9 +943,11 @@ def sum(input, property = nil, options = {}) raise_property_error(property) end - InputIterator.new(values_for_sum, context).sum do |item| + result = InputIterator.new(values_for_sum, context).sum do |item| Utils.to_number(item) end + + result.is_a?(BigDecimal) ? result.to_f : result end private diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb index 34d3c6448..0ceb12f1c 100644 --- a/test/integration/standard_filter_test.rb +++ b/test/integration/standard_filter_test.rb @@ -176,7 +176,17 @@ def test_base64_encode end def test_base64_decode - assert_equal('one two three', @filters.base64_decode('b25lIHR3byB0aHJlZQ==')) + decoded = @filters.base64_decode('b25lIHR3byB0aHJlZQ==') + assert_equal('one two three', decoded) + assert_equal(Encoding::UTF_8, decoded.encoding) + + decoded = @filters.base64_decode('4pyF') + assert_equal('✅', decoded) + assert_equal(Encoding::UTF_8, decoded.encoding) + + decoded = @filters.base64_decode("/w==") + assert_equal(Encoding::ASCII_8BIT, decoded.encoding) + assert_equal((+"\xFF").force_encoding(Encoding::ASCII_8BIT), decoded) exception = assert_raises(Liquid::ArgumentError) do @filters.base64_decode("invalidbase64") @@ -194,10 +204,21 @@ def test_base64_url_safe_encode end def test_base64_url_safe_decode + decoded = @filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8') assert_equal( 'abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890 !@#$%^&*()-=_+/?.:;[]{}\|', - @filters.base64_url_safe_decode('YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVogMTIzNDU2Nzg5MCAhQCMkJV4mKigpLT1fKy8_Ljo7W117fVx8'), + decoded, ) + assert_equal(Encoding::UTF_8, decoded.encoding) + + decoded = @filters.base64_url_safe_decode('4pyF') + assert_equal('✅', decoded) + assert_equal(Encoding::UTF_8, decoded.encoding) + + decoded = @filters.base64_url_safe_decode("_w==") + assert_equal(Encoding::ASCII_8BIT, decoded.encoding) + assert_equal((+"\xFF").force_encoding(Encoding::ASCII_8BIT), decoded) + exception = assert_raises(Liquid::ArgumentError) do @filters.base64_url_safe_decode("invalidbase64") end @@ -1285,6 +1306,42 @@ def test_sum_deep_off_by_default assert_equal(10, @filters.sum(input, "foo.quantity")) end + def test_sum_of_floats + input = [0.1, 0.2, 0.3] + assert_equal(0.6, @filters.sum(input)) + assert_template_result("0.6", "{{ input | sum }}", { "input" => input }) + end + + def test_sum_of_negative_floats + input = [0.1, 0.2, -0.3] + assert_equal(0.0, @filters.sum(input)) + assert_template_result("0.0", "{{ input | sum }}", { "input" => input }) + end + + def test_sum_with_float_strings + input = [0.1, "0.2", "0.3"] + assert_equal(0.6, @filters.sum(input)) + assert_template_result("0.6", "{{ input | sum }}", { "input" => input }) + end + + def test_sum_resulting_in_negative_float + input = [0.1, -0.2, -0.3] + assert_equal(-0.4, @filters.sum(input)) + assert_template_result("-0.4", "{{ input | sum }}", { "input" => input }) + end + + def test_sum_with_floats_and_indexable_map_values + input = [{ "quantity" => 1 }, { "quantity" => 0.2, "weight" => -0.3 }, { "weight" => 0.4 }] + assert_equal(0.0, @filters.sum(input)) + assert_equal(1.2, @filters.sum(input, "quantity")) + assert_equal(0.1, @filters.sum(input, "weight")) + assert_equal(0.0, @filters.sum(input, "subtotal")) + assert_template_result("0", "{{ input | sum }}", { "input" => input }) + assert_template_result("1.2", "{{ input | sum: 'quantity' }}", { "input" => input }) + assert_template_result("0.1", "{{ input | sum: 'weight' }}", { "input" => input }) + assert_template_result("0", "{{ input | sum: 'subtotal' }}", { "input" => input }) + end + private def with_timezone(tz)