    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 *      http://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 * IMAPReply stores IMAP reply code constants.
    028 */
    029public final class IMAPReply {
    030    /** The reply code indicating success of an operation. */
    031    public static final int OK = 0;
    032
    033    /** The reply code indicating failure of an operation. */
    034    public static final int NO = 1;
    035
    036    /** The reply code indicating command rejection. */
    037    public static final int BAD = 2;
    038
    039    /** The reply code indicating command continuation. */
    040    public static final int CONT = 3;
    041
    042    /**
    043     * 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
    044     * on return.
    045     *
    046     * @since 3.4
    047     */
    048    public static final int PARTIAL = 3;
    049
    050    /** The IMAP reply String indicating success of an operation. */
    051    private static final String IMAP_OK = "OK";
    052
    053    /** The IMAP reply String indicating failure of an operation. */
    054    private static final String IMAP_NO = "NO";
    055
    056    /** The IMAP reply String indicating command rejection. */
    057    private static final String IMAP_BAD = "BAD";
    058
    059    // Start of line for untagged replies
    060    private static final String IMAP_UNTAGGED_PREFIX = "* ";
    061
    062    // Start of line for continuation replies
    063    private static final String IMAP_CONTINUATION_PREFIX = "+";
    064
    065    private static final String TAGGED_RESPONSE = "^\\w+ (\\S+).*"; // TODO perhaps be less strict on tag match?
    066
    067    // tag cannot contain: + ( ) { SP CTL % * " \ ]
    068    private static final Pattern TAGGED_PATTERN = Pattern.compile(TAGGED_RESPONSE);
    069
    070    private static final String UNTAGGED_RESPONSE = "^\\* (\\S+).*";
    071
    072    private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE);
    073    private static final Pattern LITERAL_PATTERN = Pattern.compile("\\{(\\d+)\\}$"); // {dd}
    074
    075    /**
    076     * Interpret the String reply code - OK, NO, BAD - in a tagged response as an integer.
    077     *
    078     * @param line the tagged line to be checked
    079     * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
    080     * @throws IOException if the input has an unexpected format
    081     */
    082    public static int getReplyCode(final String line) throws IOException {
    083        return getReplyCode(line, TAGGED_PATTERN);
    084    }
    085
    086    // Helper method to process both tagged and untagged replies.
    087    private static int getReplyCode(final String line, final Pattern pattern) throws IOException {
    088        if (isContinuation(line)) {
    089            return CONT;
    090        }
    091        final Matcher m = pattern.matcher(line);
    092        if (m.matches()) { // TODO would lookingAt() be more efficient? If so, then drop trailing .* from patterns
    093            final String code = m.group(1);
    094            if (code.equals(IMAP_OK)) {
    095                return OK;
    096            }
    097            if (code.equals(IMAP_BAD)) {
    098                return BAD;
    099            }
    100            if (code.equals(IMAP_NO)) {
    101                return NO;
    102            }
    103        }
    104        throw new MalformedServerReplyException("Received unexpected IMAP protocol response from server: '" + line + "'.");
    105    }
    106
    107    /**
    108     * Interpret the String reply code - OK, NO, BAD - in an untagged response as an integer.
    109     *
    110     * @param line the untagged line to be checked
    111     * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
    112     * @throws IOException if the input has an unexpected format
    113     */
    114    public static int getUntaggedReplyCode(final String line) throws IOException {
    115        return getReplyCode(line, UNTAGGED_PATTERN);
    116    }
    117
    118    /**
    119     * Checks if 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    /**
    129     * Checks if the reply line is a continuation, i.e. starts with "+"
    130     *
    131     * @param line the line to be checked
    132     * @return {@code true} if the line is a continuation
    133     */
    134    public static boolean isContinuation(final String line) {
    135        return line.startsWith(IMAP_CONTINUATION_PREFIX);
    136    }
    137
    138    /**
    139     * Checks whether the reply code indicates success or not
    140     *
    141     * @param replyCode the code to check
    142     * @return {@code true} if the code equals {@link #OK}
    143     */
    144    public static boolean isSuccess(final int replyCode) {
    145        return replyCode == OK;
    146    }
    147
    148    /**
    149     * Checks if the reply line is untagged - e.g. "* OK ..."
    150     *
    151     * @param line to be checked
    152     * @return {@code true} if the line is untagged
    153     */
    154    public static boolean isUntagged(final String line) {
    155        return line.startsWith(IMAP_UNTAGGED_PREFIX);
    156    }
    157
    158    /**
    159     * Checks if the line introduces a literal, i.e. ends with {dd}
    160     *
    161     * @param line the line to check
    162     * @return the literal count, or -1 if there was no literal.
    163     */
    164    public static int literalCount(final String line) {
    165        final Matcher m = LITERAL_PATTERN.matcher(line);
    166        if (m.find()) {
    167            return Integer.parseInt(m.group(1)); // Should always parse because we matched \d+
    168        }
    169        return -1;
    170    }
    171
    172    /** Cannot be instantiated. */
    173    private IMAPReply() {
    174    }
    175
    176}
    177
    178