1/* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26/* FROM mail.jar */ 27package com.sun.xml.internal.org.jvnet.mimepull; 28 29import java.io.*; 30 31/** 32 * This class implements a QP Decoder. It is implemented as 33 * a FilterInputStream, so one can just wrap this class around 34 * any input stream and read bytes from this filter. The decoding 35 * is done as the bytes are read out. 36 * 37 * @author John Mani 38 */ 39 40final class QPDecoderStream extends FilterInputStream { 41 private byte[] ba = new byte[2]; 42 private int spaces = 0; 43 44 /** 45 * Create a Quoted Printable decoder that decodes the specified 46 * input stream. 47 * @param in the input stream 48 */ 49 public QPDecoderStream(InputStream in) { 50 super(new PushbackInputStream(in, 2)); // pushback of size=2 51 } 52 53 /** 54 * Read the next decoded byte from this input stream. The byte 55 * is returned as an <code>int</code> in the range <code>0</code> 56 * to <code>255</code>. If no byte is available because the end of 57 * the stream has been reached, the value <code>-1</code> is returned. 58 * This method blocks until input data is available, the end of the 59 * stream is detected, or an exception is thrown. 60 * 61 * @return the next byte of data, or <code>-1</code> if the end of the 62 * stream is reached. 63 * @exception IOException if an I/O error occurs. 64 */ 65 @Override 66 public int read() throws IOException { 67 if (spaces > 0) { 68 // We have cached space characters, return one 69 spaces--; 70 return ' '; 71 } 72 73 int c = in.read(); 74 75 if (c == ' ') { 76 // Got space, keep reading till we get a non-space char 77 while ((c = in.read()) == ' ') { 78 spaces++; 79 } 80 81 if (c == '\r' || c == '\n' || c == -1) { 82 spaces = 0; 83 } else { 84 // The non-space char is NOT CR/LF, the spaces are valid. 85 ((PushbackInputStream)in).unread(c); 86 c = ' '; 87 } 88 return c; // return either <SPACE> or <CR/LF> 89 } 90 else if (c == '=') { 91 // QP Encoded atom. Decode the next two bytes 92 int a = in.read(); 93 94 if (a == '\n') { 95 /* Hmm ... not really confirming QP encoding, but lets 96 * allow this as a LF terminated encoded line .. and 97 * consider this a soft linebreak and recurse to fetch 98 * the next char. 99 */ 100 return read(); 101 } else if (a == '\r') { 102 // Expecting LF. This forms a soft linebreak to be ignored. 103 int b = in.read(); 104 if (b != '\n') { 105 ((PushbackInputStream)in).unread(b); 106 } 107 return read(); 108 } else if (a == -1) { 109 // Not valid QP encoding, but we be nice and tolerant here ! 110 return -1; 111 } else { 112 ba[0] = (byte)a; 113 ba[1] = (byte)in.read(); 114 try { 115 return ASCIIUtility.parseInt(ba, 0, 2, 16); 116 } catch (NumberFormatException nex) { 117 /* 118 System.err.println( 119 "Illegal characters in QP encoded stream: " + 120 ASCIIUtility.toString(ba, 0, 2) 121 ); 122 */ 123 124 ((PushbackInputStream)in).unread(ba); 125 return c; 126 } 127 } 128 } 129 return c; 130 } 131 132 /** 133 * Reads up to <code>len</code> decoded bytes of data from this input stream 134 * into an array of bytes. This method blocks until some input is 135 * available. 136 * <p> 137 * 138 * @param buf the buffer into which the data is read. 139 * @param off the start offset of the data. 140 * @param len the maximum number of bytes read. 141 * @return the total number of bytes read into the buffer, or 142 * <code>-1</code> if there is no more data because the end of 143 * the stream has been reached. 144 * @exception IOException if an I/O error occurs. 145 */ 146 @Override 147 public int read(byte[] buf, int off, int len) throws IOException { 148 int i, c; 149 for (i = 0; i < len; i++) { 150 if ((c = read()) == -1) { 151 if (i == 0) { 152 i = -1; // return -1 , NOT 0. 153 } 154 break; 155 } 156 buf[off+i] = (byte)c; 157 } 158 return i; 159 } 160 161 /** 162 * Skips over and discards n bytes of data from this stream. 163 */ 164 @Override 165 public long skip(long n) throws IOException { 166 long skipped = 0; 167 while (n-- > 0 && read() >= 0) { 168 skipped++; 169 } 170 return skipped; 171 } 172 173 /** 174 * Tests if this input stream supports marks. Currently this class 175 * does not support marks 176 */ 177 @Override 178 public boolean markSupported() { 179 return false; 180 } 181 182 /** 183 * Returns the number of bytes that can be read from this input 184 * stream without blocking. The QP algorithm does not permit 185 * a priori knowledge of the number of bytes after decoding, so 186 * this method just invokes the <code>available</code> method 187 * of the original input stream. 188 */ 189 @Override 190 public int available() throws IOException { 191 // This is bogus ! We don't really know how much 192 // bytes are available *after* decoding 193 return in.available(); 194 } 195 196 /**** begin TEST program 197 public static void main(String argv[]) throws Exception { 198 FileInputStream infile = new FileInputStream(argv[0]); 199 QPDecoderStream decoder = new QPDecoderStream(infile); 200 int c; 201 202 while ((c = decoder.read()) != -1) 203 System.out.print((char)c); 204 System.out.println(); 205 } 206 *** end TEST program ****/ 207} 208