/*
 * Copyright (C) 2008, 2010 Google Inc.
 * Copyright (C) 2008, 2009 Robin Rosenberg <robin.rosenberg@dewire.com>
 * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> 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 java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.text.MessageFormat;
import java.util.Iterator;

import org.eclipse.jgit.errors.PackProtocolException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.MutableObjectId;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Read Git style pkt-line formatting from an input stream.

This class is not thread safe and may issue multiple reads to the underlying stream for each method call made.

This class performs no buffering on its own. This makes it suitable to interleave reads performed by this class with reads performed directly against the underlying InputStream.

/** * Read Git style pkt-line formatting from an input stream. * <p> * This class is not thread safe and may issue multiple reads to the underlying * stream for each method call made. * <p> * This class performs no buffering on its own. This makes it suitable to * interleave reads performed by this class with reads performed directly * against the underlying InputStream. */
public class PacketLineIn { private static final Logger log = LoggerFactory.getLogger(PacketLineIn.class);
Magic return from readString() when a flush packet is found.
Deprecated:Callers should use isEnd(String) to check if a string is the end marker, or readStrings() to iterate over all strings in the input stream until the marker is reached.
/** * Magic return from {@link #readString()} when a flush packet is found. * * @deprecated Callers should use {@link #isEnd(String)} to check if a * string is the end marker, or * {@link PacketLineIn#readStrings()} to iterate over all * strings in the input stream until the marker is reached. */
@Deprecated public static final String END = new String(); /* must not string pool */
Magic return from readString() when a delim packet is found.
Since:5.0
Deprecated:Callers should use isDelimiter(String) to check if a string is the delimiter.
/** * Magic return from {@link #readString()} when a delim packet is found. * * @since 5.0 * @deprecated Callers should use {@link #isDelimiter(String)} to check if a * string is the delimiter. */
@Deprecated public static final String DELIM = new String(); /* must not string pool */ enum AckNackResult {
NAK
/** NAK */
NAK,
ACK
/** ACK */
ACK,
ACK + continue
/** ACK + continue */
ACK_CONTINUE,
ACK + common
/** ACK + common */
ACK_COMMON,
ACK + ready
/** ACK + ready */
ACK_READY; } private final byte[] lineBuffer = new byte[SideBandOutputStream.SMALL_BUF]; private final InputStream in; private long limit;
Create a new packet line reader.
Params:
  • in – the input stream to consume.
/** * Create a new packet line reader. * * @param in * the input stream to consume. */
public PacketLineIn(InputStream in) { this(in, 0); }
Create a new packet line reader.
Params:
  • in – the input stream to consume.
  • limit – bytes to read from the input; unlimited if set to 0.
Since:4.7
/** * Create a new packet line reader. * * @param in * the input stream to consume. * @param limit * bytes to read from the input; unlimited if set to 0. * @since 4.7 */
public PacketLineIn(InputStream in, long limit) { this.in = in; this.limit = limit; } AckNackResult readACK(MutableObjectId returnedId) throws IOException { final String line = readString(); if (line.length() == 0) throw new PackProtocolException(JGitText.get().expectedACKNAKFoundEOF); if ("NAK".equals(line)) //$NON-NLS-1$ return AckNackResult.NAK; if (line.startsWith("ACK ")) { //$NON-NLS-1$ returnedId.fromString(line.substring(4, 44)); if (line.length() == 44) return AckNackResult.ACK; final String arg = line.substring(44); switch (arg) { case " continue": //$NON-NLS-1$ return AckNackResult.ACK_CONTINUE; case " common": //$NON-NLS-1$ return AckNackResult.ACK_COMMON; case " ready": //$NON-NLS-1$ return AckNackResult.ACK_READY; default: break; } } if (line.startsWith("ERR ")) //$NON-NLS-1$ throw new PackProtocolException(line.substring(4)); throw new PackProtocolException(MessageFormat.format(JGitText.get().expectedACKNAKGot, line)); }
Read a single UTF-8 encoded string packet from the input stream.

If the string ends with an LF, it will be removed before returning the value to the caller. If this automatic trimming behavior is not desired, use readStringRaw() instead.

Throws:
Returns:the string. END if the string was the magic flush packet, DELIM if the string was the magic DELIM packet.
/** * Read a single UTF-8 encoded string packet from the input stream. * <p> * If the string ends with an LF, it will be removed before returning the * value to the caller. If this automatic trimming behavior is not desired, * use {@link #readStringRaw()} instead. * * @return the string. {@link #END} if the string was the magic flush * packet, {@link #DELIM} if the string was the magic DELIM * packet. * @throws java.io.IOException * the stream cannot be read. */
public String readString() throws IOException { int len = readLength(); if (len == 0) { log.debug("git< 0000"); //$NON-NLS-1$ return END; } if (len == 1) { log.debug("git< 0001"); //$NON-NLS-1$ return DELIM; } len -= 4; // length header (4 bytes) if (len == 0) { log.debug("git< "); //$NON-NLS-1$ return ""; //$NON-NLS-1$ } byte[] raw; if (len <= lineBuffer.length) raw = lineBuffer; else raw = new byte[len]; IO.readFully(in, raw, 0, len); if (raw[len - 1] == '\n') len--; String s = RawParseUtils.decode(UTF_8, raw, 0, len); log.debug("git< " + s); //$NON-NLS-1$ return s; }
Get an iterator to read strings from the input stream.
Throws:
  • IOException – on failure to read the initial packet line.
Returns:an iterator that calls readString() until END is encountered.
Since:5.4
/** * Get an iterator to read strings from the input stream. * * @return an iterator that calls {@link #readString()} until {@link #END} * is encountered. * * @throws IOException * on failure to read the initial packet line. * @since 5.4 */
public PacketLineInIterator readStrings() throws IOException { return new PacketLineInIterator(this); }
Read a single UTF-8 encoded string packet from the input stream.

Unlike readString() a trailing LF will be retained.

Throws:
Returns:the string. END if the string was the magic flush packet.
/** * Read a single UTF-8 encoded string packet from the input stream. * <p> * Unlike {@link #readString()} a trailing LF will be retained. * * @return the string. {@link #END} if the string was the magic flush * packet. * @throws java.io.IOException * the stream cannot be read. */
public String readStringRaw() throws IOException { int len = readLength(); if (len == 0) { log.debug("git< 0000"); //$NON-NLS-1$ return END; } len -= 4; // length header (4 bytes) byte[] raw; if (len <= lineBuffer.length) raw = lineBuffer; else raw = new byte[len]; IO.readFully(in, raw, 0, len); String s = RawParseUtils.decode(UTF_8, raw, 0, len); log.debug("git< " + s); //$NON-NLS-1$ return s; }
Check if a string is the delimiter marker.
Params:
  • s – the string to check
Returns:true if the given string is DELIM, otherwise false.
Since:5.4
/** * Check if a string is the delimiter marker. * * @param s * the string to check * @return true if the given string is {@link #DELIM}, otherwise false. * @since 5.4 */
@SuppressWarnings({ "ReferenceEquality", "StringEquality" }) public static boolean isDelimiter(String s) { return s == DELIM; }
Get the delimiter marker.

Intended for use only in tests.

Returns:The delimiter marker.
/** * Get the delimiter marker. * <p> * Intended for use only in tests. * * @return The delimiter marker. */
static String delimiter() { return DELIM; }
Get the end marker.

Intended for use only in tests.

Returns:The end marker.
/** * Get the end marker. * <p> * Intended for use only in tests. * * @return The end marker. */
static String end() { return END; }
Check if a string is the packet end marker.
Params:
  • s – the string to check
Returns:true if the given string is END, otherwise false.
Since:5.4
/** * Check if a string is the packet end marker. * * @param s * the string to check * @return true if the given string is {@link #END}, otherwise false. * @since 5.4 */
@SuppressWarnings({ "ReferenceEquality", "StringEquality" }) public static boolean isEnd(String s) { return s == END; } void discardUntilEnd() throws IOException { for (;;) { int n = readLength(); if (n == 0) { break; } IO.skipFully(in, n - 4); } } int readLength() throws IOException { IO.readFully(in, lineBuffer, 0, 4); int len; try { len = RawParseUtils.parseHexInt16(lineBuffer, 0); } catch (ArrayIndexOutOfBoundsException err) { throw invalidHeader(err); } if (len == 0) { return 0; } else if (len == 1) { return 1; } else if (len < 4) { throw invalidHeader(); } if (limit != 0) { int n = len - 4; if (limit < n) { limit = -1; try { IO.skipFully(in, n); } catch (IOException e) { // Ignore failure discarding packet over limit. } throw new InputOverLimitIOException(); } // if set limit must not be 0 (means unlimited). limit = n < limit ? limit - n : -1; } return len; } private IOException invalidHeader() { return new IOException(MessageFormat.format(JGitText.get().invalidPacketLineHeader, "" + (char) lineBuffer[0] + (char) lineBuffer[1] //$NON-NLS-1$ + (char) lineBuffer[2] + (char) lineBuffer[3])); } private IOException invalidHeader(Throwable cause) { IOException ioe = invalidHeader(); ioe.initCause(cause); return ioe; }
IOException thrown by read when the configured input limit is exceeded.
Since:4.7
/** * IOException thrown by read when the configured input limit is exceeded. * * @since 4.7 */
public static class InputOverLimitIOException extends IOException { private static final long serialVersionUID = 1L; }
Iterator over packet lines.

Calls PacketLineIn.readString() on the PacketLineIn until PacketLineIn.END is encountered.

Since:5.4
/** * Iterator over packet lines. * <p> * Calls {@link #readString()} on the {@link PacketLineIn} until * {@link #END} is encountered. * * @since 5.4 * */
public static class PacketLineInIterator implements Iterable<String> { private PacketLineIn in; private String current; PacketLineInIterator(PacketLineIn in) throws IOException { this.in = in; current = in.readString(); } @Override public Iterator<String> iterator() { return new Iterator<String>() { @Override public boolean hasNext() { return !PacketLineIn.isEnd(current); } @Override public String next() { String next = current; try { current = in.readString(); } catch (IOException e) { throw new UncheckedIOException(e); } return next; } }; } } }