You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BasePackConnection.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  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> and others
  6. *
  7. * This program and the accompanying materials are made available under the
  8. * terms of the Eclipse Distribution License v. 1.0 which is available at
  9. * https://www.eclipse.org/org/documents/edl-v10.php.
  10. *
  11. * SPDX-License-Identifier: BSD-3-Clause
  12. */
  13. package org.eclipse.jgit.transport;
  14. import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT;
  15. import java.io.EOFException;
  16. import java.io.IOException;
  17. import java.io.InputStream;
  18. import java.io.OutputStream;
  19. import java.text.MessageFormat;
  20. import java.util.Arrays;
  21. import java.util.Collection;
  22. import java.util.HashSet;
  23. import java.util.Iterator;
  24. import java.util.LinkedHashMap;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import org.eclipse.jgit.errors.InvalidObjectIdException;
  28. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  29. import org.eclipse.jgit.errors.PackProtocolException;
  30. import org.eclipse.jgit.errors.RemoteRepositoryException;
  31. import org.eclipse.jgit.errors.TransportException;
  32. import org.eclipse.jgit.internal.JGitText;
  33. import org.eclipse.jgit.lib.ObjectId;
  34. import org.eclipse.jgit.lib.ObjectIdRef;
  35. import org.eclipse.jgit.lib.Ref;
  36. import org.eclipse.jgit.lib.Repository;
  37. import org.eclipse.jgit.lib.SymbolicRef;
  38. import org.eclipse.jgit.util.io.InterruptTimer;
  39. import org.eclipse.jgit.util.io.TimeoutInputStream;
  40. import org.eclipse.jgit.util.io.TimeoutOutputStream;
  41. /**
  42. * Base helper class for pack-based operations implementations. Provides partial
  43. * implementation of pack-protocol - refs advertising and capabilities support,
  44. * and some other helper methods.
  45. *
  46. * @see BasePackFetchConnection
  47. * @see BasePackPushConnection
  48. */
  49. abstract class BasePackConnection extends BaseConnection {
  50. protected static final String CAPABILITY_SYMREF_PREFIX = "symref="; //$NON-NLS-1$
  51. /** The repository this transport fetches into, or pushes out of. */
  52. protected final Repository local;
  53. /** Remote repository location. */
  54. protected final URIish uri;
  55. /** A transport connected to {@link #uri}. */
  56. protected final Transport transport;
  57. /** Low-level input stream, if a timeout was configured. */
  58. protected TimeoutInputStream timeoutIn;
  59. /** Low-level output stream, if a timeout was configured. */
  60. protected TimeoutOutputStream timeoutOut;
  61. /** Timer to manage {@link #timeoutIn} and {@link #timeoutOut}. */
  62. private InterruptTimer myTimer;
  63. /** Input stream reading from the remote. */
  64. protected InputStream in;
  65. /** Output stream sending to the remote. */
  66. protected OutputStream out;
  67. /** Packet line decoder around {@link #in}. */
  68. protected PacketLineIn pckIn;
  69. /** Packet line encoder around {@link #out}. */
  70. protected PacketLineOut pckOut;
  71. /** Send {@link PacketLineOut#end()} before closing {@link #out}? */
  72. protected boolean outNeedsEnd;
  73. /** True if this is a stateless RPC connection. */
  74. protected boolean statelessRPC;
  75. /** Capability tokens advertised by the remote side. */
  76. private final Set<String> remoteCapablities = new HashSet<>();
  77. /** Extra objects the remote has, but which aren't offered as refs. */
  78. protected final Set<ObjectId> additionalHaves = new HashSet<>();
  79. BasePackConnection(PackTransport packTransport) {
  80. transport = (Transport) packTransport;
  81. local = transport.local;
  82. uri = transport.uri;
  83. }
  84. /**
  85. * Configure this connection with the directional pipes.
  86. *
  87. * @param myIn
  88. * input stream to receive data from the peer. Caller must ensure
  89. * the input is buffered, otherwise read performance may suffer.
  90. * @param myOut
  91. * output stream to transmit data to the peer. Caller must ensure
  92. * the output is buffered, otherwise write performance may
  93. * suffer.
  94. */
  95. protected final void init(InputStream myIn, OutputStream myOut) {
  96. final int timeout = transport.getTimeout();
  97. if (timeout > 0) {
  98. final Thread caller = Thread.currentThread();
  99. if (myTimer == null) {
  100. myTimer = new InterruptTimer(caller.getName() + "-Timer"); //$NON-NLS-1$
  101. }
  102. timeoutIn = new TimeoutInputStream(myIn, myTimer);
  103. timeoutOut = new TimeoutOutputStream(myOut, myTimer);
  104. timeoutIn.setTimeout(timeout * 1000);
  105. timeoutOut.setTimeout(timeout * 1000);
  106. myIn = timeoutIn;
  107. myOut = timeoutOut;
  108. }
  109. in = myIn;
  110. out = myOut;
  111. pckIn = new PacketLineIn(in);
  112. pckOut = new PacketLineOut(out);
  113. outNeedsEnd = true;
  114. }
  115. /**
  116. * Reads the advertised references through the initialized stream.
  117. * <p>
  118. * Subclass implementations may call this method only after setting up the
  119. * input and output streams with {@link #init(InputStream, OutputStream)}.
  120. * <p>
  121. * If any errors occur, this connection is automatically closed by invoking
  122. * {@link #close()} and the exception is wrapped (if necessary) and thrown
  123. * as a {@link org.eclipse.jgit.errors.TransportException}.
  124. *
  125. * @throws org.eclipse.jgit.errors.TransportException
  126. * the reference list could not be scanned.
  127. */
  128. protected void readAdvertisedRefs() throws TransportException {
  129. try {
  130. readAdvertisedRefsImpl();
  131. } catch (TransportException err) {
  132. close();
  133. throw err;
  134. } catch (IOException | RuntimeException err) {
  135. close();
  136. throw new TransportException(err.getMessage(), err);
  137. }
  138. }
  139. private void readAdvertisedRefsImpl() throws IOException {
  140. final LinkedHashMap<String, Ref> avail = new LinkedHashMap<>();
  141. for (;;) {
  142. String line;
  143. try {
  144. line = pckIn.readString();
  145. } catch (EOFException eof) {
  146. if (avail.isEmpty())
  147. throw noRepository();
  148. throw eof;
  149. }
  150. if (PacketLineIn.isEnd(line))
  151. break;
  152. if (line.startsWith("ERR ")) { //$NON-NLS-1$
  153. // This is a customized remote service error.
  154. // Users should be informed about it.
  155. throw new RemoteRepositoryException(uri, line.substring(4));
  156. }
  157. if (avail.isEmpty()) {
  158. final int nul = line.indexOf('\0');
  159. if (nul >= 0) {
  160. // The first line (if any) may contain "hidden"
  161. // capability values after a NUL byte.
  162. remoteCapablities.addAll(
  163. Arrays.asList(line.substring(nul + 1).split(" "))); //$NON-NLS-1$
  164. line = line.substring(0, nul);
  165. }
  166. }
  167. // Expecting to get a line in the form "sha1 refname"
  168. if (line.length() < 41 || line.charAt(40) != ' ') {
  169. throw invalidRefAdvertisementLine(line);
  170. }
  171. String name = line.substring(41, line.length());
  172. if (avail.isEmpty() && name.equals("capabilities^{}")) { //$NON-NLS-1$
  173. // special line from git-receive-pack to show
  174. // capabilities when there are no refs to advertise
  175. continue;
  176. }
  177. final ObjectId id;
  178. try {
  179. id = ObjectId.fromString(line.substring(0, 40));
  180. } catch (InvalidObjectIdException e) {
  181. PackProtocolException ppe = invalidRefAdvertisementLine(line);
  182. ppe.initCause(e);
  183. throw ppe;
  184. }
  185. if (name.equals(".have")) { //$NON-NLS-1$
  186. additionalHaves.add(id);
  187. } else if (name.endsWith("^{}")) { //$NON-NLS-1$
  188. name = name.substring(0, name.length() - 3);
  189. final Ref prior = avail.get(name);
  190. if (prior == null)
  191. throw new PackProtocolException(uri, MessageFormat.format(
  192. JGitText.get().advertisementCameBefore, name, name));
  193. if (prior.getPeeledObjectId() != null)
  194. throw duplicateAdvertisement(name + "^{}"); //$NON-NLS-1$
  195. avail.put(name, new ObjectIdRef.PeeledTag(
  196. Ref.Storage.NETWORK, name, prior.getObjectId(), id));
  197. } else {
  198. final Ref prior = avail.put(name, new ObjectIdRef.PeeledNonTag(
  199. Ref.Storage.NETWORK, name, id));
  200. if (prior != null)
  201. throw duplicateAdvertisement(name);
  202. }
  203. }
  204. updateWithSymRefs(avail, extractSymRefsFromCapabilities(remoteCapablities));
  205. available(avail);
  206. }
  207. /**
  208. * Finds values in the given capabilities of the form:
  209. *
  210. * <pre>
  211. * symref=<em>source</em>:<em>target</em>
  212. * </pre>
  213. *
  214. * And returns a Map of source->target entries.
  215. *
  216. * @param capabilities
  217. * the capabilities lines
  218. * @return a Map of the symref entries from capabilities
  219. * @throws NullPointerException
  220. * if capabilities, or any entry in it, is null
  221. */
  222. static Map<String, String> extractSymRefsFromCapabilities(Collection<String> capabilities) {
  223. final Map<String, String> symRefs = new LinkedHashMap<>();
  224. for (String option : capabilities) {
  225. if (option.startsWith(CAPABILITY_SYMREF_PREFIX)) {
  226. String[] symRef = option
  227. .substring(CAPABILITY_SYMREF_PREFIX.length())
  228. .split(":", 2); //$NON-NLS-1$
  229. if (symRef.length == 2) {
  230. symRefs.put(symRef[0], symRef[1]);
  231. }
  232. }
  233. }
  234. return symRefs;
  235. }
  236. /**
  237. * Updates the given refMap with {@link SymbolicRef}s defined by the given
  238. * symRefs.
  239. * <p>
  240. * For each entry, symRef, in symRefs, whose value is a key in refMap, adds
  241. * a new entry to refMap with that same key and value of a new
  242. * {@link SymbolicRef} with source=symRef.key and
  243. * target=refMap.get(symRef.value), then removes that entry from symRefs.
  244. * <p>
  245. * If refMap already contains an entry for symRef.key, it is replaced.
  246. * </p>
  247. * </p>
  248. * <p>
  249. * For example, given:
  250. * </p>
  251. *
  252. * <pre>
  253. * refMap.put("refs/heads/main", ref);
  254. * symRefs.put("HEAD", "refs/heads/main");
  255. * </pre>
  256. *
  257. * then:
  258. *
  259. * <pre>
  260. * updateWithSymRefs(refMap, symRefs);
  261. * </pre>
  262. *
  263. * has the <em>effect</em> of:
  264. *
  265. * <pre>
  266. * refMap.put("HEAD",
  267. * new SymbolicRef("HEAD", refMap.get(symRefs.remove("HEAD"))))
  268. * </pre>
  269. * <p>
  270. * Any entry in symRefs whose value is not a key in refMap is ignored. Any
  271. * circular symRefs are ignored.
  272. * </p>
  273. * <p>
  274. * Upon completion, symRefs will contain only any unresolvable entries.
  275. * </p>
  276. *
  277. * @param refMap
  278. * a non-null, modifiable, Map to update, and the provider of
  279. * symref targets.
  280. * @param symRefs
  281. * a non-null, modifiable, Map of symrefs.
  282. * @throws NullPointerException
  283. * if refMap or symRefs is null
  284. */
  285. static void updateWithSymRefs(Map<String, Ref> refMap, Map<String, String> symRefs) {
  286. boolean haveNewRefMapEntries = !refMap.isEmpty();
  287. while (!symRefs.isEmpty() && haveNewRefMapEntries) {
  288. haveNewRefMapEntries = false;
  289. final Iterator<Map.Entry<String, String>> iterator = symRefs.entrySet().iterator();
  290. while (iterator.hasNext()) {
  291. final Map.Entry<String, String> symRef = iterator.next();
  292. if (!symRefs.containsKey(symRef.getValue())) { // defer forward reference
  293. final Ref r = refMap.get(symRef.getValue());
  294. if (r != null) {
  295. refMap.put(symRef.getKey(), new SymbolicRef(symRef.getKey(), r));
  296. haveNewRefMapEntries = true;
  297. iterator.remove();
  298. }
  299. }
  300. }
  301. }
  302. }
  303. /**
  304. * Create an exception to indicate problems finding a remote repository. The
  305. * caller is expected to throw the returned exception.
  306. *
  307. * Subclasses may override this method to provide better diagnostics.
  308. *
  309. * @return a TransportException saying a repository cannot be found and
  310. * possibly why.
  311. */
  312. protected TransportException noRepository() {
  313. return new NoRemoteRepositoryException(uri, JGitText.get().notFound);
  314. }
  315. /**
  316. * Whether this option is supported
  317. *
  318. * @param option
  319. * option string
  320. * @return whether this option is supported
  321. */
  322. protected boolean isCapableOf(String option) {
  323. return remoteCapablities.contains(option);
  324. }
  325. /**
  326. * Request capability
  327. *
  328. * @param b
  329. * buffer
  330. * @param option
  331. * option we want
  332. * @return {@code true} if the requested option is supported
  333. */
  334. protected boolean wantCapability(StringBuilder b, String option) {
  335. if (!isCapableOf(option))
  336. return false;
  337. b.append(' ');
  338. b.append(option);
  339. return true;
  340. }
  341. /**
  342. * Add user agent capability
  343. *
  344. * @param b
  345. * a {@link java.lang.StringBuilder} object.
  346. */
  347. protected void addUserAgentCapability(StringBuilder b) {
  348. String a = UserAgent.get();
  349. if (a != null && UserAgent.hasAgent(remoteCapablities)) {
  350. b.append(' ').append(OPTION_AGENT).append('=').append(a);
  351. }
  352. }
  353. /** {@inheritDoc} */
  354. @Override
  355. public String getPeerUserAgent() {
  356. return UserAgent.getAgent(remoteCapablities, super.getPeerUserAgent());
  357. }
  358. private PackProtocolException duplicateAdvertisement(String name) {
  359. return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name));
  360. }
  361. private PackProtocolException invalidRefAdvertisementLine(String line) {
  362. return new PackProtocolException(uri, MessageFormat.format(JGitText.get().invalidRefAdvertisementLine, line));
  363. }
  364. /** {@inheritDoc} */
  365. @Override
  366. public void close() {
  367. if (out != null) {
  368. try {
  369. if (outNeedsEnd) {
  370. outNeedsEnd = false;
  371. pckOut.end();
  372. }
  373. out.close();
  374. } catch (IOException err) {
  375. // Ignore any close errors.
  376. } finally {
  377. out = null;
  378. pckOut = null;
  379. }
  380. }
  381. if (in != null) {
  382. try {
  383. in.close();
  384. } catch (IOException err) {
  385. // Ignore any close errors.
  386. } finally {
  387. in = null;
  388. pckIn = null;
  389. }
  390. }
  391. if (myTimer != null) {
  392. try {
  393. myTimer.terminate();
  394. } finally {
  395. myTimer = null;
  396. timeoutIn = null;
  397. timeoutOut = null;
  398. }
  399. }
  400. }
  401. /**
  402. * Tell the peer we are disconnecting, if it cares to know.
  403. */
  404. protected void endOut() {
  405. if (outNeedsEnd && out != null) {
  406. try {
  407. outNeedsEnd = false;
  408. pckOut.end();
  409. } catch (IOException e) {
  410. try {
  411. out.close();
  412. } catch (IOException err) {
  413. // Ignore any close errors.
  414. } finally {
  415. out = null;
  416. pckOut = null;
  417. }
  418. }
  419. }
  420. }
  421. }