Package: Response
Response
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Response(Protocol) |
|
|
|
|
|
||||||||||||||||||||
Response(Response) |
|
|
|
|
|
||||||||||||||||||||
Response(String) |
|
|
|
|
|
||||||||||||||||||||
Response(String, boolean) |
|
|
|
|
|
||||||||||||||||||||
byeResponse(Exception) |
|
|
|
|
|
||||||||||||||||||||
getException() |
|
|
|
|
|
||||||||||||||||||||
getRest() |
|
|
|
|
|
||||||||||||||||||||
getTag() |
|
|
|
|
|
||||||||||||||||||||
getType() |
|
|
|
|
|
||||||||||||||||||||
isBAD() |
|
|
|
|
|
||||||||||||||||||||
isBYE() |
|
|
|
|
|
||||||||||||||||||||
isContinuation() |
|
|
|
|
|
||||||||||||||||||||
isNO() |
|
|
|
|
|
||||||||||||||||||||
isNextNonSpace(char) |
|
|
|
|
|
||||||||||||||||||||
isOK() |
|
|
|
|
|
||||||||||||||||||||
isSynthetic() |
|
|
|
|
|
||||||||||||||||||||
isTagged() |
|
|
|
|
|
||||||||||||||||||||
isUnTagged() |
|
|
|
|
|
||||||||||||||||||||
parse() |
|
|
|
|
|
||||||||||||||||||||
parseString(boolean, boolean) |
|
|
|
|
|
||||||||||||||||||||
peekByte() |
|
|
|
|
|
||||||||||||||||||||
readAtom() |
|
|
|
|
|
||||||||||||||||||||
readAtomString() |
|
|
|
|
|
||||||||||||||||||||
readAtomStringList() |
|
|
|
|
|
||||||||||||||||||||
readByte() |
|
|
|
|
|
||||||||||||||||||||
readByteArray() |
|
|
|
|
|
||||||||||||||||||||
readBytes() |
|
|
|
|
|
||||||||||||||||||||
readDelimString(String) |
|
|
|
|
|
||||||||||||||||||||
readLong() |
|
|
|
|
|
||||||||||||||||||||
readNumber() |
|
|
|
|
|
||||||||||||||||||||
readString() |
|
|
|
|
|
||||||||||||||||||||
readString(char) |
|
|
|
|
|
||||||||||||||||||||
readStringList() |
|
|
|
|
|
||||||||||||||||||||
readStringList(boolean) |
|
|
|
|
|
||||||||||||||||||||
reset() |
|
|
|
|
|
||||||||||||||||||||
skip(int) |
|
|
|
|
|
||||||||||||||||||||
skipSpaces() |
|
|
|
|
|
||||||||||||||||||||
skipToken() |
|
|
|
|
|
||||||||||||||||||||
static {...} |
|
|
|
|
|
||||||||||||||||||||
supportsUtf8() |
|
|
|
|
|
||||||||||||||||||||
toString() |
|
|
|
|
|
||||||||||||||||||||
toString(byte[], int, int) |
|
|
|
|
|
Coverage
1: /*
2: * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
3: *
4: * This program and the accompanying materials are made available under the
5: * terms of the Eclipse Public License v. 2.0, which is available at
6: * http://www.eclipse.org/legal/epl-2.0.
7: *
8: * This Source Code may also be made available under the following Secondary
9: * Licenses when the conditions for such availability set forth in the
10: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11: * version 2 with the GNU Classpath Exception, which is available at
12: * https://www.gnu.org/software/classpath/license.html.
13: *
14: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15: */
16:
17: package org.eclipse.angus.mail.iap;
18:
19: import java.io.*;
20: import java.util.*;
21:
22: import org.eclipse.angus.mail.util.ASCIIUtility;
23:
24: import java.nio.charset.StandardCharsets;
25:
26: /**
27: * This class represents a response obtained from the input stream
28: * of an IMAP server.
29: *
30: * @author John Mani
31: * @author Bill Shannon
32: */
33:
34: public class Response {
35: protected int index; // internal index (updated during the parse)
36: protected int pindex; // index after parse, for reset
37: protected int size; // number of valid bytes in our buffer
38: protected byte[] buffer = null;
39: protected int type = 0;
40: protected String tag = null;
41: /** @since JavaMail 1.5.4 */
42: protected Exception ex;
43: protected boolean utf8;
44:
45: private static final int increment = 100;
46:
47: // The first and second bits indicate whether this response
48: // is a Continuation, Tagged or Untagged
49: public final static int TAG_MASK          = 0x03;
50: public final static int CONTINUATION = 0x01;
51: public final static int TAGGED          = 0x02;
52: public final static int UNTAGGED          = 0x03;
53:
54: // The third, fourth and fifth bits indicate whether this response
55: // is an OK, NO, BAD or BYE response
56: public final static int TYPE_MASK          = 0x1C;
57: public final static int OK                   = 0x04;
58: public final static int NO                   = 0x08;
59: public final static int BAD                  = 0x0C;
60: public final static int BYE                  = 0x10;
61:
62: // The sixth bit indicates whether a BYE response is synthetic or real
63: public final static int SYNTHETIC          = 0x20;
64:
65: /**
66: * An ATOM is any CHAR delimited by:
67: * SPACE | CTL | '(' | ')' | '{' | '%' | '*' | '"' | '\' | ']'
68: * (CTL is handled in readDelimString.)
69: */
70: private static String ATOM_CHAR_DELIM = " (){%*\"\\]";
71:
72: /**
73: * An ASTRING_CHAR is any CHAR delimited by:
74: * SPACE | CTL | '(' | ')' | '{' | '%' | '*' | '"' | '\'
75: * (CTL is handled in readDelimString.)
76: */
77: private static String ASTRING_CHAR_DELIM = " (){%*\"\\";
78:
79: public Response(String s) {
80:         this(s, true);
81: }
82:
83: /**
84: * Constructor for testing.
85: *
86: * @param        s        the response string
87: * @param        supportsUtf8        allow UTF-8 in response?
88: * @since        JavaMail 1.6.0
89: */
90: public Response(String s, boolean supportsUtf8) {
91:•        if (supportsUtf8)
92:          buffer = s.getBytes(StandardCharsets.UTF_8);
93:         else
94:          buffer = s.getBytes(StandardCharsets.US_ASCII);
95:         size = buffer.length;
96:         utf8 = supportsUtf8;
97:         parse();
98: }
99:
100: /**
101: * Read a new Response from the given Protocol
102: *
103: * @param        p        the Protocol object
104: * @exception        IOException        for I/O errors
105: * @exception        ProtocolException        for protocol failures
106: */
107: public Response(Protocol p) throws IOException, ProtocolException {
108:         // read one response into 'buffer'
109:         ByteArray ba = p.getResponseBuffer();
110:         ByteArray response = p.getInputStream().readResponse(ba);
111:         buffer = response.getBytes();
112:         size = response.getCount() - 2; // Skip the terminating CRLF
113:         utf8 = p.supportsUtf8();
114:
115:         parse();
116: }
117:
118: /**
119: * Copy constructor.
120: *
121: * @param        r        the Response to copy
122: */
123: public Response(Response r) {
124:         index = r.index;
125:         pindex = r.pindex;
126:         size = r.size;
127:         buffer = r.buffer;
128:         type = r.type;
129:         tag = r.tag;
130:         ex = r.ex;
131:         utf8 = r.utf8;
132: }
133:
134: /**
135: * Return a Response object that looks like a BYE protocol response.
136: * Include the details of the exception in the response string.
137: *
138: * @param        ex        the exception
139: * @return                the synthetic Response object
140: */
141: public static Response byeResponse(Exception ex) {
142:         String err = "* BYE Jakarta Mail Exception: " + ex.toString();
143:         err = err.replace('\r', ' ').replace('\n', ' ');
144:         Response r = new Response(err);
145:         r.type |= SYNTHETIC;
146:         r.ex = ex;
147:         return r;
148: }
149:
150: /**
151: * Does the server support UTF-8?
152: *
153: * @return                true if the server supports UTF-8
154: * @since        JavaMail 1.6.0
155: */
156: public boolean supportsUtf8() {
157:         return utf8;
158: }
159:
160: private void parse() {
161:         index = 0; // position internal index at start
162:
163:•        if (size == 0)        // empty line
164:          return;
165:•        if (buffer[index] == '+') { // Continuation statement
166:          type |= CONTINUATION;
167:          index += 1; // Position beyond the '+'
168:          return;        // return
169:•        } else if (buffer[index] == '*') { // Untagged statement
170:          type |= UNTAGGED;
171:          index += 1; // Position beyond the '*'
172:         } else { // Tagged statement
173:          type |= TAGGED;
174:          tag = readAtom();        // read the TAG, index positioned beyond tag
175:•         if (tag == null)
176:                 tag = "";        // avoid possible NPE
177:         }
178:
179:         int mark = index; // mark
180:         String s = readAtom();        // updates index
181:•        if (s == null)
182:          s = "";                // avoid possible NPE
183:•        if (s.equalsIgnoreCase("OK"))
184:          type |= OK;
185:•        else if (s.equalsIgnoreCase("NO"))
186:          type |= NO;
187:•        else if (s.equalsIgnoreCase("BAD"))
188:          type |= BAD;
189:•        else if (s.equalsIgnoreCase("BYE"))
190:          type |= BYE;
191:         else
192:          index = mark; // reset
193:
194:         pindex = index;
195:         return;
196: }
197:
198: public void skipSpaces() {
199:•        while (index < size && buffer[index] == ' ')
200:          index++;
201: }
202:
203: /**
204: * Skip past any spaces. If the next non-space character is c,
205: * consume it and return true. Otherwise stop at that point
206: * and return false.
207: *
208: * @param        c        the character to look for
209: * @return                true if the character is found
210: */
211: public boolean isNextNonSpace(char c) {
212:         skipSpaces();
213:•        if (index < size && buffer[index] == (byte)c) {
214:          index++;
215:          return true;
216:         }
217:         return false;
218: }
219:
220: /**
221: * Skip to the next space, for use in error recovery while parsing.
222: */
223: public void skipToken() {
224:•        while (index < size && buffer[index] != ' ')
225:          index++;
226: }
227:
228: public void skip(int count) {
229:         index += count;
230: }
231:
232: public byte peekByte() {
233:•        if (index < size)
234:          return buffer[index];
235:         else
236:          return 0;                // XXX - how else to signal error?
237: }
238:
239: /**
240: * Return the next byte from this Statement.
241: *
242: * @return the next byte
243: */
244: public byte readByte() {
245:•        if (index < size)
246:          return buffer[index++];
247:         else
248:          return 0;                // XXX - how else to signal error?
249: }
250:
251: /**
252: * Extract an ATOM, starting at the current position. Updates
253: * the internal index to beyond the Atom.
254: *
255: * @return an Atom
256: */
257: public String readAtom() {
258:         return readDelimString(ATOM_CHAR_DELIM);
259: }
260:
261: /**
262: * Extract a string stopping at control characters or any
263: * character in delim.
264: */
265: private String readDelimString(String delim) {
266:         skipSpaces();
267:
268:•        if (index >= size) // already at end of response
269:          return null;
270:
271:         int b;
272:         int start = index;
273:•        while (index < size && ((b = (((int)buffer[index])&0xff)) >= ' ') &&
274:•         delim.indexOf((char)b) < 0 && b != 0x7f)
275:          index++;
276:
277:         return toString(buffer, start, index);
278: }
279:
280: /**
281: * Read a string as an arbitrary sequence of characters,
282: * stopping at the delimiter Used to read part of a
283: * response code inside [].
284: *
285: * @param        delim        the delimiter character
286: * @return                the string
287: */
288: public String readString(char delim) {
289:         skipSpaces();
290:
291:•        if (index >= size) // already at end of response
292:          return null;
293:
294:         int start = index;
295:•        while (index < size && buffer[index] != delim)
296:          index++;
297:
298:         return toString(buffer, start, index);
299: }
300:
301: public String[] readStringList() {
302:         return readStringList(false);
303: }
304:
305: public String[] readAtomStringList() {
306:         return readStringList(true);
307: }
308:
309: private String[] readStringList(boolean atom) {
310:         skipSpaces();
311:
312:•        if (buffer[index] != '(') { // not what we expected
313:          return null;
314:         }
315:         index++; // skip '('
316:
317:         // to handle buggy IMAP servers, we tolerate multiple spaces as
318:         // well as spaces after the left paren or before the right paren
319:         List<String> result = new ArrayList<>();
320:•        while (!isNextNonSpace(')')) {
321:•         String s = atom ? readAtomString() : readString();
322:•         if (s == null)        // not the expected string or atom
323:                 break;
324:          result.add(s);
325:         }
326:
327:         return result.toArray(new String[result.size()]);
328: }
329:
330: /**
331: * Extract an integer, starting at the current position. Updates the
332: * internal index to beyond the number. Returns -1 if a number was
333: * not found.
334: *
335: * @return a number
336: */
337: public int readNumber() {
338:         // Skip leading spaces
339:         skipSpaces();
340:
341: int start = index;
342:• while (index < size && Character.isDigit((char)buffer[index]))
343: index++;
344:
345:• if (index > start) {
346:          try {
347:                 return ASCIIUtility.parseInt(buffer, start, index);
348:          } catch (NumberFormatException nex) { }
349:         }
350:
351:         return -1;
352: }
353:
354: /**
355: * Extract a long number, starting at the current position. Updates the
356: * internal index to beyond the number. Returns -1 if a long number
357: * was not found.
358: *
359: * @return a long
360: */
361: public long readLong() {
362:         // Skip leading spaces
363:         skipSpaces();
364:
365: int start = index;
366:• while (index < size && Character.isDigit((char)buffer[index]))
367: index++;
368:
369:• if (index > start) {
370:          try {
371:                 return ASCIIUtility.parseLong(buffer, start, index);
372:          } catch (NumberFormatException nex) { }
373:         }
374:
375:         return -1;
376: }
377:
378: /**
379: * Extract a NSTRING, starting at the current position. Return it as
380: * a String. The sequence 'NIL' is returned as null
381: *
382: * NSTRING := QuotedString | Literal | "NIL"
383: *
384: * @return a String
385: */
386: public String readString() {
387:         return (String)parseString(false, true);
388: }
389:
390: /**
391: * Extract a NSTRING, starting at the current position. Return it as
392: * a ByteArrayInputStream. The sequence 'NIL' is returned as null
393: *
394: * NSTRING := QuotedString | Literal | "NIL"
395: *
396: * @return a ByteArrayInputStream
397: */
398: public ByteArrayInputStream readBytes() {
399:         ByteArray ba = readByteArray();
400:•        if (ba != null)
401:          return ba.toByteArrayInputStream();
402:         else
403:          return null;
404: }
405:
406: /**
407: * Extract a NSTRING, starting at the current position. Return it as
408: * a ByteArray. The sequence 'NIL' is returned as null
409: *
410: * NSTRING := QuotedString | Literal | "NIL"
411: *
412: * @return a ByteArray
413: */
414: public ByteArray readByteArray() {
415:         /*
416:          * Special case, return the data after the continuation uninterpreted.
417:          * It's usually a challenge for an AUTHENTICATE command.
418:          */
419:•        if (isContinuation()) {
420:          skipSpaces();
421:          return new ByteArray(buffer, index, size - index);
422:         }
423:         return (ByteArray)parseString(false, false);
424: }
425:
426: /**
427: * Extract an ASTRING, starting at the current position
428: * and return as a String. An ASTRING can be a QuotedString, a
429: * Literal or an Atom (plus ']').
430: *
431: * Any errors in parsing returns null
432: *
433: * ASTRING := QuotedString | Literal | 1*ASTRING_CHAR
434: *
435: * @return a String
436: */
437: public String readAtomString() {
438:         return (String)parseString(true, true);
439: }
440:
441: /**
442: * Generic parsing routine that can parse out a Quoted-String,
443: * Literal or Atom and return the parsed token as a String
444: * or a ByteArray. Errors or NIL data will return null.
445: */
446: private Object parseString(boolean parseAtoms, boolean returnString) {
447:         byte b;
448:
449:         // Skip leading spaces
450:         skipSpaces();
451:         
452:         b = buffer[index];
453:•        if (b == '"') { // QuotedString
454:          index++; // skip the quote
455:          int start = index;
456:          int copyto = index;
457:
458:•         while (index < size && (b = buffer[index]) != '"') {
459:•                if (b == '\\') // skip escaped byte
460:                  index++;
461:•                if (index != copyto) { // only copy if we need to
462:                  // Beware: this is a destructive copy. I'm
463:                  // pretty sure this is OK, but ... ;>
464:                  buffer[copyto] = buffer[index];
465:                 }
466:                 copyto++;
467:                 index++;
468:          }
469:•         if (index >= size) {
470:                 // didn't find terminating quote, something is seriously wrong
471:                 //throw new ArrayIndexOutOfBoundsException(
472:                 //                 "index = " + index + ", size = " + size);
473:                 return null;
474:          } else
475:                 index++; // skip past the terminating quote
476:
477:•         if (returnString)
478:                 return toString(buffer, start, copyto);
479:          else
480:                 return new ByteArray(buffer, start, copyto-start);
481:•        } else if (b == '{') { // Literal
482:          int start = ++index; // note the start position
483:
484:•         while (buffer[index] != '}')
485:                 index++;
486:
487:          int count = 0;
488:          try {
489:                 count = ASCIIUtility.parseInt(buffer, start, index);
490:          } catch (NumberFormatException nex) {
491:                  // throw new ParsingException();
492:                 return null;
493:          }
494:
495:          start = index + 3; // skip "}\r\n"
496:          index = start + count; // position index to beyond the literal
497:
498:•         if (returnString) // return as String
499:                 return toString(buffer, start, start + count);
500:          else
501:                  return new ByteArray(buffer, start, count);
502:•        } else if (parseAtoms) { // parse as ASTRING-CHARs
503:          int start = index;        // track this, so that we can use to
504:                                 // creating ByteArrayInputStream below.
505:          String s = readDelimString(ASTRING_CHAR_DELIM);
506:•         if (returnString)
507:                 return s;
508:          else // *very* unlikely
509:                 return new ByteArray(buffer, start, index);
510:•        } else if (b == 'N' || b == 'n') { // the only valid value is 'NIL'
511:          index += 3; // skip past NIL
512:          return null;
513:         }
514:         return null; // Error
515: }
516:
517: private String toString(byte[] buffer, int start, int end) {
518:•        return utf8 ?
519:                 new String(buffer, start, end - start, StandardCharsets.UTF_8) :
520:                 ASCIIUtility.toString(buffer, start, end);
521: }
522:
523: public int getType() {
524:         return type;
525: }
526:
527: public boolean isContinuation() {
528:•        return ((type & TAG_MASK) == CONTINUATION);
529: }
530:
531: public boolean isTagged() {
532:•        return ((type & TAG_MASK) == TAGGED);
533: }
534:
535: public boolean isUnTagged() {
536:•        return ((type & TAG_MASK) == UNTAGGED);
537: }
538:
539: public boolean isOK() {
540:•        return ((type & TYPE_MASK) == OK);
541: }
542:
543: public boolean isNO() {
544:•        return ((type & TYPE_MASK) == NO);
545: }
546:
547: public boolean isBAD() {
548:•        return ((type & TYPE_MASK) == BAD);
549: }
550:
551: public boolean isBYE() {
552:•        return ((type & TYPE_MASK) == BYE);
553: }
554:
555: public boolean isSynthetic() {
556:•        return ((type & SYNTHETIC) == SYNTHETIC);
557: }
558:
559: /**
560: * Return the tag, if this is a tagged statement.
561: *
562: * @return tag of this tagged statement
563: */
564: public String getTag() {
565:         return tag;
566: }
567:
568: /**
569: * Return the rest of the response as a string, usually used to
570: * return the arbitrary message text after a NO response.
571: *
572: * @return        the rest of the response
573: */
574: public String getRest() {
575:         skipSpaces();
576:         return toString(buffer, index, size);
577: }
578:
579: /**
580: * Return the exception for a synthetic BYE response.
581: *
582: * @return        the exception
583: * @since        JavaMail 1.5.4
584: */
585: public Exception getException() {
586:         return ex;
587: }
588:
589: /**
590: * Reset pointer to beginning of response.
591: */
592: public void reset() {
593:         index = pindex;
594: }
595:
596: @Override
597: public String toString() {
598:         return toString(buffer, 0, size);
599: }
600:
601: }