Fail gracefully when tar file contains duplicate entries (#3805)

diff --git a/lib/src/io.dart b/lib/src/io.dart
index 54736c9..4a09496 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -983,6 +983,7 @@
 
   destination = path.absolute(destination);
   final reader = TarReader(stream.transform(gzip.decoder));
+  final paths = <String>{};
   while (await reader.moveNext()) {
     final entry = reader.current;
 
@@ -991,6 +992,11 @@
       // Tar file names always use forward slashes
       ...path.posix.split(entry.name),
     ]);
+    if (!paths.add(filePath)) {
+      // The tar file contained the same entry twice. Assume it is broken.
+      await reader.cancel();
+      throw FormatException('Tar file contained duplicate path ${entry.name}');
+    }
 
     if (!path.isWithin(destination, filePath)) {
       // The tar contains entries that would be written outside of the
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index 64c5936..d2928ae 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -1161,8 +1161,11 @@
 
       var tempDir = cache.createTempDir();
       try {
-        await extractTarGz(readBinaryFileAsStream(archivePath), tempDir);
-
+        try {
+          await extractTarGz(readBinaryFileAsStream(archivePath), tempDir);
+        } on FormatException catch (e) {
+          dataError('Failed to extract `$archivePath`: ${e.message}.');
+        }
         ensureDir(p.dirname(destPath));
       } catch (e) {
         deleteEntry(tempDir);
diff --git a/test/get/hosted/get_test.dart b/test/get/hosted/get_test.dart
index 73434be..692b605 100644
--- a/test/get/hosted/get_test.dart
+++ b/test/get/hosted/get_test.dart
@@ -4,6 +4,7 @@
 
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
+import 'package:pub/src/exit_codes.dart';
 import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
 import 'package:yaml/yaml.dart';
@@ -388,4 +389,21 @@
       );
     });
   });
+
+  test('Fails gracefully on tar.gz with duplicate entries', () async {
+    final server = await servePackages();
+    server.serve(
+      'foo',
+      '1.0.0',
+      contents: [
+        d.dir('blah', [d.file('myduplicatefile'), d.file('myduplicatefile')])
+      ],
+    );
+    await d.appDir(dependencies: {'foo': 'any'}).create();
+    await pubGet(
+      error:
+          contains('Tar file contained duplicate path blah/myduplicatefile.'),
+      exitCode: DATA,
+    );
+  });
 }