mirror of
https://github.com/Shopify/liquid.git
synced 2026-09-20 11:20:41 -07:00
change: Render an opaque internal error by default for non-Liquid::Error (#835)
These errors may contain sensitive information, so is safer to render a more vague message by default. This is done by replacing non-Liquid::Error exceptions with a Liquid::InternalError exception with the non-Liquid::Error accessible on through the cause method. This also allows the template name and line number to be attached to the template errors. The exception_handler render option has been changed to exception_renderer since now it should raise an exception to re-raise on a liquid rendering error or return a string to be rendered where the error occurred.
This commit is contained in:
committed by
GitHub
parent
a9b84b7806
commit
f27bd619b9
@@ -3,6 +3,7 @@
|
|||||||
## 4.0.0 / not yet released / branch "master"
|
## 4.0.0 / not yet released / branch "master"
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
* Render an opaque internal error by default for non-Liquid::Error (#835) [Dylan Thacker-Smith]
|
||||||
* Ruby 2.0 support dropped (#832) [Dylan Thacker-Smith]
|
* Ruby 2.0 support dropped (#832) [Dylan Thacker-Smith]
|
||||||
* Add to_number Drop method to allow custom drops to work with number filters (#731)
|
* Add to_number Drop method to allow custom drops to work with number filters (#731)
|
||||||
* Add strict_variables and strict_filters options to detect undefined references (#691)
|
* Add strict_variables and strict_filters options to detect undefined references (#691)
|
||||||
|
|||||||
+15
-27
@@ -13,7 +13,7 @@ module Liquid
|
|||||||
# context['bob'] #=> nil class Context
|
# context['bob'] #=> nil class Context
|
||||||
class Context
|
class Context
|
||||||
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
attr_reader :scopes, :errors, :registers, :environments, :resource_limits
|
||||||
attr_accessor :exception_handler, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
attr_accessor :exception_renderer, :template_name, :partial, :global_filter, :strict_variables, :strict_filters
|
||||||
|
|
||||||
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
def initialize(environments = {}, outer_scope = {}, registers = {}, rethrow_errors = false, resource_limits = nil)
|
||||||
@environments = [environments].flatten
|
@environments = [environments].flatten
|
||||||
@@ -28,7 +28,7 @@ module Liquid
|
|||||||
@this_stack_used = false
|
@this_stack_used = false
|
||||||
|
|
||||||
if rethrow_errors
|
if rethrow_errors
|
||||||
self.exception_handler = ->(e) { raise }
|
self.exception_renderer = ->(e) { raise }
|
||||||
end
|
end
|
||||||
|
|
||||||
@interrupts = []
|
@interrupts = []
|
||||||
@@ -74,32 +74,13 @@ module Liquid
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_error(e, line_number = nil, raw_token = nil)
|
def handle_error(e, line_number = nil, raw_token = nil)
|
||||||
if e.is_a?(Liquid::Error)
|
e = internal_error unless e.is_a?(Liquid::Error)
|
||||||
e.template_name ||= template_name
|
e.template_name ||= template_name
|
||||||
e.line_number ||= line_number
|
e.line_number ||= line_number
|
||||||
end
|
|
||||||
|
|
||||||
output = nil
|
|
||||||
|
|
||||||
if exception_handler
|
|
||||||
args = [e]
|
|
||||||
args << { line_number: line_number, raw_token: raw_token } if exception_handler.arity == 2
|
|
||||||
result = exception_handler.call(*args)
|
|
||||||
case result
|
|
||||||
when Exception
|
|
||||||
e = result
|
|
||||||
if e.is_a?(Liquid::Error)
|
|
||||||
e.template_name ||= template_name
|
|
||||||
e.line_number ||= line_number
|
|
||||||
end
|
|
||||||
when String
|
|
||||||
output = result
|
|
||||||
else
|
|
||||||
raise if result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
errors.push(e)
|
errors.push(e)
|
||||||
output || Liquid::Error.render(e)
|
|
||||||
|
e = exception_renderer.call(e) if exception_renderer
|
||||||
|
e.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def invoke(method, *args)
|
def invoke(method, *args)
|
||||||
@@ -223,6 +204,13 @@ module Liquid
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def internal_error
|
||||||
|
# raise and catch to set backtrace and cause on exception
|
||||||
|
raise Liquid::InternalError, 'internal'
|
||||||
|
rescue Liquid::InternalError => exc
|
||||||
|
exc
|
||||||
|
end
|
||||||
|
|
||||||
def squash_instance_assigns_with_environments
|
def squash_instance_assigns_with_environments
|
||||||
@scopes.last.each_key do |k|
|
@scopes.last.each_key do |k|
|
||||||
@environments.each do |env|
|
@environments.each do |env|
|
||||||
|
|||||||
@@ -17,14 +17,6 @@ module Liquid
|
|||||||
str
|
str
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.render(e)
|
|
||||||
if e.is_a?(Liquid::Error)
|
|
||||||
e.to_s
|
|
||||||
else
|
|
||||||
"Liquid error: #{e}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def message_prefix
|
def message_prefix
|
||||||
@@ -60,4 +52,5 @@ module Liquid
|
|||||||
UndefinedDropMethod = Class.new(Error)
|
UndefinedDropMethod = Class.new(Error)
|
||||||
UndefinedFilter = Class.new(Error)
|
UndefinedFilter = Class.new(Error)
|
||||||
MethodOverrideError = Class.new(Error)
|
MethodOverrideError = Class.new(Error)
|
||||||
|
InternalError = Class.new(Error)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ module Liquid
|
|||||||
c = args.shift
|
c = args.shift
|
||||||
|
|
||||||
if @rethrow_errors
|
if @rethrow_errors
|
||||||
c.exception_handler = ->(e) { raise }
|
c.exception_renderer = ->(e) { raise }
|
||||||
end
|
end
|
||||||
|
|
||||||
c
|
c
|
||||||
@@ -241,7 +241,7 @@ module Liquid
|
|||||||
def apply_options_to_context(context, options)
|
def apply_options_to_context(context, options)
|
||||||
context.add_filters(options[:filters]) if options[:filters]
|
context.add_filters(options[:filters]) if options[:filters]
|
||||||
context.global_filter = options[:global_filter] if options[:global_filter]
|
context.global_filter = options[:global_filter] if options[:global_filter]
|
||||||
context.exception_handler = options[:exception_handler] if options[:exception_handler]
|
context.exception_renderer = options[:exception_renderer] if options[:exception_renderer]
|
||||||
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
context.strict_variables = options[:strict_variables] if options[:strict_variables]
|
||||||
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
context.strict_filters = options[:strict_filters] if options[:strict_filters]
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -202,22 +202,26 @@ class ErrorHandlingTest < Minitest::Test
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_exception_handler_with_string_result
|
def test_default_exception_renderer_with_internal_error
|
||||||
template = Liquid::Template.parse('This is an argument error: {{ errors.argument_error }}')
|
|
||||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: ->(e) { '' })
|
|
||||||
assert_equal 'This is an argument error: ', output
|
|
||||||
assert_equal [ArgumentError], template.errors.map(&:class)
|
|
||||||
end
|
|
||||||
|
|
||||||
class InternalError < Liquid::Error
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_exception_handler_with_exception_result
|
|
||||||
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
||||||
handler = ->(e) { e.is_a?(Liquid::Error) ? e : InternalError.new('internal') }
|
|
||||||
output = template.render({ 'errors' => ErrorDrop.new }, exception_handler: handler)
|
output = template.render({ 'errors' => ErrorDrop.new })
|
||||||
|
|
||||||
assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
|
assert_equal 'This is a runtime error: Liquid error (line 1): internal', output
|
||||||
assert_equal [InternalError], template.errors.map(&:class)
|
assert_equal [Liquid::InternalError], template.errors.map(&:class)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_exception_renderer_exposing_non_liquid_error
|
||||||
|
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
|
||||||
|
exceptions = []
|
||||||
|
handler = ->(e) { exceptions << e; e.cause }
|
||||||
|
|
||||||
|
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
|
||||||
|
|
||||||
|
assert_equal 'This is a runtime error: runtime error', output
|
||||||
|
assert_equal [Liquid::InternalError], exceptions.map(&:class)
|
||||||
|
assert_equal exceptions, template.errors
|
||||||
|
assert_equal '#<RuntimeError: runtime error>', exceptions.first.cause.inspect
|
||||||
end
|
end
|
||||||
|
|
||||||
class TestFileSystem
|
class TestFileSystem
|
||||||
|
|||||||
@@ -215,16 +215,20 @@ class TemplateTest < Minitest::Test
|
|||||||
assert_equal 'ruby error in drop', e.message
|
assert_equal 'ruby error in drop', e.message
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_exception_handler_doesnt_reraise_if_it_returns_false
|
def test_exception_renderer_that_returns_string
|
||||||
exception = nil
|
exception = nil
|
||||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; false })
|
handler = ->(e) { exception = e; '<!-- error -->' }
|
||||||
|
|
||||||
|
output = Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: handler)
|
||||||
|
|
||||||
assert exception.is_a?(Liquid::ZeroDivisionError)
|
assert exception.is_a?(Liquid::ZeroDivisionError)
|
||||||
|
assert_equal '<!-- error -->', output
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_exception_handler_does_reraise_if_it_returns_true
|
def test_exception_renderer_that_raises
|
||||||
exception = nil
|
exception = nil
|
||||||
assert_raises(Liquid::ZeroDivisionError) do
|
assert_raises(Liquid::ZeroDivisionError) do
|
||||||
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_handler: ->(e) { exception = e; true })
|
Template.parse("{{ 1 | divided_by: 0 }}").render({}, exception_renderer: ->(e) { exception = e; raise })
|
||||||
end
|
end
|
||||||
assert exception.is_a?(Liquid::ZeroDivisionError)
|
assert exception.is_a?(Liquid::ZeroDivisionError)
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user