--- /dev/null
+/*
+ * Copyright (C) 2022, Google LLC. and others
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.eclipse.jgit.internal.transport.parser;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Map;
+
+import org.junit.Test;
+
+public class FirstCommandTest {
+ @Test
+ public void testClientSID() {
+ String oldStr = "0000000000000000000000000000000000000000";
+ String newStr = "deadbeefdeadbeefdeadbeefdeadbeefdeadbeef";
+ String refName = "refs/heads/master";
+ String command = oldStr + " " + newStr + " " + refName;
+ String fl = command + "\0"
+ + "some capabilities session-id=the-clients-SID and more unknownCap=some-value";
+ FirstCommand fc = FirstCommand.fromLine(fl);
+
+ Map<String, String> options = fc.getCapabilities();
+
+ assertEquals("the-clients-SID", options.get("session-id"));
+ assertEquals(command, fc.getLine());
+ assertTrue(options.containsKey("unknownCap"));
+ assertEquals(6, options.size());
+ }
+}
*/
package org.eclipse.jgit.internal.transport.parser;
-import static java.util.Arrays.asList;
-import static java.util.Collections.emptySet;
-import static java.util.Collections.unmodifiableSet;
-import static java.util.stream.Collectors.toSet;
-import java.util.Set;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
import org.eclipse.jgit.annotations.NonNull;
*/
public final class FirstCommand {
private final String line;
- private final Set<String> capabilities;
+ private final Map<String, String> capabilities;
/**
* Parse the first line of a receive-pack request.
public static FirstCommand fromLine(String line) {
int nul = line.indexOf('\0');
if (nul < 0) {
- return new FirstCommand(line, emptySet());
+ return new FirstCommand(line,
+ Collections.<String, String> emptyMap());
}
- Set<String> opts =
- asList(line.substring(nul + 1).split(" ")) //$NON-NLS-1$
- .stream()
- .collect(toSet());
- return new FirstCommand(line.substring(0, nul), unmodifiableSet(opts));
+ String[] splitCapablities = line.substring(nul + 1).split(" "); //$NON-NLS-1$
+ Map<String, String> options = new HashMap<>();
+
+ for (String c : splitCapablities) {
+ int i = c.indexOf("="); //$NON-NLS-1$
+ if (i != -1) {
+ options.put(c.substring(0, i), c.substring(i + 1));
+ } else {
+ options.put(c, null);
+ }
+ }
+
+ return new FirstCommand(line.substring(0, nul),
+ Collections.<String, String> unmodifiableMap(options));
}
- private FirstCommand(String line, Set<String> capabilities) {
+ private FirstCommand(String line, Map<String, String> capabilities) {
this.line = line;
this.capabilities = capabilities;
}
return line;
}
- /** @return capabilities parsed from the line, as an immutable set. */
+ /** @return capabilities parsed from the line, as an immutable map. */
@NonNull
- public Set<String> getCapabilities() {
+ public Map<String, String> getCapabilities() {
return capabilities;
}
}
import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR;
import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR;
import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
/** @return capabilities parsed from the line. */
public Set<String> getCapabilities() {
- return command.getCapabilities();
+ Set<String> reconstructedCapabilites = new HashSet<>();
+ for (Map.Entry<String, String> e : command.getCapabilities()
+ .entrySet()) {
+ String cap = e.getValue() == null ? e.getKey()
+ : e.getKey() + "=" + e.getValue(); //$NON-NLS-1$
+ reconstructedCapabilites.add(cap);
+ }
+
+ return reconstructedCapabilites;
}
}
private boolean allowQuiet = true;
+ /** Should the server advertise and accept the session-id capability. */
+ private boolean allowReceiveClientSID;
+
/** Identity to record action as within the reflog. */
private PersonIdent refLogIdent;
private Set<ObjectId> advertisedHaves;
/** Capabilities requested by the client. */
- private Set<String> enabledCapabilities;
+ private Map<String, String> enabledCapabilities;
+
+ /** Session ID sent from the client. Null if none was received. */
+ private String clientSID;
String userAgent;
allowNonFastForwards = rc.allowNonFastForwards;
allowOfsDelta = rc.allowOfsDelta;
allowPushOptions = rc.allowPushOptions;
+ allowReceiveClientSID = rc.allowReceiveClientSID;
maxCommandBytes = rc.maxCommandBytes;
maxDiscardBytes = rc.maxDiscardBytes;
advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
final boolean allowPushOptions;
+ final boolean allowReceiveClientSID;
+
final long maxCommandBytes;
final long maxDiscardBytes;
true);
allowPushOptions = config.getBoolean("receive", "pushoptions", //$NON-NLS-1$ //$NON-NLS-2$
false);
+ // TODO: This should not be enabled until the corresponding change to
+ // upload pack has been implemented.
+ allowReceiveClientSID = config.getBoolean("transfer", //$NON-NLS-1$
+ "advertisesid", false); //$NON-NLS-1$
maxCommandBytes = config.getLong("receive", //$NON-NLS-1$
"maxCommandBytes", //$NON-NLS-1$
3 << 20);
*/
public boolean isSideBand() throws RequestNotYetReadException {
checkRequestWasRead();
- return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K);
+ return enabledCapabilities.containsKey(CAPABILITY_SIDE_BAND_64K);
}
/**
* @since 4.0
*/
public String getPeerUserAgent() {
- return UserAgent.getAgent(enabledCapabilities, userAgent);
+ if (enabledCapabilities == null || enabledCapabilities.isEmpty()) {
+ return userAgent;
+ }
+
+ return enabledCapabilities.getOrDefault(OPTION_AGENT, userAgent);
}
/**
pckOut = new PacketLineOut(rawOut);
pckOut.setFlushOnEnd(false);
- enabledCapabilities = new HashSet<>();
+ enabledCapabilities = new HashMap<>();
commands = new ArrayList<>();
}
adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K);
adv.advertiseCapability(CAPABILITY_DELETE_REFS);
adv.advertiseCapability(CAPABILITY_REPORT_STATUS);
- if (allowQuiet)
+ if (allowReceiveClientSID) {
+ adv.advertiseCapability(OPTION_SESSION_ID);
+ }
+ if (allowQuiet) {
adv.advertiseCapability(CAPABILITY_QUIET);
+ }
String nonce = getPushCertificateParser().getAdvertiseNonce();
if (nonce != null) {
adv.advertiseCapability(nonce);
}
- if (db.getRefDatabase().performsAtomicTransactions())
+ if (db.getRefDatabase().performsAtomicTransactions()) {
adv.advertiseCapability(CAPABILITY_ATOMIC);
- if (allowOfsDelta)
+ }
+ if (allowOfsDelta) {
adv.advertiseCapability(CAPABILITY_OFS_DELTA);
+ }
if (allowPushOptions) {
adv.advertiseCapability(CAPABILITY_PUSH_OPTIONS);
}
adv.advertiseCapability(OPTION_AGENT, UserAgent.get());
adv.send(getAdvertisedOrDefaultRefs().values());
- for (ObjectId obj : advertisedHaves)
+ for (ObjectId obj : advertisedHaves) {
adv.advertiseHave(obj);
- if (adv.isEmpty())
+ }
+ if (adv.isEmpty()) {
adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$
+ }
adv.end();
}
usePushOptions = isCapabilityEnabled(CAPABILITY_PUSH_OPTIONS);
sideBand = isCapabilityEnabled(CAPABILITY_SIDE_BAND_64K);
quiet = allowQuiet && isCapabilityEnabled(CAPABILITY_QUIET);
+
+ clientSID = enabledCapabilities.get(OPTION_SESSION_ID);
+
if (sideBand) {
OutputStream out = rawOut;
* @return true if the peer requested the capability to be enabled.
*/
private boolean isCapabilityEnabled(String name) {
- return enabledCapabilities.contains(name);
+ return enabledCapabilities.containsKey(name);
}
private void checkRequestWasRead() {
// No-op.
}
+ /**
+ * @return The client session-id.
+ * @since 6.4
+ */
+ public String getClientSID() {
+ return clientSID;
+ }
+
/**
* Execute the receive task on the socket.
*