changeset 1012:6b69c7a12f76

TW-60219: escape unsafe characters in URL before calling hg commands with this URL
author pavel.sher
date Fri, 03 May 2019 10:18:19 +0200
parents 17bd4181c476
children 9c8525c5af12
files mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/AuthSettings.java mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommandTest.java
diffstat 3 files changed, 73 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/AuthSettings.java	Thu Jan 31 19:14:24 2019 +0300
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/AuthSettings.java	Fri May 03 10:18:19 2019 +0200
@@ -17,6 +17,8 @@
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
 import jetbrains.buildServer.log.Loggers;
+import jetbrains.buildServer.serverSide.TeamCityProperties;
+import jetbrains.buildServer.util.StringUtil;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -55,20 +57,49 @@
   }
 
   public String getRepositoryUrlWithCredentials(@NotNull String repositoryUrl) {
-    if (isRequireCredentials(repositoryUrl)) {
-      if (containsCredentials(repositoryUrl))
-        return repositoryUrl;
+    String preparedUrl = escapeUnsafeChars(repositoryUrl);
+    if (isRequireCredentials(preparedUrl)) {
+      if (containsCredentials(preparedUrl))
+        return preparedUrl;
       try {
-        return createURLWithCredentials(repositoryUrl);
+        return createURLWithCredentials(preparedUrl);
       } catch (MalformedURLException e) {
-        Loggers.VCS.warn("Error while parsing url " + repositoryUrl, e);
+        Loggers.VCS.warn("Error while parsing url " + preparedUrl, e);
       }
-      return repositoryUrl;
+      return preparedUrl;
     } else {
-      return repositoryUrl;
+      return preparedUrl;
     }
   }
 
+  @NotNull
+  private String escapeUnsafeChars(@NotNull String repositoryUrl) {
+    if (!TeamCityProperties.getBooleanOrTrue("teamcity.mercurial.identifyCommand.escapeUnsafeChars")) return repositoryUrl;
+
+    StringBuilder res = new StringBuilder();
+
+    for (char c: repositoryUrl.toCharArray()) {
+      switch (c) {
+        case '"':
+          res.append("%22");
+          break;
+        case '\'':
+          res.append("%27");
+          break;
+        case ' ':
+          res.append("%20");
+          break;
+        case ';':
+          res.append("%3B");
+          break;
+        default:
+          res.append(c);
+      }
+    }
+
+    return res.toString();
+  }
+
   public String getRepositoryUrlWithHiddenPassword(@NotNull String repositoryUrl) {
     if (isEmpty(myPassword))
       return repositoryUrl;
--- a/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Thu Jan 31 19:14:24 2019 +0300
+++ b/mercurial-common/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommand.java	Fri May 03 10:18:19 2019 +0200
@@ -82,7 +82,19 @@
   }
 
   public String call() throws VcsException {
-    MercurialCommandLine cli = createCL();
+    CommandResult res = runCommand(createCL());
+    String output = res.getRawStdout().trim();
+    if (myShowBranch) {
+      return output;
+    } else {
+      return output.contains(" ") ? output.substring(0, output.indexOf(" ")) : output;
+    }
+  }
+
+  @NotNull
+  @Override
+  protected MercurialCommandLine createCL() {
+    MercurialCommandLine cli = super.createCL();
     cli.addParameter("identify");
     if (myInLocalRepository) {
       cli.setWorkDirectory(this.getWorkDirectory().getAbsolutePath());
@@ -102,13 +114,7 @@
       cli.addParameter("--rev");
       cli.addParameter(myNamedRevision);
     }
-    CommandResult res = runCommand(cli);
-    String output = res.getRawStdout().trim();
-    if (myShowBranch) {
-      return output;
-    } else {
-      return output.contains(" ") ? output.substring(0, output.indexOf(" ")) : output;
-    }
+    return cli;
   }
 
   @NotNull
--- a/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommandTest.java	Thu Jan 31 19:14:24 2019 +0300
+++ b/mercurial-tests/src/jetbrains/buildServer/buildTriggers/vcs/mercurial/command/IdentifyCommandTest.java	Fri May 03 10:18:19 2019 +0200
@@ -16,12 +16,9 @@
 
 package jetbrains.buildServer.buildTriggers.vcs.mercurial.command;
 
-import jetbrains.buildServer.buildTriggers.vcs.mercurial.HgPathProvider;
 import jetbrains.buildServer.vcs.VcsException;
-import org.jetbrains.annotations.NotNull;
 import org.testng.annotations.Test;
 
-import java.io.File;
 import java.io.IOException;
 
 /**
@@ -51,15 +48,27 @@
     }
   }
 
-  private Void runIdentify(final ChangeSet cset) throws IOException, VcsException {
-    return runCommand(new CommandExecutor<Void>() {
-      public Void execute(@NotNull HgVcsRoot root, @NotNull HgPathProvider hgPathProvider, @NotNull File workingDir) throws VcsException {
-        new IdentifyCommand(new TestCommandSettingsFactory().create(), hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings())
-                .revision(cset)
-                .inLocalRepository()
-                .call();
-        return null;
-      }
+  public void should_escape_url_before_calling_command() throws IOException, VcsException {
+    setRepository("mercurial-tests/testData/rep1", false);
+    runCommand((root, hgPathProvider, workingDir) -> {
+      MercurialCommandLine cli = new IdentifyCommand(new TestCommandSettingsFactory().create(), hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings())
+              .repository("www.google.com \"';")
+              .withAuthSettings(new AuthSettings())
+              .createCL();
+
+      String commandLine = cli.toGeneralCommandLine().getCommandLineString();
+      assertTrue(commandLine, commandLine.contains("identify www.google.com%20%22%27%3B"));
+      return null;
+    });
+  }
+
+  private void runIdentify(final ChangeSet cset) throws IOException, VcsException {
+    runCommand((root, hgPathProvider, workingDir) -> {
+      new IdentifyCommand(new TestCommandSettingsFactory().create(), hgPathProvider.getHgPath(root), workingDir, root.getAuthSettings())
+              .revision(cset)
+              .inLocalRepository()
+              .call();
+      return null;
     });
   }
 }