diff --git a/src/grizzly/common/bugzilla.py b/src/grizzly/common/bugzilla.py index d385de22..1dda05af 100644 --- a/src/grizzly/common/bugzilla.py +++ b/src/grizzly/common/bugzilla.py @@ -82,6 +82,14 @@ def _fetch_attachments( or attachment.file_name.split(".")[-1] in ignore ): continue + target = (self._data / attachment.file_name).resolve() + # check file name + if target.is_dir() or not target.is_relative_to(self._data): + LOG.debug( + "bug %d: skipping attachment %r", self._bug.id, attachment.file_name + ) + continue + # decode data try: data = b64decode(attachment.data or "") except binascii.Error as exc: @@ -89,7 +97,7 @@ def _fetch_attachments( "Failed to decode attachment: %r (%s)", attachment.file_name, exc ) continue - (self._data / attachment.file_name).write_bytes(data) + target.write_bytes(data) def _unpack_archives(self) -> None: """Unpack and remove archives. diff --git a/src/grizzly/common/test_bugzilla.py b/src/grizzly/common/test_bugzilla.py index ba70e0f9..212c5414 100644 --- a/src/grizzly/common/test_bugzilla.py +++ b/src/grizzly/common/test_bugzilla.py @@ -3,15 +3,28 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # pylint: disable=protected-access from base64 import b64encode +from pathlib import Path from zipfile import ZipFile from bugsy import Attachment, Bug, BugsyException -from pytest import mark +from pytest import fixture, mark from requests.exceptions import ConnectionError as RequestsConnectionError from .bugzilla import BugzillaBug +@fixture(autouse=True) +def tmp_path_grz_tmp(tmp_path, mocker): + """Provide an alternate working directory for testing.""" + + def _grz_tmp(*subdir): + path = Path(tmp_path, "grizzly", *subdir) + path.mkdir(parents=True, exist_ok=True) + return path + + mocker.patch("grizzly.common.bugzilla.grz_tmp", _grz_tmp) + + def test_bugzilla_01(mocker): """test BugzillaBug._fetch_attachments()""" bug = mocker.Mock(spec=Bug, id=123) @@ -47,11 +60,28 @@ def test_bugzilla_01(mocker): file_name="broken.html", data=b"bad-b64", ), + # invalid file name + mocker.Mock( + spec=Attachment, + is_obsolete=False, + content_type="text/html", + file_name=".", + data=b64encode(b"foo"), + ), + # invalid file name + mocker.Mock( + spec=Attachment, + is_obsolete=False, + content_type="text/html", + file_name="../escape.html", + data=b64encode(b"foo"), + ), ) with BugzillaBug(bug) as bz_bug: assert len(tuple(bz_bug.path.iterdir())) == 1 assert (bz_bug.path / "test.html").is_file() assert (bz_bug.path / "test.html").read_text() == "foo" + assert not (bz_bug.path / ".." / "escape.html").is_file() @mark.parametrize(