1 |
/* |
/* |
2 |
* $Id$ |
* IMAPResponseTokenizer.java |
3 |
* Copyright (C) 2003 The Free Software Foundation |
* Copyright (C) 2003 The Free Software Foundation |
4 |
* |
* |
5 |
* This file is part of GNU inetlib, a library. |
* This file is part of GNU inetlib, a library. |
51 |
* protocol server responses into IMAPResponse tokens. |
* protocol server responses into IMAPResponse tokens. |
52 |
* |
* |
53 |
* @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> |
* @author <a href='mailto:dog@gnu.org'>Chris Burdess</a> |
|
* @version $Revision$ $Date$ |
|
54 |
*/ |
*/ |
55 |
public class IMAPResponseTokenizer implements IMAPConstants |
public class IMAPResponseTokenizer |
56 |
|
implements IMAPConstants |
57 |
{ |
{ |
58 |
|
|
59 |
/** |
/** |
80 |
* Constructor. |
* Constructor. |
81 |
* @param in the server socket input stream |
* @param in the server socket input stream |
82 |
*/ |
*/ |
83 |
public IMAPResponseTokenizer (InputStream in) |
public IMAPResponseTokenizer(InputStream in) |
84 |
{ |
{ |
85 |
this.in = in; |
this.in = in; |
86 |
} |
} |
87 |
|
|
88 |
/* |
/* |
89 |
* Reads bytes, from the underlying stream if necessary, or from the |
* Reads bytes, from the underlying stream if necessary, or from the |
90 |
* cache. If moreNeeded is specified, always performs a read to append |
* cache. If moreNeeded is specified, always performs a read to append |
91 |
* more to the cache. |
* more to the cache. |
92 |
*/ |
*/ |
93 |
byte[] read (boolean moreNeeded) throws IOException |
byte[] read(boolean moreNeeded) |
94 |
{ |
throws IOException |
95 |
if (buffer != null && !moreNeeded && buffer.length > 0) |
{ |
96 |
{ |
if (buffer != null && !moreNeeded && buffer.length > 0) |
97 |
return buffer; |
{ |
98 |
} |
return buffer; |
99 |
int max = in.available (); |
} |
100 |
if (max < 1) |
int max = in.available(); |
101 |
{ |
if (max < 1) |
102 |
max = BUFFER_SIZE; |
{ |
103 |
} |
max = BUFFER_SIZE; |
104 |
byte[] tmp = new byte[max]; |
} |
105 |
int len = 0; |
byte[] tmp = new byte[max]; |
106 |
while (len == 0) |
int len = 0; |
107 |
{ |
while (len == 0) |
108 |
len = in.read (tmp, 0, max); |
{ |
109 |
} |
len = in.read(tmp, 0, max); |
110 |
if (len == -1) |
} |
111 |
{ |
if (len == -1) |
112 |
return null; // EOF |
{ |
113 |
} |
return null; // EOF |
114 |
int blen = (buffer == null) ? 0 : buffer.length; |
} |
115 |
byte[] uni = new byte[blen + len]; |
int blen =(buffer == null) ? 0 : buffer.length; |
116 |
if (blen != 0) |
byte[] uni = new byte[blen + len]; |
117 |
{ |
if (blen != 0) |
118 |
System.arraycopy (buffer, 0, uni, 0, blen); |
{ |
119 |
} |
System.arraycopy(buffer, 0, uni, 0, blen); |
120 |
System.arraycopy (tmp, 0, uni, blen, len); |
} |
121 |
buffer = uni; |
System.arraycopy(tmp, 0, uni, blen, len); |
122 |
return buffer; |
buffer = uni; |
123 |
} |
return buffer; |
124 |
|
} |
125 |
|
|
126 |
/* |
/* |
127 |
* Invalidates the byte cache up to the specified index. |
* Invalidates the byte cache up to the specified index. |
128 |
*/ |
*/ |
129 |
void mark (int index) |
void mark(int index) |
130 |
{ |
{ |
131 |
int len = buffer.length; |
int len = buffer.length; |
132 |
int start = index + 1; |
int start = index + 1; |
133 |
if (start < len) |
if (start < len) |
134 |
{ |
{ |
135 |
int n = (len - start); |
int n =(len - start); |
136 |
byte[] tmp = new byte[n]; |
byte[] tmp = new byte[n]; |
137 |
System.arraycopy (buffer, start, tmp, 0, n); |
System.arraycopy(buffer, start, tmp, 0, n); |
138 |
buffer = tmp; |
buffer = tmp; |
139 |
} |
} |
140 |
else |
else |
141 |
{ |
{ |
142 |
buffer = null; |
buffer = null; |
143 |
} |
} |
144 |
} |
} |
145 |
|
|
146 |
/** |
/** |
147 |
* Returns the next IMAPResponse. |
* Returns the next IMAPResponse. |
148 |
*/ |
*/ |
149 |
public IMAPResponse next () throws IOException |
public IMAPResponse next() |
150 |
{ |
throws IOException |
151 |
// Perform read |
{ |
152 |
byte[] buf = read (false); |
// Perform read |
153 |
if (buf == null) |
byte[] buf = read(false); |
154 |
{ |
if (buf == null) |
155 |
return null; // pass EOF back up the chain |
{ |
156 |
} |
return null; // pass EOF back up the chain |
157 |
int len = buf.length; |
} |
158 |
|
int len = buf.length; |
159 |
IMAPResponse response = new IMAPResponse (); |
|
160 |
ByteArrayOutputStream genericSink = new ByteArrayOutputStream (); |
IMAPResponse response = new IMAPResponse(); |
161 |
ByteArrayOutputStream literalSink = null; |
ByteArrayOutputStream genericSink = new ByteArrayOutputStream(); |
162 |
int literalCount = 0, literalLength = -1; |
ByteArrayOutputStream literalSink = null; |
163 |
Stack context = new Stack (); |
int literalCount = 0, literalLength = -1; |
164 |
int state = STATE_TAG; |
Stack context = new Stack(); |
165 |
boolean inQuote = false; |
int state = STATE_TAG; |
166 |
boolean inContent = false; |
boolean inQuote = false; |
167 |
for (int i = 0; i < len; i++) |
boolean inContent = false; |
168 |
{ |
for (int i = 0; i < len; i++) |
169 |
byte b = buf[i]; |
{ |
170 |
switch (state) |
byte b = buf[i]; |
171 |
{ |
switch (state) |
172 |
case STATE_TAG: // expect tag |
{ |
173 |
if (i == 0 && b == 0x2a) // untagged |
case STATE_TAG: // expect tag |
174 |
{ |
if (i == 0 && b == 0x2a) // untagged |
175 |
response.tag = IMAPResponse.UNTAGGED; |
{ |
176 |
} |
response.tag = IMAPResponse.UNTAGGED; |
177 |
else if (i == 0 && b == 0x2b) // continuation |
} |
178 |
{ |
else if (i == 0 && b == 0x2b) // continuation |
179 |
response.tag = IMAPResponse.CONTINUATION; |
{ |
180 |
} |
response.tag = IMAPResponse.CONTINUATION; |
181 |
else if (b == 0x20) // delimiter |
} |
182 |
{ |
else if (b == 0x20) // delimiter |
183 |
if (response.tag == null) |
{ |
184 |
{ |
if (response.tag == null) |
185 |
byte[] tb = genericSink.toByteArray (); |
{ |
186 |
response.tag = new String (tb, DEFAULT_ENCODING); |
byte[] tb = genericSink.toByteArray(); |
187 |
} |
response.tag = new String(tb, DEFAULT_ENCODING); |
188 |
genericSink.reset (); |
} |
189 |
if (response.isContinuation ()) |
genericSink.reset(); |
190 |
{ |
if (response.isContinuation()) |
191 |
state = STATE_TEXT; |
{ |
192 |
} |
state = STATE_TEXT; |
193 |
else |
} |
194 |
{ |
else |
195 |
state = STATE_COUNT; |
{ |
196 |
} |
state = STATE_COUNT; |
197 |
} |
} |
198 |
else // tag literal |
} |
199 |
{ |
else // tag literal |
200 |
genericSink.write (b); |
{ |
201 |
} |
genericSink.write(b); |
202 |
break; |
} |
203 |
case STATE_COUNT: // expect count or id |
break; |
204 |
if (b < 0x30 || b > 0x39) |
case STATE_COUNT: // expect count or id |
205 |
{ |
if (b < 0x30 || b > 0x39) |
206 |
state = STATE_ID; |
{ |
207 |
} |
state = STATE_ID; |
208 |
if (b == 0x20) // delimiter |
} |
209 |
{ |
if (b == 0x20) // delimiter |
210 |
byte[] cb = genericSink.toByteArray (); |
{ |
211 |
genericSink.reset (); |
byte[] cb = genericSink.toByteArray(); |
212 |
String cs = new String (cb, DEFAULT_ENCODING); |
genericSink.reset(); |
213 |
try |
String cs = new String(cb, DEFAULT_ENCODING); |
214 |
{ |
try |
215 |
response.count = Integer.parseInt (cs); |
{ |
216 |
} |
response.count = Integer.parseInt(cs); |
217 |
catch (NumberFormatException e) |
} |
218 |
{ |
catch (NumberFormatException e) |
219 |
throw new ProtocolException ("Expecting number: " + cs); |
{ |
220 |
} |
throw new ProtocolException("Expecting number: " + cs); |
221 |
state = STATE_ID; |
} |
222 |
} |
state = STATE_ID; |
223 |
else |
} |
224 |
{ |
else |
225 |
genericSink.write (b); |
{ |
226 |
} |
genericSink.write(b); |
227 |
break; |
} |
228 |
case STATE_ID: // expect id |
break; |
229 |
if (b == 0x20) // delimiter |
case STATE_ID: // expect id |
230 |
{ |
if (b == 0x20) // delimiter |
231 |
byte[] ib = genericSink.toByteArray (); |
{ |
232 |
genericSink.reset (); |
byte[] ib = genericSink.toByteArray(); |
233 |
response.id = new String (ib, DEFAULT_ENCODING).intern (); |
genericSink.reset(); |
234 |
state = STATE_MAYBE_CODE; |
response.id = new String(ib, DEFAULT_ENCODING).intern(); |
235 |
} |
state = STATE_MAYBE_CODE; |
236 |
else if (b == 0x0a) // EOL |
} |
237 |
{ |
else if (b == 0x0a) // EOL |
238 |
byte[] ib = genericSink.toByteArray (); |
{ |
239 |
genericSink.reset (); |
byte[] ib = genericSink.toByteArray(); |
240 |
response.id = new String (ib, DEFAULT_ENCODING).intern (); |
genericSink.reset(); |
241 |
state = STATE_TAG; |
response.id = new String(ib, DEFAULT_ENCODING).intern(); |
242 |
// mark bytes read |
state = STATE_TAG; |
243 |
mark (i); |
// mark bytes read |
244 |
return response; |
mark(i); |
245 |
} |
return response; |
246 |
else if (b != 0x0d) // id literal |
} |
247 |
{ |
else if (b != 0x0d) // id literal |
248 |
genericSink.write (b); |
{ |
249 |
} |
genericSink.write(b); |
250 |
break; |
} |
251 |
case STATE_MAYBE_CODE: // expect code or text |
break; |
252 |
if (b == 0x28 || b == 0x5b) |
case STATE_MAYBE_CODE: // expect code or text |
253 |
{ |
if (b == 0x28 || b == 0x5b) |
254 |
List top = new ArrayList (); |
{ |
255 |
response.code = top; |
List top = new ArrayList(); |
256 |
context.push (top); |
response.code = top; |
257 |
state = STATE_CODE; |
context.push(top); |
258 |
} |
state = STATE_CODE; |
259 |
else |
} |
260 |
{ |
else |
261 |
if (response.id == FETCH) |
{ |
262 |
{ |
if (response.id == FETCH) |
263 |
// We can't have text here so it must be the beginning of |
{ |
264 |
// FETCH FLAGS. Go back to ID state. |
// We can't have text here so it must be the beginning of |
265 |
genericSink.reset (); |
// FETCH FLAGS. Go back to ID state. |
266 |
byte[] fetchBytes = |
genericSink.reset(); |
267 |
new byte[] { 0x46, 0x45, 0x54, 0x43, 0x48, 0x20}; |
byte[] fetchBytes = |
268 |
genericSink.write (fetchBytes); |
new byte[] { 0x46, 0x45, 0x54, 0x43, 0x48, 0x20}; |
269 |
genericSink.write (b); |
genericSink.write(fetchBytes); |
270 |
state = STATE_ID; |
genericSink.write(b); |
271 |
} |
state = STATE_ID; |
272 |
else if (response.id == STATUS) |
} |
273 |
{ |
else if (response.id == STATUS) |
274 |
// We are in the mailbox name part of the STATUS response |
{ |
275 |
genericSink.write (b); |
// We are in the mailbox name part of the STATUS response |
276 |
state = STATE_STATUS; |
genericSink.write(b); |
277 |
} |
state = STATE_STATUS; |
278 |
else |
} |
279 |
{ |
else |
280 |
genericSink.write (b); |
{ |
281 |
state = STATE_TEXT; |
genericSink.write(b); |
282 |
} |
state = STATE_TEXT; |
283 |
} |
} |
284 |
break; |
} |
285 |
case STATE_STATUS: |
break; |
286 |
if (b == 0x20) // delimiter |
case STATE_STATUS: |
287 |
{ |
if (b == 0x20) // delimiter |
288 |
response.mailbox = genericSink.toString (); |
{ |
289 |
genericSink.reset (); |
response.mailbox = genericSink.toString(); |
290 |
state = STATE_MAYBE_CODE; |
genericSink.reset(); |
291 |
} |
state = STATE_MAYBE_CODE; |
292 |
else |
} |
293 |
{ |
else |
294 |
genericSink.write (b); |
{ |
295 |
} |
genericSink.write(b); |
296 |
break; |
} |
297 |
case STATE_CODE: // response code inside parentheses |
break; |
298 |
if (b == 0x22) // quote delimiter |
case STATE_CODE: // response code inside parentheses |
299 |
{ |
if (b == 0x22) // quote delimiter |
300 |
inQuote = !inQuote; |
{ |
301 |
} |
inQuote = !inQuote; |
302 |
else if (inQuote) |
} |
303 |
{ |
else if (inQuote) |
304 |
genericSink.write (b); |
{ |
305 |
} |
genericSink.write(b); |
306 |
else |
} |
307 |
{ |
else |
308 |
if (b == 0x28 || b == 0x5b) // start parenthesis/bracket |
{ |
309 |
{ |
if (b == 0x28 || b == 0x5b) // start parenthesis/bracket |
310 |
List parent = (List) context.peek (); |
{ |
311 |
List top = new ArrayList (); |
List parent =(List) context.peek(); |
312 |
if (genericSink.size () > 0) |
List top = new ArrayList(); |
313 |
{ |
if (genericSink.size() > 0) |
314 |
byte[] tb = genericSink.toByteArray (); |
{ |
315 |
String token = |
byte[] tb = genericSink.toByteArray(); |
316 |
new String (tb, DEFAULT_ENCODING).intern (); |
String token = |
317 |
Pair pair = new Pair (token, top); |
new String(tb, DEFAULT_ENCODING).intern(); |
318 |
parent.add (pair); |
Pair pair = new Pair(token, top); |
319 |
genericSink.reset (); |
parent.add(pair); |
320 |
} |
genericSink.reset(); |
321 |
else |
} |
322 |
{ |
else |
323 |
parent.add (top); |
{ |
324 |
} |
parent.add(top); |
325 |
context.push (top); |
} |
326 |
} |
context.push(top); |
327 |
else if (b == 0x29 || b == 0x5d) // end parenthesis/bracket |
} |
328 |
{ |
else if (b == 0x29 || b == 0x5d) // end parenthesis/bracket |
329 |
List top = (List) context.pop (); |
{ |
330 |
// flush genericSink |
List top =(List) context.pop(); |
331 |
if (genericSink.size () > 0) |
// flush genericSink |
332 |
{ |
if (genericSink.size() > 0) |
333 |
byte[] tb = genericSink.toByteArray (); |
{ |
334 |
String token = |
byte[] tb = genericSink.toByteArray(); |
335 |
new String (tb, DEFAULT_ENCODING).intern (); |
String token = |
336 |
top.add (token); |
new String(tb, DEFAULT_ENCODING).intern(); |
337 |
genericSink.reset (); |
top.add(token); |
338 |
} |
genericSink.reset(); |
339 |
} |
} |
340 |
else if (b == 0x7b) // literal length |
} |
341 |
{ |
else if (b == 0x7b) // literal length |
342 |
genericSink.reset (); |
{ |
343 |
state = STATE_LITERAL_LENGTH; |
genericSink.reset(); |
344 |
} |
state = STATE_LITERAL_LENGTH; |
345 |
else if (b == 0x20) // token delimiter |
} |
346 |
{ |
else if (b == 0x20) // token delimiter |
347 |
if (context.size () == 0) // end state |
{ |
348 |
{ |
if (context.size() == 0) // end state |
349 |
state = STATE_TEXT; |
{ |
350 |
} |
state = STATE_TEXT; |
351 |
else // add token |
} |
352 |
{ |
else // add token |
353 |
List top = (List) context.peek (); |
{ |
354 |
// flush genericSink |
List top =(List) context.peek(); |
355 |
if (genericSink.size () > 0) |
// flush genericSink |
356 |
{ |
if (genericSink.size() > 0) |
357 |
byte[] tb = genericSink.toByteArray (); |
{ |
358 |
String token = |
byte[] tb = genericSink.toByteArray(); |
359 |
new String (tb, DEFAULT_ENCODING).intern (); |
String token = |
360 |
top.add (token); |
new String(tb, DEFAULT_ENCODING).intern(); |
361 |
genericSink.reset (); |
top.add(token); |
362 |
} |
genericSink.reset(); |
363 |
} |
} |
364 |
} |
} |
365 |
else if (b == 0x0a) // EOL |
} |
366 |
{ |
else if (b == 0x0a) // EOL |
367 |
state = STATE_TAG; |
{ |
368 |
// mark bytes read |
state = STATE_TAG; |
369 |
mark (i); |
// mark bytes read |
370 |
return response; |
mark(i); |
371 |
} |
return response; |
372 |
else if (b != 0x0d) // ignore CR |
} |
373 |
{ |
else if (b != 0x0d) // ignore CR |
374 |
genericSink.write (b); |
{ |
375 |
} |
genericSink.write(b); |
376 |
} |
} |
377 |
break; |
} |
378 |
case STATE_LITERAL_LENGTH: |
break; |
379 |
if (b == 0x7d) // end literal length |
case STATE_LITERAL_LENGTH: |
380 |
{ |
if (b == 0x7d) // end literal length |
381 |
byte[] cb = genericSink.toByteArray (); |
{ |
382 |
genericSink.reset (); |
byte[] cb = genericSink.toByteArray(); |
383 |
String cs = new String (cb, DEFAULT_ENCODING); |
genericSink.reset(); |
384 |
try |
String cs = new String(cb, DEFAULT_ENCODING); |
385 |
{ |
try |
386 |
literalLength = Integer.parseInt (cs); |
{ |
387 |
} |
literalLength = Integer.parseInt(cs); |
388 |
catch (NumberFormatException e) |
} |
389 |
{ |
catch (NumberFormatException e) |
390 |
throw new ProtocolException ("Expecting number: " + cs); |
{ |
391 |
} |
throw new ProtocolException("Expecting number: " + cs); |
392 |
} |
} |
393 |
else if (b == 0x0a) // start literal |
} |
394 |
{ |
else if (b == 0x0a) // start literal |
395 |
state = STATE_LITERAL; |
{ |
396 |
literalSink = new ByteArrayOutputStream (); |
state = STATE_LITERAL; |
397 |
literalCount = 0; |
literalSink = new ByteArrayOutputStream(); |
398 |
} |
literalCount = 0; |
399 |
else if (b != 0x0d) // ignore CR |
} |
400 |
{ |
else if (b != 0x0d) // ignore CR |
401 |
genericSink.write (b); |
{ |
402 |
} |
genericSink.write(b); |
403 |
break; |
} |
404 |
case STATE_LITERAL: |
break; |
405 |
if (literalCount >= literalLength) |
case STATE_LITERAL: |
406 |
{ |
if (literalCount >= literalLength) |
407 |
List top = (List) context.peek (); |
{ |
408 |
byte[] literal = literalSink.toByteArray (); |
List top =(List) context.peek(); |
409 |
top.add (literal); |
byte[] literal = literalSink.toByteArray(); |
410 |
literalSink = null; |
top.add(literal); |
411 |
inContent = false; |
literalSink = null; |
412 |
state = STATE_CODE; |
inContent = false; |
413 |
} |
state = STATE_CODE; |
414 |
else |
} |
415 |
{ |
else |
416 |
literalSink.write (b); |
{ |
417 |
literalCount++; |
literalSink.write(b); |
418 |
} |
literalCount++; |
419 |
break; |
} |
420 |
case STATE_TEXT: // human-readable text |
break; |
421 |
if (b == 0x0a) // delimiter |
case STATE_TEXT: // human-readable text |
422 |
{ |
if (b == 0x0a) // delimiter |
423 |
byte[] tb = genericSink.toByteArray (); |
{ |
424 |
genericSink.reset (); |
byte[] tb = genericSink.toByteArray(); |
425 |
response.text = new String (tb, DEFAULT_ENCODING); |
genericSink.reset(); |
426 |
state = STATE_TAG; |
response.text = new String(tb, DEFAULT_ENCODING); |
427 |
// mark bytes read |
state = STATE_TAG; |
428 |
mark (i); |
// mark bytes read |
429 |
return response; |
mark(i); |
430 |
} |
return response; |
431 |
else if (b != 0x0d) // ignore CR |
} |
432 |
{ |
else if (b != 0x0d) // ignore CR |
433 |
genericSink.write (b); |
{ |
434 |
} |
genericSink.write(b); |
435 |
break; |
} |
436 |
} |
break; |
437 |
} |
} |
438 |
// get more bytes |
} |
439 |
buf = read (true); |
// get more bytes |
440 |
return next (); |
buf = read(true); |
441 |
} |
return next(); |
442 |
|
} |
443 |
|
|
444 |
} |
} |
445 |
|
|