BinaryHunkInputStream.java
/*
* Copyright (C) 2021, 2022 Thomas Wolf <thomas.wolf@paranor.ch> 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.util.io;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.StreamCorruptedException;
import java.text.MessageFormat;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.util.Base85;
/**
* A stream that decodes git binary patch data on the fly.
*
* @since 5.12
*/
public class BinaryHunkInputStream extends InputStream {
private final InputStream in;
private int lineNumber;
private byte[] buffer;
private int pos = 0;
/**
* Creates a new {@link BinaryHunkInputStream}.
*
* @param in
* {@link InputStream} to read the base-85 encoded patch data
* from
*/
public BinaryHunkInputStream(InputStream in) {
this.in = in;
}
@Override
public int read() throws IOException {
if (pos < 0) {
return -1;
}
if (buffer == null || pos == buffer.length) {
fillBuffer();
}
if (pos >= 0) {
return buffer[pos++] & 0xFF;
}
return -1;
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return super.read(b, off, len);
}
@Override
public void close() throws IOException {
in.close();
buffer = null;
}
private void fillBuffer() throws IOException {
int length = in.read();
if (length < 0) {
pos = length;
buffer = null;
return;
}
lineNumber++;
// Length is encoded with characters, A..Z for 1..26 and a..z for 27..52
if ('A' <= length && length <= 'Z') {
length = length - 'A' + 1;
} else if ('a' <= length && length <= 'z') {
length = length - 'a' + 27;
} else {
throw new StreamCorruptedException(MessageFormat.format(
JGitText.get().binaryHunkInvalidLength,
Integer.valueOf(lineNumber), Integer.toHexString(length)));
}
byte[] encoded = new byte[Base85.encodedLength(length)];
for (int i = 0; i < encoded.length; i++) {
int b = in.read();
if (b < 0 || b == '\r' || b == '\n') {
throw new EOFException(MessageFormat.format(
JGitText.get().binaryHunkInvalidLength,
Integer.valueOf(lineNumber)));
}
encoded[i] = (byte) b;
}
// Must be followed by a newline; tolerate EOF.
int b = in.read();
if (b == '\r') {
// Be lenient and accept CR-LF, too.
b = in.read();
}
if (b >= 0 && b != '\n') {
throw new StreamCorruptedException(MessageFormat.format(
JGitText.get().binaryHunkMissingNewline,
Integer.valueOf(lineNumber)));
}
try {
buffer = Base85.decode(encoded, length);
} catch (IllegalArgumentException e) {
StreamCorruptedException ex = new StreamCorruptedException(
MessageFormat.format(JGitText.get().binaryHunkDecodeError,
Integer.valueOf(lineNumber)));
ex.initCause(e);
throw ex;
}
pos = 0;
}
}