org.eclipse.egit.core.op.ConfigureGerritAfterCloneTask.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.egit.core.op.ConfigureGerritAfterCloneTask.java

Source

/*******************************************************************************
 * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
 * Copyright (C) 2015, Thomas Wolf <thomas.wolf@paranor.ch>
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *******************************************************************************/
package org.eclipse.egit.core.op;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.egit.core.Activator;
import org.eclipse.egit.core.NetUtil;
import org.eclipse.egit.core.internal.gerrit.GerritUtil;
import org.eclipse.egit.core.op.CloneOperation.PostCloneTask;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.RemoteSession;
import org.eclipse.jgit.transport.SshSessionFactory;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.io.MessageWriter;
import org.eclipse.jgit.util.io.StreamCopyThread;

/**
 * Configure Gerrit if repository was cloned from a Gerrit server
 */
public class ConfigureGerritAfterCloneTask implements PostCloneTask {

    private static final String GIT_ECLIPSE_ORG = "git.eclipse.org"; //$NON-NLS-1$

    private static final String GERRIT_CONTEXT_ROOT = "/r/"; //$NON-NLS-1$

    private static final String HTTP = "http"; //$NON-NLS-1$

    private static final String HTTPS = "https"; //$NON-NLS-1$

    private static final String SSH = "ssh"; //$NON-NLS-1$

    private static final String GERRIT_CONFIG_SERVER_VERSION_API = "/config/server/version"; //$NON-NLS-1$

    private static final int GERRIT_SSHD_DEFAULT_PORT = 29418;

    private static final String GERRIT_SSHD_VERSION_API = "gerrit version"; //$NON-NLS-1$

    /**
     * Pattern to match the sshd reply[1] against to determine whether it's a
     * Gerrit.
     * <p>
     * [1]<a href=
     * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-version.html">
     * Gerrit 2.11 gerrit version ssh command</a>
     * </p>
     * <p>
     * We match the whole reply from Gerrit's sshd (as opposed to a prefix match
     * for "gerrit version") just in case a non-Gerrit has the great idea to
     * return an error message like "gerrit version: unknown command" or some
     * such on its stdout...
     * </p>
     */
    private static final Pattern GERRIT_SSHD_REPLY = Pattern
            .compile(GERRIT_SSHD_VERSION_API + "\\s+(?:\\d+(?:\\.\\d+)+|.+-\\d+-g[0-9a-fA-F]{7})"); //$NON-NLS-1$

    /**
     * To prevent against Cross Site Script Inclusion (XSSI) attacks, the Gerrit
     * JSON response body starts with a magic prefix line we can use as a second
     * marker beyond the get version endpoint [1] to detect a Gerrit server
     * <p/>
     * [1] <a href=
     * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
     * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
     * endpoint</a>
     */
    private static final String GERRIT_XSSI_MAGIC_STRING = ")]}\'\n"; //$NON-NLS-1$

    private final String uri;

    private final String remoteName;

    private final CredentialsProvider credentialsProvider;

    private int timeout;

    /**
     * @param uri
     *            not null
     * @param remoteName
     *            not null
     * @param credentialsProvider
     *            {@link CredentialsProvider} to use for remote communication;
     *            if {@code null} auto-configuration for repositories cloned
     *            over ssh will work only for git.eclipse.org
     * @param timeout
     *            timeout for remote communication in seconds
     */
    public ConfigureGerritAfterCloneTask(String uri, String remoteName, CredentialsProvider credentialsProvider,
            int timeout) {
        this.uri = uri;
        this.remoteName = remoteName;
        this.timeout = timeout;
        this.credentialsProvider = credentialsProvider;
    }

    @Override
    public void execute(Repository repository, IProgressMonitor monitor) throws CoreException {
        try {
            if (isGerrit(repository)) {
                Activator.logInfo(uri + " was detected to be hosted by a Gerrit server"); //$NON-NLS-1$
                configureGerrit(repository);
            }
        } catch (Exception e) {
            throw new CoreException(Activator.error(e.getMessage(), e));
        }
    }

