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 2.0
7 * which accompanies this distribution, and is available at
8 * https://www.eclipse.org/legal/epl-2.0/
10 * SPDX-License-Identifier: EPL-2.0
11 *******************************************************************************/
12 package org
.eclipse
.egit
.core
.op
;
14 import java
.io
.ByteArrayOutputStream
;
15 import java
.io
.IOException
;
16 import java
.io
.InputStream
;
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
.errors
.CommandFailedException
;
30 import org
.eclipse
.jgit
.lib
.Constants
;
31 import org
.eclipse
.jgit
.lib
.Repository
;
32 import org
.eclipse
.jgit
.lib
.StoredConfig
;
33 import org
.eclipse
.jgit
.transport
.CredentialsProvider
;
34 import org
.eclipse
.jgit
.transport
.RemoteConfig
;
35 import org
.eclipse
.jgit
.transport
.URIish
;
36 import org
.eclipse
.jgit
.util
.SshSupport
;
39 * Configure Gerrit if repository was cloned from a Gerrit server
41 public class ConfigureGerritAfterCloneTask
implements PostCloneTask
{
43 private static final String GIT_ECLIPSE_ORG
= "git.eclipse.org"; //$NON-NLS-1$
45 private static final String GERRIT_CONTEXT_ROOT
= "/r/"; //$NON-NLS-1$
47 private static final String HTTP
= "http"; //$NON-NLS-1$
49 private static final String HTTPS
= "https"; //$NON-NLS-1$
51 private static final String SSH
= "ssh"; //$NON-NLS-1$
53 private static final String GERRIT_CONFIG_SERVER_VERSION_API
= "/config/server/version"; //$NON-NLS-1$
55 private static final int GERRIT_SSHD_DEFAULT_PORT
= 29418;
57 private static final String GERRIT_SSHD_VERSION_API
= "gerrit version"; //$NON-NLS-1$
60 * Pattern to match the sshd reply[1] against to determine whether it's a
64 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-version.html">
65 * Gerrit 2.11 gerrit version ssh command</a>
68 * We match the whole reply from Gerrit's sshd (as opposed to a prefix match
69 * for "gerrit version") just in case a non-Gerrit has the great idea to
70 * return an error message like "gerrit version: unknown command" or some
71 * such on its stdout...
74 private static final Pattern GERRIT_SSHD_REPLY
= Pattern
75 .compile(GERRIT_SSHD_VERSION_API
76 + "\\s+(?:\\d+(?:\\.\\d+)+|.+-\\d+-g[0-9a-fA-F]{7,})"); //$NON-NLS-1$
79 * To prevent against Cross Site Script Inclusion (XSSI) attacks, the Gerrit
80 * JSON response body starts with a magic prefix line we can use as a second
81 * marker beyond the get version endpoint [1] to detect a Gerrit server
84 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
85 * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
88 private static final String GERRIT_XSSI_MAGIC_STRING
= ")]}\'\n"; //$NON-NLS-1$
90 private final String uri
;
92 private final String remoteName
;
94 private final CredentialsProvider credentialsProvider
;
103 * @param credentialsProvider
104 * {@link CredentialsProvider} to use for remote communication;
105 * if {@code null} auto-configuration for repositories cloned
106 * over ssh will work only for git.eclipse.org
108 * timeout for remote communication in seconds
110 public ConfigureGerritAfterCloneTask(String uri
, String remoteName
,
111 CredentialsProvider credentialsProvider
, int timeout
) {
113 this.remoteName
= remoteName
;
114 this.timeout
= timeout
;
115 this.credentialsProvider
= credentialsProvider
;
119 public void execute(Repository repository
, IProgressMonitor monitor
)
120 throws CoreException
{
122 if (isGerrit(repository
)) {
123 Activator
.logInfo(uri
124 + " was detected to be hosted by a Gerrit server"); //$NON-NLS-1$
125 configureGerrit(repository
);
127 } catch (Exception e
) {
128 throw new CoreException(Activator
.error(e
.getMessage(), e
));
133 * Try to use Gerrit's "Get Version" REST API endpoint [1] to detect if the
134 * repository is hosted on a Gerrit server.
137 * "https://gerrit-documentation.storage.googleapis.com/Documentation/2.11
138 * /rest-api-config.html#get-version">Gerrit 2.11 Get Version REST
142 * the repository to be configured
144 * @return {@code true} if the repository is hosted on a Gerrit server
145 * @throws IOException
146 * @throws MalformedURLException
147 * @throws URISyntaxException
149 private boolean isGerrit(Repository repo
) throws MalformedURLException
,
152 URIish u
= new URIish(uri
);
153 final String s
= u
.getScheme();
154 final String host
= u
.getHost();
155 final String path
= u
.getPath();
157 // shortcut for Eclipse Gerrit server
158 if (host
!= null && host
.equals(GIT_ECLIPSE_ORG
)) {
159 if (HTTPS
.equals(s
) && (u
.getPort() == 443 || u
.getPort() == -1)
160 && path
!= null && path
.startsWith(GERRIT_CONTEXT_ROOT
)) {
162 } else if (SSH
.equals(s
)
163 && u
.getPort() == GERRIT_SSHD_DEFAULT_PORT
) {
168 if (path
!= null && (HTTP
.equals(s
) || HTTPS
.equals(s
))) {
169 String baseURL
= u
.setPath("/").toString(); //$NON-NLS-1$
170 baseURL
= baseURL
.substring(0, baseURL
.length() - 1);
171 String tmpPath
= ""; //$NON-NLS-1$
174 HttpURLConnection httpConnection
= null;
176 httpConnection
= (HttpURLConnection
) new URL(baseURL
177 + tmpPath
+ GERRIT_CONFIG_SERVER_VERSION_API
)
179 NetUtil
.setSslVerification(repo
, httpConnection
);
180 httpConnection
.setRequestMethod("GET"); //$NON-NLS-1$
181 httpConnection
.setReadTimeout(1000 * timeout
);
182 int responseCode
= httpConnection
.getResponseCode();
183 switch (responseCode
) {
184 case HttpURLConnection
.HTTP_OK
:
185 try (InputStream in
= httpConnection
.getInputStream()) {
186 String response
= readFully(in
, "UTF-8"); //$NON-NLS-1$
188 .startsWith(GERRIT_XSSI_MAGIC_STRING
);
190 case HttpURLConnection
.HTTP_NOT_FOUND
:
191 if (slash
> path
.length()) {
194 slash
= path
.indexOf('/', slash
);
196 // try the entire path
197 slash
= path
.length();
199 tmpPath
= path
.substring(0, slash
);
205 } catch (IOException e
) {
208 if (httpConnection
!= null) {
209 httpConnection
.disconnect();
213 } else if (SSH
.equals(s
)) {
214 if (u
.getPort() < 0) {
217 URIish sshUri
= u
.setPath(""); //$NON-NLS-1$
219 String result
= SshSupport
.runSshCommand(sshUri
,
220 credentialsProvider
, repo
.getFS(),
221 GERRIT_SSHD_VERSION_API
, timeout
);
222 if (result
!= null && result
.contains("\n")) { //$NON-NLS-1$
223 result
= result
.substring(0, result
.indexOf('\n'));
225 return result
!= null
226 && GERRIT_SSHD_REPLY
.matcher(result
).matches();
227 } catch (IOException
| CommandFailedException e
) {
228 // Something went wrong with the connection or with the command
229 // execution. Maybe the server didn't recognize the command. Do
230 // the safe thing and claim it wasn't a Gerrit. In the worst
231 // case, the user may have to do the Gerrit config setup via
232 // the ConfigureGerritWizard.
239 private String
readFully(InputStream inputStream
, String encoding
)
241 return new String(readFully(inputStream
), encoding
);
244 private byte[] readFully(InputStream inputStream
) throws IOException
{
245 ByteArrayOutputStream os
= new ByteArrayOutputStream();
246 byte[] buffer
= new byte[1024];
248 while ((length
= inputStream
.read(buffer
)) != -1) {
249 os
.write(buffer
, 0, length
);
251 return os
.toByteArray();
254 private void configureGerrit(Repository repository
)
255 throws URISyntaxException
, IOException
{
256 StoredConfig config
= repository
.getConfig();
257 RemoteConfig remoteConfig
;
258 remoteConfig
= GerritUtil
.findRemoteConfig(config
, remoteName
);
259 if (remoteConfig
== null) {
262 GerritUtil
.configurePushURI(remoteConfig
, new URIish(uri
));
263 GerritUtil
.configurePushRefSpec(remoteConfig
, Constants
.MASTER
);
264 GerritUtil
.configureFetchNotes(remoteConfig
);
265 GerritUtil
.setCreateChangeId(config
);
266 remoteConfig
.update(config
);