aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java')
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java337
1 files changed, 270 insertions, 67 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
index e1769f84ed..614ad88246 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PacketLineIn.java
@@ -1,60 +1,32 @@
/*
- * Copyright (C) 2008-2010, Google Inc.
- * Copyright (C) 2008-2009, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * and other copyright owners as documented in the project's IP log.
+ * 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 v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
+ * 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.
*
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * 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.Constants;
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.
@@ -67,10 +39,19 @@ import org.eclipse.jgit.util.RawParseUtils;
* against the underlying InputStream.
*/
public class PacketLineIn {
- /** Magic return from {@link #readString()} when a flush packet is found. */
- public static final String END = new StringBuilder(0).toString(); /* must not string pool */
+ private static final Logger log = LoggerFactory.getLogger(PacketLineIn.class);
- static enum AckNackResult {
+ /**
+ * Magic return from {@link #readString()} when a flush packet is found.
+ */
+ private static final String END = new String(); /* must not string pool */
+
+ /**
+ * Magic return from {@link #readString()} when a delim packet is found.
+ */
+ private static final String DELIM = new String(); /* must not string pool */
+
+ enum AckNackResult {
/** NAK */
NAK,
/** ACK */
@@ -83,22 +64,67 @@ public class PacketLineIn {
ACK_READY;
}
+ private final byte[] lineBuffer = new byte[SideBandOutputStream.SMALL_BUF];
private final InputStream in;
+ private long limit;
- private final byte[] lineBuffer;
+ /**
+ * 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.
*
- * @param i
+ * @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;
+ }
+
+ /**
+ * Parses a ACK/NAK line in protocol V2.
+ *
+ * @param line
+ * to parse
+ * @param returnedId
+ * in case of {@link AckNackResult#ACK_COMMON ACK_COMMON}
+ * @return one of {@link AckNackResult#NAK NAK},
+ * {@link AckNackResult#ACK_COMMON ACK_COMMON}, or
+ * {@link AckNackResult#ACK_READY ACK_READY}
+ * @throws IOException
+ * on protocol or transport errors
*/
- public PacketLineIn(final InputStream i) {
- in = i;
- lineBuffer = new byte[SideBandOutputStream.SMALL_BUF];
+ static AckNackResult parseACKv2(String line, MutableObjectId returnedId)
+ throws IOException {
+ if ("NAK".equals(line)) { //$NON-NLS-1$
+ return AckNackResult.NAK;
+ }
+ if (line.startsWith("ACK ") && line.length() == 44) { //$NON-NLS-1$
+ returnedId.fromString(line.substring(4, 44));
+ return AckNackResult.ACK_COMMON;
+ }
+ if ("ready".equals(line)) { //$NON-NLS-1$
+ return AckNackResult.ACK_READY;
+ }
+ if (line.startsWith("ERR ")) { //$NON-NLS-1$
+ throw new PackProtocolException(line.substring(4));
+ }
+ throw new PackProtocolException(
+ MessageFormat.format(JGitText.get().expectedACKNAKGot, line));
}
- AckNackResult readACK(final MutableObjectId returnedId) throws IOException {
+ AckNackResult readACK(MutableObjectId returnedId) throws IOException {
final String line = readString();
if (line.length() == 0)
throw new PackProtocolException(JGitText.get().expectedACKNAKFoundEOF);
@@ -110,12 +136,16 @@ public class PacketLineIn {
return AckNackResult.ACK;
final String arg = line.substring(44);
- if (arg.equals(" continue")) //$NON-NLS-1$
+ switch (arg) {
+ case " continue": //$NON-NLS-1$
return AckNackResult.ACK_CONTINUE;
- else if (arg.equals(" common")) //$NON-NLS-1$
+ case " common": //$NON-NLS-1$
return AckNackResult.ACK_COMMON;
- else if (arg.equals(" ready")) //$NON-NLS-1$
+ case " ready": //$NON-NLS-1$
return AckNackResult.ACK_READY;
+ default:
+ break;
+ }
}
if (line.startsWith("ERR ")) //$NON-NLS-1$
throw new PackProtocolException(line.substring(4));
@@ -130,18 +160,27 @@ public class PacketLineIn {
* 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 IOException
+ * @throws java.io.IOException
* the stream cannot be read.
*/
public String readString() throws IOException {
int len = readLength();
- if (len == 0)
+ 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)
+ if (len == 0) {
+ log.debug("git< "); //$NON-NLS-1$
return ""; //$NON-NLS-1$
+ }
byte[] raw;
if (len <= lineBuffer.length)
@@ -152,7 +191,24 @@ public class PacketLineIn {
IO.readFully(in, raw, 0, len);
if (raw[len - 1] == '\n')
len--;
- return RawParseUtils.decode(Constants.CHARSET, raw, 0, 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.
+ *
+ * @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);
}
/**
@@ -162,13 +218,15 @@ public class PacketLineIn {
*
* @return the string. {@link #END} if the string was the magic flush
* packet.
- * @throws IOException
+ * @throws java.io.IOException
* the stream cannot be read.
*/
public String readStringRaw() throws IOException {
int len = readLength();
- if (len == 0)
+ if (len == 0) {
+ log.debug("git< 0000"); //$NON-NLS-1$
return END;
+ }
len -= 4; // length header (4 bytes)
@@ -179,20 +237,165 @@ public class PacketLineIn {
raw = new byte[len];
IO.readFully(in, raw, 0, len);
- return RawParseUtils.decode(Constants.CHARSET, 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.
+ *
+ * @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.
+ * <p>
+ * Intended for use only in tests.
+ *
+ * @return The delimiter marker.
+ */
+ static String delimiter() {
+ return DELIM;
+ }
+
+ /**
+ * 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.
+ *
+ * @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 {
- final int len = RawParseUtils.parseHexInt16(lineBuffer, 0);
- if (len != 0 && len < 4)
- throw new ArrayIndexOutOfBoundsException();
- return len;
+ len = RawParseUtils.parseHexInt16(lineBuffer, 0);
} catch (ArrayIndexOutOfBoundsException err) {
- throw new IOException(MessageFormat.format(JGitText.get().invalidPacketLineHeader,
- "" + (char) lineBuffer[0] + (char) lineBuffer[1] //$NON-NLS-1$
- + (char) lineBuffer[2] + (char) lineBuffer[3]));
+ 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
+ */
+ public static class InputOverLimitIOException extends IOException {
+ private static final long serialVersionUID = 1L;
+ }
+
+ /**
+ * 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<>() {
+
+ @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;
+ }
+ };
+ }
+
}
}