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(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(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(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(String line) {
116        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(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(String line, Pattern pattern) throws IOException{
136        if (isContinuation(line)) {
137            return CONT;
138        }
139        Matcher m = pattern.matcher(line);
140        if (m.matches()) { // TODO would lookingAt() be more efficient? If so, then drop trailing .* from patterns
141            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(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(int replyCode) {
171        return replyCode == CONT;
172    }
173
174}
175
176/* kate: indent-width 4; replace-tabs on; */