View Javadoc
1   /*
2    * Copyright (C) 2018, Google LLC.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  package org.eclipse.jgit.transport;
44  
45  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_DEEPEN_RELATIVE;
46  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER;
47  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
48  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG;
49  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_NO_PROGRESS;
50  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_OFS_DELTA;
51  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SERVER_OPTION;
52  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_ALL;
53  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K;
54  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK;
55  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WANT_REF;
56  
57  import java.io.IOException;
58  import java.text.MessageFormat;
59  import java.util.ArrayList;
60  import java.util.List;
61  import java.util.function.Consumer;
62  
63  import org.eclipse.jgit.errors.PackProtocolException;
64  import org.eclipse.jgit.internal.JGitText;
65  import org.eclipse.jgit.lib.ObjectId;
66  
67  /**
68   * Parse the incoming git protocol lines from the wire and translate them into a
69   * Request object.
70   *
71   * It requires a transferConfig object to know what the server supports (e.g.
72   * ref-in-want and/or filters).
73   */
74  final class ProtocolV2Parser {
75  
76  	private final TransferConfig transferConfig;
77  
78  	ProtocolV2Parser(TransferConfig transferConfig) {
79  		this.transferConfig = transferConfig;
80  	}
81  
82  	/*
83  	 * Read lines until DELIM or END, calling the appropiate consumer.
84  	 *
85  	 * Returns the last read line (so caller can check if there is more to read
86  	 * in the line).
87  	 */
88  	private static String consumeCapabilities(PacketLineIn pckIn,
89  			Consumer<String> serverOptionConsumer,
90  			Consumer<String> agentConsumer) throws IOException {
91  
92  		String serverOptionPrefix = OPTION_SERVER_OPTION + '=';
93  		String agentPrefix = OPTION_AGENT + '=';
94  
95  		String line = pckIn.readString();
96  		while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) {
97  			if (line.startsWith(serverOptionPrefix)) {
98  				serverOptionConsumer
99  						.accept(line.substring(serverOptionPrefix.length()));
100 			} else if (line.startsWith(agentPrefix)) {
101 				agentConsumer.accept(line.substring(agentPrefix.length()));
102 			} else {
103 				// Unrecognized capability. Ignore it.
104 			}
105 			line = pckIn.readString();
106 		}
107 
108 		return line;
109 	}
110 
111 	/**
112 	 * Parse the incoming fetch request arguments from the wire. The caller must
113 	 * be sure that what is comings is a fetch request before coming here.
114 	 *
115 	 * @param pckIn
116 	 *            incoming lines
117 	 * @return A FetchV2Request populated with information received from the
118 	 *         wire.
119 	 * @throws PackProtocolException
120 	 *             incompatible options, wrong type of arguments or other issues
121 	 *             where the request breaks the protocol.
122 	 * @throws IOException
123 	 *             an IO error prevented reading the incoming message.
124 	 */
125 	FetchV2Request parseFetchRequest(PacketLineIn pckIn)
126 			throws PackProtocolException, IOException {
127 		FetchV2Request.Builder reqBuilder = FetchV2Request.builder();
128 
129 		// Packs are always sent multiplexed and using full 64K
130 		// lengths.
131 		reqBuilder.addClientCapability(OPTION_SIDE_BAND_64K);
132 
133 		String line = consumeCapabilities(pckIn,
134 				serverOption -> reqBuilder.addServerOption(serverOption),
135 				agent -> reqBuilder.setAgent(agent));
136 
137 		if (PacketLineIn.isEnd(line)) {
138 			return reqBuilder.build();
139 		}
140 
141 		if (!PacketLineIn.isDelimiter(line)) {
142 			throw new PackProtocolException(
143 					MessageFormat.format(JGitText.get().unexpectedPacketLine,
144 							line));
145 		}
146 
147 		boolean filterReceived = false;
148 		for (String line2 : pckIn.readStrings()) {
149 			if (line2.startsWith("want ")) { //$NON-NLS-1$
150 				reqBuilder.addWantId(ObjectId.fromString(line2.substring(5)));
151 			} else if (transferConfig.isAllowRefInWant()
152 					&& line2.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$
153 				reqBuilder.addWantedRef(
154 						line2.substring(OPTION_WANT_REF.length() + 1));
155 			} else if (line2.startsWith("have ")) { //$NON-NLS-1$
156 				reqBuilder.addPeerHas(ObjectId.fromString(line2.substring(5)));
157 			} else if (line2.equals("done")) { //$NON-NLS-1$
158 				reqBuilder.setDoneReceived();
159 			} else if (line2.equals(OPTION_THIN_PACK)) {
160 				reqBuilder.addClientCapability(OPTION_THIN_PACK);
161 			} else if (line2.equals(OPTION_NO_PROGRESS)) {
162 				reqBuilder.addClientCapability(OPTION_NO_PROGRESS);
163 			} else if (line2.equals(OPTION_INCLUDE_TAG)) {
164 				reqBuilder.addClientCapability(OPTION_INCLUDE_TAG);
165 			} else if (line2.equals(OPTION_OFS_DELTA)) {
166 				reqBuilder.addClientCapability(OPTION_OFS_DELTA);
167 			} else if (line2.startsWith("shallow ")) { //$NON-NLS-1$
168 				reqBuilder.addClientShallowCommit(
169 						ObjectId.fromString(line2.substring(8)));
170 			} else if (line2.startsWith("deepen ")) { //$NON-NLS-1$
171 				int parsedDepth = Integer.parseInt(line2.substring(7));
172 				if (parsedDepth <= 0) {
173 					throw new PackProtocolException(
174 							MessageFormat.format(JGitText.get().invalidDepth,
175 									Integer.valueOf(parsedDepth)));
176 				}
177 				if (reqBuilder.getDeepenSince() != 0) {
178 					throw new PackProtocolException(
179 							JGitText.get().deepenSinceWithDeepen);
180 				}
181 				if (reqBuilder.hasDeepenNotRefs()) {
182 					throw new PackProtocolException(
183 							JGitText.get().deepenNotWithDeepen);
184 				}
185 				reqBuilder.setDepth(parsedDepth);
186 			} else if (line2.startsWith("deepen-not ")) { //$NON-NLS-1$
187 				reqBuilder.addDeepenNotRef(line2.substring(11));
188 				if (reqBuilder.getDepth() != 0) {
189 					throw new PackProtocolException(
190 							JGitText.get().deepenNotWithDeepen);
191 				}
192 			} else if (line2.equals(OPTION_DEEPEN_RELATIVE)) {
193 				reqBuilder.addClientCapability(OPTION_DEEPEN_RELATIVE);
194 			} else if (line2.startsWith("deepen-since ")) { //$NON-NLS-1$
195 				int ts = Integer.parseInt(line2.substring(13));
196 				if (ts <= 0) {
197 					throw new PackProtocolException(MessageFormat
198 							.format(JGitText.get().invalidTimestamp, line2));
199 				}
200 				if (reqBuilder.getDepth() != 0) {
201 					throw new PackProtocolException(
202 							JGitText.get().deepenSinceWithDeepen);
203 				}
204 				reqBuilder.setDeepenSince(ts);
205 			} else if (transferConfig.isAllowFilter()
206 					&& line2.startsWith(OPTION_FILTER + ' ')) {
207 				if (filterReceived) {
208 					throw new PackProtocolException(
209 							JGitText.get().tooManyFilters);
210 				}
211 				filterReceived = true;
212 				reqBuilder.setFilterSpec(FilterSpec.fromFilterLine(
213 						line2.substring(OPTION_FILTER.length() + 1)));
214 			} else if (transferConfig.isAllowSidebandAll()
215 					&& line2.equals(OPTION_SIDEBAND_ALL)) {
216 				reqBuilder.setSidebandAll(true);
217 			} else if (line2.startsWith("packfile-uris ")) { //$NON-NLS-1$
218 				for (String s : line2.substring(14).split(",")) { //$NON-NLS-1$
219 					reqBuilder.addPackfileUriProtocol(s);
220 				}
221 			} else {
222 				throw new PackProtocolException(MessageFormat
223 						.format(JGitText.get().unexpectedPacketLine, line2));
224 			}
225 		}
226 
227 		return reqBuilder.build();
228 	}
229 
230 	/**
231 	 * Parse the incoming ls-refs request arguments from the wire. This is meant
232 	 * for calling immediately after the caller has consumed a "command=ls-refs"
233 	 * line indicating the beginning of a ls-refs request.
234 	 *
235 	 * The incoming PacketLineIn is consumed until an END line, but the caller
236 	 * is responsible for closing it (if needed)
237 	 *
238 	 * @param pckIn
239 	 *            incoming lines. This method will read until an END line.
240 	 * @return a LsRefsV2Request object with the data received in the wire.
241 	 * @throws PackProtocolException
242 	 *             for inconsistencies in the protocol (e.g. unexpected lines)
243 	 * @throws IOException
244 	 *             reporting problems reading the incoming messages from the
245 	 *             wire
246 	 */
247 	LsRefsV2Request parseLsRefsRequest(PacketLineIn pckIn)
248 			throws PackProtocolException, IOException {
249 		LsRefsV2Request.Builder builder = LsRefsV2Request.builder();
250 		List<String> prefixes = new ArrayList<>();
251 
252 		String line = consumeCapabilities(pckIn,
253 				serverOption -> builder.addServerOption(serverOption),
254 				agent -> builder.setAgent(agent));
255 
256 		if (PacketLineIn.isEnd(line)) {
257 			return builder.build();
258 		}
259 
260 		if (!PacketLineIn.isDelimiter(line)) {
261 			throw new PackProtocolException(MessageFormat
262 					.format(JGitText.get().unexpectedPacketLine, line));
263 		}
264 
265 		for (String line2 : pckIn.readStrings()) {
266 			if (line2.equals("peel")) { //$NON-NLS-1$
267 				builder.setPeel(true);
268 			} else if (line2.equals("symrefs")) { //$NON-NLS-1$
269 				builder.setSymrefs(true);
270 			} else if (line2.startsWith("ref-prefix ")) { //$NON-NLS-1$
271 				prefixes.add(line2.substring("ref-prefix ".length())); //$NON-NLS-1$
272 			} else {
273 				throw new PackProtocolException(MessageFormat
274 						.format(JGitText.get().unexpectedPacketLine, line2));
275 			}
276 		}
277 
278 		return builder.setRefPrefixes(prefixes).build();
279 	}
280 
281 }