View Javadoc
1   /*
2    * Copyright (C) 2008-2010, Google Inc.
3    * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
4    * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
5    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6    * and other copyright owners as documented in the project's IP log.
7    *
8    * This program and the accompanying materials are made available
9    * under the terms of the Eclipse Distribution License v1.0 which
10   * accompanies this distribution, is reproduced below, and is
11   * available at http://www.eclipse.org/org/documents/edl-v10.php
12   *
13   * All rights reserved.
14   *
15   * Redistribution and use in source and binary forms, with or
16   * without modification, are permitted provided that the following
17   * conditions are met:
18   *
19   * - Redistributions of source code must retain the above copyright
20   *   notice, this list of conditions and the following disclaimer.
21   *
22   * - Redistributions in binary form must reproduce the above
23   *   copyright notice, this list of conditions and the following
24   *   disclaimer in the documentation and/or other materials provided
25   *   with the distribution.
26   *
27   * - Neither the name of the Eclipse Foundation, Inc. nor the
28   *   names of its contributors may be used to endorse or promote
29   *   products derived from this software without specific prior
30   *   written permission.
31   *
32   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
33   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
34   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
35   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
36   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
37   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
38   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
39   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
40   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
41   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
42   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
43   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
44   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
45   */
46  
47  package org.eclipse.jgit.transport;
48  
49  import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
50  
51  import java.io.EOFException;
52  import java.io.IOException;
53  import java.io.InputStream;
54  import java.io.OutputStream;
55  import java.text.MessageFormat;
56  import java.util.HashSet;
57  import java.util.LinkedHashMap;
58  import java.util.Set;
59  
60  import org.eclipse.jgit.errors.InvalidObjectIdException;
61  import org.eclipse.jgit.errors.NoRemoteRepositoryException;
62  import org.eclipse.jgit.errors.PackProtocolException;
63  import org.eclipse.jgit.errors.RemoteRepositoryException;
64  import org.eclipse.jgit.errors.TransportException;
65  import org.eclipse.jgit.internal.JGitText;
66  import org.eclipse.jgit.lib.ObjectId;
67  import org.eclipse.jgit.lib.ObjectIdRef;
68  import org.eclipse.jgit.lib.Ref;
69  import org.eclipse.jgit.lib.Repository;
70  import org.eclipse.jgit.util.io.InterruptTimer;
71  import org.eclipse.jgit.util.io.TimeoutInputStream;
72  import org.eclipse.jgit.util.io.TimeoutOutputStream;
73  
74  /**
75   * Base helper class for pack-based operations implementations. Provides partial
76   * implementation of pack-protocol - refs advertising and capabilities support,
77   * and some other helper methods.
78   *
79   * @see BasePackFetchConnection
80   * @see BasePackPushConnection
81   */
82  abstract class BasePackConnection extends BaseConnection {
83  
84  	/** The repository this transport fetches into, or pushes out of. */
85  	protected final Repository local;
86  
87  	/** Remote repository location. */
88  	protected final URIish uri;
89  
90  	/** A transport connected to {@link #uri}. */
91  	protected final Transport transport;
92  
93  	/** Low-level input stream, if a timeout was configured. */
94  	protected TimeoutInputStream timeoutIn;
95  
96  	/** Low-level output stream, if a timeout was configured. */
97  	protected TimeoutOutputStream timeoutOut;
98  
99  	/** Timer to manage {@link #timeoutIn} and {@link #timeoutOut}. */
100 	private InterruptTimer myTimer;
101 
102 	/** Input stream reading from the remote. */
103 	protected InputStream in;
104 
105 	/** Output stream sending to the remote. */
106 	protected OutputStream out;
107 
108 	/** Packet line decoder around {@link #in}. */
109 	protected PacketLineIn pckIn;
110 
111 	/** Packet line encoder around {@link #out}. */
112 	protected PacketLineOut pckOut;
113 
114 	/** Send {@link PacketLineOut#end()} before closing {@link #out}? */
115 	protected boolean outNeedsEnd;
116 
117 	/** True if this is a stateless RPC connection. */
118 	protected boolean statelessRPC;
119 
120 	/** Capability tokens advertised by the remote side. */
121 	private final Set<String> remoteCapablities = new HashSet<>();
122 
123 	/** Extra objects the remote has, but which aren't offered as refs. */
124 	protected final Set<ObjectId> additionalHaves = new HashSet<>();
125 
126 	BasePackConnection(PackTransport packTransport) {
127 		transport = (Transport) packTransport;
128 		local = transport.local;
129 		uri = transport.uri;
130 	}
131 
132 	/**
133 	 * Configure this connection with the directional pipes.
134 	 *
135 	 * @param myIn
136 	 *            input stream to receive data from the peer. Caller must ensure
137 	 *            the input is buffered, otherwise read performance may suffer.
138 	 * @param myOut
139 	 *            output stream to transmit data to the peer. Caller must ensure
140 	 *            the output is buffered, otherwise write performance may
141 	 *            suffer.
142 	 */
143 	protected final void init(InputStream myIn, OutputStream myOut) {
144 		final int timeout = transport.getTimeout();
145 		if (timeout > 0) {
146 			final Thread caller = Thread.currentThread();
147 			if (myTimer == null) {
148 				myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
149 			}
150 			timeoutIn = new TimeoutInputStream(myIn, myTimer);
151 			timeoutOut = new TimeoutOutputStream(myOut, myTimer);
152 			timeoutIn.setTimeout(timeout * 1000);
153 			timeoutOut.setTimeout(timeout * 1000);
154 			myIn = timeoutIn;
155 			myOut = timeoutOut;
156 		}
157 
158 		in = myIn;
159 		out = myOut;
160 
161 		pckIn = new PacketLineIn(in);
162 		pckOut = new PacketLineOut(out);
163 		outNeedsEnd = true;
164 	}
165 
166 	/**
167 	 * Reads the advertised references through the initialized stream.
168 	 * <p>
169 	 * Subclass implementations may call this method only after setting up the
170 	 * input and output streams with {@link #init(InputStream, OutputStream)}.
171 	 * <p>
172 	 * If any errors occur, this connection is automatically closed by invoking
173 	 * {@link #close()} and the exception is wrapped (if necessary) and thrown
174 	 * as a {@link org.eclipse.jgit.errors.TransportException}.
175 	 *
176 	 * @throws org.eclipse.jgit.errors.TransportException
177 	 *             the reference list could not be scanned.
178 	 */
179 	protected void readAdvertisedRefs() throws TransportException {
180 		try {
181 			readAdvertisedRefsImpl();
182 		} catch (TransportException err) {
183 			close();
184 			throw err;
185 		} catch (IOException err) {
186 			close();
187 			throw new TransportException(err.getMessage(), err);
188 		} catch (RuntimeException err) {
189 			close();
190 			throw new TransportException(err.getMessage(), err);
191 		}
192 	}
193 
194 	private void readAdvertisedRefsImpl() throws IOException {
195 		final LinkedHashMap<String, Ref> avail = new LinkedHashMap<>();
196 		for (;;) {
197 			String line;
198 
199 			try {
200 				line = pckIn.readString();
201 			} catch (EOFException eof) {
202 				if (avail.isEmpty())
203 					throw noRepository();
204 				throw eof;
205 			}
206 			if (line == PacketLineIn.END)
207 				break;
208 
209 			if (line.startsWith("ERR ")) { //$NON-NLS-1$
210 				// This is a customized remote service error.
211 				// Users should be informed about it.
212 				throw new RemoteRepositoryException(uri, line.substring(4));
213 			}
214 
215 			if (avail.isEmpty()) {
216 				final int nul = line.indexOf('\0');
217 				if (nul >= 0) {
218 					// The first line (if any) may contain "hidden"
219 					// capability values after a NUL byte.
220 					for (String c : line.substring(nul + 1).split(" ")) //$NON-NLS-1$
221 						remoteCapablities.add(c);
222 					line = line.substring(0, nul);
223 				}
224 			}
225 
226 			// Expecting to get a line in the form "sha1 refname"
227 			if (line.length() < 41 || line.charAt(40) != ' ') {
228 				throw invalidRefAdvertisementLine(line);
229 			}
230 			String name = line.substring(41, line.length());
231 			if (avail.isEmpty() && name.equals("capabilities^{}")) { //$NON-NLS-1$
232 				// special line from git-receive-pack to show
233 				// capabilities when there are no refs to advertise
234 				continue;
235 			}
236 
237 			final ObjectId id;
238 			try {
239 				id  = ObjectId.fromString(line.substring(0, 40));
240 			} catch (InvalidObjectIdException e) {
241 				throw invalidRefAdvertisementLine(line);
242 			}
243 			if (name.equals(".have")) { //$NON-NLS-1$
244 				additionalHaves.add(id);
245 			} else if (name.endsWith("^{}")) { //$NON-NLS-1$
246 				name = name.substring(0, name.length() - 3);
247 				final Ref prior = avail.get(name);
248 				if (prior == null)
249 					throw new PackProtocolException(uri, MessageFormat.format(
250 							JGitText.get().advertisementCameBefore, name, name));
251 
252 				if (prior.getPeeledObjectId() != null)
253 					throw duplicateAdvertisement(name + "^{}"); //$NON-NLS-1$
254 
255 				avail.put(name, new ObjectIdRef.PeeledTag(
256 						Ref.Storage.NETWORK, name, prior.getObjectId(), id));
257 			} else {
258 				final Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
259 						Ref.Storage.NETWORK, name, id));
260 				if (prior != null)
261 					throw duplicateAdvertisement(name);
262 			}
263 		}
264 		available(avail);
265 	}
266 
267 	/**
268 	 * Create an exception to indicate problems finding a remote repository. The
269 	 * caller is expected to throw the returned exception.
270 	 *
271 	 * Subclasses may override this method to provide better diagnostics.
272 	 *
273 	 * @return a TransportException saying a repository cannot be found and
274 	 *         possibly why.
275 	 */
276 	protected TransportException noRepository() {
277 		return new NoRemoteRepositoryException(uri, JGitText.get().notFound);
278 	}
279 
280 	/**
281 	 * Whether this option is supported
282 	 *
283 	 * @param option
284 	 *            option string
285 	 * @return whether this option is supported
286 	 */
287 	protected boolean isCapableOf(String option) {
288 		return remoteCapablities.contains(option);
289 	}
290 
291 	/**
292 	 * Request capability
293 	 *
294 	 * @param b
295 	 *            buffer
296 	 * @param option
297 	 *            option we want
298 	 * @return {@code true} if the requested option is supported
299 	 */
300 	protected boolean wantCapability(StringBuilder b, String option) {
301 		if (!isCapableOf(option))
302 			return false;
303 		b.append(' ');
304 		b.append(option);
305 		return true;
306 	}
307 
308 	/**
309 	 * Add user agent capability
310 	 *
311 	 * @param b
312 	 *            a {@link java.lang.StringBuilder} object.
313 	 */
314 	protected void addUserAgentCapability(StringBuilder b) {
315 		String a = UserAgent.get();
316 		if (a != null && UserAgent.hasAgent(remoteCapablities)) {
317 			b.append(' ').append(OPTION_AGENT).append('=').append(a);
318 		}
319 	}
320 
321 	/** {@inheritDoc} */
322 	@Override
323 	public String getPeerUserAgent() {
324 		return UserAgent.getAgent(remoteCapablities, super.getPeerUserAgent());
325 	}
326 
327 	private PackProtocolException duplicateAdvertisement(String name) {
328 		return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name));
329 	}
330 
331 	private PackProtocolException invalidRefAdvertisementLine(String line) {
332 		return new PackProtocolException(uri, MessageFormat.format(JGitText.get().invalidRefAdvertisementLine, line));
333 	}
334 
335 	/** {@inheritDoc} */
336 	@Override
337 	public void close() {
338 		if (out != null) {
339 			try {
340 				if (outNeedsEnd) {
341 					outNeedsEnd = false;
342 					pckOut.end();
343 				}
344 				out.close();
345 			} catch (IOException err) {
346 				// Ignore any close errors.
347 			} finally {
348 				out = null;
349 				pckOut = null;
350 			}
351 		}
352 
353 		if (in != null) {
354 			try {
355 				in.close();
356 			} catch (IOException err) {
357 				// Ignore any close errors.
358 			} finally {
359 				in = null;
360 				pckIn = null;
361 			}
362 		}
363 
364 		if (myTimer != null) {
365 			try {
366 				myTimer.terminate();
367 			} finally {
368 				myTimer = null;
369 				timeoutIn = null;
370 				timeoutOut = null;
371 			}
372 		}
373 	}
374 
375 	/**
376 	 * Tell the peer we are disconnecting, if it cares to know.
377 	 */
378 	protected void endOut() {
379 		if (outNeedsEnd && out != null) {
380 			try {
381 				outNeedsEnd = false;
382 				pckOut.end();
383 			} catch (IOException e) {
384 				try {
385 					out.close();
386 				} catch (IOException err) {
387 					// Ignore any close errors.
388 				} finally {
389 					out = null;
390 					pckOut = null;
391 				}
392 			}
393 		}
394 	}
395 }