View Javadoc
1   /*
2    * Copyright (C) 2021 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  
12  import java.io.EOFException;
13  import java.io.IOException;
14  import java.io.InputStream;
15  import java.io.StreamCorruptedException;
16  import java.text.MessageFormat;
17  
18  import org.eclipse.jgit.internal.JGitText;
19  import org.eclipse.jgit.util.Base85;
20  
21  /**
22   * A stream that decodes git binary patch data on the fly.
23   *
24   * @since 5.12
25   */
26  public class BinaryHunkInputStream extends InputStream {
27  
28  	private final InputStream in;
29  
30  	private int lineNumber;
31  
32  	private byte[] buffer;
33  
34  	private int pos = 0;
35  
36  	/**
37  	 * Creates a new {@link BinaryHunkInputStream}.
38  	 *
39  	 * @param in
40  	 *            {@link InputStream} to read the base-85 encoded patch data
41  	 *            from
42  	 */
43  	public BinaryHunkInputStream(InputStream in) {
44  		this.in = in;
45  	}
46  
47  	@Override
48  	public int read() throws IOException {
49  		if (pos < 0) {
50  			return -1;
51  		}
52  		if (buffer == null || pos == buffer.length) {
53  			fillBuffer();
54  		}
55  		if (pos >= 0) {
56  			return buffer[pos++] & 0xFF;
57  		}
58  		return -1;
59  	}
60  
61  	@Override
62  	public int read(byte[] b, int off, int len) throws IOException {
63  		return super.read(b, off, len);
64  	}
65  
66  	@Override
67  	public void close() throws IOException {
68  		in.close();
69  		buffer = null;
70  	}
71  
72  	private void fillBuffer() throws IOException {
73  		int length = in.read();
74  		if (length < 0) {
75  			pos = length;
76  			buffer = null;
77  			return;
78  		}
79  		lineNumber++;
80  		// Length is encoded with characters, A..Z for 1..26 and a..z for 27..52
81  		if ('A' <= length && length <= 'Z') {
82  			length = length - 'A' + 1;
83  		} else if ('a' <= length && length <= 'z') {
84  			length = length - 'a' + 27;
85  		} else {
86  			throw new StreamCorruptedException(MessageFormat.format(
87  					JGitText.get().binaryHunkInvalidLength,
88  					Integer.valueOf(lineNumber), Integer.toHexString(length)));
89  		}
90  		byte[] encoded = new byte[Base85.encodedLength(length)];
91  		for (int i = 0; i < encoded.length; i++) {
92  			int b = in.read();
93  			if (b < 0 || b == '\n') {
94  				throw new EOFException(MessageFormat.format(
95  						JGitText.get().binaryHunkInvalidLength,
96  						Integer.valueOf(lineNumber)));
97  			}
98  			encoded[i] = (byte) b;
99  		}
100 		// Must be followed by a newline; tolerate EOF.
101 		int b = in.read();
102 		if (b >= 0 && b != '\n') {
103 			throw new StreamCorruptedException(MessageFormat.format(
104 					JGitText.get().binaryHunkMissingNewline,
105 					Integer.valueOf(lineNumber)));
106 		}
107 		try {
108 			buffer = Base85.decode(encoded, length);
109 		} catch (IllegalArgumentException e) {
110 			StreamCorruptedException ex = new StreamCorruptedException(
111 					MessageFormat.format(JGitText.get().binaryHunkDecodeError,
112 							Integer.valueOf(lineNumber)));
113 			ex.initCause(e);
114 			throw ex;
115 		}
116 		pos = 0;
117 	}
118 }