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.  *      https://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.      * Guards against Polynomial regular expression used on uncontrolled data.
  53.      * <ol>
  54.      * <li>the start of a line.</li>
  55.      * <li>letters, up to 80.</li>
  56.      * <li>a space.</li>
  57.      * <li>non-whitespace characters, up to 80, for example {@code OK}.</li>
  58.      * <li>up to 500 extra characters.</li>
  59.      * </ol>
  60.      */
  61.     private static final String TAGGED_RESPONSE = "^\\w{1,80} (\\S{1,80}).{0,500}";

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

  66.     /**
  67.      * Guards against Polynomial regular expression used on uncontrolled data.
  68.      * <ol>
  69.      * <li>the start of a line, then a star, then a space.</li>
  70.      * <li>non-whitespace characters, up to 80, for example {@code OK}.</li>
  71.      * <li>up to 500 extra characters.</li>
  72.      * </ol>
  73.      */
  74.     private static final String UNTAGGED_RESPONSE = "^\\* (\\S{1,80}).{0,500}";

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

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

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

  108.     /**
  109.      * Gets the String reply code - OK, NO, BAD - in an untagged response as an integer.
  110.      *
  111.      * @param line the untagged line to be checked
  112.      * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
  113.      * @throws IOException if the input has an unexpected format
  114.      */
  115.     public static int getUntaggedReplyCode(final String line) throws IOException {
  116.         return getReplyCode(line, UNTAGGED_PATTERN);
  117.     }

  118.     /**
  119.      * Tests whether the reply line is a continuation, i.e. starts with "+"
  120.      *
  121.      * @param replyCode the code to be checked
  122.      * @return {@code true} if the response was a continuation
  123.      */
  124.     public static boolean isContinuation(final int replyCode) {
  125.         return replyCode == CONT;
  126.     }

  127.     /**
  128.      * Tests whether if the reply line is a continuation, i.e. starts with "+"
  129.      *
  130.      * @param line the line to be checked
  131.      * @return {@code true} if the line is a continuation
  132.      */
  133.     public static boolean isContinuation(final String line) {
  134.         return line.startsWith(IMAP_CONTINUATION_PREFIX);
  135.     }

  136.     /**
  137.      * Tests whether whether the reply code indicates success or not
  138.      *
  139.      * @param replyCode the code to check
  140.      * @return {@code true} if the code equals {@link #OK}
  141.      */
  142.     public static boolean isSuccess(final int replyCode) {
  143.         return replyCode == OK;
  144.     }

  145.     /**
  146.      * Tests whether if the reply line is untagged - e.g. "* OK ..."
  147.      *
  148.      * @param line to be checked
  149.      * @return {@code true} if the line is untagged
  150.      */
  151.     public static boolean isUntagged(final String line) {
  152.         return line.startsWith(IMAP_UNTAGGED_PREFIX);
  153.     }

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

  167.     /** Cannot be instantiated. */
  168.     private IMAPReply() {
  169.     }

  170. }