diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java')
-rw-r--r-- | org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java new file mode 100644 index 0000000000..e437f22d02 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2018, 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.transport; + +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SERVER_OPTION; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_ALL; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT_REF; + +import java.io.IOException; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import org.eclipse.jgit.errors.InvalidObjectIdException; +import org.eclipse.jgit.errors.PackProtocolException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.ObjectId; + +/** + * Parse the incoming git protocol lines from the wire and translate them into a + * Request object. + * + * It requires a transferConfig object to know what the server supports (e.g. + * ref-in-want and/or filters). + */ +final class ProtocolV2Parser { + + private final TransferConfig transferConfig; + + ProtocolV2Parser(TransferConfig transferConfig) { + this.transferConfig = transferConfig; + } + + /* + * Read lines until DELIM or END, calling the appropiate consumer. + * + * Returns the last read line (so caller can check if there is more to read + * in the line). + */ + private static String consumeCapabilities(PacketLineIn pckIn, + Consumer<String> serverOptionConsumer, + Consumer<String> agentConsumer, + Consumer<String> clientSIDConsumer) throws IOException { + + String serverOptionPrefix = OPTION_SERVER_OPTION + '='; + String agentPrefix = OPTION_AGENT + '='; + String clientSIDPrefix = OPTION_SESSION_ID + '='; + + String line = pckIn.readString(); + while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) { + if (line.startsWith(serverOptionPrefix)) { + serverOptionConsumer + .accept(line.substring(serverOptionPrefix.length())); + } else if (line.startsWith(agentPrefix)) { + agentConsumer.accept(line.substring(agentPrefix.length())); + } else if (line.startsWith(clientSIDPrefix)) { + clientSIDConsumer + .accept(line.substring(clientSIDPrefix.length())); + } else { + // Unrecognized capability. Ignore it. + } + line = pckIn.readString(); + } + + return line; + } + + /** + * Parse the incoming fetch request arguments from the wire. The caller must + * be sure that what is comings is a fetch request before coming here. + * + * @param pckIn + * incoming lines + * @return A FetchV2Request populated with information received from the + * wire. + * @throws PackProtocolException + * incompatible options, wrong type of arguments or other issues + * where the request breaks the protocol. + * @throws IOException + * an IO error prevented reading the incoming message. + */ + FetchV2Request parseFetchRequest(PacketLineIn pckIn) + throws PackProtocolException, IOException { + FetchV2Request.Builder reqBuilder = FetchV2Request.builder(); + + // Packs are always sent multiplexed and using full 64K + // lengths. + reqBuilder.addClientCapability(OPTION_SIDE_BAND_64K); + + String line = consumeCapabilities(pckIn, + serverOption -> reqBuilder.addServerOption(serverOption), + agent -> reqBuilder.setAgent(agent), + clientSID -> reqBuilder.setClientSID(clientSID)); + + if (PacketLineIn.isEnd(line)) { + return reqBuilder.build(); + } + + if (!PacketLineIn.isDelimiter(line)) { + throw new PackProtocolException( + MessageFormat.format(JGitText.get().unexpectedPacketLine, + line)); + } + + boolean filterReceived = false; + for (String line2 : pckIn.readStrings()) { + if (line2.startsWith(PACKET_WANT)) { + reqBuilder.addWantId(ObjectId + .fromString(line2.substring(PACKET_WANT.length()))); + } else if (transferConfig.isAllowRefInWant() + && line2.startsWith(PACKET_WANT_REF)) { + reqBuilder.addWantedRef( + line2.substring(PACKET_WANT_REF.length())); + } else if (line2.startsWith(PACKET_HAVE)) { + reqBuilder.addPeerHas(ObjectId + .fromString(line2.substring(PACKET_HAVE.length()))); + } else if (line2.equals(PACKET_DONE)) { + reqBuilder.setDoneReceived(); + } else if (line2.equals(OPTION_WAIT_FOR_DONE)) { + reqBuilder.setWaitForDone(); + } else if (line2.equals(OPTION_THIN_PACK)) { + reqBuilder.addClientCapability(OPTION_THIN_PACK); + } else if (line2.equals(OPTION_NO_PROGRESS)) { + reqBuilder.addClientCapability(OPTION_NO_PROGRESS); + } else if (line2.equals(OPTION_INCLUDE_TAG)) { + reqBuilder.addClientCapability(OPTION_INCLUDE_TAG); + } else if (line2.equals(OPTION_OFS_DELTA)) { + reqBuilder.addClientCapability(OPTION_OFS_DELTA); + } else if (line2.startsWith(PACKET_SHALLOW)) { + reqBuilder.addClientShallowCommit( + ObjectId.fromString( + line2.substring(PACKET_SHALLOW.length()))); + } else if (line2.startsWith(PACKET_DEEPEN)) { + int parsedDepth = Integer + .parseInt(line2.substring(PACKET_DEEPEN.length())); + if (parsedDepth <= 0) { + throw new PackProtocolException( + MessageFormat.format(JGitText.get().invalidDepth, + Integer.valueOf(parsedDepth))); + } + if (reqBuilder.getDeepenSince() != 0) { + throw new PackProtocolException( + JGitText.get().deepenSinceWithDeepen); + } + if (reqBuilder.hasDeepenNots()) { + throw new PackProtocolException( + JGitText.get().deepenNotWithDeepen); + } + reqBuilder.setDepth(parsedDepth); + } else if (line2.startsWith(PACKET_DEEPEN_NOT)) { + reqBuilder.addDeepenNot( + line2.substring(PACKET_DEEPEN_NOT.length())); + if (reqBuilder.getDepth() != 0) { + throw new PackProtocolException( + JGitText.get().deepenNotWithDeepen); + } + } else if (line2.equals(OPTION_DEEPEN_RELATIVE)) { + reqBuilder.addClientCapability(OPTION_DEEPEN_RELATIVE); + } else if (line2.startsWith(PACKET_DEEPEN_SINCE)) { + int ts = Integer.parseInt( + line2.substring(PACKET_DEEPEN_SINCE.length())); + if (ts <= 0) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidTimestamp, line2)); + } + if (reqBuilder.getDepth() != 0) { + throw new PackProtocolException( + JGitText.get().deepenSinceWithDeepen); + } + reqBuilder.setDeepenSince(ts); + } else if (transferConfig.isAllowFilter() + && line2.startsWith(OPTION_FILTER + ' ')) { + if (filterReceived) { + throw new PackProtocolException( + JGitText.get().tooManyFilters); + } + filterReceived = true; + reqBuilder.setFilterSpec(FilterSpec.fromFilterLine( + line2.substring(OPTION_FILTER.length() + 1))); + } else if (transferConfig.isAllowSidebandAll() + && line2.equals(OPTION_SIDEBAND_ALL)) { + reqBuilder.setSidebandAll(true); + } else if (line2.startsWith("packfile-uris ")) { //$NON-NLS-1$ + for (String s : line2.substring(14).split(",")) { //$NON-NLS-1$ + reqBuilder.addPackfileUriProtocol(s); + } + } else { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().unexpectedPacketLine, line2)); + } + } + + return reqBuilder.build(); + } + + /** + * Parse the incoming ls-refs request arguments from the wire. This is meant + * for calling immediately after the caller has consumed a "command=ls-refs" + * line indicating the beginning of a ls-refs request. + * + * The incoming PacketLineIn is consumed until an END line, but the caller + * is responsible for closing it (if needed) + * + * @param pckIn + * incoming lines. This method will read until an END line. + * @return a LsRefsV2Request object with the data received in the wire. + * @throws PackProtocolException + * for inconsistencies in the protocol (e.g. unexpected lines) + * @throws IOException + * reporting problems reading the incoming messages from the + * wire + */ + LsRefsV2Request parseLsRefsRequest(PacketLineIn pckIn) + throws PackProtocolException, IOException { + LsRefsV2Request.Builder builder = LsRefsV2Request.builder(); + List<String> prefixes = new ArrayList<>(); + + String line = consumeCapabilities(pckIn, + serverOption -> builder.addServerOption(serverOption), + agent -> builder.setAgent(agent), + clientSID -> builder.setClientSID(clientSID)); + + if (PacketLineIn.isEnd(line)) { + return builder.build(); + } + + if (!PacketLineIn.isDelimiter(line)) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().unexpectedPacketLine, line)); + } + + for (String line2 : pckIn.readStrings()) { + if (line2.equals("peel")) { //$NON-NLS-1$ + builder.setPeel(true); + } else if (line2.equals("symrefs")) { //$NON-NLS-1$ + builder.setSymrefs(true); + } else if (line2.startsWith("ref-prefix ")) { //$NON-NLS-1$ + prefixes.add(line2.substring("ref-prefix ".length())); //$NON-NLS-1$ + } else { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().unexpectedPacketLine, line2)); + } + } + + return builder.setRefPrefixes(prefixes).build(); + } + + ObjectInfoRequest parseObjectInfoRequest(PacketLineIn pckIn) + throws PackProtocolException, IOException { + ObjectInfoRequest.Builder builder = ObjectInfoRequest.builder(); + List<ObjectId> objectIDs = new ArrayList<>(); + + String line = pckIn.readString(); + + if (PacketLineIn.isEnd(line)) { + return builder.build(); + } + + if (!PacketLineIn.isDelimiter(line)) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().unexpectedPacketLine, line)); + } + + line = pckIn.readString(); + if (!line.equals("size")) { //$NON-NLS-1$ + throw new PackProtocolException(MessageFormat + .format(JGitText.get().unexpectedPacketLine, line)); + } + + for (String line2 : pckIn.readStrings()) { + if (!line2.startsWith("oid ")) { //$NON-NLS-1$ + throw new PackProtocolException(MessageFormat + .format(JGitText.get().unexpectedPacketLine, line2)); + } + + String oidStr = line2.substring("oid ".length()); //$NON-NLS-1$ + + try { + objectIDs.add(ObjectId.fromString(oidStr)); + } catch (InvalidObjectIdException e) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidObject, oidStr), e); + } + } + + return builder.setObjectIDs(objectIDs).build(); + } +} |