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
import org.eclipse.jgit.errors.UnpackException; | import org.eclipse.jgit.errors.UnpackException; | ||||
import org.eclipse.jgit.lib.Repository; | import org.eclipse.jgit.lib.Repository; | ||||
import org.eclipse.jgit.transport.InternalHttpServerGlue; | |||||
import org.eclipse.jgit.transport.ReceivePack; | import org.eclipse.jgit.transport.ReceivePack; | ||||
import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; | import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; | ||||
import org.eclipse.jgit.transport.resolver.ReceivePackFactory; | import org.eclipse.jgit.transport.resolver.ReceivePackFactory; | ||||
throws IOException, ServiceNotEnabledException, | throws IOException, ServiceNotEnabledException, | ||||
ServiceNotAuthorizedException { | ServiceNotAuthorizedException { | ||||
ReceivePack rp = receivePackFactory.create(req, db); | ReceivePack rp = receivePackFactory.create(req, db); | ||||
InternalHttpServerGlue.setPeerUserAgent( | |||||
rp, | |||||
req.getHeader(HDR_USER_AGENT)); | |||||
req.setAttribute(ATTRIBUTE_HANDLER, rp); | req.setAttribute(ATTRIBUTE_HANDLER, rp); | ||||
} | } | ||||
import javax.servlet.http.HttpServletResponse; | import javax.servlet.http.HttpServletResponse; | ||||
import org.eclipse.jgit.lib.Repository; | import org.eclipse.jgit.lib.Repository; | ||||
import org.eclipse.jgit.transport.InternalHttpServerGlue; | |||||
import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; | import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; | ||||
import org.eclipse.jgit.transport.UploadPack; | import org.eclipse.jgit.transport.UploadPack; | ||||
import org.eclipse.jgit.transport.UploadPackInternalServerErrorException; | import org.eclipse.jgit.transport.UploadPackInternalServerErrorException; | ||||
throws IOException, ServiceNotEnabledException, | throws IOException, ServiceNotEnabledException, | ||||
ServiceNotAuthorizedException { | ServiceNotAuthorizedException { | ||||
UploadPack up = uploadPackFactory.create(req, db); | UploadPack up = uploadPackFactory.create(req, db); | ||||
InternalHttpServerGlue.setPeerUserAgent( | |||||
up, | |||||
req.getHeader(HDR_USER_AGENT)); | |||||
req.setAttribute(ATTRIBUTE_HANDLER, up); | req.setAttribute(ATTRIBUTE_HANDLER, up); | ||||
} | } | ||||
public abstract class BaseConnection implements Connection { | public abstract class BaseConnection implements Connection { | ||||
private Map<String, Ref> advertisedRefs = Collections.emptyMap(); | private Map<String, Ref> advertisedRefs = Collections.emptyMap(); | ||||
private String peerUserAgent; | |||||
private boolean startedOperation; | private boolean startedOperation; | ||||
private Writer messageWriter; | private Writer messageWriter; | ||||
return messageWriter != null ? messageWriter.toString() : ""; //$NON-NLS-1$ | 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(); | public abstract void close(); | ||||
/** | /** |
package org.eclipse.jgit.transport; | package org.eclipse.jgit.transport; | ||||
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; | |||||
import java.io.EOFException; | import java.io.EOFException; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.io.InputStream; | import java.io.InputStream; | ||||
return true; | 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) { | private PackProtocolException duplicateAdvertisement(final String name) { | ||||
return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name)); | return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name)); | ||||
} | } |
OPTION_MULTI_ACK_DETAILED)); | OPTION_MULTI_ACK_DETAILED)); | ||||
} | } | ||||
addUserAgentCapability(line); | |||||
return line.toString(); | return line.toString(); | ||||
} | } | ||||
outputStream); | outputStream); | ||||
pckIn = new PacketLineIn(in); | pckIn = new PacketLineIn(in); | ||||
} | } | ||||
addUserAgentCapability(line); | |||||
if (line.length() > 0) | if (line.length() > 0) | ||||
line.setCharAt(0, '\0'); | line.setCharAt(0, '\0'); |
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA; | 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_REPORT_STATUS; | ||||
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; | 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_DATA; | ||||
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS; | import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS; | ||||
import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF; | import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF; | ||||
/** Capabilities requested by the client. */ | /** Capabilities requested by the client. */ | ||||
private Set<String> enabledCapabilities; | private Set<String> enabledCapabilities; | ||||
String userAgent; | |||||
private Set<ObjectId> clientShallowCommits; | private Set<ObjectId> clientShallowCommits; | ||||
private List<ReceiveCommand> commands; | private List<ReceiveCommand> commands; | ||||
return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K); | 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. */ | /** @return all of the command received by the current request. */ | ||||
public List<ReceiveCommand> getAllCommands() { | public List<ReceiveCommand> getAllCommands() { | ||||
return Collections.unmodifiableList(commands); | return Collections.unmodifiableList(commands); | ||||
adv.advertiseCapability(CAPABILITY_ATOMIC); | adv.advertiseCapability(CAPABILITY_ATOMIC); | ||||
if (allowOfsDelta) | if (allowOfsDelta) | ||||
adv.advertiseCapability(CAPABILITY_OFS_DELTA); | adv.advertiseCapability(CAPABILITY_OFS_DELTA); | ||||
adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); | |||||
adv.send(getAdvertisedOrDefaultRefs()); | adv.send(getAdvertisedOrDefaultRefs()); | ||||
for (ObjectId obj : advertisedHaves) | for (ObjectId obj : advertisedHaves) | ||||
adv.advertiseHave(obj); | adv.advertiseHave(obj); |
* remote produced no additional messages. | * remote produced no additional messages. | ||||
*/ | */ | ||||
public String getMessages(); | 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(); | |||||
} | } |
conn = transport.openFetch(); | conn = transport.openFetch(); | ||||
try { | try { | ||||
result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap()); | result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap()); | ||||
result.peerUserAgent = conn.getPeerUserAgent(); | |||||
final Set<Ref> matched = new HashSet<Ref>(); | final Set<Ref> matched = new HashSet<Ref>(); | ||||
for (final RefSpec spec : toFetch) { | for (final RefSpec spec : toFetch) { | ||||
if (spec.getSource() == null) | if (spec.getSource() == null) |
*/ | */ | ||||
public static final String CAPABILITY_PUSH_CERT = "push-cert"; //$NON-NLS-1$ | 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 { | static enum MultiAck { | ||||
OFF, CONTINUE, DETAILED; | OFF, CONTINUE, DETAILED; | ||||
} | } |
/* | |||||
* 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() { | |||||
} | |||||
} |
StringBuilder messageBuffer; | StringBuilder messageBuffer; | ||||
String peerUserAgent; | |||||
/** | /** | ||||
* Get the URI this result came from. | * Get the URI this result came from. | ||||
* <p> | * <p> | ||||
messageBuffer.append('\n'); | 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; | |||||
} | |||||
} | } |
try { | try { | ||||
res.setAdvertisedRefs(transport.getURI(), connection | res.setAdvertisedRefs(transport.getURI(), connection | ||||
.getRefsMap()); | .getRefsMap()); | ||||
res.peerUserAgent = connection.getPeerUserAgent(); | |||||
res.setRemoteUpdates(toPush); | res.setRemoteUpdates(toPush); | ||||
monitor.endTask(); | monitor.endTask(); | ||||
capablities.add(name); | 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. | * Add a symbolic ref to capabilities. | ||||
* <p> | * <p> | ||||
* @since 3.6 | * @since 3.6 | ||||
*/ | */ | ||||
public void addSymref(String from, String to) { | 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); | |||||
} | } | ||||
/** | /** |
private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$ | 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() { | static final TransportProtocol PROTO_HTTP = new TransportProtocol() { | ||||
private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$ | private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$ | ||||
} | } | ||||
}; | }; | ||||
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>() { | private static final Config.SectionParser<HttpConfig> HTTP_KEY = new SectionParser<HttpConfig>() { | ||||
public HttpConfig parse(final Config cfg) { | public HttpConfig parse(final Config cfg) { | ||||
return new HttpConfig(cfg); | return new HttpConfig(cfg); | ||||
final HttpConnection c = connect(service); | final HttpConnection c = connect(service); | ||||
final InputStream in = openInputStream(c); | final InputStream in = openInputStream(c); | ||||
try { | try { | ||||
BaseConnection f; | |||||
if (isSmartHttp(c, service)) { | if (isSmartHttp(c, service)) { | ||||
readSmartHeaders(in, service); | readSmartHeaders(in, service); | ||||
return new SmartHttpFetchConnection(in); | |||||
f = new SmartHttpFetchConnection(in); | |||||
} else { | } else { | ||||
// Assume this server doesn't support smart HTTP fetch | // Assume this server doesn't support smart HTTP fetch | ||||
// and fall back on dumb object walking. | // and fall back on dumb object walking. | ||||
// | |||||
return newDumbConnection(in); | |||||
f = newDumbConnection(in); | |||||
} | } | ||||
f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER)); | |||||
return (FetchConnection) f; | |||||
} finally { | } finally { | ||||
in.close(); | in.close(); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private FetchConnection newDumbConnection(InputStream in) | |||||
private WalkFetchConnection newDumbConnection(InputStream in) | |||||
throws IOException, PackProtocolException { | throws IOException, PackProtocolException { | ||||
HttpObjectDB d = new HttpObjectDB(objectsUrl); | HttpObjectDB d = new HttpObjectDB(objectsUrl); | ||||
BufferedReader br = toBufferedReader(in); | BufferedReader br = toBufferedReader(in); | ||||
final InputStream in = openInputStream(c); | final InputStream in = openInputStream(c); | ||||
try { | try { | ||||
if (isSmartHttp(c, service)) { | if (isSmartHttp(c, service)) { | ||||
readSmartHeaders(in, service); | |||||
return new SmartHttpPushConnection(in); | |||||
return smartPush(service, c, in); | |||||
} else if (!useSmartHttp) { | } else if (!useSmartHttp) { | ||||
final String msg = JGitText.get().smartHTTPPushDisabled; | final String msg = JGitText.get().smartHTTPPushDisabled; | ||||
throw new NotSupportedException(msg); | throw new NotSupportedException(msg); | ||||
} | } | ||||
} | } | ||||
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 | @Override | ||||
public void close() { | public void close() { | ||||
// No explicit connections are maintained. | // No explicit connections are maintained. | ||||
conn.setUseCaches(false); | conn.setUseCaches(false); | ||||
conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP); | conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP); | ||||
conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$ | 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(); | int timeOut = getTimeout(); | ||||
if (timeOut != -1) { | if (timeOut != -1) { | ||||
int effTimeOut = timeOut * 1000; | int effTimeOut = timeOut * 1000; |
package org.eclipse.jgit.transport; | package org.eclipse.jgit.transport; | ||||
import static org.eclipse.jgit.lib.RefDatabase.ALL; | 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_ALLOW_TIP_SHA1_IN_WANT; | ||||
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG; | import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG; | ||||
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK; | import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK; | ||||
/** Capabilities requested by the client. */ | /** Capabilities requested by the client. */ | ||||
private Set<String> options; | private Set<String> options; | ||||
String userAgent; | |||||
/** Raw ObjectIds the client has asked for, before validating them. */ | /** Raw ObjectIds the client has asked for, before validating them. */ | ||||
private final Set<ObjectId> wantIds = new HashSet<ObjectId>(); | private final Set<ObjectId> wantIds = new HashSet<ObjectId>(); | ||||
|| policy == RequestPolicy.REACHABLE_COMMIT_TIP | || policy == RequestPolicy.REACHABLE_COMMIT_TIP | ||||
|| policy == null) | || policy == null) | ||||
adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT); | adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT); | ||||
adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); | |||||
adv.setDerefTags(true); | adv.setDerefTags(true); | ||||
Map<String, Ref> refs = getAdvertisedOrDefaultRefs(); | Map<String, Ref> refs = getAdvertisedOrDefaultRefs(); | ||||
findSymrefs(adv, refs); | findSymrefs(adv, refs); | ||||
* @since 4.0 | * @since 4.0 | ||||
*/ | */ | ||||
public int getDepth() { | public int getDepth() { | ||||
if (options == null) { | |||||
throw new IllegalStateException("do not call getDepth() before recvWants()"); | |||||
} | |||||
if (options == null) | |||||
throw new RequestNotYetReadException(); | |||||
return depth; | 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 { | private boolean negotiate() throws IOException { | ||||
okToGiveUp = Boolean.FALSE; | okToGiveUp = Boolean.FALSE; | ||||
/* | |||||
* 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() { | |||||
} | |||||
} |
/** The {@code User-Agent} header. */ | /** The {@code User-Agent} header. */ | ||||
public static final String HDR_USER_AGENT = "User-Agent"; //$NON-NLS-1$ | 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. */ | /** The {@code Date} header. */ | ||||
public static final String HDR_DATE = "Date"; //$NON-NLS-1$ | public static final String HDR_DATE = "Date"; //$NON-NLS-1$ | ||||