Skip to content

Commit

Permalink
Merge pull request from GHSA-jh85-wwv9-24hv
Browse files Browse the repository at this point in the history
* Add `restrict_base_path` and make it the default

New option to restrict snippets to be actual children of the base path
for a more sane default.

* Update grammar
  • Loading branch information
facelessuser authored May 14, 2023
1 parent a8fb966 commit b7bb487
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 9 deletions.
6 changes: 6 additions & 0 deletions docs/src/markdown/about/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changelog

## 10.0

- **Break**: Snippets: snippets will restrict snippets to ensure they are under the `base_path` preventing snippets
relative to the `base_path` but not explicitly under it. `restrict_base_path` can be set to `False` for legacy
behavior.

## 9.11

- **NEW**: Emoji: Update to new CDN and use Twemoji 14.1.2.
Expand Down
6 changes: 6 additions & 0 deletions docs/src/markdown/extensions/snippets.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

## Overview

/// warning | Not Meant for User Facing Sites
Snippets is meant to make including snippets in documentation easier, but it should not be used for user facing sites
that take and parse user content dynamically.
///

Snippets is an extension to insert markdown or HTML snippets into another markdown file. Snippets is great for
situations where you have content you need to insert into multiple documents. For instance, this document keeps all its
hyperlinks in a separate file and then includes those hyperlinks at the bottom of a document via Snippets. If a link
Expand Down Expand Up @@ -260,3 +265,4 @@ Option | Type | Default | Description
`url_timeout` | float | `#!py3 10.0` | Passes an arbitrary timeout in seconds to URL requestor. By default this is set to 10 seconds.
`url_request_headers` | {string:string} | `#!py3 {}` | Passes arbitrary headers to URL requestor. By default this is set to empty map.
`dedent_subsections` | bool | `#!py3 False` | Remove any common leading whitespace from every line in text of a subsection that is inserted via "sections" or by "lines".
`restrict_base_path` | bool | `#!py True` | Ensure that the specified snippets are children of the specified base path(s). This prevents a path relative to the base path, but not explicitly a child of the base path.
2 changes: 1 addition & 1 deletion pymdownx/__meta__.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,5 +185,5 @@ def parse_version(ver, pre=False):
return Version(major, minor, micro, release, pre, post, dev)


__version_info__ = Version(9, 11, 0, "final")
__version_info__ = Version(10, 0, 0, "final")
__version__ = __version_info__._get_canonical()
25 changes: 17 additions & 8 deletions pymdownx/snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,8 @@ def __init__(self, config, md):
base = config.get('base_path')
if isinstance(base, str):
base = [base]
self.base_path = base
self.base_path = [os.path.abspath(b) for b in base]
self.restrict_base_path = config['restrict_base_path']
self.encoding = config.get('encoding')
self.check_paths = config.get('check_paths')
self.auto_append = config.get('auto_append')
Expand Down Expand Up @@ -159,18 +160,22 @@ def get_snippet_path(self, path):
for base in self.base_path:
if os.path.exists(base):
if os.path.isdir(base):
filename = os.path.join(base, path)
if self.restrict_base_path:
filename = os.path.abspath(os.path.join(base, path))
# If the absolute path is no longer under the specified base path, reject the file
if not os.path.samefile(base, os.path.dirname(filename)):
continue
else:
filename = os.path.join(base, path)
if os.path.exists(filename):
snippet = filename
break
else:
basename = os.path.basename(base)
dirname = os.path.dirname(base)
if basename.lower() == path.lower():
filename = os.path.join(dirname, path)
if os.path.exists(filename):
snippet = filename
break
filename = os.path.join(dirname, path)
if os.path.exists(filename) and os.path.samefile(filename, base):
snippet = filename
break
return snippet

@functools.lru_cache()
Expand Down Expand Up @@ -367,6 +372,10 @@ def __init__(self, *args, **kwargs):

self.config = {
'base_path': [["."], "Base path for snippet paths - Default: [\".\"]"],
'restrict_base_path': [
True,
"Restrict snippet paths such that they are under the base paths - Default: True"
],
'encoding': ["utf-8", "Encoding of snippets - Default: \"utf-8\""],
'check_paths': [False, "Make the build fail if a snippet can't be found - Default: \"False\""],
"auto_append": [
Expand Down
1 change: 1 addition & 0 deletions tests/test_extensions/_snippets/nested/nested.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Snippet
57 changes: 57 additions & 0 deletions tests/test_extensions/test_snippets.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,63 @@ def test_user(self):
)


class TestSnippetsNested(util.MdCase):
"""Test nested restriction."""

extension = [
'pymdownx.snippets',
]

extension_configs = {
'pymdownx.snippets': {
'base_path': os.path.join(BASE, '_snippets', 'nested'),
'check_paths': True
}
}

def test_restricted(self):
"""Test file restriction."""

with self.assertRaises(SnippetMissingError):
self.check_markdown(
R'''
--8<-- "../b.txt"
''',
'''
<p>Snippet</p>
''',
True
)


class TestSnippetsNestedUnrestricted(util.MdCase):
"""Test nested no bounds."""

extension = [
'pymdownx.snippets',
]

extension_configs = {
'pymdownx.snippets': {
'base_path': os.path.join(BASE, '_snippets', 'nested'),
'restrict_base_path': False
}
}

def test_restricted(self):
"""Test file restriction."""

self.check_markdown(
R'''
--8<-- "../b.txt"
''',
'''
<p>Snippet</p>
''',
True
)


class TestSnippetsAutoAppend(util.MdCase):
"""Test snippet file case."""

Expand Down

0 comments on commit b7bb487

Please sign in to comment.