BinaryHunkInputStream.java

  1. /*
  2.  * Copyright (C) 2021, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.util.io;

  11. import java.io.EOFException;
  12. import java.io.IOException;
  13. import java.io.InputStream;
  14. import java.io.StreamCorruptedException;
  15. import java.text.MessageFormat;

  16. import org.eclipse.jgit.internal.JGitText;
  17. import org.eclipse.jgit.util.Base85;

  18. /**
  19.  * A stream that decodes git binary patch data on the fly.
  20.  *
  21.  * @since 5.12
  22.  */
  23. public class BinaryHunkInputStream extends InputStream {

  24.     private final InputStream in;

  25.     private int lineNumber;

  26.     private byte[] buffer;

  27.     private int pos = 0;

  28.     /**
  29.      * Creates a new {@link BinaryHunkInputStream}.
  30.      *
  31.      * @param in
  32.      *            {@link InputStream} to read the base-85 encoded patch data
  33.      *            from
  34.      */
  35.     public BinaryHunkInputStream(InputStream in) {
  36.         this.in = in;
  37.     }

  38.     @Override
  39.     public int read() throws IOException {
  40.         if (pos < 0) {
  41.             return -1;
  42.         }
  43.         if (buffer == null || pos == buffer.length) {
  44.             fillBuffer();
  45.         }
  46.         if (pos >= 0) {
  47.             return buffer[pos++] & 0xFF;
  48.         }
  49.         return -1;
  50.     }

  51.     @Override
  52.     public int read(byte[] b, int off, int len) throws IOException {
  53.         return super.read(b, off, len);
  54.     }

  55.     @Override
  56.     public void close() throws IOException {
  57.         in.close();
  58.         buffer = null;
  59.     }

  60.     private void fillBuffer() throws IOException {
  61.         int length = in.read();
  62.         if (length < 0) {
  63.             pos = length;
  64.             buffer = null;
  65.             return;
  66.         }
  67.         lineNumber++;
  68.         // Length is encoded with characters, A..Z for 1..26 and a..z for 27..52
  69.         if ('A' <= length && length <= 'Z') {
  70.             length = length - 'A' + 1;
  71.         } else if ('a' <= length && length <= 'z') {
  72.             length = length - 'a' + 27;
  73.         } else {
  74.             throw new StreamCorruptedException(MessageFormat.format(
  75.                     JGitText.get().binaryHunkInvalidLength,
  76.                     Integer.valueOf(lineNumber), Integer.toHexString(length)));
  77.         }
  78.         byte[] encoded = new byte[Base85.encodedLength(length)];
  79.         for (int i = 0; i < encoded.length; i++) {
  80.             int b = in.read();
  81.             if (b < 0 || b == '\r' || b == '\n') {
  82.                 throw new EOFException(MessageFormat.format(
  83.                         JGitText.get().binaryHunkInvalidLength,
  84.                         Integer.valueOf(lineNumber)));
  85.             }
  86.             encoded[i] = (byte) b;
  87.         }
  88.         // Must be followed by a newline; tolerate EOF.
  89.         int b = in.read();
  90.         if (b == '\r') {
  91.             // Be lenient and accept CR-LF, too.
  92.             b = in.read();
  93.         }
  94.         if (b >= 0 && b != '\n') {
  95.             throw new StreamCorruptedException(MessageFormat.format(
  96.                     JGitText.get().binaryHunkMissingNewline,
  97.                     Integer.valueOf(lineNumber)));
  98.         }
  99.         try {
  100.             buffer = Base85.decode(encoded, length);
  101.         } catch (IllegalArgumentException e) {
  102.             StreamCorruptedException ex = new StreamCorruptedException(
  103.                     MessageFormat.format(JGitText.get().binaryHunkDecodeError,
  104.                             Integer.valueOf(lineNumber)));
  105.             ex.initCause(e);
  106.             throw ex;
  107.         }
  108.         pos = 0;
  109.     }
  110. }