1
2 /**
3 * Clase que transforma un InputStream cualquiera en un InputStream en formato MIME, estableciendo
4 * en la cabecera el content-type que se le pasa como argumento al constructor y codificando los
5 * bytes de entrada en base64. Se utiliza la codificación de caracteres UTF-8.
6 * @author Jesús Reyes (3dígits)
7 * @version 0.98
8 */
9 package es.caib.signatura.impl;
10 import java.io.FilterInputStream;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.io.UnsupportedEncodingException;
14
15
16 public class MIMEInputStream extends FilterInputStream {
17
18 /** Flag que indica si se han leido todos los bytes de la entrada */
19 private boolean finished;
20
21 private byte[] headData = null; //buffer con los bytes de la cabecera
22 private int headDataOff; // Posición dentro del buffer de la cabecera
23 private int headDataSize; // Tamaño del buffer de la cabecera
24 private String contentType = null;
25 private boolean inHead; //flag que indica si estamos procesando la cabecera
26 private byte[] bodyData = new byte[66];//buffer de los bytes procesados
27 private int bodyDataOff;//Posición dentro del buffer de los bytes procesados
28 private int bodyDataSize;//Tamaño del buffer de los bytes procesados
29
30 // Constructor de la clase
31
32 /** Crea un nuevo MIMEInputStream
33 * @throws UnsupportedEncodingException
34 *
35 *
36 * */
37 public MIMEInputStream(InputStream is, String contentType) throws UnsupportedEncodingException{
38 super(is);
39 this.contentType = contentType;
40
41
42 //String mime = new String("MIME-Version: 1.0\r\n");
43
44 //Inicialización de la cabecera
45 String mime = new String("");
46 mime = mime + "Content-Type: " + contentType + "\r\n";
47 mime = mime + "Content-Transfer-Encoding: base64\r\n\r\n";
48
49 this.headData = mime.getBytes("UTF-8");
50 this.headDataOff = 0;
51 this.headDataSize = this.headData.length;
52 this.inHead = true;
53
54
55
56 }
57
58
59 public synchronized void close ()
60 throws IOException {
61 finished = true;
62 super.close();
63 }
64
65 /**
66 * Lee el siguiente byte procesado
67 * @return el siguiente byte de datos o bien -1 si se ha llegado al final del stream
68 * @throws IOException
69 */
70 public synchronized int read ()
71 throws IOException {
72 int b;
73
74 //Si estamos en la cabecera leemos los bytes del buffer de cabecera
75 //Cuando se termina la cabecera se inicializa el buffer de los bytes procesados
76 if (inHead)
77 {
78 b = headData[headDataOff];
79 headDataOff++;
80 if (headDataOff==headDataSize)
81 {
82 inHead = false;
83 bodyDataSize = this.getBodyData(bodyData);
84 if (bodyDataSize==-1)
85 finished = true;
86 else
87 bodyDataOff = 0;
88 }
89 }
90
91 else
92 {
93 if (finished) return -1;
94 b = bodyData[bodyDataOff];
95 bodyDataOff++;
96 if (bodyDataOff == bodyDataSize)
97 {
98 bodyDataSize = this.getBodyData(bodyData);
99 if (bodyDataSize==-1)
100 finished = true;
101 else
102 bodyDataOff = 0;
103 }
104 }
105 return b & 0xFF;
106 }
107
108
109
110
111 public synchronized int read (byte[] buffer, int offset, int length)
112 throws IOException {
113 for (int i = 0; i < length; ++i) {
114 int c = read();
115 if (c < 0) return i == 0 ? -1 : i;
116 buffer[offset++] = (byte) c;
117 }
118 return length;
119 }
120
121
122
123 /**
124 * Skips over and discards <i>n</i> bytes of data from the
125 * input stream.
126 * @param n the number of bytes to be skipped.
127 * @return the actual number of bytes skipped.
128 * @exception IOException if an I/O error occurs.
129 */
130 public synchronized long skip(long n) throws IOException {
131 for (long i = 0; i < n; i++)
132 if (this.read() < 0) return i;
133 return n;
134 }
135
136 /**
137 * Devuelve el número de bytes que se pueden leer sin que se bloquee el stream
138 *
139 * @throws IOException i
140 */
141 public synchronized int available()
142 throws IOException {
143 if (inHead)
144 return (headDataSize - headDataOff);
145 else
146 return (bodyDataSize - bodyDataOff);
147
148 }
149
150 /**
151 * Does nothing, since this class does not support mark/reset.
152 */
153 public void mark(int readlimit) {}
154
155 /**
156 * Always throws an IOException, since this class does not support mark/reset.
157 */
158 public void reset() throws IOException {
159 throw new IOException("Base64InputStream does not support mark/reset");
160 }
161
162 /**
163 * Tests if this input stream supports the <code>mark</code> and
164 * <code>reset</code> methods of InputStream, which it does not.
165 *
166 * @return <code>false</code>, since this class does not support the
167 * <code>mark</code> and <code>reset</code> methods.
168 */
169 public boolean markSupported() { return false; }
170
171
172 // Own methods
173
174
175 /**
176 *
177 * @throws java.io.IOException
178 * @return número de bytes procesados o -1 si se ha llegado al final del stream
179 * @param bodyData buffer de bytes procesados
180 */
181
182 private int getBodyData(byte[] bodyData) throws IOException
183 {
184 int size = 0;
185 // La codificación base64 codifica 3 bytes de entrada en 4 bytes de salida
186 // El buffer de entrada es de 48 bytes, por lo que la codificación en base64 nos
187 // producirá 64 bytes.
188 byte[] inputBuffer = new byte[48];
189 size = 0;
190 do
191 {
192 int read = in.read(inputBuffer, size, inputBuffer.length - size);
193 if (read < 0 && size == 0)
194 return -1;
195 else if (read < 0 )
196 break;
197 size = size + read;
198 } while (size < inputBuffer.length);
199
200 if (size >= 0)
201 {
202 byte result[] = Base64.toBase64(inputBuffer, 0, size);
203
204 size = result.length;
205 System.arraycopy( result, 0, bodyData, 0, size);
206 }
207 return size;
208 }
209 }