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