001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.util;
006
007import java.io.IOException;
008import java.io.InputStream;
009
010import org.bouncycastle.bcpg.ArmoredInputStream;
011
012import javax.annotation.Nonnull;
013
014/**
015 * Utility class that causes read(bytes, offset, length) to properly throw exceptions
016 * caused by faulty CRC checksums.
017 *
018 * Furthermore, this class swallows exceptions from BC's ArmoredInputStream that are caused
019 * by missing CRC checksums.
020 */
021public class CRCingArmoredInputStreamWrapper extends ArmoredInputStream {
022
023    private final ArmoredInputStream inputStream;
024
025    public CRCingArmoredInputStreamWrapper(ArmoredInputStream inputStream) throws IOException {
026        super(inputStream, false);
027        this.inputStream = inputStream;
028    }
029
030    public static InputStream possiblyWrap(InputStream inputStream) throws IOException {
031        if (inputStream instanceof CRCingArmoredInputStreamWrapper) {
032            return inputStream;
033        }
034
035        if (inputStream instanceof ArmoredInputStream) {
036            return new CRCingArmoredInputStreamWrapper((ArmoredInputStream) inputStream);
037        }
038
039        return inputStream;
040    }
041
042    @Override
043    public boolean isClearText() {
044        return inputStream.isClearText();
045    }
046
047    @Override
048    public boolean isEndOfStream() {
049        return inputStream.isEndOfStream();
050    }
051
052    @Override
053    public String getArmorHeaderLine() {
054        return inputStream.getArmorHeaderLine();
055    }
056
057    @Override
058    public String[] getArmorHeaders() {
059        return inputStream.getArmorHeaders();
060    }
061
062    @Override
063    public int read() throws IOException {
064        try {
065            return inputStream.read();
066        } catch (IOException e) {
067            if (e.getMessage().equals("no crc found in armored message.") || e.getMessage().equals("crc check not found.")) {
068                // swallow exception
069                return -1;
070            } else {
071                throw e;
072            }
073        }
074    }
075
076    @Override
077    public int read(@Nonnull byte[] b) throws IOException {
078        return read(b, 0, b.length);
079    }
080    /**
081     * Reads up to <code>len</code> bytes of data from the input stream into
082     * an array of bytes.  An attempt is made to read as many as
083     * <code>len</code> bytes, but a smaller number may be read.
084     * The number of bytes actually read is returned as an integer.
085     *
086     * The first byte read is stored into element <code>b[off]</code>, the
087     * next one into <code>b[off+1]</code>, and so on. The number of bytes read
088     * is, at most, equal to <code>len</code>.
089     *
090     * NOTE: We need to override the custom behavior of Java's {@link InputStream#read(byte[], int, int)},
091     * as the upstream method silently swallows {@link IOException IOExceptions}.
092     * This would cause CRC checksum errors to go unnoticed.
093     *
094     * @see <a href="https://github.com/bcgit/bc-java/issues/998">Related BC bug report</a>
095     * @param b byte array
096     * @param off offset at which we start writing data to the array
097     * @param len number of bytes we write into the array
098     * @return total number of bytes read into the buffer
099     *
100     * @throws IOException if an exception happens AT ANY POINT
101     */
102    @Override
103    public int read(byte[] b, int off, int len) throws IOException {
104        checkIndexSize(b.length, off, len);
105
106        if (len == 0) {
107            return 0;
108        }
109
110        int c = read();
111        if (c == -1) {
112            return -1;
113        }
114        b[off] = (byte) c;
115
116        int i = 1;
117        for (; i < len ; i++) {
118            c = read();
119            if (c == -1) {
120                break;
121            }
122            b[off + i] = (byte) c;
123        }
124        return i;
125    }
126
127    private void checkIndexSize(int size, int off, int len) {
128        if (off < 0 || len < 0) {
129            throw new IndexOutOfBoundsException("Offset and length cannot be negative.");
130        }
131        if (size < off + len) {
132            throw new IndexOutOfBoundsException("Invalid offset and length.");
133        }
134    }
135
136    @Override
137    public long skip(long n) throws IOException {
138        return inputStream.skip(n);
139    }
140
141    @Override
142    public int available() throws IOException {
143        return inputStream.available();
144    }
145
146    @Override
147    public void close() throws IOException {
148        inputStream.close();
149    }
150
151    @Override
152    public synchronized void mark(int readlimit) {
153        inputStream.mark(readlimit);
154    }
155
156    @Override
157    public synchronized void reset() throws IOException {
158        inputStream.reset();
159    }
160
161    @Override
162    public boolean markSupported() {
163        return inputStream.markSupported();
164    }
165}