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    /** 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    private static final String TAGGED_RESPONSE = "^\\w+ (\\S+).*"; // TODO perhaps be less strict on tag match?
067
068    // tag cannot contain: + ( ) { SP CTL % * " \ ]
069    private static final Pattern TAGGED_PATTERN = Pattern.compile(TAGGED_RESPONSE);
070
071    private static final String UNTAGGED_RESPONSE = "^\\* (\\S+).*";
072
073    private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE);
074    private static final Pattern LITERAL_PATTERN = Pattern.compile("\\{(\\d+)\\}$"); // {dd}
075
076    /**
077     * Intepret the String reply code - OK, NO, BAD - in a tagged response as a integer.
078     *
079     * @param line the tagged line to be checked
080     * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
081     * @throws IOException if the input has an unexpected format
082     */
083    public static int getReplyCode(final String line) throws IOException {
084        return getReplyCode(line, TAGGED_PATTERN);
085    }
086
087    // Helper method to process both tagged and untagged replies.
088    private static int getReplyCode(final String line, final Pattern pattern) throws IOException {
089        if (isContinuation(line)) {
090            return CONT;
091        }
092        final Matcher m = pattern.matcher(line);
093        if (m.matches()) { // TODO would lookingAt() be more efficient? If so, then drop trailing .* from patterns
094            final String code = m.group(1);
095            if (code.equals(IMAP_OK)) {
096                return OK;
097            }
098            if (code.equals(IMAP_BAD)) {
099                return BAD;
100            }
101            if (code.equals(IMAP_NO)) {
102                return NO;
103            }
104        }
105        throw new MalformedServerReplyException("Received unexpected IMAP protocol response from server: '" + line + "'.");
106    }
107
108    /**
109     * Intepret the String reply code - OK, NO, BAD - in an untagged response as a 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    /**
120     * Checks if the reply line is a continuation, i.e. starts with "+"
121     *
122     * @param replyCode the code to be checked
123     * @return {@code true} if the response was a continuation
124     */
125    public static boolean isContinuation(final int replyCode) {
126        return replyCode == CONT;
127    }
128
129    /**
130     * Checks if the reply line is a continuation, i.e. starts with "+"
131     *
132     * @param line the line to be checked
133     * @return {@code true} if the line is untagged
134     */
135    public static boolean isContinuation(final String line) {
136        return line.startsWith(IMAP_CONTINUATION_PREFIX);
137    }
138
139    /**
140     * Checks whether the reply code indicates success or not
141     *
142     * @param replyCode the code to check
143     * @return {@code true} if the code equals {@link #OK}
144     */
145    public static boolean isSuccess(final int replyCode) {
146        return replyCode == OK;
147    }
148
149    /**
150     * Checks if the reply line is untagged - e.g. "* OK ..."
151     *
152     * @param line to be checked
153     * @return {@code true} if the line is untagged
154     */
155    public static boolean isUntagged(final String line) {
156        return line.startsWith(IMAP_UNTAGGED_PREFIX);
157    }
158
159    /**
160     * Checks if the line introduces a literal, i.e. ends with {dd}
161     *
162     * @param line the line to check
163     *
164     * @return the literal count, or -1 if there was no literal.
165     */
166    public static int literalCount(final String line) {
167        final Matcher m = LITERAL_PATTERN.matcher(line);
168        if (m.find()) {
169            return Integer.parseInt(m.group(1)); // Should always parse because we matched \d+
170        }
171        return -1;
172    }
173
174    // Cannot be instantiated.
175    private IMAPReply() {
176    }
177
178}
179
180/* kate: indent-width 4; replace-tabs on; */