    /**
     * Try to use Gerrit's "Get Version" REST API endpoint [1] to detect if the
     * repository is hosted on a Gerrit server.
     * <p/>
     * [1] <a href=
     * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
     * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
     * endpoint</a>
     *
     * @param repo
     *            the repository to be configured
     *
     * @return {@code true} if the repository is hosted on a Gerrit server
     * @throws IOException
     * @throws MalformedURLException
     * @throws URISyntaxException
     */
    private boolean isGerrit(Repository repo) throws MalformedURLException, IOException, URISyntaxException {
        URIish u = new URIish(uri);
        final String s = u.getScheme();
        final String host = u.getHost();
        final String path = u.getPath();

        // shortcut for Eclipse Gerrit server
        if (host != null && host.equals(GIT_ECLIPSE_ORG)) {
            if (HTTPS.equals(s) && (u.getPort() == 443 || u.getPort() == -1) && path != null
                    && path.startsWith(GERRIT_CONTEXT_ROOT)) {
                return true;
            } else if (SSH.equals(s) && u.getPort() == GERRIT_SSHD_DEFAULT_PORT) {
                return true;
            }
        }

        if (path != null && (HTTP.equals(s) || HTTPS.equals(s))) {
            String baseURL = u.setPath("/").toString(); //$NON-NLS-1$
            baseURL = baseURL.substring(0, baseURL.length() - 1);
            String tmpPath = ""; //$NON-NLS-1$
            int slash = 1;
            while (true) {
                HttpURLConnection httpConnection = null;
                try {
                    httpConnection = (HttpURLConnection) new URL(
                            baseURL + tmpPath + GERRIT_CONFIG_SERVER_VERSION_API).openConnection();
                    NetUtil.setSslVerification(repo, httpConnection);
                    httpConnection.setRequestMethod("GET"); //$NON-NLS-1$
                    httpConnection.setReadTimeout(1000 * timeout);
                    int responseCode = httpConnection.getResponseCode();
                    switch (responseCode) {
                    case HttpURLConnection.HTTP_OK:
                        try (InputStream in = httpConnection.getInputStream()) {
                            String response = readFully(in, "UTF-8"); //$NON-NLS-1$
                            return response.startsWith(GERRIT_XSSI_MAGIC_STRING);
                        }
                    case HttpURLConnection.HTTP_NOT_FOUND:
                        if (slash > path.length()) {
                            return false;
                        }
                        slash = path.indexOf('/', slash);
                        if (slash == -1) {
                            // try the entire path
                            slash = path.length();
                        }
                        tmpPath = path.substring(0, slash);
                        slash++;
                        break;
                    default:
                        return false;
                    }
                } catch (IOException e) {
                    return false;
                } finally {
                    if (httpConnection != null) {
                        httpConnection.disconnect();
                    }
                }
            }
        } else if (SSH.equals(s)) {
            if (u.getPort() < 0 || u.getUser() == null || credentialsProvider == null) {
                return false;
            }
            URIish sshUri = u.setPath(""); //$NON-NLS-1$
            try {
                String result = runSshCommand(sshUri, credentialsProvider, repo.getFS(), GERRIT_SSHD_VERSION_API);
                return result != null && GERRIT_SSHD_REPLY.matcher(result).matches();
            } catch (IOException e) {
                // Something went wrong with the connection or with the command
                // execution. Maybe the server didn't recognize the command. Do
                // the safe thing and claim it wasn't a Gerrit. In the worst
                // case, the user may have to do the Gerrit config setup via
                // the ConfigureGerritWizard.
                return false;
            }
        }
        return false;
    }

    private String readFully(InputStream inputStream, String encoding) throws IOException {
        return new String(readFully(inputStream), encoding);
    }

    private byte[] readFully(InputStream inputStream) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length = 0;
        while ((length = inputStream.read(buffer)) != -1) {
            os.write(buffer, 0, length);
        }
        return os.toByteArray();
    }

    private void configureGerrit(Repository repository) throws URISyntaxException, IOException {
        StoredConfig config = repository.getConfig();
        RemoteConfig remoteConfig;
        remoteConfig = GerritUtil.findRemoteConfig(config, remoteName);
        if (remoteConfig == null) {
            return;
        }
        GerritUtil.configurePushURI(remoteConfig, new URIish(uri));
        GerritUtil.configurePushRefSpec(remoteConfig, Constants.MASTER);
        GerritUtil.configureFetchNotes(remoteConfig);
        GerritUtil.setCreateChangeId(config);
        remoteConfig.update(config);
        config.save();
    }

    private String runSshCommand(URIish sshUri, CredentialsProvider provider, 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, 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);
            }
        }
    }

}