summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Pearce <spearce@spearce.org>2015-04-30 12:43:46 -0700
committerShawn Pearce <spearce@spearce.org>2015-04-30 14:40:35 -0700
commit4a984e20332a765a81cadeaa4875b228ebf290fb (patch)
tree68591da210bd0d795e9e7da3b9ad790d26261840
parent7e0ebb8e29a1bc73ead72bc00569f06169079a26 (diff)
downloadjgit-4a984e20332a765a81cadeaa4875b228ebf290fb.tar.gz
jgit-4a984e20332a765a81cadeaa4875b228ebf290fb.zip
Support agent= capability in wire protocol
Since git-core ff5effd (v1.7.12.1) the native wire protocol transmits the server and client implementation and version strings using capability "agent=git/1.7.12.1" or similar. Support this in JGit and hang the implementation data off UploadPack and ReceivePack. On HTTP transports default to the User-Agent HTTP header until the client overrides this with the optional capability string in the first line. Extract the user agent string into a UserAgent class under transport where it can be specified to a different value if the application's build process has broken the Implementation-Version header in the JGit package. Change-Id: Icfc6524d84a787386d1786310b421b2f92ae9e65
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java4
-rw-r--r--org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java24
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java80
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java40
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java147
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java6
18 files changed, 392 insertions, 27 deletions
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
index d1c258047b..a8e312d3f0 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java
@@ -76,6 +76,7 @@ import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.errors.UnpackException;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.InternalHttpServerGlue;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
@@ -100,6 +101,9 @@ class ReceivePackServlet extends HttpServlet {
throws IOException, ServiceNotEnabledException,
ServiceNotAuthorizedException {
ReceivePack rp = receivePackFactory.create(req, db);
+ InternalHttpServerGlue.setPeerUserAgent(
+ rp,
+ req.getHeader(HDR_USER_AGENT));
req.setAttribute(ATTRIBUTE_HANDLER, rp);
}
diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
index c5272b55eb..7aefcbd809 100644
--- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
+++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java
@@ -74,6 +74,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.transport.InternalHttpServerGlue;
import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser;
import org.eclipse.jgit.transport.UploadPack;
import org.eclipse.jgit.transport.UploadPackInternalServerErrorException;
@@ -100,6 +101,9 @@ class UploadPackServlet extends HttpServlet {
throws IOException, ServiceNotEnabledException,
ServiceNotAuthorizedException {
UploadPack up = uploadPackFactory.create(req, db);
+ InternalHttpServerGlue.setPeerUserAgent(
+ up,
+ req.getHeader(HDR_USER_AGENT));
req.setAttribute(ATTRIBUTE_HANDLER, up);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
index 1072d58ada..59ff1bd997 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java
@@ -65,6 +65,8 @@ import org.eclipse.jgit.lib.Ref;
public abstract class BaseConnection implements Connection {
private Map<String, Ref> advertisedRefs = Collections.emptyMap();
+ private String peerUserAgent;
+
private boolean startedOperation;
private Writer messageWriter;
@@ -85,6 +87,28 @@ public abstract class BaseConnection implements Connection {
return messageWriter != null ? messageWriter.toString() : ""; //$NON-NLS-1$
}
+ /**
+ * User agent advertised by the remote server.
+ *
+ * @return agent (version of Git) running on the remote server. Null if the
+ * server does not advertise this version.
+ * @since 4.0
+ */
+ public String getPeerUserAgent() {
+ return peerUserAgent;
+ }
+
+ /**
+ * Remember the remote peer's agent.
+ *
+ * @param agent
+ * remote peer agent string.
+ * @since 4.0
+ */
+ protected void setPeerUserAgent(String agent) {
+ peerUserAgent = agent;
+ }
+
public abstract void close();
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
index 8f825ea14f..7f9cec734d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java
@@ -46,6 +46,8 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
+
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
@@ -275,6 +277,18 @@ abstract class BasePackConnection extends BaseConnection {
return true;
}
+ protected void addUserAgentCapability(StringBuilder b) {
+ String a = UserAgent.get();
+ if (a != null && UserAgent.hasAgent(remoteCapablities)) {
+ b.append(' ').append(OPTION_AGENT).append('=').append(a);
+ }
+ }
+
+ @Override
+ public String getPeerUserAgent() {
+ return UserAgent.getAgent(remoteCapablities, super.getPeerUserAgent());
+ }
+
private PackProtocolException duplicateAdvertisement(final String name) {
return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name));
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index 4036c00280..a6fc633593 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -521,6 +521,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
OPTION_MULTI_ACK_DETAILED));
}
+ addUserAgentCapability(line);
return line.toString();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
index 863934da16..1e5b8e8ad6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java
@@ -268,6 +268,7 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen
outputStream);
pckIn = new PacketLineIn(in);
}
+ addUserAgentCapability(line);
if (line.length() > 0)
line.setCharAt(0, '\0');
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index cf1d92e8e8..cf6b2fd3d0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -48,6 +48,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF;
@@ -224,6 +225,7 @@ public abstract class BaseReceivePack {
/** Capabilities requested by the client. */
private Set<String> enabledCapabilities;
+ String userAgent;
private Set<ObjectId> clientShallowCommits;
private List<ReceiveCommand> commands;
@@ -738,6 +740,25 @@ public abstract class BaseReceivePack {
return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K);
}
+ /**
+ * Get the user agent of the client.
+ * <p>
+ * If the client is new enough to use {@code agent=} capability that value
+ * will be returned. Older HTTP clients may also supply their version using
+ * the HTTP {@code User-Agent} header. The capability overrides the HTTP
+ * header if both are available.
+ * <p>
+ * When an HTTP request has been received this method returns the HTTP
+ * {@code User-Agent} header value until capabilities have been parsed.
+ *
+ * @return user agent supplied by the client. Available only if the client
+ * is new enough to advertise its user agent.
+ * @since 4.0
+ */
+ public String getPeerUserAgent() {
+ return UserAgent.getAgent(enabledCapabilities, userAgent);
+ }
+
/** @return all of the command received by the current request. */
public List<ReceiveCommand> getAllCommands() {
return Collections.unmodifiableList(commands);
@@ -955,6 +976,7 @@ public abstract class BaseReceivePack {
adv.advertiseCapability(CAPABILITY_ATOMIC);
if (allowOfsDelta)
adv.advertiseCapability(CAPABILITY_OFS_DELTA);
+ adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
adv.send(getAdvertisedOrDefaultRefs());
for (ObjectId obj : advertisedHaves)
adv.advertiseHave(obj);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
index e386c26c1f..0ff9fcea74 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java
@@ -127,4 +127,13 @@ public interface Connection {
* remote produced no additional messages.
*/
public String getMessages();
+
+ /**
+ * User agent advertised by the remote server.
+ *
+ * @return agent (version of Git) running on the remote server. Null if the
+ * server does not advertise this version.
+ * @since 4.0
+ */
+ public String getPeerUserAgent();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
index d2902a35b1..9aae1c37aa 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
@@ -136,6 +136,7 @@ class FetchProcess {
conn = transport.openFetch();
try {
result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap());
+ result.peerUserAgent = conn.getPeerUserAgent();
final Set<Ref> matched = new HashSet<Ref>();
for (final RefSpec spec : toFetch) {
if (spec.getSource() == null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index 27052db674..8d9d2b718d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -186,6 +186,13 @@ public class GitProtocolConstants {
*/
public static final String CAPABILITY_PUSH_CERT = "push-cert"; //$NON-NLS-1$
+ /**
+ * Implementation name and version of the client or server.
+ *
+ * @since 4.0
+ */
+ public static final String OPTION_AGENT = "agent"; //$NON-NLS-1$
+
static enum MultiAck {
OFF, CONTINUE, DETAILED;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java
new file mode 100644
index 0000000000..fe7aaf7699
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015, Google Inc.
+ * 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.transport;
+
+/**
+ * Internal API to to assist {@code org.eclipse.jgit.http.server}.
+ * <p>
+ * <b>Do not call.</b>
+ *
+ * @since 4.0
+ */
+public class InternalHttpServerGlue {
+ /**
+ * Apply a default user agent for a request.
+ *
+ * @param up
+ * current UploadPack instance.
+ * @param agent
+ * user agent string from the HTTP headers.
+ */
+ public static void setPeerUserAgent(UploadPack up, String agent) {
+ up.userAgent = agent;
+ }
+
+ /**
+ * Apply a default user agent for a request.
+ *
+ * @param rp
+ * current ReceivePack instance.
+ * @param agent
+ * user agent string from the HTTP headers.
+ */
+ public static void setPeerUserAgent(ReceivePack rp, String agent) {
+ rp.userAgent = agent;
+ }
+
+ private InternalHttpServerGlue() {
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
index b4a48b0165..ad51f3e70c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java
@@ -68,6 +68,8 @@ public abstract class OperationResult {
StringBuilder messageBuffer;
+ String peerUserAgent;
+
/**
* Get the URI this result came from.
* <p>
@@ -165,4 +167,15 @@ public abstract class OperationResult {
messageBuffer.append('\n');
}
}
+
+ /**
+ * Get the user agent advertised by the peer server, if available.
+ *
+ * @return advertised user agent, e.g. {@code "JGit/4.0"}. Null if the peer
+ * did not advertise version information.
+ * @since 4.0
+ */
+ public String getPeerUserAgent() {
+ return peerUserAgent;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
index 53fba55572..00f84f70ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java
@@ -155,6 +155,7 @@ class PushProcess {
try {
res.setAdvertisedRefs(transport.getURI(), connection
.getRefsMap());
+ res.peerUserAgent = connection.getPeerUserAgent();
res.setRemoteUpdates(toPush);
monitor.endTask();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index 76547a628b..f72a4b2b30 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -148,6 +148,21 @@ public abstract class RefAdvertiser {
}
/**
+ * Add one protocol capability with a value ({@code "name=value"}).
+ *
+ * @param name
+ * name of the capability.
+ * @param value
+ * value. If null the capability will not be added.
+ * @since 4.0
+ */
+ public void advertiseCapability(String name, String value) {
+ if (value != null) {
+ capablities.add(name + '=' + value);
+ }
+ }
+
+ /**
* Add a symbolic ref to capabilities.
* <p>
* This method must be invoked prior to any of the following:
@@ -164,8 +179,7 @@ public abstract class RefAdvertiser {
* @since 3.6
*/
public void addSymref(String from, String to) {
- String symref = String.format("%s=%s:%s", OPTION_SYMREF, from, to); //$NON-NLS-1$
- advertiseCapability(symref);
+ advertiseCapability(OPTION_SYMREF, from + ':' + to);
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 82d1737b96..b23771e952 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -134,8 +134,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$
- private static final String userAgent = computeUserAgent();
-
static final TransportProtocol PROTO_HTTP = new TransportProtocol() {
private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$
@@ -204,17 +202,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
}
};
- private static String computeUserAgent() {
- String version;
- final Package pkg = TransportHttp.class.getPackage();
- if (pkg != null && pkg.getImplementationVersion() != null) {
- version = pkg.getImplementationVersion();
- } else {
- version = "unknown"; //$NON-NLS-1$
- }
- return "JGit/" + version; //$NON-NLS-1$
- }
-
private static final Config.SectionParser<HttpConfig> HTTP_KEY = new SectionParser<HttpConfig>() {
public HttpConfig parse(final Config cfg) {
return new HttpConfig(cfg);
@@ -309,16 +296,17 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
final HttpConnection c = connect(service);
final InputStream in = openInputStream(c);
try {
+ BaseConnection f;
if (isSmartHttp(c, service)) {
readSmartHeaders(in, service);
- return new SmartHttpFetchConnection(in);
-
+ f = new SmartHttpFetchConnection(in);
} else {
// Assume this server doesn't support smart HTTP fetch
// and fall back on dumb object walking.
- //
- return newDumbConnection(in);
+ f = newDumbConnection(in);
}
+ f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
+ return (FetchConnection) f;
} finally {
in.close();
}
@@ -331,7 +319,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
}
}
- private FetchConnection newDumbConnection(InputStream in)
+ private WalkFetchConnection newDumbConnection(InputStream in)
throws IOException, PackProtocolException {
HttpObjectDB d = new HttpObjectDB(objectsUrl);
BufferedReader br = toBufferedReader(in);
@@ -400,9 +388,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
final InputStream in = openInputStream(c);
try {
if (isSmartHttp(c, service)) {
- readSmartHeaders(in, service);
- return new SmartHttpPushConnection(in);
-
+ return smartPush(service, c, in);
} else if (!useSmartHttp) {
final String msg = JGitText.get().smartHTTPPushDisabled;
throw new NotSupportedException(msg);
@@ -423,6 +409,14 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
}
}
+ private PushConnection smartPush(String service, HttpConnection c,
+ InputStream in) throws IOException, TransportException {
+ readSmartHeaders(in, service);
+ SmartHttpPushConnection p = new SmartHttpPushConnection(in);
+ p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER));
+ return p;
+ }
+
@Override
public void close() {
// No explicit connections are maintained.
@@ -551,7 +545,9 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
conn.setUseCaches(false);
conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP);
conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$
- conn.setRequestProperty(HDR_USER_AGENT, userAgent);
+ if (UserAgent.get() != null) {
+ conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get());
+ }
int timeOut = getTimeout();
if (timeOut != -1) {
int effTimeOut = timeOut * 1000;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index 51718c0279..3afdb6114d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -44,6 +44,7 @@
package org.eclipse.jgit.transport;
import static org.eclipse.jgit.lib.RefDatabase.ALL;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK;
@@ -253,6 +254,7 @@ public class UploadPack {
/** Capabilities requested by the client. */
private Set<String> options;
+ String userAgent;
/** Raw ObjectIds the client has asked for, before validating them. */
private final Set<ObjectId> wantIds = new HashSet<ObjectId>();
@@ -806,6 +808,7 @@ public class UploadPack {
|| policy == RequestPolicy.REACHABLE_COMMIT_TIP
|| policy == null)
adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT);
+ adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
adv.setDerefTags(true);
Map<String, Ref> refs = getAdvertisedOrDefaultRefs();
findSymrefs(adv, refs);
@@ -891,12 +894,30 @@ public class UploadPack {
* @since 4.0
*/
public int getDepth() {
- if (options == null) {
- throw new IllegalStateException("do not call getDepth() before recvWants()");
- }
+ if (options == null)
+ throw new RequestNotYetReadException();
return depth;
}
+ /**
+ * Get the user agent of the client.
+ * <p>
+ * If the client is new enough to use {@code agent=} capability that value
+ * will be returned. Older HTTP clients may also supply their version using
+ * the HTTP {@code User-Agent} header. The capability overrides the HTTP
+ * header if both are available.
+ * <p>
+ * When an HTTP request has been received this method returns the HTTP
+ * {@code User-Agent} header value until capabilities have been parsed.
+ *
+ * @return user agent supplied by the client. Available only if the client
+ * is new enough to advertise its user agent.
+ * @since 4.0
+ */
+ public String getPeerUserAgent() {
+ return UserAgent.getAgent(options, userAgent);
+ }
+
private boolean negotiate() throws IOException {
okToGiveUp = Boolean.FALSE;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
new file mode 100644
index 0000000000..eadb92dd59
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015, Google Inc.
+ * 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.transport;
+
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
+
+import java.util.Set;
+
+import org.eclipse.jgit.util.StringUtils;
+
+/**
+ * User agent to be reported by this JGit client and server on the network.
+ * <p>
+ * On HTTP transports this user agent string is always supplied by the JGit
+ * client in the {@code User-Agent} HTTP header.
+ * <p>
+ * On native transports this user agent string is always sent when JGit is a
+ * server. When JGit is a client the user agent string will be supplied to the
+ * remote server only if the remote server advertises its own agent identity.
+ *
+ * @since 4.0
+ */
+public class UserAgent {
+ private static volatile String userAgent = computeUserAgent();
+
+ private static String computeUserAgent() {
+ return clean("JGit/" + computeVersion()); //$NON-NLS-1$
+ }
+
+ private static String computeVersion() {
+ Package pkg = UserAgent.class.getPackage();
+ if (pkg != null) {
+ String ver = pkg.getImplementationVersion();
+ if (!StringUtils.isEmptyOrNull(ver)) {
+ return ver;
+ }
+ }
+ return "unknown"; //$NON-NLS-1$
+ }
+
+ private static String clean(String s) {
+ s = s.trim();
+ StringBuilder b = new StringBuilder(s.length());
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (c <= 32 || c >= 127) {
+ if (b.length() > 0 && b.charAt(b.length() - 1) == '.')
+ continue;
+ c = '.';
+ }
+ b.append(c);
+ }
+ return b.length() > 0 ? b.toString() : null;
+ }
+
+ /**
+ * Get the user agent string advertised by JGit.
+ *
+ * @return a string similar to {@code "JGit/4.0"}; null if the agent has
+ * been cleared and should not be shared with a peer.
+ */
+ public static String get() {
+ return userAgent;
+ }
+
+ /**
+ * Change the user agent string advertised by JGit.
+ * <p>
+ * The new string should start with {@code "JGit/"} (for example
+ * {@code "JGit/4.0"}) to advertise the implementation as JGit based.
+ * <p>
+ * Spaces and other whitespace should be avoided as these will be
+ * automatically converted to {@code "."}.
+ * <p>
+ * User agent strings are restricted to printable ASCII.
+ *
+ * @param agent
+ * new user agent string for this running JGit library. Setting
+ * to null or empty string will avoid sending any identification
+ * to the peer.
+ */
+ public static void set(String agent) {
+ userAgent = StringUtils.isEmptyOrNull(agent) ? null : clean(agent);
+ }
+
+ static String getAgent(Set<String> options, String transportAgent) {
+ if (options == null || options.isEmpty()) {
+ return transportAgent;
+ }
+ for (String o : options) {
+ if (o.startsWith(OPTION_AGENT)
+ && o.length() > OPTION_AGENT.length()
+ && o.charAt(OPTION_AGENT.length()) == '=') {
+ return o.substring(OPTION_AGENT.length() + 1);
+ }
+ }
+ return transportAgent;
+ }
+
+ static boolean hasAgent(Set<String> options) {
+ return getAgent(options, null) != null;
+ }
+
+ private UserAgent() {
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
index 37c9f7b8a4..8b4ad0aa29 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java
@@ -74,6 +74,12 @@ public class HttpSupport {
/** The {@code User-Agent} header. */
public static final String HDR_USER_AGENT = "User-Agent"; //$NON-NLS-1$
+ /**
+ * The {@code Server} header.
+ * @since 4.0
+ */
+ public static final String HDR_SERVER = "Server"; //$NON-NLS-1$
+
/** The {@code Date} header. */
public static final String HDR_DATE = "Date"; //$NON-NLS-1$