001: package org.bouncycastle.bcpg;
002:
003: import java.io.*;
004: import java.util.Vector;
005:
006: /**
007: * reader for Base64 armored objects - read the headers and then start returning
008: * bytes when the data is reached. An IOException is thrown if the CRC check
009: * fails.
010: */
011: public class ArmoredInputStream extends InputStream {
012: /*
013: * set up the decoding table.
014: */
015: private static final byte[] decodingTable;
016:
017: static {
018: decodingTable = new byte[128];
019:
020: for (int i = 'A'; i <= 'Z'; i++) {
021: decodingTable[i] = (byte) (i - 'A');
022: }
023:
024: for (int i = 'a'; i <= 'z'; i++) {
025: decodingTable[i] = (byte) (i - 'a' + 26);
026: }
027:
028: for (int i = '0'; i <= '9'; i++) {
029: decodingTable[i] = (byte) (i - '0' + 52);
030: }
031:
032: decodingTable['+'] = 62;
033: decodingTable['/'] = 63;
034: }
035:
036: /**
037: * decode the base 64 encoded input data.
038: *
039: * @return the offset the data starts in out.
040: */
041: private int decode(int in0, int in1, int in2, int in3, int[] out)
042: throws EOFException {
043: int b1, b2, b3, b4;
044:
045: if (in3 < 0) {
046: throw new EOFException(
047: "unexpected end of file in armored stream.");
048: }
049:
050: if (in2 == '=') {
051: b1 = decodingTable[in0] & 0xff;
052: b2 = decodingTable[in1] & 0xff;
053:
054: out[2] = ((b1 << 2) | (b2 >> 4)) & 0xff;
055:
056: return 2;
057: } else if (in3 == '=') {
058: b1 = decodingTable[in0];
059: b2 = decodingTable[in1];
060: b3 = decodingTable[in2];
061:
062: out[1] = ((b1 << 2) | (b2 >> 4)) & 0xff;
063: out[2] = ((b2 << 4) | (b3 >> 2)) & 0xff;
064:
065: return 1;
066: } else {
067: b1 = decodingTable[in0];
068: b2 = decodingTable[in1];
069: b3 = decodingTable[in2];
070: b4 = decodingTable[in3];
071:
072: out[0] = ((b1 << 2) | (b2 >> 4)) & 0xff;
073: out[1] = ((b2 << 4) | (b3 >> 2)) & 0xff;
074: out[2] = ((b3 << 6) | b4) & 0xff;
075:
076: return 0;
077: }
078: }
079:
080: InputStream in;
081: boolean start = true;
082: int[] outBuf = new int[3];
083: int bufPtr = 3;
084: CRC24 crc = new CRC24();
085: boolean crcFound = false;
086: boolean hasHeaders = true;
087: String header = null;
088: boolean newLineFound = false;
089: boolean clearText = false;
090: boolean restart = false;
091: Vector headerList = new Vector();
092: int lastC = 0;
093:
094: /**
095: * Create a stream for reading a PGP armoured message, parsing up to a header
096: * and then reading the data that follows.
097: *
098: * @param in
099: */
100: public ArmoredInputStream(InputStream in) throws IOException {
101: this (in, true);
102: }
103:
104: /**
105: * Create an armoured input stream which will assume the data starts
106: * straight away, or parse for headers first depending on the value of
107: * hasHeaders.
108: *
109: * @param in
110: * @param hasHeaders true if headers are to be looked for, false otherwise.
111: */
112: public ArmoredInputStream(InputStream in, boolean hasHeaders)
113: throws IOException {
114: this .in = in;
115: this .hasHeaders = hasHeaders;
116:
117: if (hasHeaders) {
118: parseHeaders();
119: }
120:
121: start = false;
122: }
123:
124: public int available() throws IOException {
125: return in.available();
126: }
127:
128: private boolean parseHeaders() throws IOException {
129: header = null;
130:
131: int c;
132: int last = 0;
133: boolean headerFound = false;
134:
135: headerList = new Vector();
136:
137: //
138: // if restart we already have a header
139: //
140: if (restart) {
141: headerFound = true;
142: } else {
143: while ((c = in.read()) >= 0) {
144: if (c == '-'
145: && (last == 0 || last == '\n' || last == '\r')) {
146: headerFound = true;
147: break;
148: }
149:
150: last = c;
151: }
152: }
153:
154: if (headerFound) {
155: StringBuffer buf = new StringBuffer("-");
156: boolean eolReached = false;
157: boolean crLf = false;
158:
159: if (restart) // we've had to look ahead two '-'
160: {
161: buf.append('-');
162: }
163:
164: while ((c = in.read()) >= 0) {
165: if (last == '\r' && c == '\n') {
166: crLf = true;
167: }
168: if (eolReached && (last != '\r' && c == '\n')) {
169: break;
170: }
171: if (eolReached && c == '\r') {
172: break;
173: }
174: if (c == '\r' || (last != '\r' && c == '\n')) {
175: headerList.addElement(buf.toString());
176: buf.setLength(0);
177: }
178:
179: if (c != '\n' && c != '\r') {
180: buf.append((char) c);
181: eolReached = false;
182: } else {
183: if (c == '\r' || (last != '\r' && c == '\n')) {
184: eolReached = true;
185: }
186: }
187:
188: last = c;
189: }
190:
191: if (crLf) {
192: in.read(); // skip last \n
193: }
194: }
195:
196: if (headerList.size() > 0) {
197: header = (String) headerList.elementAt(0);
198: }
199:
200: clearText = "-----BEGIN PGP SIGNED MESSAGE-----".equals(header);
201: newLineFound = true;
202:
203: return headerFound;
204: }
205:
206: /**
207: * @return true if we are inside the clear text section of a PGP
208: * signed message.
209: */
210: public boolean isClearText() {
211: return clearText;
212: }
213:
214: /**
215: * Return the armor header line (if there is one)
216: * @return the armor header line, null if none present.
217: */
218: public String getArmorHeaderLine() {
219: return header;
220: }
221:
222: /**
223: * Return the armor headers (the lines after the armor header line),
224: * @return an array of armor headers, null if there aren't any.
225: */
226: public String[] getArmorHeaders() {
227: if (headerList.size() <= 1) {
228: return null;
229: }
230:
231: String[] hdrs = new String[headerList.size() - 1];
232:
233: for (int i = 0; i != hdrs.length; i++) {
234: hdrs[i] = (String) headerList.elementAt(i + 1);
235: }
236:
237: return hdrs;
238: }
239:
240: private int readIgnoreSpace() throws IOException {
241: int c = in.read();
242:
243: while (c == ' ' || c == '\t') {
244: c = in.read();
245: }
246:
247: return c;
248: }
249:
250: public int read() throws IOException {
251: int c;
252:
253: if (start) {
254: if (hasHeaders) {
255: parseHeaders();
256: }
257:
258: crc.reset();
259: start = false;
260: }
261:
262: if (clearText) {
263: c = in.read();
264:
265: if (c == '\r' || (c == '\n' && lastC != '\r')) {
266: newLineFound = true;
267: } else if (newLineFound && c == '-') {
268: c = in.read();
269: if (c == '-') // a header, not dash escaped
270: {
271: clearText = false;
272: start = true;
273: restart = true;
274: } else // a space - must be a dash escape
275: {
276: c = in.read();
277: }
278: newLineFound = false;
279: } else {
280: if (c != '\n' && lastC != '\r') {
281: newLineFound = false;
282: }
283: }
284:
285: lastC = c;
286:
287: return c;
288: }
289:
290: if (bufPtr > 2 || crcFound) {
291: c = readIgnoreSpace();
292:
293: if (c == '\r' || c == '\n') {
294: c = readIgnoreSpace();
295:
296: while (c == '\n' || c == '\r') {
297: c = readIgnoreSpace();
298: }
299:
300: if (c < 0) // EOF
301: {
302: return -1;
303: }
304:
305: if (c == '=') // crc reached
306: {
307: bufPtr = decode(readIgnoreSpace(),
308: readIgnoreSpace(), readIgnoreSpace(),
309: readIgnoreSpace(), outBuf);
310: if (bufPtr == 0) {
311: int i = ((outBuf[0] & 0xff) << 16)
312: | ((outBuf[1] & 0xff) << 8)
313: | (outBuf[2] & 0xff);
314:
315: crcFound = true;
316:
317: if (i != crc.getValue()) {
318: throw new IOException(
319: "crc check failed in armored message.");
320: }
321: return read();
322: } else {
323: throw new IOException(
324: "no crc found in armored message.");
325: }
326: } else if (c == '-') // end of record reached
327: {
328: while ((c = in.read()) >= 0) {
329: if (c == '\n' || c == '\r') {
330: break;
331: }
332: }
333:
334: if (!crcFound) {
335: throw new IOException("crc check not found.");
336: }
337:
338: crcFound = false;
339: start = true;
340: bufPtr = 3;
341:
342: return -1;
343: } else // data
344: {
345: bufPtr = decode(c, readIgnoreSpace(),
346: readIgnoreSpace(), readIgnoreSpace(),
347: outBuf);
348: }
349: } else {
350: if (c >= 0) {
351: bufPtr = decode(c, readIgnoreSpace(),
352: readIgnoreSpace(), readIgnoreSpace(),
353: outBuf);
354: } else {
355: return -1;
356: }
357: }
358: }
359:
360: c = outBuf[bufPtr++];
361:
362: crc.update(c);
363:
364: return c;
365: }
366:
367: public void close() throws IOException {
368: in.close();
369: }
370: }
|