We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Pip processes corrupt packages containing  using ../ chars placing files outside target directory
- Pip version: tested on pip versions 6.0.8 and  8.0.2,
- Python version: 2.7
- Operating System: Linux / Windows
### Description:

pip processes invalid packages .tar/.zip archives containing "../" directory traversal chars and places package files outside of target directories even after pip complains and throws errors and aborts the install.

Pip processes corrupt packages containing  using ../ chars placing files outside target directory
- Pip version: tested on pip versions 6.0.8 and  8.0.2,
- Python version: 2.7
- Operating System: Linux / Windows
### Description:

pip processes invalid packages .tar/.zip archives containing "../" directory traversal chars and places package files outside of target directories even after pip complains and throws errors and aborts the install.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/pip/_internal/utils/unpacking.py`
Content:
```
1 """Utilities related archives.
2 """
3 
4 # The following comment should be removed at some point in the future.
5 # mypy: strict-optional=False
6 
7 from __future__ import absolute_import
8 
9 import logging
10 import os
11 import shutil
12 import stat
13 import tarfile
14 import zipfile
15 
16 from pip._internal.exceptions import InstallationError
17 from pip._internal.utils.filetypes import (
18     BZ2_EXTENSIONS,
19     TAR_EXTENSIONS,
20     XZ_EXTENSIONS,
21     ZIP_EXTENSIONS,
22 )
23 from pip._internal.utils.misc import ensure_dir
24 from pip._internal.utils.typing import MYPY_CHECK_RUNNING
25 
26 if MYPY_CHECK_RUNNING:
27     from typing import Iterable, List, Optional, Text, Union
28 
29 
30 logger = logging.getLogger(__name__)
31 
32 
33 SUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS
34 
35 try:
36     import bz2  # noqa
37     SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS
38 except ImportError:
39     logger.debug('bz2 module is not available')
40 
41 try:
42     # Only for Python 3.3+
43     import lzma  # noqa
44     SUPPORTED_EXTENSIONS += XZ_EXTENSIONS
45 except ImportError:
46     logger.debug('lzma module is not available')
47 
48 
49 def current_umask():
50     """Get the current umask which involves having to set it temporarily."""
51     mask = os.umask(0)
52     os.umask(mask)
53     return mask
54 
55 
56 def split_leading_dir(path):
57     # type: (Union[str, Text]) -> List[Union[str, Text]]
58     path = path.lstrip('/').lstrip('\\')
59     if (
60         '/' in path and (
61             ('\\' in path and path.find('/') < path.find('\\')) or
62             '\\' not in path
63         )
64     ):
65         return path.split('/', 1)
66     elif '\\' in path:
67         return path.split('\\', 1)
68     else:
69         return [path, '']
70 
71 
72 def has_leading_dir(paths):
73     # type: (Iterable[Union[str, Text]]) -> bool
74     """Returns true if all the paths have the same leading path name
75     (i.e., everything is in one subdirectory in an archive)"""
76     common_prefix = None
77     for path in paths:
78         prefix, rest = split_leading_dir(path)
79         if not prefix:
80             return False
81         elif common_prefix is None:
82             common_prefix = prefix
83         elif prefix != common_prefix:
84             return False
85     return True
86 
87 
88 def unzip_file(filename, location, flatten=True):
89     # type: (str, str, bool) -> None
90     """
91     Unzip the file (with path `filename`) to the destination `location`.  All
92     files are written based on system defaults and umask (i.e. permissions are
93     not preserved), except that regular file members with any execute
94     permissions (user, group, or world) have "chmod +x" applied after being
95     written. Note that for windows, any execute changes using os.chmod are
96     no-ops per the python docs.
97     """
98     ensure_dir(location)
99     zipfp = open(filename, 'rb')
100     try:
101         zip = zipfile.ZipFile(zipfp, allowZip64=True)
102         leading = has_leading_dir(zip.namelist()) and flatten
103         for info in zip.infolist():
104             name = info.filename
105             fn = name
106             if leading:
107                 fn = split_leading_dir(name)[1]
108             fn = os.path.join(location, fn)
109             dir = os.path.dirname(fn)
110             if fn.endswith('/') or fn.endswith('\\'):
111                 # A directory
112                 ensure_dir(fn)
113             else:
114                 ensure_dir(dir)
115                 # Don't use read() to avoid allocating an arbitrarily large
116                 # chunk of memory for the file's content
117                 fp = zip.open(name)
118                 try:
119                     with open(fn, 'wb') as destfp:
120                         shutil.copyfileobj(fp, destfp)
121                 finally:
122                     fp.close()
123                     mode = info.external_attr >> 16
124                     # if mode and regular file and any execute permissions for
125                     # user/group/world?
126                     if mode and stat.S_ISREG(mode) and mode & 0o111:
127                         # make dest file have execute for user/group/world
128                         # (chmod +x) no-op on windows per python docs
129                         os.chmod(fn, (0o777 - current_umask() | 0o111))
130     finally:
131         zipfp.close()
132 
133 
134 def untar_file(filename, location):
135     # type: (str, str) -> None
136     """
137     Untar the file (with path `filename`) to the destination `location`.
138     All files are written based on system defaults and umask (i.e. permissions
139     are not preserved), except that regular file members with any execute
140     permissions (user, group, or world) have "chmod +x" applied after being
141     written.  Note that for windows, any execute changes using os.chmod are
142     no-ops per the python docs.
143     """
144     ensure_dir(location)
145     if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):
146         mode = 'r:gz'
147     elif filename.lower().endswith(BZ2_EXTENSIONS):
148         mode = 'r:bz2'
149     elif filename.lower().endswith(XZ_EXTENSIONS):
150         mode = 'r:xz'
151     elif filename.lower().endswith('.tar'):
152         mode = 'r'
153     else:
154         logger.warning(
155             'Cannot determine compression type for file %s', filename,
156         )
157         mode = 'r:*'
158     tar = tarfile.open(filename, mode)
159     try:
160         leading = has_leading_dir([
161             member.name for member in tar.getmembers()
162         ])
163         for member in tar.getmembers():
164             fn = member.name
165             if leading:
166                 # https://github.com/python/mypy/issues/1174
167                 fn = split_leading_dir(fn)[1]  # type: ignore
168             path = os.path.join(location, fn)
169             if member.isdir():
170                 ensure_dir(path)
171             elif member.issym():
172                 try:
173                     # https://github.com/python/typeshed/issues/2673
174                     tar._extract_member(member, path)  # type: ignore
175                 except Exception as exc:
176                     # Some corrupt tar files seem to produce this
177                     # (specifically bad symlinks)
178                     logger.warning(
179                         'In the tar file %s the member %s is invalid: %s',
180                         filename, member.name, exc,
181                     )
182                     continue
183             else:
184                 try:
185                     fp = tar.extractfile(member)
186                 except (KeyError, AttributeError) as exc:
187                     # Some corrupt tar files seem to produce this
188                     # (specifically bad symlinks)
189                     logger.warning(
190                         'In the tar file %s the member %s is invalid: %s',
191                         filename, member.name, exc,
192                     )
193                     continue
194                 ensure_dir(os.path.dirname(path))
195                 with open(path, 'wb') as destfp:
196                     shutil.copyfileobj(fp, destfp)
197                 fp.close()
198                 # Update the timestamp (useful for cython compiled files)
199                 # https://github.com/python/typeshed/issues/2673
200                 tar.utime(member, path)  # type: ignore
201                 # member have any execute permissions for user/group/world?
202                 if member.mode & 0o111:
203                     # make dest file have execute for user/group/world
204                     # no-op on windows per python docs
205                     os.chmod(path, (0o777 - current_umask() | 0o111))
206     finally:
207         tar.close()
208 
209 
210 def unpack_file(
211         filename,  # type: str
212         location,  # type: str
213         content_type=None,  # type: Optional[str]
214 ):
215     # type: (...) -> None
216     filename = os.path.realpath(filename)
217     if (
218         content_type == 'application/zip' or
219         filename.lower().endswith(ZIP_EXTENSIONS) or
220         zipfile.is_zipfile(filename)
221     ):
222         unzip_file(
223             filename,
224             location,
225             flatten=not filename.endswith('.whl')
226         )
227     elif (
228         content_type == 'application/x-gzip' or
229         tarfile.is_tarfile(filename) or
230         filename.lower().endswith(
231             TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS
232         )
233     ):
234         untar_file(filename, location)
235     else:
236         # FIXME: handle?
237         # FIXME: magic signatures?
238         logger.critical(
239             'Cannot unpack file %s (downloaded from %s, content-type: %s); '
240             'cannot detect archive format',
241             filename, location, content_type,
242         )
243         raise InstallationError(
244             'Cannot determine archive format of {}'.format(location)
245         )
246
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py
--- a/src/pip/_internal/utils/unpacking.py
+++ b/src/pip/_internal/utils/unpacking.py
@@ -85,6 +85,18 @@
     return True
 
 
