1 /*******************************************************************************
2 * Copyright (C) 2015, Matthias Sohn <matthias.sohn@sap.com>
3 * Copyright (C) 2015, Thomas Wolf <thomas.wolf@paranor.ch>
5 * All rights reserved. This program and the accompanying materials
6 * are made available under the terms of the Eclipse Public License v1.0
7 * which accompanies this distribution, and is available at
8 * http://www.eclipse.org/legal/epl-v10.html
9 *******************************************************************************/
10 package org
.eclipse
.egit
.core
.op
;
12 import java
.io
.BufferedReader
;
13 import java
.io
.ByteArrayOutputStream
;
14 import java
.io
.IOException
;
15 import java
.io
.InputStream
;
16 import java
.io
.InputStreamReader
;
17 import java
.net
.HttpURLConnection
;
18 import java
.net
.MalformedURLException
;
19 import java
.net
.URISyntaxException
;
21 import java
.util
.regex
.Pattern
;
23 import org
.eclipse
.core
.runtime
.CoreException
;
24 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
25 import org
.eclipse
.egit
.core
.Activator
;
26 import org
.eclipse
.egit
.core
.NetUtil
;
27 import org
.eclipse
.egit
.core
.internal
.gerrit
.GerritUtil
;
28 import org
.eclipse
.egit
.core
.op
.CloneOperation
.PostCloneTask
;
29 import org
.eclipse
.jgit
.lib
.Constants
;
30 import org
.eclipse
.jgit
.lib
.Repository
;
31 import org
.eclipse
.jgit
.lib
.StoredConfig
;
32 import org
.eclipse
.jgit
.transport
.CredentialsProvider
;
33 import org
.eclipse
.jgit
.transport
.RemoteConfig
;
34 import org
.eclipse
.jgit
.transport
.RemoteSession
;
35 import org
.eclipse
.jgit
.transport
.SshSessionFactory
;
36 import org
.eclipse
.jgit
.transport
.URIish
;
37 import org
.eclipse
.jgit
.util
.FS
;
38 import org
.eclipse
.jgit
.util
.io
.MessageWriter
;
39 import org
.eclipse
.jgit
.util
.io
.StreamCopyThread
;
42 * Configure Gerrit if repository was cloned from a Gerrit server
44 public class ConfigureGerritAfterCloneTask
implements PostCloneTask
{
46 private static final String GIT_ECLIPSE_ORG
= "git.eclipse.org"; //$NON-NLS-1$
48 private static final String GERRIT_CONTEXT_ROOT
= "/r/"; //$NON-NLS-1$
50 private static final String HTTP
= "http"; //$NON-NLS-1$
52 private static final String HTTPS
= "https"; //$NON-NLS-1$
54 private static final String SSH
= "ssh"; //$NON-NLS-1$
56 private static final String GERRIT_CONFIG_SERVER_VERSION_API
= "/config/server/version"; //$NON-NLS-1$
58 private static final int GERRIT_SSHD_DEFAULT_PORT
= 29418;
60 private static final String GERRIT_SSHD_VERSION_API
= "gerrit version"; //$NON-NLS-1$
63 * Pattern to match the sshd reply[1] against to determine whether it's a
67 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-version.html">
68 * Gerrit 2.11 gerrit version ssh command</a>
71 * We match the whole reply from Gerrit's sshd (as opposed to a prefix match
72 * for "gerrit version") just in case a non-Gerrit has the great idea to
73 * return an error message like "gerrit version: unknown command" or some
74 * such on its stdout...
77 private static final Pattern GERRIT_SSHD_REPLY
= Pattern
78 .compile(GERRIT_SSHD_VERSION_API
79 + "\\s+(?:\\d+(?:\\.\\d+)+|.+-\\d+-g[0-9a-fA-F]{7,})"); //$NON-NLS-1$
82 * To prevent against Cross Site Script Inclusion (XSSI) attacks, the Gerrit
83 * JSON response body starts with a magic prefix line we can use as a second
84 * marker beyond the get version endpoint [1] to detect a Gerrit server
87 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
88 * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
91 private static final String GERRIT_XSSI_MAGIC_STRING
= ")]}\'\n"; //$NON-NLS-1$
93 private final String uri
;
95 private final String remoteName
;
97 private final CredentialsProvider credentialsProvider
;
106 * @param credentialsProvider
107 * {@link CredentialsProvider} to use for remote communication;
108 * if {@code null} auto-configuration for repositories cloned
109 * over ssh will work only for git.eclipse.org
111 * timeout for remote communication in seconds
113 public ConfigureGerritAfterCloneTask(String uri
, String remoteName
,
114 CredentialsProvider credentialsProvider
, int timeout
) {
116 this.remoteName
= remoteName
;
117 this.timeout
= timeout
;
118 this.credentialsProvider
= credentialsProvider
;
122 public void execute(Repository repository
, IProgressMonitor monitor
)
123 throws CoreException
{
125 if (isGerrit(repository
)) {
126 Activator
.logInfo(uri
127 + " was detected to be hosted by a Gerrit server"); //$NON-NLS-1$
128 configureGerrit(repository
);
130 } catch (Exception e
) {
131 throw new CoreException(Activator
.error(e
.getMessage(), e
));
136 * Try to use Gerrit's "Get Version" REST API endpoint [1] to detect if the
137 * repository is hosted on a Gerrit server.
140 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
141 * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
145 * the repository to be configured
147 * @return {@code true} if the repository is hosted on a Gerrit server
148 * @throws IOException
149 * @throws MalformedURLException
150 * @throws URISyntaxException
152 private boolean isGerrit(Repository repo
) throws MalformedURLException
,
155 URIish u
= new URIish(uri
);
156 final String s
= u
.getScheme();
157 final String host
= u
.getHost();
158 final String path
= u
.getPath();
160 // shortcut for Eclipse Gerrit server
161 if (host
!= null && host
.equals(GIT_ECLIPSE_ORG
)) {
162 if (HTTPS
.equals(s
) && (u
.getPort() == 443 || u
.getPort() == -1)
163 && path
!= null && path
.startsWith(GERRIT_CONTEXT_ROOT
)) {
165 } else if (SSH
.equals(s
)
166 && u
.getPort() == GERRIT_SSHD_DEFAULT_PORT
) {
171 if (path
!= null && (HTTP
.equals(s
) || HTTPS
.equals(s
))) {
172 String baseURL
= u
.setPath("/").toString(); //$NON-NLS-1$
173 baseURL
= baseURL
.substring(0, baseURL
.length() - 1);
174 String tmpPath
= ""; //$NON-NLS-1$
177 HttpURLConnection httpConnection
= null;
179 httpConnection
= (HttpURLConnection
) new URL(baseURL
180 + tmpPath
+ GERRIT_CONFIG_SERVER_VERSION_API
)
182 NetUtil
.setSslVerification(repo
, httpConnection
);
183 httpConnection
.setRequestMethod("GET"); //$NON-NLS-1$
184 httpConnection
.setReadTimeout(1000 * timeout
);
185 int responseCode
= httpConnection
.getResponseCode();
186 switch (responseCode
) {
187 case HttpURLConnection
.HTTP_OK
:
188 try (InputStream in
= httpConnection
.getInputStream()) {
189 String response
= readFully(in
, "UTF-8"); //$NON-NLS-1$
191 .startsWith(GERRIT_XSSI_MAGIC_STRING
);
193 case HttpURLConnection
.HTTP_NOT_FOUND
:
194 if (slash
> path
.length()) {
197 slash
= path
.indexOf('/', slash
);
199 // try the entire path
200 slash
= path
.length();
202 tmpPath
= path
.substring(0, slash
);
208 } catch (IOException e
) {
211 if (httpConnection
!= null) {
212 httpConnection
.disconnect();
216 } else if (SSH
.equals(s
)) {
217 if (u
.getPort() < 0) {
220 URIish sshUri
= u
.setPath(""); //$NON-NLS-1$
222 String result
= runSshCommand(sshUri
, credentialsProvider
,
223 repo
.getFS(), GERRIT_SSHD_VERSION_API
);
224 return result
!= null
225 && GERRIT_SSHD_REPLY
.matcher(result
).matches();
226 } catch (IOException e
) {
227 // Something went wrong with the connection or with the command
228 // execution. Maybe the server didn't recognize the command. Do
229 // the safe thing and claim it wasn't a Gerrit. In the worst
230 // case, the user may have to do the Gerrit config setup via
231 // the ConfigureGerritWizard.
238 private String
readFully(InputStream inputStream
, String encoding
)
240 return new String(readFully(inputStream
), encoding
);
243 private byte[] readFully(InputStream inputStream
) throws IOException
{
244 ByteArrayOutputStream os
= new ByteArrayOutputStream();
245 byte[] buffer
= new byte[1024];
247 while ((length
= inputStream
.read(buffer
)) != -1) {
248 os
.write(buffer
, 0, length
);
250 return os
.toByteArray();
253 private void configureGerrit(Repository repository
)
254 throws URISyntaxException
, IOException
{
255 StoredConfig config
= repository
.getConfig();
256 RemoteConfig remoteConfig
;
257 remoteConfig
= GerritUtil
.findRemoteConfig(config
, remoteName
);
258 if (remoteConfig
== null) {
261 GerritUtil
.configurePushURI(remoteConfig
, new URIish(uri
));
262 GerritUtil
.configurePushRefSpec(remoteConfig
, Constants
.MASTER
);
263 GerritUtil
.configureFetchNotes(remoteConfig
);
264 GerritUtil
.setCreateChangeId(config
);
265 remoteConfig
.update(config
);
269 private String
runSshCommand(URIish sshUri
, CredentialsProvider provider
,
270 FS fs
, String command
) throws IOException
{
271 RemoteSession session
= null;
272 Process process
= null;
273 StreamCopyThread errorThread
= null;
274 try (MessageWriter stderr
= new MessageWriter()) {
275 session
= SshSessionFactory
.getInstance().getSession(sshUri
,
276 provider
, fs
, 1000 * timeout
);
277 process
= session
.exec(command
, 0);
278 errorThread
= new StreamCopyThread(process
.getErrorStream(),
279 stderr
.getRawStream());
281 try (BufferedReader reader
= new BufferedReader(
282 new InputStreamReader(process
.getInputStream(),
283 Constants
.CHARSET
))) {
284 return reader
.readLine();
287 if (errorThread
!= null) {
290 } catch (InterruptedException e
) {
291 // Stop waiting and return anyway.
296 if (process
!= null) {
299 if (session
!= null) {
300 SshSessionFactory
.getInstance().releaseSession(session
);