ProtocolV2Parser.java

  1. /*
  2.  * Copyright (C) 2018, 2022 Google LLC. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.transport;

  11. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE;
  12. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
  13. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
  14. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
  15. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS;
  16. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA;
  17. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SERVER_OPTION;
  18. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_ALL;
  19. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
  20. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
  21. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE;
  22. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SESSION_ID;
  23. import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN;
  24. import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT;
  25. import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE;
  26. import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE;
  27. import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE;
  28. import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW;
  29. import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT;
  30. import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT_REF;

  31. import java.io.IOException;
  32. import java.text.MessageFormat;
  33. import java.util.ArrayList;
  34. import java.util.List;
  35. import java.util.function.Consumer;

  36. import org.eclipse.jgit.errors.InvalidObjectIdException;
  37. import org.eclipse.jgit.errors.PackProtocolException;
  38. import org.eclipse.jgit.internal.JGitText;
  39. import org.eclipse.jgit.lib.ObjectId;

  40. /**
  41.  * Parse the incoming git protocol lines from the wire and translate them into a
  42.  * Request object.
  43.  *
  44.  * It requires a transferConfig object to know what the server supports (e.g.
  45.  * ref-in-want and/or filters).
  46.  */
  47. final class ProtocolV2Parser {

  48.     private final TransferConfig transferConfig;

  49.     ProtocolV2Parser(TransferConfig transferConfig) {
  50.         this.transferConfig = transferConfig;
  51.     }

  52.     /*
  53.      * Read lines until DELIM or END, calling the appropiate consumer.
  54.      *
  55.      * Returns the last read line (so caller can check if there is more to read
  56.      * in the line).
  57.      */
  58.     private static String consumeCapabilities(PacketLineIn pckIn,
  59.             Consumer<String> serverOptionConsumer,
  60.             Consumer<String> agentConsumer,
  61.             Consumer<String> clientSIDConsumer) throws IOException {

  62.         String serverOptionPrefix = OPTION_SERVER_OPTION + '=';
  63.         String agentPrefix = OPTION_AGENT + '=';
  64.         String clientSIDPrefix = OPTION_SESSION_ID + '=';

  65.         String line = pckIn.readString();
  66.         while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) {
  67.             if (line.startsWith(serverOptionPrefix)) {
  68.                 serverOptionConsumer
  69.                         .accept(line.substring(serverOptionPrefix.length()));
  70.             } else if (line.startsWith(agentPrefix)) {
  71.                 agentConsumer.accept(line.substring(agentPrefix.length()));
  72.             } else if (line.startsWith(clientSIDPrefix)) {
  73.                 clientSIDConsumer
  74.                         .accept(line.substring(clientSIDPrefix.length()));
  75.             } else {
  76.                 // Unrecognized capability. Ignore it.
  77.             }
  78.             line = pckIn.readString();
  79.         }

  80.         return line;
  81.     }

  82.     /**
  83.      * Parse the incoming fetch request arguments from the wire. The caller must
  84.      * be sure that what is comings is a fetch request before coming here.
  85.      *
  86.      * @param pckIn
  87.      *            incoming lines
  88.      * @return A FetchV2Request populated with information received from the
  89.      *         wire.
  90.      * @throws PackProtocolException
  91.      *             incompatible options, wrong type of arguments or other issues
  92.      *             where the request breaks the protocol.
  93.      * @throws IOException
  94.      *             an IO error prevented reading the incoming message.
  95.      */
  96.     FetchV2Request parseFetchRequest(PacketLineIn pckIn)
  97.             throws PackProtocolException, IOException {
  98.         FetchV2Request.Builder reqBuilder = FetchV2Request.builder();

  99.         // Packs are always sent multiplexed and using full 64K
  100.         // lengths.
  101.         reqBuilder.addClientCapability(OPTION_SIDE_BAND_64K);

  102.         String line = consumeCapabilities(pckIn,
  103.                 serverOption -> reqBuilder.addServerOption(serverOption),
  104.                 agent -> reqBuilder.setAgent(agent),
  105.                 clientSID -> reqBuilder.setClientSID(clientSID));

  106.         if (PacketLineIn.isEnd(line)) {
  107.             return reqBuilder.build();
  108.         }

  109.         if (!PacketLineIn.isDelimiter(line)) {
  110.             throw new PackProtocolException(
  111.                     MessageFormat.format(JGitText.get().unexpectedPacketLine,
  112.                             line));
  113.         }

  114.         boolean filterReceived = false;
  115.         for (String line2 : pckIn.readStrings()) {
  116.             if (line2.startsWith(PACKET_WANT)) {
  117.                 reqBuilder.addWantId(ObjectId
  118.                         .fromString(line2.substring(PACKET_WANT.length())));
  119.             } else if (transferConfig.isAllowRefInWant()
  120.                     && line2.startsWith(PACKET_WANT_REF)) {
  121.                 reqBuilder.addWantedRef(
  122.                         line2.substring(PACKET_WANT_REF.length()));
  123.             } else if (line2.startsWith(PACKET_HAVE)) {
  124.                 reqBuilder.addPeerHas(ObjectId
  125.                         .fromString(line2.substring(PACKET_HAVE.length())));
  126.             } else if (line2.equals(PACKET_DONE)) {
  127.                 reqBuilder.setDoneReceived();
  128.             } else if (line2.equals(OPTION_WAIT_FOR_DONE)) {
  129.                 reqBuilder.setWaitForDone();
  130.             } else if (line2.equals(OPTION_THIN_PACK)) {
  131.                 reqBuilder.addClientCapability(OPTION_THIN_PACK);
  132.             } else if (line2.equals(OPTION_NO_PROGRESS)) {
  133.                 reqBuilder.addClientCapability(OPTION_NO_PROGRESS);
  134.             } else if (line2.equals(OPTION_INCLUDE_TAG)) {
  135.                 reqBuilder.addClientCapability(OPTION_INCLUDE_TAG);
  136.             } else if (line2.equals(OPTION_OFS_DELTA)) {
  137.                 reqBuilder.addClientCapability(OPTION_OFS_DELTA);
  138.             } else if (line2.startsWith(PACKET_SHALLOW)) {
  139.                 reqBuilder.addClientShallowCommit(
  140.                         ObjectId.fromString(
  141.                                 line2.substring(PACKET_SHALLOW.length())));
  142.             } else if (line2.startsWith(PACKET_DEEPEN)) {
  143.                 int parsedDepth = Integer
  144.                         .parseInt(line2.substring(PACKET_DEEPEN.length()));
  145.                 if (parsedDepth <= 0) {
  146.                     throw new PackProtocolException(
  147.                             MessageFormat.format(JGitText.get().invalidDepth,
  148.                                     Integer.valueOf(parsedDepth)));
  149.                 }
  150.                 if (reqBuilder.getDeepenSince() != 0) {
  151.                     throw new PackProtocolException(
  152.                             JGitText.get().deepenSinceWithDeepen);
  153.                 }
  154.                 if (reqBuilder.hasDeepenNots()) {
  155.                     throw new PackProtocolException(
  156.                             JGitText.get().deepenNotWithDeepen);
  157.                 }
  158.                 reqBuilder.setDepth(parsedDepth);
  159.             } else if (line2.startsWith(PACKET_DEEPEN_NOT)) {
  160.                 reqBuilder.addDeepenNot(
  161.                         line2.substring(PACKET_DEEPEN_NOT.length()));
  162.                 if (reqBuilder.getDepth() != 0) {
  163.                     throw new PackProtocolException(
  164.                             JGitText.get().deepenNotWithDeepen);
  165.                 }
  166.             } else if (line2.equals(OPTION_DEEPEN_RELATIVE)) {
  167.                 reqBuilder.addClientCapability(OPTION_DEEPEN_RELATIVE);
  168.             } else if (line2.startsWith(PACKET_DEEPEN_SINCE)) {
  169.                 int ts = Integer.parseInt(
  170.                         line2.substring(PACKET_DEEPEN_SINCE.length()));
  171.                 if (ts <= 0) {
  172.                     throw new PackProtocolException(MessageFormat
  173.                             .format(JGitText.get().invalidTimestamp, line2));
  174.                 }
  175.                 if (reqBuilder.getDepth() != 0) {
  176.                     throw new PackProtocolException(
  177.                             JGitText.get().deepenSinceWithDeepen);
  178.                 }
  179.                 reqBuilder.setDeepenSince(ts);
  180.             } else if (transferConfig.isAllowFilter()
  181.                     && line2.startsWith(OPTION_FILTER + ' ')) {
  182.                 if (filterReceived) {
  183.                     throw new PackProtocolException(
  184.                             JGitText.get().tooManyFilters);
  185.                 }
  186.                 filterReceived = true;
  187.                 reqBuilder.setFilterSpec(FilterSpec.fromFilterLine(
  188.                         line2.substring(OPTION_FILTER.length() + 1)));
  189.             } else if (transferConfig.isAllowSidebandAll()
  190.                     && line2.equals(OPTION_SIDEBAND_ALL)) {
  191.                 reqBuilder.setSidebandAll(true);
  192.             } else if (line2.startsWith("packfile-uris ")) { //$NON-NLS-1$
  193.                 for (String s : line2.substring(14).split(",")) { //$NON-NLS-1$
  194.                     reqBuilder.addPackfileUriProtocol(s);
  195.                 }
  196.             } else {
  197.                 throw new PackProtocolException(MessageFormat
  198.                         .format(JGitText.get().unexpectedPacketLine, line2));
  199.             }
  200.         }

  201.         return reqBuilder.build();
  202.     }

  203.     /**
  204.      * Parse the incoming ls-refs request arguments from the wire. This is meant
  205.      * for calling immediately after the caller has consumed a "command=ls-refs"
  206.      * line indicating the beginning of a ls-refs request.
  207.      *
  208.      * The incoming PacketLineIn is consumed until an END line, but the caller
  209.      * is responsible for closing it (if needed)
  210.      *
  211.      * @param pckIn
  212.      *            incoming lines. This method will read until an END line.
  213.      * @return a LsRefsV2Request object with the data received in the wire.
  214.      * @throws PackProtocolException
  215.      *             for inconsistencies in the protocol (e.g. unexpected lines)
  216.      * @throws IOException
  217.      *             reporting problems reading the incoming messages from the
  218.      *             wire
  219.      */
  220.     LsRefsV2Request parseLsRefsRequest(PacketLineIn pckIn)
  221.             throws PackProtocolException, IOException {
  222.         LsRefsV2Request.Builder builder = LsRefsV2Request.builder();
  223.         List<String> prefixes = new ArrayList<>();

  224.         String line = consumeCapabilities(pckIn,
  225.                 serverOption -> builder.addServerOption(serverOption),
  226.                 agent -> builder.setAgent(agent),
  227.                 clientSID -> builder.setClientSID(clientSID));

  228.         if (PacketLineIn.isEnd(line)) {
  229.             return builder.build();
  230.         }

  231.         if (!PacketLineIn.isDelimiter(line)) {
  232.             throw new PackProtocolException(MessageFormat
  233.                     .format(JGitText.get().unexpectedPacketLine, line));
  234.         }

  235.         for (String line2 : pckIn.readStrings()) {
  236.             if (line2.equals("peel")) { //$NON-NLS-1$
  237.                 builder.setPeel(true);
  238.             } else if (line2.equals("symrefs")) { //$NON-NLS-1$
  239.                 builder.setSymrefs(true);
  240.             } else if (line2.startsWith("ref-prefix ")) { //$NON-NLS-1$
  241.                 prefixes.add(line2.substring("ref-prefix ".length())); //$NON-NLS-1$
  242.             } else {
  243.                 throw new PackProtocolException(MessageFormat
  244.                         .format(JGitText.get().unexpectedPacketLine, line2));
  245.             }
  246.         }

  247.         return builder.setRefPrefixes(prefixes).build();
  248.     }

  249.     ObjectInfoRequest parseObjectInfoRequest(PacketLineIn pckIn)
  250.             throws PackProtocolException, IOException {
  251.         ObjectInfoRequest.Builder builder = ObjectInfoRequest.builder();
  252.         List<ObjectId> objectIDs = new ArrayList<>();

  253.         String line = pckIn.readString();

  254.         if (PacketLineIn.isEnd(line)) {
  255.             return builder.build();
  256.         }

  257.         if (!line.equals("size")) { //$NON-NLS-1$
  258.             throw new PackProtocolException(MessageFormat
  259.                     .format(JGitText.get().unexpectedPacketLine, line));
  260.         }

  261.         for (String line2 : pckIn.readStrings()) {
  262.             if (!line2.startsWith("oid ")) { //$NON-NLS-1$
  263.                 throw new PackProtocolException(MessageFormat
  264.                         .format(JGitText.get().unexpectedPacketLine, line2));
  265.             }

  266.             String oidStr = line2.substring("oid ".length()); //$NON-NLS-1$

  267.             try {
  268.                 objectIDs.add(ObjectId.fromString(oidStr));
  269.             } catch (InvalidObjectIdException e) {
  270.                 throw new PackProtocolException(MessageFormat
  271.                         .format(JGitText.get().invalidObject, oidStr), e);
  272.             }
  273.         }

  274.         return builder.setObjectIDs(objectIDs).build();
  275.     }
  276. }