+def is_within_directory(directory, target):
+    # type: ((Union[str, Text]), (Union[str, Text])) -> bool
+    """
+    Return true if the absolute path of target is within the directory
+    """
+    abs_directory = os.path.abspath(directory)
+    abs_target = os.path.abspath(target)
+
+    prefix = os.path.commonprefix([abs_directory, abs_target])
+    return prefix == abs_directory
+
+
 def unzip_file(filename, location, flatten=True):
     # type: (str, str, bool) -> None
     """
@@ -107,6 +119,12 @@
                 fn = split_leading_dir(name)[1]
             fn = os.path.join(location, fn)
             dir = os.path.dirname(fn)
+            if not is_within_directory(location, fn):
+                message = (
+                    'The zip file ({}) has a file ({}) trying to install '
+                    'outside target directory ({})'
+                )
+                raise InstallationError(message.format(filename, fn, location))
             if fn.endswith('/') or fn.endswith('\\'):
                 # A directory
                 ensure_dir(fn)
@@ -166,6 +184,14 @@
                 # https://github.com/python/mypy/issues/1174
                 fn = split_leading_dir(fn)[1]  # type: ignore
             path = os.path.join(location, fn)
+            if not is_within_directory(location, path):
+                message = (
+                    'The tar file ({}) has a file ({}) trying to install '
+                    'outside target directory ({})'
+                )
+                raise InstallationError(
+                    message.format(filename, path, location)
+                )
             if member.isdir():
                 ensure_dir(path)
             elif member.issym():

{"golden_diff": "diff --git a/src/pip/_internal/utils/unpacking.py b/src/pip/_internal/utils/unpacking.py\n--- a/src/pip/_internal/utils/unpacking.py\n+++ b/src/pip/_internal/utils/unpacking.py\n@@ -85,6 +85,18 @@\n     return True\n \n \n+def is_within_directory(directory, target):\n+    # type: ((Union[str, Text]), (Union[str, Text])) -> bool\n+    \"\"\"\n+    Return true if the absolute path of target is within the directory\n+    \"\"\"\n+    abs_directory = os.path.abspath(directory)\n+    abs_target = os.path.abspath(target)\n+\n+    prefix = os.path.commonprefix([abs_directory, abs_target])\n+    return prefix == abs_directory\n+\n+\n def unzip_file(filename, location, flatten=True):\n     # type: (str, str, bool) -> None\n     \"\"\"\n@@ -107,6 +119,12 @@\n                 fn = split_leading_dir(name)[1]\n             fn = os.path.join(location, fn)\n             dir = os.path.dirname(fn)\n+            if not is_within_directory(location, fn):\n+                message = (\n+                    'The zip file ({}) has a file ({}) trying to install '\n+                    'outside target directory ({})'\n+                )\n+                raise InstallationError(message.format(filename, fn, location))\n             if fn.endswith('/') or fn.endswith('\\\\'):\n                 # A directory\n                 ensure_dir(fn)\n@@ -166,6 +184,14 @@\n                 # https://github.com/python/mypy/issues/1174\n                 fn = split_leading_dir(fn)[1]  # type: ignore\n             path = os.path.join(location, fn)\n+            if not is_within_directory(location, path):\n+                message = (\n+                    'The tar file ({}) has a file ({}) trying to install '\n+                    'outside target directory ({})'\n+                )\n+                raise InstallationError(\n+                    message.format(filename, path, location)\n+                )\n             if member.isdir():\n                 ensure_dir(path)\n             elif member.issym():\n", "issue": "Pip processes corrupt packages containing  using ../ chars placing files outside target directory\n- Pip version: tested on pip versions 6.0.8 and  8.0.2,\n- Python version: 2.7\n- Operating System: Linux / Windows\n### Description:\n\npip processes invalid packages .tar/.zip archives containing \"../\" directory traversal chars and places package files outside of target directories even after pip complains and throws errors and aborts the install.\n\nPip processes corrupt packages containing  using ../ chars placing files outside target directory\n- Pip version: tested on pip versions 6.0.8 and  8.0.2,\n- Python version: 2.7\n- Operating System: Linux / Windows\n### Description:\n\npip processes invalid packages .tar/.zip archives containing \"../\" directory traversal chars and places package files outside of target directories even after pip complains and throws errors and aborts the install.\n\n", "before_files": [{"content": "\"\"\"Utilities related archives.\n\"\"\"\n\n# The following comment should be removed at some point in the future.\n# mypy: strict-optional=False\n\nfrom __future__ import absolute_import\n\nimport logging\nimport os\nimport shutil\nimport stat\nimport tarfile\nimport zipfile\n\nfrom pip._internal.exceptions import InstallationError\nfrom pip._internal.utils.filetypes import (\n    BZ2_EXTENSIONS,\n    TAR_EXTENSIONS,\n    XZ_EXTENSIONS,\n    ZIP_EXTENSIONS,\n)\nfrom pip._internal.utils.misc import ensure_dir\nfrom pip._internal.utils.typing import MYPY_CHECK_RUNNING\n\nif MYPY_CHECK_RUNNING:\n    from typing import Iterable, List, Optional, Text, Union\n\n\nlogger = logging.getLogger(__name__)\n\n\nSUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS\n\ntry:\n    import bz2  # noqa\n    SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS\nexcept ImportError:\n    logger.debug('bz2 module is not available')\n\ntry:\n    # Only for Python 3.3+\n    import lzma  # noqa\n    SUPPORTED_EXTENSIONS += XZ_EXTENSIONS\nexcept ImportError:\n    logger.debug('lzma module is not available')\n\n\ndef current_umask():\n    \"\"\"Get the current umask which involves having to set it temporarily.\"\"\"\n    mask = os.umask(0)\n    os.umask(mask)\n    return mask\n\n\ndef split_leading_dir(path):\n    # type: (Union[str, Text]) -> List[Union[str, Text]]\n    path = path.lstrip('/').lstrip('\\\\')\n    if (\n        '/' in path and (\n            ('\\\\' in path and path.find('/') < path.find('\\\\')) or\n            '\\\\' not in path\n        )\n    ):\n        return path.split('/', 1)\n    elif '\\\\' in path:\n        return path.split('\\\\', 1)\n    else:\n        return [path, '']\n\n\ndef has_leading_dir(paths):\n    # type: (Iterable[Union[str, Text]]) -> bool\n    \"\"\"Returns true if all the paths have the same leading path name\n    (i.e., everything is in one subdirectory in an archive)\"\"\"\n    common_prefix = None\n    for path in paths:\n        prefix, rest = split_leading_dir(path)\n        if not prefix:\n            return False\n        elif common_prefix is None:\n            common_prefix = prefix\n        elif prefix != common_prefix:\n            return False\n    return True\n\n\ndef unzip_file(filename, location, flatten=True):\n    # type: (str, str, bool) -> None\n    \"\"\"\n    Unzip the file (with path `filename`) to the destination `location`.  All\n    files are written based on system defaults and umask (i.e. permissions are\n    not preserved), except that regular file members with any execute\n    permissions (user, group, or world) have \"chmod +x\" applied after being\n    written. Note that for windows, any execute changes using os.chmod are\n    no-ops per the python docs.\n    \"\"\"\n    ensure_dir(location)\n    zipfp = open(filename, 'rb')\n    try:\n        zip = zipfile.ZipFile(zipfp, allowZip64=True)\n        leading = has_leading_dir(zip.namelist()) and flatten\n        for info in zip.infolist():\n            name = info.filename\n            fn = name\n            if leading:\n                fn = split_leading_dir(name)[1]\n            fn = os.path.join(location, fn)\n            dir = os.path.dirname(fn)\n            if fn.endswith('/') or fn.endswith('\\\\'):\n                # A directory\n                ensure_dir(fn)\n            else:\n                ensure_dir(dir)\n                # Don't use read() to avoid allocating an arbitrarily large\n                # chunk of memory for the file's content\n                fp = zip.open(name)\n                try:\n                    with open(fn, 'wb') as destfp:\n                        shutil.copyfileobj(fp, destfp)\n                finally:\n                    fp.close()\n                    mode = info.external_attr >> 16\n                    # if mode and regular file and any execute permissions for\n                    # user/group/world?\n                    if mode and stat.S_ISREG(mode) and mode & 0o111:\n                        # make dest file have execute for user/group/world\n                        # (chmod +x) no-op on windows per python docs\n                        os.chmod(fn, (0o777 - current_umask() | 0o111))\n    finally:\n        zipfp.close()\n\n\ndef untar_file(filename, location):\n    # type: (str, str) -> None\n    \"\"\"\n    Untar the file (with path `filename`) to the destination `location`.\n    All files are written based on system defaults and umask (i.e. permissions\n    are not preserved), except that regular file members with any execute\n    permissions (user, group, or world) have \"chmod +x\" applied after being\n    written.  Note that for windows, any execute changes using os.chmod are\n    no-ops per the python docs.\n    \"\"\"\n    ensure_dir(location)\n    if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):\n        mode = 'r:gz'\n    elif filename.lower().endswith(BZ2_EXTENSIONS):\n        mode = 'r:bz2'\n    elif filename.lower().endswith(XZ_EXTENSIONS):\n        mode = 'r:xz'\n    elif filename.lower().endswith('.tar'):\n        mode = 'r'\n    else:\n        logger.warning(\n            'Cannot determine compression type for file %s', filename,\n        )\n        mode = 'r:*'\n    tar = tarfile.open(filename, mode)\n    try:\n        leading = has_leading_dir([\n            member.name for member in tar.getmembers()\n        ])\n        for member in tar.getmembers():\n            fn = member.name\n            if leading:\n                # https://github.com/python/mypy/issues/1174\n                fn = split_leading_dir(fn)[1]  # type: ignore\n            path = os.path.join(location, fn)\n            if member.isdir():\n                ensure_dir(path)\n            elif member.issym():\n                try:\n                    # https://github.com/python/typeshed/issues/2673\n                    tar._extract_member(member, path)  # type: ignore\n                except Exception as exc:\n                    # Some corrupt tar files seem to produce this\n                    # (specifically bad symlinks)\n                    logger.warning(\n                        'In the tar file %s the member %s is invalid: %s',\n                        filename, member.name, exc,\n                    )\n                    continue\n            else:\n                try:\n                    fp = tar.extractfile(member)\n                except (KeyError, AttributeError) as exc:\n                    # Some corrupt tar files seem to produce this\n                    # (specifically bad symlinks)\n                    logger.warning(\n                        'In the tar file %s the member %s is invalid: %s',\n                        filename, member.name, exc,\n                    )\n                    continue\n                ensure_dir(os.path.dirname(path))\n                with open(path, 'wb') as destfp:\n                    shutil.copyfileobj(fp, destfp)\n                fp.close()\n                # Update the timestamp (useful for cython compiled files)\n                # https://github.com/python/typeshed/issues/2673\n                tar.utime(member, path)  # type: ignore\n                # member have any execute permissions for user/group/world?\n                if member.mode & 0o111:\n                    # make dest file have execute for user/group/world\n                    # no-op on windows per python docs\n                    os.chmod(path, (0o777 - current_umask() | 0o111))\n    finally:\n        tar.close()\n\n\ndef unpack_file(\n        filename,  # type: str\n        location,  # type: str\n        content_type=None,  # type: Optional[str]\n):\n    # type: (...) -> None\n    filename = os.path.realpath(filename)\n    if (\n        content_type == 'application/zip' or\n        filename.lower().endswith(ZIP_EXTENSIONS) or\n        zipfile.is_zipfile(filename)\n    ):\n        unzip_file(\n            filename,\n            location,\n            flatten=not filename.endswith('.whl')\n        )\n    elif (\n        content_type == 'application/x-gzip' or\n        tarfile.is_tarfile(filename) or\n        filename.lower().endswith(\n            TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS\n        )\n    ):\n        untar_file(filename, location)\n    else:\n        # FIXME: handle?\n        # FIXME: magic signatures?\n        logger.critical(\n            'Cannot unpack file %s (downloaded from %s, content-type: %s); '\n            'cannot detect archive format',\n            filename, location, content_type,\n        )\n        raise InstallationError(\n            'Cannot determine archive format of {}'.format(location)\n        )\n", "path": "src/pip/_internal/utils/unpacking.py"}], "after_files": [{"content": "\"\"\"Utilities related archives.\n\"\"\"\n\n# The following comment should be removed at some point in the future.\n# mypy: strict-optional=False\n\nfrom __future__ import absolute_import\n\nimport logging\nimport os\nimport shutil\nimport stat\nimport tarfile\nimport zipfile\n\nfrom pip._internal.exceptions import InstallationError\nfrom pip._internal.utils.filetypes import (\n    BZ2_EXTENSIONS,\n    TAR_EXTENSIONS,\n    XZ_EXTENSIONS,\n    ZIP_EXTENSIONS,\n)\nfrom pip._internal.utils.misc import ensure_dir\nfrom pip._internal.utils.typing import MYPY_CHECK_RUNNING\n\nif MYPY_CHECK_RUNNING:\n    from typing import Iterable, List, Optional, Text, Union\n\n\nlogger = logging.getLogger(__name__)\n\n\nSUPPORTED_EXTENSIONS = ZIP_EXTENSIONS + TAR_EXTENSIONS\n\ntry:\n    import bz2  # noqa\n    SUPPORTED_EXTENSIONS += BZ2_EXTENSIONS\nexcept ImportError:\n    logger.debug('bz2 module is not available')\n\ntry:\n    # Only for Python 3.3+\n    import lzma  # noqa\n    SUPPORTED_EXTENSIONS += XZ_EXTENSIONS\nexcept ImportError:\n    logger.debug('lzma module is not available')\n\n\ndef current_umask():\n    \"\"\"Get the current umask which involves having to set it temporarily.\"\"\"\n    mask = os.umask(0)\n    os.umask(mask)\n    return mask\n\n\ndef split_leading_dir(path):\n    # type: (Union[str, Text]) -> List[Union[str, Text]]\n    path = path.lstrip('/').lstrip('\\\\')\n    if (\n        '/' in path and (\n            ('\\\\' in path and path.find('/') < path.find('\\\\')) or\n            '\\\\' not in path\n        )\n    ):\n        return path.split('/', 1)\n    elif '\\\\' in path:\n        return path.split('\\\\', 1)\n    else:\n        return [path, '']\n\n\ndef has_leading_dir(paths):\n    # type: (Iterable[Union[str, Text]]) -> bool\n    \"\"\"Returns true if all the paths have the same leading path name\n    (i.e., everything is in one subdirectory in an archive)\"\"\"\n    common_prefix = None\n    for path in paths:\n        prefix, rest = split_leading_dir(path)\n        if not prefix:\n            return False\n        elif common_prefix is None:\n            common_prefix = prefix\n        elif prefix != common_prefix:\n            return False\n    return True\n\n\ndef is_within_directory(directory, target):\n    # type: ((Union[str, Text]), (Union[str, Text])) -> bool\n    \"\"\"\n    Return true if the absolute path of target is within the directory\n    \"\"\"\n    abs_directory = os.path.abspath(directory)\n    abs_target = os.path.abspath(target)\n\n    prefix = os.path.commonprefix([abs_directory, abs_target])\n    return prefix == abs_directory\n\n\ndef unzip_file(filename, location, flatten=True):\n    # type: (str, str, bool) -> None\n    \"\"\"\n    Unzip the file (with path `filename`) to the destination `location`.  All\n    files are written based on system defaults and umask (i.e. permissions are\n    not preserved), except that regular file members with any execute\n    permissions (user, group, or world) have \"chmod +x\" applied after being\n    written. Note that for windows, any execute changes using os.chmod are\n    no-ops per the python docs.\n    \"\"\"\n    ensure_dir(location)\n    zipfp = open(filename, 'rb')\n    try:\n        zip = zipfile.ZipFile(zipfp, allowZip64=True)\n        leading = has_leading_dir(zip.namelist()) and flatten\n        for info in zip.infolist():\n            name = info.filename\n            fn = name\n            if leading:\n                fn = split_leading_dir(name)[1]\n            fn = os.path.join(location, fn)\n            dir = os.path.dirname(fn)\n            if not is_within_directory(location, fn):\n                message = (\n                    'The zip file ({}) has a file ({}) trying to install '\n                    'outside target directory ({})'\n                )\n                raise InstallationError(message.format(filename, fn, location))\n            if fn.endswith('/') or fn.endswith('\\\\'):\n                # A directory\n                ensure_dir(fn)\n            else:\n                ensure_dir(dir)\n                # Don't use read() to avoid allocating an arbitrarily large\n                # chunk of memory for the file's content\n                fp = zip.open(name)\n                try:\n                    with open(fn, 'wb') as destfp:\n                        shutil.copyfileobj(fp, destfp)\n                finally:\n                    fp.close()\n                    mode = info.external_attr >> 16\n                    # if mode and regular file and any execute permissions for\n                    # user/group/world?\n                    if mode and stat.S_ISREG(mode) and mode & 0o111:\n                        # make dest file have execute for user/group/world\n                        # (chmod +x) no-op on windows per python docs\n                        os.chmod(fn, (0o777 - current_umask() | 0o111))\n    finally:\n        zipfp.close()\n\n\ndef untar_file(filename, location):\n    # type: (str, str) -> None\n    \"\"\"\n    Untar the file (with path `filename`) to the destination `location`.\n    All files are written based on system defaults and umask (i.e. permissions\n    are not preserved), except that regular file members with any execute\n    permissions (user, group, or world) have \"chmod +x\" applied after being\n    written.  Note that for windows, any execute changes using os.chmod are\n    no-ops per the python docs.\n    \"\"\"\n    ensure_dir(location)\n    if filename.lower().endswith('.gz') or filename.lower().endswith('.tgz'):\n        mode = 'r:gz'\n    elif filename.lower().endswith(BZ2_EXTENSIONS):\n        mode = 'r:bz2'\n    elif filename.lower().endswith(XZ_EXTENSIONS):\n        mode = 'r:xz'\n    elif filename.lower().endswith('.tar'):\n        mode = 'r'\n    else:\n        logger.warning(\n            'Cannot determine compression type for file %s', filename,\n        )\n        mode = 'r:*'\n    tar = tarfile.open(filename, mode)\n    try:\n        leading = has_leading_dir([\n            member.name for member in tar.getmembers()\n        ])\n        for member in tar.getmembers():\n            fn = member.name\n            if leading:\n                # https://github.com/python/mypy/issues/1174\n                fn = split_leading_dir(fn)[1]  # type: ignore\n            path = os.path.join(location, fn)\n            if not is_within_directory(location, path):\n                message = (\n                    'The tar file ({}) has a file ({}) trying to install '\n                    'outside target directory ({})'\n                )\n                raise InstallationError(\n                    message.format(filename, path, location)\n                )\n            if member.isdir():\n                ensure_dir(path)\n            elif member.issym():\n                try:\n                    # https://github.com/python/typeshed/issues/2673\n                    tar._extract_member(member, path)  # type: ignore\n                except Exception as exc:\n                    # Some corrupt tar files seem to produce this\n                    # (specifically bad symlinks)\n                    logger.warning(\n                        'In the tar file %s the member %s is invalid: %s',\n                        filename, member.name, exc,\n                    )\n                    continue\n            else:\n                try:\n                    fp = tar.extractfile(member)\n                except (KeyError, AttributeError) as exc:\n                    # Some corrupt tar files seem to produce this\n                    # (specifically bad symlinks)\n                    logger.warning(\n                        'In the tar file %s the member %s is invalid: %s',\n                        filename, member.name, exc,\n                    )\n                    continue\n                ensure_dir(os.path.dirname(path))\n                with open(path, 'wb') as destfp:\n                    shutil.copyfileobj(fp, destfp)\n                fp.close()\n                # Update the timestamp (useful for cython compiled files)\n                # https://github.com/python/typeshed/issues/2673\n                tar.utime(member, path)  # type: ignore\n                # member have any execute permissions for user/group/world?\n                if member.mode & 0o111:\n                    # make dest file have execute for user/group/world\n                    # no-op on windows per python docs\n                    os.chmod(path, (0o777 - current_umask() | 0o111))\n    finally:\n        tar.close()\n\n\ndef unpack_file(\n        filename,  # type: str\n        location,  # type: str\n        content_type=None,  # type: Optional[str]\n):\n    # type: (...) -> None\n    filename = os.path.realpath(filename)\n    if (\n        content_type == 'application/zip' or\n        filename.lower().endswith(ZIP_EXTENSIONS) or\n        zipfile.is_zipfile(filename)\n    ):\n        unzip_file(\n            filename,\n            location,\n            flatten=not filename.endswith('.whl')\n        )\n    elif (\n        content_type == 'application/x-gzip' or\n        tarfile.is_tarfile(filename) or\n        filename.lower().endswith(\n            TAR_EXTENSIONS + BZ2_EXTENSIONS + XZ_EXTENSIONS\n        )\n    ):\n        untar_file(filename, location)\n    else:\n        # FIXME: handle?\n        # FIXME: magic signatures?\n        logger.critical(\n            'Cannot unpack file %s (downloaded from %s, content-type: %s); '\n            'cannot detect archive format',\n            filename, location, content_type,\n        )\n        raise InstallationError(\n            'Cannot determine archive format of {}'.format(location)\n        )\n", "path": "src/pip/_internal/utils/unpacking.py"}]}