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