diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.java deleted file mode 100644 index cd0f6b35..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.java +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.cleartext_signatures; - -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.openpgp.PGPObjectFactory; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.util.Strings; -import org.pgpainless.exception.WrongConsumingMethodException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.util.ArmoredInputStreamFactory; - -/** - * Utility class to deal with cleartext-signed messages. - * Based on Bouncycastle's {@link org.bouncycastle.openpgp.examples.ClearSignedFileProcessor}. - */ -public final class ClearsignedMessageUtil { - - private ClearsignedMessageUtil() { - - } - - /** - * Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided - * messageOutputStream. - * - * @param clearsignedInputStream input stream containing a clearsigned message - * @param messageOutputStream output stream to which the dearmored message shall be written - * @return signatures - * - * @throws IOException if the message is not clearsigned or some other IO error happens - * @throws WrongConsumingMethodException in case the armored message is not cleartext signed - */ - public static PGPSignatureList detachSignaturesFromInbandClearsignedMessage(InputStream clearsignedInputStream, - OutputStream messageOutputStream) - throws IOException, WrongConsumingMethodException { - ArmoredInputStream in; - if (clearsignedInputStream instanceof ArmoredInputStream) { - in = (ArmoredInputStream) clearsignedInputStream; - } else { - in = ArmoredInputStreamFactory.get(clearsignedInputStream); - } - - if (!in.isClearText()) { - throw new WrongConsumingMethodException("Message is not using the Cleartext Signature Framework."); - } - - OutputStream out = new BufferedOutputStream(messageOutputStream); - try { - ByteArrayOutputStream lineOut = new ByteArrayOutputStream(); - int lookAhead = readInputLine(lineOut, in); - byte[] lineSep = getLineSeparator(); - - if (lookAhead != -1 && in.isClearText()) { - byte[] line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); - - while (lookAhead != -1 && in.isClearText()) { - lookAhead = readInputLine(lineOut, lookAhead, in); - line = lineOut.toByteArray(); - out.write(lineSep); - out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); - } - } else { - if (lookAhead != -1) { - byte[] line = lineOut.toByteArray(); - out.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)); - } - } - } finally { - out.close(); - } - - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(in); - PGPSignatureList signatures = (PGPSignatureList) objectFactory.nextObject(); - - return signatures; - } - - public static int readInputLine(ByteArrayOutputStream bOut, InputStream fIn) - throws IOException { - bOut.reset(); - - int lookAhead = -1; - int ch; - - while ((ch = fIn.read()) >= 0) { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } - - return lookAhead; - } - - public static int readInputLine(ByteArrayOutputStream bOut, int lookAhead, InputStream fIn) - throws IOException { - bOut.reset(); - - int ch = lookAhead; - - do { - bOut.write(ch); - if (ch == '\r' || ch == '\n') { - lookAhead = readPassedEOL(bOut, ch, fIn); - break; - } - } - while ((ch = fIn.read()) >= 0); - - if (ch < 0) { - lookAhead = -1; - } - - return lookAhead; - } - - private static int readPassedEOL(ByteArrayOutputStream bOut, int lastCh, InputStream fIn) - throws IOException { - int lookAhead = fIn.read(); - - if (lastCh == '\r' && lookAhead == '\n') { - bOut.write(lookAhead); - lookAhead = fIn.read(); - } - - return lookAhead; - } - - - private static byte[] getLineSeparator() { - String nl = Strings.lineSeparator(); - byte[] nlBytes = new byte[nl.length()]; - - for (int i = 0; i != nlBytes.length; i++) { - nlBytes[i] = (byte) nl.charAt(i); - } - - return nlBytes; - } - - private static int getLengthWithoutSeparatorOrTrailingWhitespace(byte[] line) { - int end = line.length - 1; - - while (end >= 0 && isWhiteSpace(line[end])) { - end--; - } - - return end + 1; - } - - private static boolean isLineEnding(byte b) { - return b == '\r' || b == '\n'; - } - - private static boolean isWhiteSpace(byte b) { - return isLineEnding(b) || b == '\t' || b == ' '; - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.java deleted file mode 100644 index 62433a2a..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.java +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.cleartext_signatures; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; - -/** - * Implementation of the {@link MultiPassStrategy}. - * This class keeps the read data in memory by caching the data inside a {@link ByteArrayOutputStream}. - * - * Note, that this class is suitable and efficient for processing small amounts of data. - * For larger data like encrypted files, use of the {@link WriteToFileMultiPassStrategy} is recommended to - * prevent {@link OutOfMemoryError OutOfMemoryErrors} and other issues. - */ -public class InMemoryMultiPassStrategy implements MultiPassStrategy { - - private final ByteArrayOutputStream cache = new ByteArrayOutputStream(); - - @Override - public ByteArrayOutputStream getMessageOutputStream() { - return cache; - } - - @Override - public ByteArrayInputStream getMessageInputStream() { - return new ByteArrayInputStream(getBytes()); - } - - public byte[] getBytes() { - return getMessageOutputStream().toByteArray(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.java deleted file mode 100644 index 5aa9f548..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.java +++ /dev/null @@ -1,70 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.cleartext_signatures; - -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures, - * a strategy for how to cache the read data is required. - * Otherwise, large data kept in memory could cause {@link OutOfMemoryError OutOfMemoryErrors} or other issues. - * - * This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes - * to do verification. - * - * This interface can be used to write the signed data stream out via {@link #getMessageOutputStream()} and later - * get access to the data again via {@link #getMessageInputStream()}. - * Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away. - */ -public interface MultiPassStrategy { - - /** - * Provide an {@link OutputStream} into which the signed data can be read into. - * - * @return output stream - * @throws IOException io error - */ - OutputStream getMessageOutputStream() throws IOException; - - /** - * Provide an {@link InputStream} which contains the data that was previously written away in - * {@link #getMessageOutputStream()}. - * - * As there may be multiple signatures that need to be processed, each call of this method MUST return - * a new {@link InputStream}. - * - * @return input stream - * @throws IOException io error - */ - InputStream getMessageInputStream() throws IOException; - - /** - * Write the message content out to a file and re-read it to verify signatures. - * This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory. - * After the message has been processed completely, the messages content are available at the provided file. - * - * @param file target file - * @return strategy - */ - static MultiPassStrategy writeMessageToFile(File file) { - return new WriteToFileMultiPassStrategy(file); - } - - /** - * Read the message content into memory. - * This strategy is best suited for small messages which fit into memory. - * After the message has been processed completely, the message content can be accessed by calling - * {@link ByteArrayOutputStream#toByteArray()} on {@link #getMessageOutputStream()}. - * - * @return strategy - */ - static InMemoryMultiPassStrategy keepMessageInMemory() { - return new InMemoryMultiPassStrategy(); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.java deleted file mode 100644 index 6c8d03ca..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.java +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.cleartext_signatures; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -/** - * Implementation of the {@link MultiPassStrategy}. - * When processing signed data the first time, the data is being written out into a file. - * For the second pass, that file is being read again. - * - * This strategy is recommended when larger amounts of data need to be processed. - * For smaller files, {@link InMemoryMultiPassStrategy} yields higher efficiency. - */ -public class WriteToFileMultiPassStrategy implements MultiPassStrategy { - - private final File file; - - /** - * Create a {@link MultiPassStrategy} which writes data to a file. - * Note that {@link #getMessageOutputStream()} will create the file if necessary. - * - * @param file file to write the data to and read from - */ - public WriteToFileMultiPassStrategy(File file) { - this.file = file; - } - - @Override - public OutputStream getMessageOutputStream() throws IOException { - if (!file.exists()) { - boolean created = file.createNewFile(); - if (!created) { - throw new IOException("New file '" + file.getAbsolutePath() + "' was not created."); - } - } - return new FileOutputStream(file); - } - - @Override - public InputStream getMessageInputStream() throws IOException { - if (!file.exists()) { - throw new IOException("File '" + file.getAbsolutePath() + "' does no longer exist."); - } - return new FileInputStream(file); - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/package-info.java deleted file mode 100644 index 3123338f..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/cleartext_signatures/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -/** - * Classes related to cleartext signature verification. - */ -package org.pgpainless.decryption_verification.cleartext_signatures; diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt new file mode 100644 index 00000000..7a3b93ee --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/ClearsignedMessageUtil.kt @@ -0,0 +1,153 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.cleartext_signatures + +import org.bouncycastle.bcpg.ArmoredInputStream +import org.bouncycastle.openpgp.PGPSignatureList +import org.bouncycastle.util.Strings +import org.pgpainless.exception.WrongConsumingMethodException +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.util.ArmoredInputStreamFactory +import java.io.* +import kotlin.jvm.Throws + +/** + * Utility class to deal with cleartext-signed messages. + * Based on Bouncycastle's [org.bouncycastle.openpgp.examples.ClearSignedFileProcessor]. + */ +class ClearsignedMessageUtil { + + companion object { + + /** + * Dearmor a clearsigned message, detach the inband signatures and write the plaintext message to the provided + * messageOutputStream. + * + * @param clearsignedInputStream input stream containing a clearsigned message + * @param messageOutputStream output stream to which the dearmored message shall be written + * @return signatures + * + * @throws IOException if the message is not clearsigned or some other IO error happens + * @throws WrongConsumingMethodException in case the armored message is not cleartext signed + */ + @JvmStatic + @Throws(WrongConsumingMethodException::class, IOException::class) + fun detachSignaturesFromInbandClearsignedMessage( + clearsignedInputStream: InputStream, + messageOutputStream: OutputStream): PGPSignatureList { + val input: ArmoredInputStream = if (clearsignedInputStream is ArmoredInputStream) { + clearsignedInputStream + } else { + ArmoredInputStreamFactory.get(clearsignedInputStream) + } + + if (!input.isClearText) { + throw WrongConsumingMethodException("Message isn't using the Cleartext Signature Framework.") + } + + BufferedOutputStream(messageOutputStream).use { output -> + val lineOut = ByteArrayOutputStream() + var lookAhead = readInputLine(lineOut, input) + val lineSep = getLineSeparator() + + if (lookAhead != -1 && input.isClearText) { + var line = lineOut.toByteArray() + output.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)) + + while (lookAhead != -1 && input.isClearText) { + lookAhead = readInputLine(lineOut, lookAhead, input) + line = lineOut.toByteArray() + output.write(lineSep) + output.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)) + } + } else { + if (lookAhead != -1) { + val line = lineOut.toByteArray() + output.write(line, 0, getLengthWithoutSeparatorOrTrailingWhitespace(line)) + } + } + } + + val objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(input) + val next = objectFactory.nextObject() ?: PGPSignatureList(arrayOf()) + return next as PGPSignatureList + } + + @JvmStatic + private fun readInputLine(bOut: ByteArrayOutputStream, fIn: InputStream): Int { + bOut.reset() + + var lookAhead = -1 + var ch: Int + + while (fIn.read().also { ch = it } >= 0) { + bOut.write(ch) + if (ch == '\r'.code || ch == '\n'.code) { + lookAhead = readPassedEOL(bOut, ch, fIn) + break + } + } + + return lookAhead + } + + @JvmStatic + private fun readInputLine(bOut: ByteArrayOutputStream, lookAhead: Int, fIn: InputStream): Int { + var mLookAhead = lookAhead + bOut.reset() + var ch = mLookAhead + do { + bOut.write(ch) + if (ch == '\r'.code || ch == '\n'.code) { + mLookAhead = readPassedEOL(bOut, ch, fIn) + break + } + } while (fIn.read().also { ch = it } >= 0) + if (ch < 0) { + mLookAhead = -1 + } + return mLookAhead + } + + @JvmStatic + private fun readPassedEOL(bOut: ByteArrayOutputStream, lastCh: Int, fIn: InputStream): Int { + var lookAhead = fIn.read() + if (lastCh == '\r'.code && lookAhead == '\n'.code) { + bOut.write(lookAhead) + lookAhead = fIn.read() + } + return lookAhead + } + + @JvmStatic + private fun getLineSeparator(): ByteArray { + val nl = Strings.lineSeparator() + val nlBytes = ByteArray(nl.length) + for (i in nlBytes.indices) { + nlBytes[i] = nl[i].code.toByte() + } + return nlBytes + } + + @JvmStatic + private fun getLengthWithoutSeparatorOrTrailingWhitespace(line: ByteArray): Int { + var end = line.size - 1 + while (end >= 0 && isWhiteSpace(line[end])) { + end-- + } + return end + 1 + } + + @JvmStatic + private fun isLineEnding(b: Byte): Boolean { + return b == '\r'.code.toByte() || b == '\n'.code.toByte() + } + + @JvmStatic + private fun isWhiteSpace(b: Byte): Boolean { + return isLineEnding(b) || b == '\t'.code.toByte() || b == ' '.code.toByte() + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt new file mode 100644 index 00000000..eed6438f --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/InMemoryMultiPassStrategy.kt @@ -0,0 +1,29 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.cleartext_signatures + +import java.io.ByteArrayInputStream +import java.io.ByteArrayOutputStream + +/** + * Implementation of the [MultiPassStrategy]. + * This class keeps the read data in memory by caching the data inside a [ByteArrayOutputStream]. + * + * Note, that this class is suitable and efficient for processing small amounts of data. + * For larger data like encrypted files, use of the [WriteToFileMultiPassStrategy] is recommended to + * prevent [OutOfMemoryError] and other issues. + */ +class InMemoryMultiPassStrategy : MultiPassStrategy { + + private val cache = ByteArrayOutputStream() + + override val messageOutputStream: ByteArrayOutputStream + get() = cache + + override val messageInputStream: ByteArrayInputStream + get() = ByteArrayInputStream(getBytes()) + + fun getBytes(): ByteArray = messageOutputStream.toByteArray() +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt new file mode 100644 index 00000000..ae69b9c5 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/MultiPassStrategy.kt @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.cleartext_signatures + +import java.io.* + +/** + * Since for verification of cleartext signed messages, we need to read the whole data twice in order to verify signatures, + * a strategy for how to cache the read data is required. + * Otherwise, large data kept in memory could cause an [OutOfMemoryError] or other issues. + * + * This is an Interface that describes a strategy to deal with the fact that detached signatures require multiple passes + * to do verification. + * + * This interface can be used to write the signed data stream out via [messageOutputStream] and later + * get access to the data again via [messageInputStream]. + * Thereby the detail where the data is being stored (memory, file, etc.) can be abstracted away. + */ +interface MultiPassStrategy { + + /** + * Provide an [OutputStream] into which the signed data can be read into. + * + * @return output stream + * @throws IOException io error + */ + val messageOutputStream: OutputStream + + /** + * Provide an [InputStream] which contains the data that was previously written away in + * [messageOutputStream]. + * + * As there may be multiple signatures that need to be processed, each call of this method MUST return + * a new [InputStream]. + * + * @return input stream + * @throws IOException io error + */ + val messageInputStream: InputStream + + companion object { + + /** + * Write the message content out to a file and re-read it to verify signatures. + * This strategy is best suited for larger messages (e.g. plaintext signed files) which might not fit into memory. + * After the message has been processed completely, the messages content are available at the provided file. + * + * @param file target file + * @return strategy + */ + @JvmStatic + fun writeMessageToFile(file: File): MultiPassStrategy { + return WriteToFileMultiPassStrategy(file) + } + + /** + * Read the message content into memory. + * This strategy is best suited for small messages which fit into memory. + * After the message has been processed completely, the message content can be accessed by calling + * [ByteArrayOutputStream.toByteArray] on [messageOutputStream]. + * + * @return strategy + */ + @JvmStatic + fun keepMessageInMemory(): InMemoryMultiPassStrategy { + return InMemoryMultiPassStrategy() + } + } +} \ No newline at end of file diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt new file mode 100644 index 00000000..5d6567a4 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/decryption_verification/cleartext_signatures/WriteToFileMultiPassStrategy.kt @@ -0,0 +1,43 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification.cleartext_signatures + +import java.io.* + +/** + * Implementation of the [MultiPassStrategy]. + * When processing signed data the first time, the data is being written out into a file. + * For the second pass, that file is being read again. + * + * This strategy is recommended when larger amounts of data need to be processed. + * For smaller files, [InMemoryMultiPassStrategy] yields higher efficiency. + * + * @param file file to write the data to and read from + */ +class WriteToFileMultiPassStrategy( + private val file: File +) : MultiPassStrategy { + + override val messageOutputStream: OutputStream + @Throws(IOException::class) + get() { + if (!file.exists()) { + if (!file.createNewFile()) { + throw IOException("New file '${file.absolutePath}' could not be created.") + } + } + return FileOutputStream(file) + } + + override val messageInputStream: InputStream + @Throws(IOException::class) + get() { + if (!file.exists()) { + throw IOException("File '${file.absolutePath}' does no longer exist.") + } + return FileInputStream(file) + } + +} \ No newline at end of file diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineDetachImpl.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineDetachImpl.java index bafc2794..da6e0917 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineDetachImpl.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/InlineDetachImpl.java @@ -69,7 +69,7 @@ public class InlineDetachImpl implements InlineDetach { if (armorIn.isClearText()) { try { signatures = ClearsignedMessageUtil.detachSignaturesFromInbandClearsignedMessage(armorIn, messageOutputStream); - if (signatures == null) { + if (signatures.isEmpty()) { throw new SOPGPException.BadData("Data did not contain OpenPGP signatures."); } } catch (WrongConsumingMethodException e) {