diff --git a/CHANGELOG b/CHANGELOG index 0f944e30..80f857a6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,7 @@ = master +* Do not update template mtime when there is an error reloading templates in the render plugin (jeremyevans) + * Add hmac_paths plugin for preventing path enumeration and supporting access control (jeremyevans) = 3.78.0 (2024-03-13) diff --git a/lib/roda/plugins/render.rb b/lib/roda/plugins/render.rb index 2aebbc99..81edcbb9 100644 --- a/lib/roda/plugins/render.rb +++ b/lib/roda/plugins/render.rb @@ -335,8 +335,13 @@ def initialize(roda_class, opts, template_opts) # If the template file exists and the modification time has # changed, rebuild the template file, then call render on it. def render(*args, &block) - modified? - @template.render(*args, &block) + res = nil + modified = false + if_modified do + res = @template.render(*args, &block) + modified = true + end + modified ? res : @template.render(*args, &block) end # Return when the template was last modified. If the template depends on any @@ -352,20 +357,18 @@ def template_last_modified # If the template file has been updated, return true and update # the template object and the modification time. Other return false. - def modified? + def if_modified begin mtime = template_last_modified rescue # ignore errors else if mtime != @mtime - @mtime = mtime reset_template - return true + yield + @mtime = mtime end end - - false end if COMPILED_METHOD_SUPPORT @@ -375,13 +378,13 @@ def define_compiled_method(roda_class, method_name, locals_keys=EMPTY_ARRAY) mod = roda_class::RodaCompiledTemplates internal_method_name = :"_#{method_name}" begin - mod.send(:define_method, internal_method_name, send(:compiled_method, locals_keys, roda_class)) + mod.send(:define_method, internal_method_name, compiled_method(locals_keys, roda_class)) rescue ::NotImplementedError return false end mod.send(:private, internal_method_name) - mod.send(:define_method, method_name, &compiled_method_lambda(self, roda_class, internal_method_name, locals_keys)) + mod.send(:define_method, method_name, &compiled_method_lambda(roda_class, internal_method_name, locals_keys)) mod.send(:private, method_name) method_name @@ -397,10 +400,11 @@ def compiled_method(locals_keys=EMPTY_ARRAY, roda_class=nil) # Return the lambda used to define the compiled template method. This # is separated into its own method so the lambda does not capture any # unnecessary local variables - def compiled_method_lambda(template, roda_class, method_name, locals_keys=EMPTY_ARRAY) + def compiled_method_lambda(roda_class, method_name, locals_keys=EMPTY_ARRAY) mod = roda_class::RodaCompiledTemplates + template = self lambda do |locals, &block| - if template.modified? + template.if_modified do mod.send(:define_method, method_name, Render.tilt_template_compiled_method(template, locals_keys, roda_class)) mod.send(:private, method_name) end diff --git a/spec/plugin/render_spec.rb b/spec/plugin/render_spec.rb index c4ab0c7f..533178f1 100644 --- a/spec/plugin/render_spec.rb +++ b/spec/plugin/render_spec.rb @@ -138,6 +138,30 @@ end end + it "does not update mtime if there was an error rebuilding the template" do + app(:bare) do + plugin :render, :views=>"./spec", :cache=>false + + route do |r| + @a = 'a' + render(iv) + end + end + + t = Time.now + body.strip.must_equal "a" + + content = File.binread(file) + File.binwrite(file, content + "<% end %>") + File.utime(t, t+1, file) + proc{body}.must_raise SyntaxError + proc{body}.must_raise SyntaxError + + File.binwrite(file, content + "b") + File.utime(t, t+1, file) + body.gsub("\n", '').must_equal "ab" + end + it "does not check mtime if :cache render option is used" do app(:bare) do plugin :render, :views=>"./spec", :cache=>false