diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java index 5d8268b73..25a70c745 100644 --- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java +++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java @@ -47,9 +47,7 @@ import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT; import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING; import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.net.ProxySelector; import java.net.URL; import java.text.SimpleDateFormat; @@ -66,20 +64,17 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.transport.HttpConfig; import org.eclipse.jgit.transport.HttpTransport; -import org.eclipse.jgit.transport.RemoteSession; -import org.eclipse.jgit.transport.SshSessionFactory; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.transport.http.HttpConnection; -import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.HttpSupport; -import org.eclipse.jgit.util.io.MessageWriter; -import org.eclipse.jgit.util.io.StreamCopyThread; +import org.eclipse.jgit.util.SshSupport; /** * Provides means to get a valid LFS connection for a given repository. */ public class LfsConnectionFactory { + private static final int SSH_AUTH_TIMEOUT_SECONDS = 5; private static final String SCHEME_HTTPS = "https"; //$NON-NLS-1$ private static final String SCHEME_SSH = "ssh"; //$NON-NLS-1$ private static final Map sshAuthCache = new TreeMap<>(); @@ -193,10 +188,11 @@ public class LfsConnectionFactory { // discover and authenticate; git-lfs does "ssh // -p -- git-lfs-authenticate // " - String json = runSshCommand(u.setPath(""), //$NON-NLS-1$ - db.getFS(), + String json = SshSupport.runSshCommand(u.setPath(""), //$NON-NLS-1$ + null, db.getFS(), "git-lfs-authenticate " + extractProjectName(u) + " " //$NON-NLS-1$//$NON-NLS-2$ - + purpose); + + purpose, + SSH_AUTH_TIMEOUT_SECONDS); action = Protocol.gson().fromJson(json, Protocol.ExpiringAction.class); @@ -253,42 +249,6 @@ public class LfsConnectionFactory { } } - private static String runSshCommand(URIish sshUri, FS fs, String command) - throws IOException { - RemoteSession session = null; - Process process = null; - StreamCopyThread errorThread = null; - try (MessageWriter stderr = new MessageWriter()) { - session = SshSessionFactory.getInstance().getSession(sshUri, null, - fs, 5_000); - process = session.exec(command, 0); - errorThread = new StreamCopyThread(process.getErrorStream(), - stderr.getRawStream()); - errorThread.start(); - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(process.getInputStream(), - org.eclipse.jgit.lib.Constants.CHARSET))) { - return reader.readLine(); - } - } finally { - if (process != null) { - process.destroy(); - } - if (errorThread != null) { - try { - errorThread.halt(); - } catch (InterruptedException e) { - // Stop waiting and return anyway. - } finally { - errorThread = null; - } - } - if (session != null) { - SshSessionFactory.getInstance().releaseSession(session); - } - } - } - /** * @param operation * the operation to perform, e.g. Protocol.OPERATION_DOWNLOAD diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java new file mode 100644 index 000000000..96123ea67 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SshSupport.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2018, Markus Duft + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.RemoteSession; +import org.eclipse.jgit.transport.SshSessionFactory; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.io.MessageWriter; +import org.eclipse.jgit.util.io.StreamCopyThread; + +/** + * Extra utilities to support usage of SSH. + * + * @since 5.0 + */ +public class SshSupport { + + /** + * Utility to execute a remote SSH command and read the first line of + * output. + * + * @param sshUri + * the SSH remote URI + * @param provider + * the {@link CredentialsProvider} or null. + * @param fs + * the {@link FS} implementation passed to + * {@link SshSessionFactory} + * @param command + * the remote command to execute. + * @param timeout + * a timeout in seconds. + * @return The first line of output read from stdout. Stderr is discarded. + * @throws IOException + */ + public static String runSshCommand(URIish sshUri, + @Nullable CredentialsProvider provider, FS fs, String command, + int timeout) throws IOException { + RemoteSession session = null; + Process process = null; + StreamCopyThread errorThread = null; + try (MessageWriter stderr = new MessageWriter()) { + session = SshSessionFactory.getInstance().getSession(sshUri, + provider, fs, 1000 * timeout); + process = session.exec(command, 0); + errorThread = new StreamCopyThread(process.getErrorStream(), + stderr.getRawStream()); + errorThread.start(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream(), + Constants.CHARSET))) { + return reader.readLine(); + } + } finally { + if (errorThread != null) { + try { + errorThread.halt(); + } catch (InterruptedException e) { + // Stop waiting and return anyway. + } finally { + errorThread = null; + } + } + if (process != null) { + process.destroy(); + } + if (session != null) { + SshSessionFactory.getInstance().releaseSession(session); + } + } + } + +}