Move parse_content_disposition to network.download

This commit is contained in:
Chris Hunt 2019-11-29 11:48:40 -05:00
parent 32b0fc23ab
commit 762e4a0817
4 changed files with 30 additions and 30 deletions

View File

@ -1,5 +1,6 @@
"""Download files with progress indicators.
"""
import cgi
import logging
import os
@ -80,3 +81,18 @@ def sanitize_content_filename(filename):
Sanitize the "filename" value from a Content-Disposition header.
"""
return os.path.basename(filename)
def parse_content_disposition(content_disposition, default_filename):
# type: (str, str) -> str
"""
Parse the "filename" value from a Content-Disposition header, and
return the default filename if the result is empty.
"""
_type, params = cgi.parse_header(content_disposition)
filename = params.get('filename')
if filename:
# We need to sanitize the filename to prevent directory traversal
# in case the filename contains ".." path parts.
filename = sanitize_content_filename(filename)
return filename or default_filename

View File

@ -5,7 +5,6 @@
# mypy: strict-optional=False
# mypy: disallow-untyped-defs=False
import cgi
import logging
import mimetypes
import os
@ -30,7 +29,7 @@ from pip._internal.exceptions import (
)
from pip._internal.network.download import (
_prepare_download,
sanitize_content_filename,
parse_content_disposition,
)
from pip._internal.network.session import PipSession
from pip._internal.utils.compat import expanduser
@ -308,21 +307,6 @@ def unpack_url(
)
def parse_content_disposition(content_disposition, default_filename):
# type: (str, str) -> str
"""
Parse the "filename" value from a Content-Disposition header, and
return the default filename if the result is empty.
"""
_type, params = cgi.parse_header(content_disposition)
filename = params.get('filename')
if filename:
# We need to sanitize the filename to prevent directory traversal
# in case the filename contains ".." path parts.
filename = sanitize_content_filename(filename)
return filename or default_filename
def _get_http_response_filename(resp, link):
# type: (Response, Link) -> str
"""Get an ideal filename from the given HTTP response, falling back to

View File

@ -6,6 +6,7 @@ import pytest
from pip._internal.models.link import Link
from pip._internal.network.download import (
_prepare_download,
parse_content_disposition,
sanitize_content_filename,
)
from tests.lib.requests_mocks import MockResponse
@ -77,3 +78,15 @@ def test_sanitize_content_filename__platform_dependent(
else:
expected = non_win_expected
assert sanitize_content_filename(filename) == expected
@pytest.mark.parametrize("content_disposition, default_filename, expected", [
('attachment;filename="../file"', 'df', 'file'),
])
def test_parse_content_disposition(
content_disposition,
default_filename,
expected
):
actual = parse_content_disposition(content_disposition, default_filename)
assert actual == expected

View File

@ -14,7 +14,6 @@ from pip._internal.operations.prepare import (
Downloader,
_copy_source_tree,
_download_http_url,
parse_content_disposition,
unpack_file_url,
unpack_http_url,
)
@ -106,18 +105,6 @@ def test_unpack_http_url_bad_downloaded_checksum(mock_unpack_file):
rmtree(download_dir)
@pytest.mark.parametrize("content_disposition, default_filename, expected", [
('attachment;filename="../file"', 'df', 'file'),
])
def test_parse_content_disposition(
content_disposition,
default_filename,
expected
):
actual = parse_content_disposition(content_disposition, default_filename)
assert actual == expected
def test_download_http_url__no_directory_traversal(tmpdir):
"""
Test that directory traversal doesn't happen on download when the