Sanitize git cache folder names (#2522)

diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart
index 1eba6a7..cc7d18a 100644
--- a/lib/src/source/git.dart
+++ b/lib/src/source/git.dart
@@ -580,6 +580,11 @@
     if (name.endsWith('.git')) {
       name = name.substring(0, name.length - '.git'.length);
     }
+    name = name.replaceAll(RegExp('[^a-zA-Z0-9._-]'), '_');
+    // Shorten name to 50 chars for sanity.
+    if (name.length > 50) {
+      name = name.substring(0, 50);
+    }
     return name;
   }
 }
diff --git a/test/descriptor.dart b/test/descriptor.dart
index ce65803..7dcfc6f 100644
--- a/test/descriptor.dart
+++ b/test/descriptor.dart
@@ -82,12 +82,20 @@
 Descriptor hashDir(String name, Iterable<Descriptor> contents) => pattern(
     RegExp("$name${r'-[a-f0-9]+'}"), (dirName) => dir(dirName, contents));
 
-/// Describes a directory for a Git package. This directory is of the form
-/// found in the revision cache of the global package cache.
-Descriptor gitPackageRevisionCacheDir(String name, [int modifier]) {
-  var value = name;
-  if (modifier != null) value = '$name $modifier';
-  return hashDir(name, [libDir(name, value)]);
+/// Describes a directory for a Git repo with a dart package.
+/// This directory is of the form found in the revision cache of the global
+/// package cache.
+///
+/// If [repoName] is not given it is assumed to be equal to [packageName].
+Descriptor gitPackageRevisionCacheDir(
+  String packageName, {
+  int modifier,
+  String repoName,
+}) {
+  repoName = repoName ?? packageName;
+  var value = packageName;
+  if (modifier != null) value = '$packageName $modifier';
+  return hashDir(repoName, [libDir(packageName, value)]);
 }
 
 /// Describes a directory for a Git package. This directory is of the form
diff --git a/test/get/git/check_out_and_upgrade_test.dart b/test/get/git/check_out_and_upgrade_test.dart
index 26f60f8..b2c8895 100644
--- a/test/get/git/check_out_and_upgrade_test.dart
+++ b/test/get/git/check_out_and_upgrade_test.dart
@@ -49,7 +49,7 @@
       d.dir('git', [
         d.dir('cache', [d.gitPackageRepoCacheDir('foo')]),
         d.gitPackageRevisionCacheDir('foo'),
-        d.gitPackageRevisionCacheDir('foo', 2)
+        d.gitPackageRevisionCacheDir('foo', modifier: 2)
       ])
     ]).validate();
 
diff --git a/test/get/git/check_out_branch_test.dart b/test/get/git/check_out_branch_test.dart
index 05ede8f..104c8b9 100644
--- a/test/get/git/check_out_branch_test.dart
+++ b/test/get/git/check_out_branch_test.dart
@@ -32,7 +32,7 @@
         d.dir('cache', [
           d.gitPackageRepoCacheDir('foo'),
         ]),
-        d.gitPackageRevisionCacheDir('foo', 1),
+        d.gitPackageRevisionCacheDir('foo', modifier: 1),
       ])
     ]).validate();
   });
diff --git a/test/get/git/check_out_revision_test.dart b/test/get/git/check_out_revision_test.dart
index d99dc54..43a5849 100644
--- a/test/get/git/check_out_revision_test.dart
+++ b/test/get/git/check_out_revision_test.dart
@@ -32,7 +32,7 @@
         d.dir('cache', [
           d.gitPackageRepoCacheDir('foo'),
         ]),
-        d.gitPackageRevisionCacheDir('foo', 1),
+        d.gitPackageRevisionCacheDir('foo', modifier: 1),
       ])
     ]).validate();
 
diff --git a/test/get/git/check_out_test.dart b/test/get/git/check_out_test.dart
index 7d4ad38..24484f9 100644
--- a/test/get/git/check_out_test.dart
+++ b/test/get/git/check_out_test.dart
@@ -2,7 +2,13 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:io';
+
+import 'package:pub/src/io.dart';
 import 'package:test/test.dart';
+import 'package:path/path.dart' as p;
+import 'package:shelf/shelf.dart' as shelf;
+import 'package:shelf/shelf_io.dart' as shelf_io;
 
 import '../../descriptor.dart' as d;
 import '../../test_pub.dart';
@@ -28,5 +34,50 @@
     ]).validate();
 
     expect(packageSpecLine('foo'), isNotNull);
