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
tags/v4.0.0.201505050340-m2
@@ -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); | |||
} | |||
@@ -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); | |||
} | |||
@@ -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(); | |||
/** |
@@ -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)); | |||
} |
@@ -521,6 +521,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection | |||
OPTION_MULTI_ACK_DETAILED)); | |||
} | |||
addUserAgentCapability(line); | |||
return line.toString(); | |||
} | |||
@@ -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'); |
@@ -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); |
@@ -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(); | |||
} |
@@ -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) |
@@ -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; | |||
} |
@@ -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() { | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -155,6 +155,7 @@ class PushProcess { | |||
try { | |||
res.setAdvertisedRefs(transport.getURI(), connection | |||
.getRefsMap()); | |||
res.peerUserAgent = connection.getPeerUserAgent(); | |||
res.setRemoteUpdates(toPush); | |||
monitor.endTask(); | |||
@@ -147,6 +147,21 @@ public abstract class RefAdvertiser { | |||
capablities.add(name); | |||
} | |||
/** | |||
* 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> | |||
@@ -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); | |||
} | |||
/** |
@@ -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; |
@@ -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; | |||
@@ -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() { | |||
} | |||
} |
@@ -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$ | |||