URL encode username in http and ssh schemes

Also add a test to verify that username was correctly encoded. This
change depends on change in gerrit core that marked DownloadConfig ctor
public.

Depends-On: https://gerrit-review.googlesource.com/c/gerrit/+/328279
Change-Id: Id7d24530607f6d05c5034bac1a91b3270c4cb698
diff --git a/BUILD b/BUILD
index 95eda17..c864ea4 100644
--- a/BUILD
+++ b/BUILD
@@ -1,4 +1,10 @@
-load("//tools/bzl:plugin.bzl", "gerrit_plugin")
+load("//tools/bzl:junit.bzl", "junit_tests")
+load(
+    "//tools/bzl:plugin.bzl",
+    "PLUGIN_DEPS",
+    "PLUGIN_TEST_DEPS",
+    "gerrit_plugin",
+)
 
 gerrit_plugin(
     name = "download-commands",
@@ -9,3 +15,13 @@
     ],
     resources = glob(["src/main/resources/**/*"]),
 )
+
+junit_tests(
+    name = "download-commands_tests",
+    size = "small",
+    srcs = glob(["src/test/java/**/*.java"]),
+    tags = ["download-commands"],
+    deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+        ":download-commands__plugin",
+    ],
+)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/download/scheme/HttpScheme.java b/src/main/java/com/googlesource/gerrit/plugins/download/scheme/HttpScheme.java
index 54d0322..85705c4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/download/scheme/HttpScheme.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/download/scheme/HttpScheme.java
@@ -24,6 +24,9 @@
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import org.eclipse.jgit.lib.Config;
 
 public class HttpScheme extends DownloadScheme {
@@ -64,7 +67,13 @@
       String host = base.substring(p + 3, s);
       r.append(base.substring(0, p + 3));
       if (userProvider.get().getUserName().isPresent()) {
-        r.append(userProvider.get().getUserName().get());
+        try {
+          r.append(
+              URLEncoder.encode(
+                  userProvider.get().getUserName().get(), StandardCharsets.UTF_8.name()));
+        } catch (UnsupportedEncodingException e) {
+          throw new RuntimeException("No UTF-8 support", e);
+        }
         r.append("@");
       }
       r.append(host);
diff --git a/src/main/java/com/googlesource/gerrit/plugins/download/scheme/SshScheme.java b/src/main/java/com/googlesource/gerrit/plugins/download/scheme/SshScheme.java
index 8478654..b39b953 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/download/scheme/SshScheme.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/download/scheme/SshScheme.java
@@ -24,8 +24,11 @@
 import com.google.gerrit.server.ssh.SshAdvertisedAddresses;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import java.io.UnsupportedEncodingException;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Optional;
 
@@ -92,7 +95,11 @@
 
     StringBuilder r = new StringBuilder();
     r.append("ssh://");
-    r.append(username.get());
+    try {
+      r.append(URLEncoder.encode(username.get(), StandardCharsets.UTF_8.name()));
+    } catch (UnsupportedEncodingException e) {
+      throw new RuntimeException("No UTF-8 support", e);
+    }
     r.append("@");
     r.append(ensureSlash(sshdAddress));
     r.append(project);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/download/scheme/SchemeTest.java b/src/test/java/com/googlesource/gerrit/plugins/download/scheme/SchemeTest.java
new file mode 100644
index 0000000..ad38898
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/download/scheme/SchemeTest.java
@@ -0,0 +1,87 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.download.scheme;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.entities.Account;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.GroupMembership;
+import com.google.gerrit.server.config.DownloadConfig;
+import com.google.inject.Provider;
+import com.google.inject.util.Providers;
+import java.util.List;
+import java.util.Optional;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SchemeTest {
+  private HttpScheme httpScheme;
+  private SshScheme sshScheme;
+
+  @Before
+  public void setUp() {
+    Config cfg = new Config();
+    Provider<String> urlProvider = Providers.of("https://gerrit.company.com/");
+    Provider<CurrentUser> userProvider = Providers.of(fakeUser());
+    DownloadConfig downloadConfig = new DownloadConfig(cfg);
+    httpScheme = new HttpScheme(cfg, urlProvider, userProvider, downloadConfig);
+    sshScheme =
+        new SshScheme(
+            List.of("gerrit.company.com:29418"), urlProvider, userProvider, downloadConfig);
+  }
+
+  @Test
+  public void ensureHttpSchemeEncodedInUrl() {
+    assertThat(httpScheme.getUrl("foo"))
+        .isEqualTo("https://john-doe%40company.com@gerrit.company.com/a/foo");
+  }
+
+  @Test
+  public void ensureSshSchemeEncodedInUrl() {
+    assertThat(sshScheme.getUrl("foo"))
+        .isEqualTo("ssh://john-doe%40company.com@gerrit.company.com:29418/foo");
+  }
+
+  private static CurrentUser fakeUser() {
+    return new CurrentUser() {
+      @Override
+      public Optional<String> getUserName() {
+        return Optional.of("john-doe@company.com");
+      }
+
+      @Override
+      public GroupMembership getEffectiveGroups() {
+        throw new UnsupportedOperationException("not implemented");
+      }
+
+      @Override
+      public Object getCacheKey() {
+        return new Object();
+      }
+
+      @Override
+      public boolean isIdentifiedUser() {
+        return true;
+      }
+
+      @Override
+      public Account.Id getAccountId() {
+        return Account.id(1);
+      }
+    };
+  }
+}