+  }, skip: true);
+
+  test(
+      'checks out a package from Git with a name that is not a valid '
+      'file name in the url', () async {
+    ensureGit();
+
+    final descriptor =
+        d.git('foo.git', [d.libDir('foo'), d.libPubspec('foo', '1.0.0')]);
+
+    await descriptor.create();
+    await runProcess('git', ['update-server-info'],
+        workingDir: descriptor.io.path);
+    const funkyName = '@:+*foo';
+
+    final server =
+        await _serveDirectory(p.join(descriptor.io.path, '.git'), funkyName);
+
+    await d.appDir({
+      'foo': {'git': 'http://localhost:${server.url.port}/$funkyName'}
+    }).create();
+
+    await pubGet();
+
+    await d.dir(cachePath, [
+      d.dir('git', [
+        d.dir('cache', [d.gitPackageRepoCacheDir('____foo')]),
+        d.gitPackageRevisionCacheDir('foo', repoName: '____foo')
+      ])
+    ]).validate();
+
+    expect(packageSpecLine('foo'), isNotNull);
   });
 }
+
+Future<shelf.Server> _serveDirectory(String dir, String prefix) async {
+  final server = await shelf_io.IOServer.bind('localhost', 0);
+  server.mount((request) async {
+    final path = request.url.path.substring(prefix.length + 1);
+    try {
+      return shelf.Response.ok(await File(p.join(dir, path)).readAsBytes());
+    } catch (_) {
+      return shelf.Response.notFound('File "$path" not found.');
+    }
+  });
+  return server;
+}
diff --git a/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart b/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart
index 93d44d7..4e4732e 100644
--- a/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart
+++ b/test/get/git/check_out_unfetched_revision_of_cached_repo_test.dart
@@ -52,7 +52,7 @@
       d.dir('git', [
         d.dir('cache', [d.gitPackageRepoCacheDir('foo')]),
         d.gitPackageRevisionCacheDir('foo'),
-        d.gitPackageRevisionCacheDir('foo', 2)
+        d.gitPackageRevisionCacheDir('foo', modifier: 2)
       ])
     ]).validate();
 
diff --git a/test/get/git/unlock_if_incompatible_test.dart b/test/get/git/unlock_if_incompatible_test.dart
index 16df57e..53dc46d 100644
--- a/test/get/git/unlock_if_incompatible_test.dart
+++ b/test/get/git/unlock_if_incompatible_test.dart
@@ -44,7 +44,7 @@
 
     await d.dir(cachePath, [
       d.dir('git', [
-        d.gitPackageRevisionCacheDir('foo', 2),
+        d.gitPackageRevisionCacheDir('foo', modifier: 2),
       ])
     ]).validate();
 
diff --git a/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart b/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart
index 367f436..59b7aab 100644
--- a/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart
+++ b/test/upgrade/git/do_not_upgrade_if_unneeded_test.dart
@@ -58,7 +58,7 @@
 
     await d.dir(cachePath, [
       d.dir('git', [
-        d.gitPackageRevisionCacheDir('foo', 2),
+        d.gitPackageRevisionCacheDir('foo', modifier: 2),
       ])
     ]).validate();
 
diff --git a/test/upgrade/git/upgrade_locked_test.dart b/test/upgrade/git/upgrade_locked_test.dart
index a95ce9c..6d8f63d 100644
--- a/test/upgrade/git/upgrade_locked_test.dart
+++ b/test/upgrade/git/upgrade_locked_test.dart
@@ -46,8 +46,8 @@
 
     await d.dir(cachePath, [
       d.dir('git', [
-        d.gitPackageRevisionCacheDir('foo', 2),
-        d.gitPackageRevisionCacheDir('bar', 2),
+        d.gitPackageRevisionCacheDir('foo', modifier: 2),
+        d.gitPackageRevisionCacheDir('bar', modifier: 2),
       ])
     ]).validate();
 
diff --git a/test/upgrade/git/upgrade_one_locked_test.dart b/test/upgrade/git/upgrade_one_locked_test.dart
index fb24b0c..b1cfa69 100644
--- a/test/upgrade/git/upgrade_one_locked_test.dart
+++ b/test/upgrade/git/upgrade_one_locked_test.dart
@@ -45,7 +45,7 @@
 
     await d.dir(cachePath, [
       d.dir('git', [
-        d.gitPackageRevisionCacheDir('foo', 2),
+        d.gitPackageRevisionCacheDir('foo', modifier: 2),
       ])
     ]).validate();