1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-06-28 14:34:49 +02:00
pgpainless/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpInputStream.java

145 lines
4.3 KiB
Java
Raw Normal View History

// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.pgpainless.implementation.ImplementationFactory;
public class OpenPgpInputStream extends BufferedInputStream {
private static final byte[] ARMOR_HEADER = "-----BEGIN PGP ".getBytes(Charset.forName("UTF8"));
// Buffer beginning bytes of the data
public static final int MAX_BUFFER_SIZE = 8192;
private final byte[] buffer;
private final int bufferLen;
private boolean containsArmorHeader;
private boolean containsOpenPgpPackets;
public OpenPgpInputStream(InputStream in) throws IOException {
super(in, MAX_BUFFER_SIZE);
mark(MAX_BUFFER_SIZE);
buffer = new byte[MAX_BUFFER_SIZE];
bufferLen = read(buffer);
reset();
inspectBuffer();
}
private void inspectBuffer() {
if (determineIsArmored()) {
return;
}
determineIsBinaryOpenPgp();
}
private boolean determineIsArmored() {
if (startsWithIgnoringWhitespace(buffer, ARMOR_HEADER, bufferLen)) {
containsArmorHeader = true;
return true;
}
return false;
}
private void determineIsBinaryOpenPgp() {
if (bufferLen == -1) {
// Empty data
return;
}
try {
ByteArrayInputStream bufferIn = new ByteArrayInputStream(buffer, 0, bufferLen);
PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(bufferIn);
while (objectFactory.nextObject() != null) {
// read all packets in buffer
}
containsOpenPgpPackets = true;
} catch (IOException e) {
if (e.getMessage().contains("premature end of stream in PartialInputStream")) {
// We *probably* hit valid, but large OpenPGP data
// This is not an optimal way of determining the nature of data, but probably the best
// we can get from BC.
containsOpenPgpPackets = true;
}
// else: seemingly random, non-OpenPGP data
}
}
private boolean startsWith(byte[] bytes, byte[] subsequence, int bufferLen) {
return indexOfSubsequence(bytes, subsequence, bufferLen) == 0;
}
private int indexOfSubsequence(byte[] bytes, byte[] subsequence, int bufferLen) {
if (bufferLen == -1) {
return -1;
}
// Naive implementation
// TODO: Could be improved by using e.g. Knuth-Morris-Pratt algorithm.
for (int i = 0; i < bufferLen; i++) {
if ((i + subsequence.length) <= bytes.length) {
boolean found = true;
for (int j = 0; j < subsequence.length; j++) {
if (bytes[i + j] != subsequence[j]) {
found = false;
break;
}
}
if (found) {
return i;
}
}
}
return -1;
}
private boolean startsWithIgnoringWhitespace(byte[] bytes, byte[] subsequence, int bufferLen) {
if (bufferLen == -1) {
return false;
}
for (int i = 0; i < bufferLen; i++) {
// Working on bytes is not trivial with unicode data, but its good enough here
if (Character.isWhitespace(bytes[i])) {
continue;
}
if ((i + subsequence.length) > bytes.length) {
return false;
}
for (int j = 0; j < subsequence.length; j++) {
if (bytes[i + j] != subsequence[j]) {
return false;
}
}
return true;
}
return false;
}
public boolean isAsciiArmored() {
return containsArmorHeader;
}
public boolean isBinaryOpenPgp() {
return containsOpenPgpPackets;
}
public boolean isNonOpenPgp() {
return !isAsciiArmored() && !isBinaryOpenPgp();
}
}