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}