diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java index 7d2b9722..e7b0cb23 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionStream.java @@ -15,7 +15,6 @@ import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.openpgp.PGPCompressedDataGenerator; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPLiteralDataGenerator; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.operator.PGPDataEncryptorBuilder; @@ -25,6 +24,7 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.util.ArmoredOutputStreamFactory; +import org.pgpainless.util.StreamGeneratorWrapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,7 +50,7 @@ public final class EncryptionStream extends OutputStream { private OutputStream publicKeyEncryptedStream = null; private PGPCompressedDataGenerator compressedDataGenerator; private BCPGOutputStream basicCompressionStream; - private PGPLiteralDataGenerator literalDataGenerator; + private StreamGeneratorWrapper streamGeneratorWrapper; private OutputStream literalDataStream; EncryptionStream(@Nonnull OutputStream targetOutputStream, @@ -147,12 +147,10 @@ public final class EncryptionStream extends OutputStream { armorOutputStream.beginClearText(firstMethod.getHashAlgorithm().getAlgorithmId()); return; } - literalDataGenerator = new PGPLiteralDataGenerator(); - literalDataStream = literalDataGenerator.open(outermostStream, - options.getEncoding().getCode(), - options.getFileName(), - options.getModificationDate(), - new byte[BUFFER_SIZE]); + + streamGeneratorWrapper = StreamGeneratorWrapper.forStreamEncoding(options.getEncoding()); + literalDataStream = streamGeneratorWrapper.open(outermostStream, + options.getFileName(), options.getModificationDate(), new byte[BUFFER_SIZE]); outermostStream = literalDataStream; resultBuilder.setFileName(options.getFileName()) @@ -212,8 +210,8 @@ public final class EncryptionStream extends OutputStream { literalDataStream.flush(); literalDataStream.close(); } - if (literalDataGenerator != null) { - literalDataGenerator.close(); + if (streamGeneratorWrapper != null) { + streamGeneratorWrapper.close(); } if (options.isCleartextSigned()) { diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/StreamGeneratorWrapper.java b/pgpainless-core/src/main/java/org/pgpainless/util/StreamGeneratorWrapper.java new file mode 100644 index 00000000..2618d022 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/util/StreamGeneratorWrapper.java @@ -0,0 +1,94 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.util; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.Date; + +import javax.annotation.Nonnull; + +import org.bouncycastle.openpgp.PGPCanonicalizedDataGenerator; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.pgpainless.algorithm.StreamEncoding; + +/** + * Literal Data can be encoded in different ways. + * BINARY encoding leaves the data as is and is generated through the {@link PGPLiteralDataGenerator}. + * However, if the data is encoded in TEXT or UTF8 encoding, we need to use the {@link PGPCanonicalizedDataGenerator} + * instead. + * + * This wrapper class acts as a handle for both options and provides a unified interface for them. + */ +public final class StreamGeneratorWrapper { + + private final StreamEncoding encoding; + private final PGPLiteralDataGenerator literalDataGenerator; + private final PGPCanonicalizedDataGenerator canonicalizedDataGenerator; + + /** + * Create a new instance for the given encoding. + * + * @param encoding stream encoding + * @return wrapper + */ + public static StreamGeneratorWrapper forStreamEncoding(@Nonnull StreamEncoding encoding) { + if (encoding == StreamEncoding.BINARY) { + return new StreamGeneratorWrapper(encoding, new PGPLiteralDataGenerator()); + } else { + return new StreamGeneratorWrapper(encoding, new PGPCanonicalizedDataGenerator()); + } + } + + private StreamGeneratorWrapper(@Nonnull StreamEncoding encoding, @Nonnull PGPLiteralDataGenerator literalDataGenerator) { + if (encoding != StreamEncoding.BINARY) { + throw new IllegalArgumentException("PGPLiteralDataGenerator can only be used with BINARY encoding."); + } + this.encoding = encoding; + this.literalDataGenerator = literalDataGenerator; + this.canonicalizedDataGenerator = null; + } + + private StreamGeneratorWrapper(@Nonnull StreamEncoding encoding, @Nonnull PGPCanonicalizedDataGenerator canonicalizedDataGenerator) { + if (encoding != StreamEncoding.TEXT && encoding != StreamEncoding.UTF8) { + throw new IllegalArgumentException("PGPCanonicalizedDataGenerator can only be used with TEXT or UTF8 encoding."); + } + this.encoding = encoding; + this.canonicalizedDataGenerator = canonicalizedDataGenerator; + this.literalDataGenerator = null; + } + + /** + * Open a new encoding stream. + * + * @param outputStream wrapped output stream + * @param filename file name + * @param modificationDate modification date + * @param buffer buffer + * @return encoding stream + * @throws IOException + */ + public OutputStream open(OutputStream outputStream, String filename, Date modificationDate, byte[] buffer) throws IOException { + if (literalDataGenerator != null) { + return literalDataGenerator.open(outputStream, encoding.getCode(), filename, modificationDate, buffer); + } else { + return canonicalizedDataGenerator.open(outputStream, encoding.getCode(), filename, modificationDate, buffer); + } + } + + /** + * Close all encoding streams opened by this generator wrapper. + * + * @throws IOException + */ + public void close() throws IOException { + if (literalDataGenerator != null) { + literalDataGenerator.close(); + } + if (canonicalizedDataGenerator != null) { + canonicalizedDataGenerator.close(); + } + } +} diff --git a/pgpainless-core/src/test/java/org/bouncycastle/LiteralDataCRLFEncodingTest.java b/pgpainless-core/src/test/java/org/bouncycastle/LiteralDataCRLFEncodingTest.java index a5f0aa52..b6f92bc6 100644 --- a/pgpainless-core/src/test/java/org/bouncycastle/LiteralDataCRLFEncodingTest.java +++ b/pgpainless-core/src/test/java/org/bouncycastle/LiteralDataCRLFEncodingTest.java @@ -23,7 +23,7 @@ public class LiteralDataCRLFEncodingTest { public void testCanonicalization() throws IOException { PGPCanonicalizedDataGenerator generator = new PGPCanonicalizedDataGenerator(); ByteArrayOutputStream out = new ByteArrayOutputStream(); - OutputStream canonicalizer = generator.open(out, PGPCanonicalizedDataGenerator.UTF8, "", new Date(), new byte[1<<9]); + OutputStream canonicalizer = generator.open(out, PGPCanonicalizedDataGenerator.UTF8, "", new Date(), new byte[1 << 9]); ByteArrayInputStream in = new ByteArrayInputStream("Foo\nBar\n".getBytes(StandardCharsets.UTF_8)); Streams.pipeAll(in, canonicalizer);