    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      https://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.net.imap;
    019
    020import java.io.IOException;
    021import java.util.regex.Matcher;
    022import java.util.regex.Pattern;
    023
    024import org.apache.commons.net.MalformedServerReplyException;
    025
    026/**
    027 * Stores IMAP reply code constants.
    028 */
    029public final class IMAPReply {
    030
    031    /** The reply code indicating success of an operation. */
    032    public static final int OK = 0;
    033
    034    /** The reply code indicating failure of an operation. */
    035    public static final int NO = 1;
    036
    037    /** The reply code indicating command rejection. */
    038    public static final int BAD = 2;
    039
    040    /** The reply code indicating command continuation. */
    041    public static final int CONT = 3;
    042
    043    /**
    044     * 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
    045     * on return.
    046     *
    047     * @since 3.4
    048     */
    049    public static final int PARTIAL = 3;
    050
    051    /** The IMAP reply String indicating success of an operation. */
    052    private static final String IMAP_OK = "OK";
    053
    054    /** The IMAP reply String indicating failure of an operation. */
    055    private static final String IMAP_NO = "NO";
    056
    057    /** The IMAP reply String indicating command rejection. */
    058    private static final String IMAP_BAD = "BAD";
    059
    060    // Start of line for untagged replies
    061    private static final String IMAP_UNTAGGED_PREFIX = "* ";
    062
    063    // Start of line for continuation replies
    064    private static final String IMAP_CONTINUATION_PREFIX = "+";
    065
    066    /**
    067     * Guards against Polynomial regular expression used on uncontrolled data.
    068     * <ol>
    069     * <li>the start of a line.</li>
    070     * <li>letters, up to 80.</li>
    071     * <li>a space.</li>
    072     * <li>non-whitespace characters, up to 80, for example {@code OK}.</li>
    073     * <li>up to 500 extra characters.</li>
    074     * </ol>
    075     */
    076    private static final String TAGGED_RESPONSE = "^\\w{1,80} (\\S{1,80}).{0,500}";
    077
    078    /**
    079     * Tag cannot contain: + ( ) { SP CTL % * " \ ]
    080     */
    081    private static final Pattern TAGGED_PATTERN = Pattern.compile(TAGGED_RESPONSE);
    082
    083    /**
    084     * Guards against Polynomial regular expression used on uncontrolled data.
    085     * <ol>
    086     * <li>the start of a line, then a star, then a space.</li>
    087     * <li>non-whitespace characters, up to 80, for example {@code OK}.</li>
    088     * <li>up to 500 extra characters.</li>
    089     * </ol>
    090     */
    091    private static final String UNTAGGED_RESPONSE = "^\\* (\\S{1,80}).{0,500}";
    092
    093    private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE);
    094    private static final Pattern LITERAL_PATTERN = Pattern.compile("\\{(\\d+)\\}$"); // {dd}
    095
    096    /**
    097     * Gets the String reply code - OK, NO, BAD - in a tagged response as an integer.
    098     *
    099     * @param line the tagged line to be checked
    100     * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
    101     * @throws IOException if the input has an unexpected format
    102     */
    103    public static int getReplyCode(final String line) throws IOException {
    104        return getReplyCode(line, TAGGED_PATTERN);
    105    }
    106
    107    // Helper method to process both tagged and untagged replies.
    108    private static int getReplyCode(final String line, final Pattern pattern) throws IOException {
    109        if (isContinuation(line)) {
    110            return CONT;
    111        }
    112        final Matcher m = pattern.matcher(line);
    113        if (m.matches()) { // TODO would lookingAt() be more efficient? If so, then drop trailing .* from patterns
    114            final String code = m.group(1);
    115            switch (code) {
    116            case IMAP_OK:
    117                return OK;
    118            case IMAP_BAD:
    119                return BAD;
    120            case IMAP_NO:
    121                return NO;
    122            default:
    123                break;
    124            }
    125        }
    126        throw new MalformedServerReplyException("Received unexpected IMAP protocol response from server: '" + line + "'.");
    127    }
    128
    129    /**
    130     * Gets the String reply code - OK, NO, BAD - in an untagged response as an integer.
    131     *
    132     * @param line the untagged line to be checked
    133     * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
    134     * @throws IOException if the input has an unexpected format
    135     */
    136    public static int getUntaggedReplyCode(final String line) throws IOException {
    137        return getReplyCode(line, UNTAGGED_PATTERN);
    138    }
    139
    140    /**
    141     * Tests whether the reply line is a continuation, i.e. starts with "+"
    142     *
    143     * @param replyCode the code to be checked
    144     * @return {@code true} if the response was a continuation
    145     */
    146    public static boolean isContinuation(final int replyCode) {
    147        return replyCode == CONT;
    148    }
    149
    150    /**
    151     * Tests whether if the reply line is a continuation, i.e. starts with "+"
    152     *
    153     * @param line the line to be checked
    154     * @return {@code true} if the line is a continuation
    155     */
    156    public static boolean isContinuation(final String line) {
    157        return line.startsWith(IMAP_CONTINUATION_PREFIX);
    158    }
    159
    160    /**
    161     * Tests whether whether the reply code indicates success or not
    162     *
    163     * @param replyCode the code to check
    164     * @return {@code true} if the code equals {@link #OK}
    165     */
    166    public static boolean isSuccess(final int replyCode) {
    167        return replyCode == OK;
    168    }
    169
    170    /**
    171     * Tests whether if the reply line is untagged - e.g. "* OK ..."
    172     *
    173     * @param line to be checked
    174     * @return {@code true} if the line is untagged
    175     */
    176    public static boolean isUntagged(final String line) {
    177        return line.startsWith(IMAP_UNTAGGED_PREFIX);
    178    }
    179
    180    /**
    181     * Checks if the line introduces a literal, i.e. ends with {dd}
    182     *
    183     * @param line the line to check
    184     * @return the literal count, or -1 if there was no literal.
    185     */
    186    public static int literalCount(final String line) {
    187        final Matcher m = LITERAL_PATTERN.matcher(line);
    188        if (m.find()) {
    189            return Integer.parseInt(m.group(1)); // Should always parse because we matched \d+
    190        }
    191        return -1;
    192    }
    193
    194    /** Cannot be instantiated. */
    195    private IMAPReply() {
    196    }
    197
    198}
    199