Skip to content

Commit

Permalink
Merge pull request #28118 from code-dot-org/markdown-partials-in-pegasus
Browse files Browse the repository at this point in the history
Add new "partials" syntax for pegasus templates
  • Loading branch information
Hamms committed Jun 13, 2019
2 parents 9dbaf37 + 39fe868 commit fb0e17c
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 2 deletions.
12 changes: 11 additions & 1 deletion pegasus/helper_modules/multiple_extname_file_utils.rb
Expand Up @@ -24,6 +24,15 @@ def self.file_has_only_extnames(filename, extnames)
(extnames & file_extnames).length == file_extnames.length
end

# Returns true if and only if the given path can be found inside the given
# directory in the filesystem.
#
# Used to make sure that tricky URIs with ".." can't load files
# from outside our template hierarchy
def self.directory_contains_path(directory, path)
File.expand_path(path).start_with?(File.expand_path(directory))
end

# Find all files of a given name in a given directory that use only the given
# extnames
#
Expand All @@ -33,7 +42,8 @@ def self.file_has_only_extnames(filename, extnames)
def self.find_with_extnames(dir, name, extnames)
target_name = File.join(dir, name)
Dir.glob(target_name + ".*").select do |filename|
file_has_only_extnames(filename.sub(target_name, 'name'), extnames)
directory_contains_path(dir, filename) &&
file_has_only_extnames(filename.sub(target_name, 'name'), extnames)
end
end
end
21 changes: 20 additions & 1 deletion pegasus/router.rb
Expand Up @@ -102,7 +102,7 @@ def self.set_max_age(type, default)
set :read_only, CDO.read_only
set :not_found_extnames, ['.not_found', '.404']
set :redirect_extnames, ['.redirect', '.moved', '.found', '.301', '.302']
set :template_extnames, ['.erb', '.haml', '.html', '.md']
set :template_extnames, ['.erb', '.haml', '.html', '.md', '.partial']
set :non_static_extnames,
settings.not_found_extnames +
settings.redirect_extnames +
Expand Down Expand Up @@ -349,6 +349,23 @@ def document(path)
raise
end

def render_partials(template_content)
# Template types that do not have thier own way of rendering partials
# (ie, markdown) can include other partials with the syntax:
#
# {{ path/to/partial }}
#
# Because such content can be translated, we want to make sure that if a
# translator accidentally translates the path to the template, we simply
# render nothing rather than throwing an error
template_content.
gsub(/{{([^}]*)}}/) do
view($1.strip)
rescue
''
end
end

def preprocess_markdown(markdown_content)
markdown_content.gsub(/```/, "```\n")
end
Expand Down Expand Up @@ -500,6 +517,8 @@ def render_(body, path=nil, line=0, locals={})
when '.md'
preprocessed = preprocess_markdown result
result = markdown preprocessed, options
when '.partial'
result = render_partials(result)
when '.redirect', '.moved', '.301'
result = redirect erb(result, options), 301
when '.found', '.302'
Expand Down
@@ -0,0 +1,3 @@
# Partials protect against directory transversal

{{ ../public/test_md }}
@@ -0,0 +1,3 @@
# Markdown With Partials

{{ partial }}
1 change: 1 addition & 0 deletions pegasus/test/fixtures/sites/code.org/views/partial.md
@@ -0,0 +1 @@
## Partial Content
8 changes: 8 additions & 0 deletions pegasus/test/test_multiple_extname_file_utils.rb
Expand Up @@ -39,4 +39,12 @@ def test_find_with_extnames

refute_empty MultipleExtnameFileUtils.find_with_extnames(dir, "public/test_view", [".md"])
end

def test_find_with_extnames_only_subdirectory
dir = File.join(File.dirname(__FILE__), "fixtures/sites/code.org/public/")
subdir = File.join(dir, "folder")
refute_empty MultipleExtnameFileUtils.find_with_extnames(dir, "test_md", [".md"])
refute_empty MultipleExtnameFileUtils.find_with_extnames(subdir, "index", [".md"])
assert_empty MultipleExtnameFileUtils.find_with_extnames(subdir, "../test_md", [".md"])
end
end
16 changes: 16 additions & 0 deletions pegasus/test/test_router.rb
Expand Up @@ -50,6 +50,22 @@ def test_haml_error
assert_equal 10, line.to_i
end

def test_markdown_partial
path = '/test_md_partials'
resp = get(path)
assert_equal 200, resp.status, path
assert_match "<h1>Markdown With Partials</h1>", resp.body
assert_match "<h2>Partial Content</h2>", resp.body
end

def test_markdown_partial_only_templates
path = '/test_md_partial_only_templates'
resp = get(path)
assert_equal 200, resp.status, path
assert_match "<h1>Partials protect against directory transversal</h1>", resp.body
refute_match "<h3>Hello</h3>", resp.body
end

def test_view
assert_equal 200, get('/test_view').status
end
Expand Down

0 comments on commit fb0e17c

Please sign in to comment.