diff options
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.java | 337 |
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; + } + }; + } + } } |