|
|
ed50e1 |
From 7917dbda14ef64a5e7fdea48383a266577484ac8 Mon Sep 17 00:00:00 2001
|
|
|
ed50e1 |
From: Tomas Orsava <torsava@redhat.com>
|
|
|
ed50e1 |
Date: Wed, 19 Aug 2020 12:51:16 +0200
|
|
|
ed50e1 |
Subject: [PATCH 2/2] FIX #6413 pip install <url> allow directory traversal
|
|
|
ed50e1 |
(tests)
|
|
|
ed50e1 |
|
|
|
ed50e1 |
---
|
|
|
ed50e1 |
tests/unit/test_download.py | 85 +++++++++++++++++++++++++++++++++++++
|
|
|
ed50e1 |
1 file changed, 85 insertions(+)
|
|
|
ed50e1 |
|
|
|
ed50e1 |
diff --git a/tests/unit/test_download.py b/tests/unit/test_download.py
|
|
|
ed50e1 |
index ee4b11c..15f99ec 100644
|
|
|
ed50e1 |
--- a/tests/unit/test_download.py
|
|
|
ed50e1 |
+++ b/tests/unit/test_download.py
|
|
|
ed50e1 |
@@ -1,5 +1,6 @@
|
|
|
ed50e1 |
import hashlib
|
|
|
ed50e1 |
import os
|
|
|
ed50e1 |
+import sys
|
|
|
ed50e1 |
from io import BytesIO
|
|
|
ed50e1 |
from shutil import rmtree, copy
|
|
|
ed50e1 |
from tempfile import mkdtemp
|
|
|
ed50e1 |
@@ -13,6 +14,7 @@ import pip
|
|
|
ed50e1 |
from pip.exceptions import HashMismatch
|
|
|
ed50e1 |
from pip.download import (
|
|
|
ed50e1 |
PipSession, SafeFileCache, path_to_url, unpack_http_url, url_to_path,
|
|
|
ed50e1 |
+ _download_http_url, parse_content_disposition, sanitize_content_filename,
|
|
|
ed50e1 |
unpack_file_url,
|
|
|
ed50e1 |
)
|
|
|
ed50e1 |
from pip.index import Link
|
|
|
ed50e1 |
@@ -123,6 +125,89 @@ def test_unpack_http_url_bad_downloaded_checksum(mock_unpack_file):
|
|
|
ed50e1 |
rmtree(download_dir)
|
|
|
ed50e1 |
|
|
|
ed50e1 |
|
|
|
ed50e1 |
+@pytest.mark.parametrize("filename, expected", [
|
|
|
ed50e1 |
+ ('dir/file', 'file'),
|
|
|
ed50e1 |
+ ('../file', 'file'),
|
|
|
ed50e1 |
+ ('../../file', 'file'),
|
|
|
ed50e1 |
+ ('../', ''),
|
|
|
ed50e1 |
+ ('../..', '..'),
|
|
|
ed50e1 |
+ ('/', ''),
|
|
|
ed50e1 |
+])
|
|
|
ed50e1 |
+def test_sanitize_content_filename(filename, expected):
|
|
|
ed50e1 |
+ """
|
|
|
ed50e1 |
+ Test inputs where the result is the same for Windows and non-Windows.
|
|
|
ed50e1 |
+ """
|
|
|
ed50e1 |
+ assert sanitize_content_filename(filename) == expected
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
+@pytest.mark.parametrize("filename, win_expected, non_win_expected", [
|
|
|
ed50e1 |
+ ('dir\\file', 'file', 'dir\\file'),
|
|
|
ed50e1 |
+ ('..\\file', 'file', '..\\file'),
|
|
|
ed50e1 |
+ ('..\\..\\file', 'file', '..\\..\\file'),
|
|
|
ed50e1 |
+ ('..\\', '', '..\\'),
|
|
|
ed50e1 |
+ ('..\\..', '..', '..\\..'),
|
|
|
ed50e1 |
+ ('\\', '', '\\'),
|
|
|
ed50e1 |
+])
|
|
|
ed50e1 |
+def test_sanitize_content_filename__platform_dependent(
|
|
|
ed50e1 |
+ filename,
|
|
|
ed50e1 |
+ win_expected,
|
|
|
ed50e1 |
+ non_win_expected
|
|
|
ed50e1 |
+):
|
|
|
ed50e1 |
+ """
|
|
|
ed50e1 |
+ Test inputs where the result is different for Windows and non-Windows.
|
|
|
ed50e1 |
+ """
|
|
|
ed50e1 |
+ if sys.platform == 'win32':
|
|
|
ed50e1 |
+ expected = win_expected
|
|
|
ed50e1 |
+ else:
|
|
|
ed50e1 |
+ expected = non_win_expected
|
|
|
ed50e1 |
+ assert sanitize_content_filename(filename) == expected
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
+@pytest.mark.parametrize("content_disposition, default_filename, expected", [
|
|
|
ed50e1 |
+ ('attachment;filename="../file"', 'df', 'file'),
|
|
|
ed50e1 |
+])
|
|
|
ed50e1 |
+def test_parse_content_disposition(
|
|
|
ed50e1 |
+ content_disposition,
|
|
|
ed50e1 |
+ default_filename,
|
|
|
ed50e1 |
+ expected
|
|
|
ed50e1 |
+):
|
|
|
ed50e1 |
+ actual = parse_content_disposition(content_disposition, default_filename)
|
|
|
ed50e1 |
+ assert actual == expected
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
+def test_download_http_url__no_directory_traversal(tmpdir):
|
|
|
ed50e1 |
+ """
|
|
|
ed50e1 |
+ Test that directory traversal doesn't happen on download when the
|
|
|
ed50e1 |
+ Content-Disposition header contains a filename with a ".." path part.
|
|
|
ed50e1 |
+ """
|
|
|
ed50e1 |
+ mock_url = 'http://www.example.com/whatever.tgz'
|
|
|
ed50e1 |
+ contents = b'downloaded'
|
|
|
ed50e1 |
+ link = Link(mock_url)
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
+ session = Mock()
|
|
|
ed50e1 |
+ resp = MockResponse(contents)
|
|
|
ed50e1 |
+ resp.url = mock_url
|
|
|
ed50e1 |
+ resp.headers = {
|
|
|
ed50e1 |
+ # Set the content-type to a random value to prevent
|
|
|
ed50e1 |
+ # mimetypes.guess_extension from guessing the extension.
|
|
|
ed50e1 |
+ 'content-type': 'random',
|
|
|
ed50e1 |
+ 'content-disposition': 'attachment;filename="../out_dir_file"'
|
|
|
ed50e1 |
+ }
|
|
|
ed50e1 |
+ session.get.return_value = resp
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
+ download_dir = tmpdir.join('download')
|
|
|
ed50e1 |
+ os.mkdir(download_dir)
|
|
|
ed50e1 |
+ file_path, content_type = _download_http_url(
|
|
|
ed50e1 |
+ link,
|
|
|
ed50e1 |
+ session,
|
|
|
ed50e1 |
+ download_dir,
|
|
|
ed50e1 |
+ hashes=None,
|
|
|
ed50e1 |
+ )
|
|
|
ed50e1 |
+ # The file should be downloaded to download_dir.
|
|
|
ed50e1 |
+ actual = os.listdir(download_dir)
|
|
|
ed50e1 |
+ assert actual == ['out_dir_file']
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
+
|
|
|
ed50e1 |
@pytest.mark.skipif("sys.platform == 'win32'")
|
|
|
ed50e1 |
def test_path_to_url_unix():
|
|
|
ed50e1 |
assert path_to_url('/tmp/file') == 'file:///tmp/file'
|
|
|
ed50e1 |
--
|
|
|
ed50e1 |
2.25.4
|
|
|
ed50e1 |
|