IMAPReply.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.net.imap;

  18. import java.io.IOException;
  19. import java.util.regex.Matcher;
  20. import java.util.regex.Pattern;

  21. import org.apache.commons.net.MalformedServerReplyException;

  22. /**
  23.  * Stores IMAP reply code constants.
  24.  */
  25. public final class IMAPReply {
  26.     /** The reply code indicating success of an operation. */
  27.     public static final int OK = 0;

  28.     /** The reply code indicating failure of an operation. */
  29.     public static final int NO = 1;

  30.     /** The reply code indicating command rejection. */
  31.     public static final int BAD = 2;

  32.     /** The reply code indicating command continuation. */
  33.     public static final int CONT = 3;

  34.     /**
  35.      * The reply code indicating a partial response. This is used when a chunk listener is registered and the listener requests that the reply lines are cleared
  36.      * on return.
  37.      *
  38.      * @since 3.4
  39.      */
  40.     public static final int PARTIAL = 3;

  41.     /** The IMAP reply String indicating success of an operation. */
  42.     private static final String IMAP_OK = "OK";

  43.     /** The IMAP reply String indicating failure of an operation. */
  44.     private static final String IMAP_NO = "NO";

  45.     /** The IMAP reply String indicating command rejection. */
  46.     private static final String IMAP_BAD = "BAD";

  47.     // Start of line for untagged replies
  48.     private static final String IMAP_UNTAGGED_PREFIX = "* ";

  49.     // Start of line for continuation replies
  50.     private static final String IMAP_CONTINUATION_PREFIX = "+";

  51.     /**
  52.      * Guard against Polynomial regular expression used on uncontrolled data.
  53.      *
  54.      * Don't look for more than 80 letters.
  55.      * Don't look for more than 80 non-whitespace.
  56.      * Don't look for more than 80 character.
  57.      */
  58.     private static final String TAGGED_RESPONSE = "^\\w{1,80} (\\S{1,80}).{0,80}";

  59.     /**
  60.      * Tag cannot contain: + ( ) { SP CTL % * " \ ]
  61.      */
  62.     private static final Pattern TAGGED_PATTERN = Pattern.compile(TAGGED_RESPONSE);

  63.     /**
  64.      * Guard against Polynomial regular expression used on uncontrolled data.
  65.      *
  66.      * Don't look for more than 80 backslashes.
  67.      * Don't look for more than 80 character.
  68.      */
  69.     private static final String UNTAGGED_RESPONSE = "^\\* (\\S{1,80}).{0,160}";

  70.     private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE);
  71.     private static final Pattern LITERAL_PATTERN = Pattern.compile("\\{(\\d+)\\}$"); // {dd}

  72.     /**
  73.      * Interpret the String reply code - OK, NO, BAD - in a tagged response as an integer.
  74.      *
  75.      * @param line the tagged line to be checked
  76.      * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
  77.      * @throws IOException if the input has an unexpected format
  78.      */
  79.     public static int getReplyCode(final String line) throws IOException {
  80.         return getReplyCode(line, TAGGED_PATTERN);
  81.     }

  82.     // Helper method to process both tagged and untagged replies.
  83.     private static int getReplyCode(final String line, final Pattern pattern) throws IOException {
  84.         if (isContinuation(line)) {
  85.             return CONT;
  86.         }
  87.         final Matcher m = pattern.matcher(line);
  88.         if (m.matches()) { // TODO would lookingAt() be more efficient? If so, then drop trailing .* from patterns
  89.             final String code = m.group(1);
  90.             if (code.equals(IMAP_OK)) {
  91.                 return OK;
  92.             }
  93.             if (code.equals(IMAP_BAD)) {
  94.                 return BAD;
  95.             }
  96.             if (code.equals(IMAP_NO)) {
  97.                 return NO;
  98.             }
  99.         }
  100.         throw new MalformedServerReplyException("Received unexpected IMAP protocol response from server: '" + line + "'.");
  101.     }

  102.     /**
  103.      * Interpret the String reply code - OK, NO, BAD - in an untagged response as an integer.
  104.      *
  105.      * @param line the untagged line to be checked
  106.      * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
  107.      * @throws IOException if the input has an unexpected format
  108.      */
  109.     public static int getUntaggedReplyCode(final String line) throws IOException {
  110.         return getReplyCode(line, UNTAGGED_PATTERN);
  111.     }

  112.     /**
  113.      * Tests whether the reply line is a continuation, i.e. starts with "+"
  114.      *
  115.      * @param replyCode the code to be checked
  116.      * @return {@code true} if the response was a continuation
  117.      */
  118.     public static boolean isContinuation(final int replyCode) {
  119.         return replyCode == CONT;
  120.     }

  121.     /**
  122.      * Tests whether if the reply line is a continuation, i.e. starts with "+"
  123.      *
  124.      * @param line the line to be checked
  125.      * @return {@code true} if the line is a continuation
  126.      */
  127.     public static boolean isContinuation(final String line) {
  128.         return line.startsWith(IMAP_CONTINUATION_PREFIX);
  129.     }

  130.     /**
  131.      * Tests whether whether the reply code indicates success or not
  132.      *
  133.      * @param replyCode the code to check
  134.      * @return {@code true} if the code equals {@link #OK}
  135.      */
  136.     public static boolean isSuccess(final int replyCode) {
  137.         return replyCode == OK;
  138.     }

  139.     /**
  140.      * Tests whether if the reply line is untagged - e.g. "* OK ..."
  141.      *
  142.      * @param line to be checked
  143.      * @return {@code true} if the line is untagged
  144.      */
  145.     public static boolean isUntagged(final String line) {
  146.         return line.startsWith(IMAP_UNTAGGED_PREFIX);
  147.     }

  148.     /**
  149.      * Checks if the line introduces a literal, i.e. ends with {dd}
  150.      *
  151.      * @param line the line to check
  152.      * @return the literal count, or -1 if there was no literal.
  153.      */
  154.     public static int literalCount(final String line) {
  155.         final Matcher m = LITERAL_PATTERN.matcher(line);
  156.         if (m.find()) {
  157.             return Integer.parseInt(m.group(1)); // Should always parse because we matched \d+
  158.         }
  159.         return -1;
  160.     }

  161.     /** Cannot be instantiated. */
  162.     private IMAPReply() {
  163.     }

  164. }