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;
throws IOException, ServiceNotEnabledException,
ServiceNotAuthorizedException {
ReceivePack rp = receivePackFactory.create(req, db);
+ InternalHttpServerGlue.setPeerUserAgent(
+ rp,
+ req.getHeader(HDR_USER_AGENT));
req.setAttribute(ATTRIBUTE_HANDLER, rp);
}
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;
throws IOException, ServiceNotEnabledException,
ServiceNotAuthorizedException {
UploadPack up = uploadPackFactory.create(req, db);
+ InternalHttpServerGlue.setPeerUserAgent(
+ up,
+ req.getHeader(HDR_USER_AGENT));
req.setAttribute(ATTRIBUTE_HANDLER, up);
}
public abstract class BaseConnection implements Connection {
private Map<String, Ref> advertisedRefs = Collections.emptyMap();
+ private String peerUserAgent;
+
private boolean startedOperation;
private Writer messageWriter;
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();
/**
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;
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));
}
OPTION_MULTI_ACK_DETAILED));
}
+ addUserAgentCapability(line);
return line.toString();
}
outputStream);
pckIn = new PacketLineIn(in);
}
+ addUserAgentCapability(line);
if (line.length() > 0)
line.setCharAt(0, '\0');
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;
/** Capabilities requested by the client. */
private Set<String> enabledCapabilities;
+ String userAgent;
private Set<ObjectId> clientShallowCommits;
private List<ReceiveCommand> commands;
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);
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);
* 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();
}
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)
*/
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;
}
--- /dev/null
+/*
+ * 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;
+ String peerUserAgent;
+
/**
* Get the URI this result came from.
* <p>
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 {
res.setAdvertisedRefs(transport.getURI(), connection
.getRefsMap());
+ res.peerUserAgent = connection.getPeerUserAgent();
res.setRemoteUpdates(toPush);
monitor.endTask();
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>
* @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);
}
/**
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$
}
};
- 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);
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();
}
}
}
- private FetchConnection newDumbConnection(InputStream in)
+ private WalkFetchConnection newDumbConnection(InputStream in)
throws IOException, PackProtocolException {
HttpObjectDB d = new HttpObjectDB(objectsUrl);
BufferedReader br = toBufferedReader(in);
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);
}
}
+ 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.
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;
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;
/** 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>();
|| 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);
* @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;
--- /dev/null
+/*
+ * 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. */
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$