diff --git a/lib/jekyll/tags/include.rb b/lib/jekyll/tags/include.rb index 0184a6a4..2b0d82b4 100644 --- a/lib/jekyll/tags/include.rb +++ b/lib/jekyll/tags/include.rb @@ -2,20 +2,28 @@ module Jekyll module Tags class IncludeTag < Liquid::Tag - MATCHER = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/ + SYNTAX_EXAMPLE = "{% include file.ext param='value' param2='value' %}" + + VALID_SYNTAX = /([\w-]+)\s*=\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([\w\.-]+))/ + + INCLUDES_DIR = '_includes' def initialize(tag_name, markup, tokens) super @file, @params = markup.strip.split(' ', 2); + validate_syntax + end + + def validate_syntax + validate_file_name + validate_params if @params end def parse_params(context) - validate_syntax - params = {} markup = @params - while match = MATCHER.match(markup) do + while match = VALID_SYNTAX.match(markup) do markup = markup[match.end(0)..-1] value = if match[2] @@ -31,32 +39,49 @@ module Jekyll params end - # ensure the entire markup string from start to end is valid syntax, and params are separated by spaces - def validate_syntax - full_matcher = Regexp.compile('\A\s*(?:' + MATCHER.to_s + '(?=\s|\z)\s*)*\z') - unless @params =~ full_matcher - raise SyntaxError.new <<-eos + def validate_file_name + if @file !~ /^[a-zA-Z0-9_\/\.-]+$/ || @file =~ /\.\// || @file =~ /\/\./ + raise ArgumentError.new <<-eos +Invalid syntax for include tag. File contains invalid characters or sequences: + + #{@file} + +Valid syntax: + + #{SYNTAX_EXAMPLE} + +eos + end + end + + def validate_params + full_valid_syntax = Regexp.compile('\A\s*(?:' + VALID_SYNTAX.to_s + '(?=\s|\z)\s*)*\z') + unless @params =~ full_valid_syntax + raise ArgumentError.new <<-eos Invalid syntax for include tag: #{@params} Valid syntax: - {% include file.ext param='value' param2="value" %} + #{SYNTAX_EXAMPLE} eos end end def render(context) - includes_dir = File.join(context.registers[:site].source, '_includes') - - if error = validate_file(includes_dir) + dir = File.join(context.registers[:site].source, INCLUDES_DIR) + if error = validate_dir(dir, context.registers[:site].safe) return error end - source = File.read(File.join(includes_dir, @file)) - partial = Liquid::Template.parse(source) + file = File.join(dir, @file) + if error = validate_file(dir, context.registers[:site].safe) + return error + end + + partial = Liquid::Template.parse(source(file)) context.stack do context['include'] = parse_params(context) if @params @@ -64,26 +89,28 @@ eos end end - def validate_file(includes_dir) - if File.symlink?(includes_dir) - return "Includes directory '#{includes_dir}' cannot be a symlink" + def validate_dir(dir, safe) + if File.symlink?(dir) && safe + "Includes directory '#{dir}' cannot be a symlink" end + end - if @file !~ /^[a-zA-Z0-9_\/\.-]+$/ || @file =~ /\.\// || @file =~ /\/\./ - return "Include file '#{@file}' contains invalid characters or sequences" - end - - file = File.join(includes_dir, @file) + def validate_file(file, safe) if !File.exists?(file) - return "Included file #{@file} not found in _includes directory" - elsif File.symlink?(file) - return "The included file '_includes/#{@file}' should not be a symlink" + "Included file '#{@file}' not found in '#{INCLUDES_DIR}' directory" + elsif File.symlink?(file) && safe + "The included file '#{INCLUDES_DIR}/#{@file}' should not be a symlink" end end def blank? false end + + # This method allows to modify the file content by inheriting from the class. + def source(file) + File.read(file) + end end end end diff --git a/test/test_tags.rb b/test/test_tags.rb index 5e25c1db..c58c9c7e 100644 --- a/test/test_tags.rb +++ b/test/test_tags.rb @@ -373,7 +373,7 @@ CONTENT end context "with invalid parameter syntax" do - should "throw a SyntaxError" do + should "throw a ArgumentError" do content = < 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true}) end @@ -392,7 +392,7 @@ title: Invalid parameter syntax {% include params.html params="value %} CONTENT - assert_raise SyntaxError, 'Did not raise exception on invalid "include" syntax' do + assert_raise ArgumentError, 'Did not raise exception on invalid "include" syntax' do create_post(content, {'permalink' => 'pretty', 'source' => source_dir, 'destination' => dest_dir, 'read_posts' => true}) end end