Repository: conan-io/conan

Issue:
tar_extract is affected by symlinks in current directory
If a current directory has a symlink with the name matching a directory in the installed conan_package.tgz, then the directory is not extracted to the package directory.

Reproduced on 1.15.0-dev. Notice the `include` directory disappears from the package directory.
```
$ mkdir /tmp/testing && cd /tmp/testing
$ conan new Hello/0.1 -t
$ conan create . demo/testing
$ conan upload Hello/0.1@demo/testing --all
$ conan remove Hello/0.1@demo/testing --force
$ conan install Hello/0.1@demo/testing
$ conan get Hello/0.1@demo/testing:57547fe65fffc300f05aa42ee64b3b02eeabb6d7 .
Listing directory '.':
 conaninfo.txt
 conanmanifest.txt
 include
 lib
$ ln -s /tmp include
$ conan remove Hello/0.1@demo/testing --force
$ conan install Hello/0.1@demo/testing
$ conan get Hello/0.1@demo/testing:57547fe65fffc300f05aa42ee64b3b02eeabb6d7 .
Listing directory '.':
 conaninfo.txt
 conanmanifest.txt
 lib
```

The issue is caused by `badpath` check. The `base` is the current directory. Joining current directory and `include`, and resolving the symlink makes it to not to start with the current directory. What is the true intent for the `badpath` method? Is there a good test case to show it's necessary?
```python
  293 def tar_extract(fileobj, destination_dir):
  294     """Extract tar file controlling not absolute paths and fixing the routes
  295     if the tar was zipped in windows"""
  296     def badpath(path, base):
  297         # joinpath will ignore base if path is absolute
  298         return not realpath(abspath(joinpath(base, path))).startswith(base)
  299
  300     def safemembers(members):
  301         base = realpath(abspath("."))
  302
  303         for finfo in members:
  304             if badpath(finfo.name, base) or finfo.islnk():
  305                 continue
  306             else:
  307                 # Fixes unzip a windows zipped file in linux
  308                 finfo.name = finfo.name.replace("\\", "/")
  309                 yield finfo
```




- [x] I've read the [CONTRIBUTING guide](https://github.com/conan-io/conan/blob/develop/.github/CONTRIBUTING.md).
- [x] I've specified the Conan version, operating system version and any tool that can be relevant.
- [x] I've explained the steps to reproduce the error or the motivation/use case of the question/suggestion.




Discussion:
Thanks a lot @klimkin for pointing at this. At first sight, I think that the `base` folder should be the `destination_dir` (the current working dir makes no sense, IMO). I'm going to check if there is any test failing without the `badpath` check too.

Thanks! 

Resolving patch:
diff --git a/conans/util/files.py b/conans/util/files.py
--- a/conans/util/files.py
+++ b/conans/util/files.py
@@ -298,7 +298,7 @@ def badpath(path, base):
         return not realpath(abspath(joinpath(base, path))).startswith(base)
 
     def safemembers(members):
-        base = realpath(abspath("."))
+        base = realpath(abspath(destination_dir))
 
         for finfo in members:
             if badpath(finfo.name, base) or finfo.islnk():


Test patch:
diff --git a/conans/test/unittests/util/files/tar_extract_test.py b/conans/test/unittests/util/files/tar_extract_test.py
new file mode 100644
--- /dev/null
+++ b/conans/test/unittests/util/files/tar_extract_test.py
@@ -0,0 +1,63 @@
+# coding=utf-8
+
+import os
+import platform
+import tarfile
+import unittest
+
+from conans.client.tools.files import chdir
+from conans.model.manifest import gather_files
+from conans.test.utils.test_files import temp_folder
+from conans.util.files import tar_extract, gzopen_without_timestamps, save
+
+
+class TarExtractTest(unittest.TestCase):
+
+    def setUp(self):
+        self.tmp_folder = temp_folder()
+        with chdir(self.tmp_folder):
+            # Create a couple of files
+            ori_files_dir = os.path.join(self.tmp_folder, "ori")
+            file1 = os.path.join(ori_files_dir, "file1")
+            file2 = os.path.join(ori_files_dir, "folder", "file2")
+            save(file1, "")
+            save(file2, "")
+
+            # Create a tar.gz file with the above files
+            self.tgz_file = os.path.join(self.tmp_folder, "file.tar.gz")
+            with open(self.tgz_file, "wb") as tgz_handle:
+                tgz = gzopen_without_timestamps("name", mode="w", fileobj=tgz_handle)
+
+                files, _ = gather_files(ori_files_dir)
+                for filename, abs_path in files.items():
+                    info = tarfile.TarInfo(name=filename)
+                    with open(file1, 'rb') as file_handler:
+                        tgz.addfile(tarinfo=info, fileobj=file_handler)
+                tgz.close()
+
+    @unittest.skipUnless(platform.system() == "Linux", "Requires Linux")
+    def test_link_folder(self):
+        # If there is a linked folder in the current directory that matches one file in the tar.
+        # https://github.com/conan-io/conan/issues/4959
+
+        # Once unpackaged, this is the content of the destination directory
+        def check_files(destination_dir):
+            d = os.listdir(destination_dir)
+            self.assertListEqual(d, ["folder", "file1"])
+            d_folder = os.listdir(os.path.join(destination_dir, "folder"))
+            self.assertEqual(d_folder, ["file2"])
+
+        working_dir = temp_folder()
+        with chdir(working_dir):
+            # Unpack and check
+            destination_dir = os.path.join(self.tmp_folder, "dest")
+            with open(self.tgz_file, 'rb') as file_handler:
+                tar_extract(file_handler, destination_dir)
+            check_files(destination_dir)
+
+            # Unpack and check (now we have a symlinked local folder)
+            os.symlink(temp_folder(), "folder")
+            destination_dir = os.path.join(self.tmp_folder, "dest2")
+            with open(self.tgz_file, 'rb') as file_handler:
+                tar_extract(file_handler, destination_dir)
+            check_files(destination_dir)