View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.net.imap;
19  
20  import java.io.IOException;
21  import java.util.regex.Matcher;
22  import java.util.regex.Pattern;
23  
24  import org.apache.commons.net.MalformedServerReplyException;
25  
26  /**
27   * Stores IMAP reply code constants.
28   */
29  public final class IMAPReply {
30      /** The reply code indicating success of an operation. */
31      public static final int OK = 0;
32  
33      /** The reply code indicating failure of an operation. */
34      public static final int NO = 1;
35  
36      /** The reply code indicating command rejection. */
37      public static final int BAD = 2;
38  
39      /** The reply code indicating command continuation. */
40      public static final int CONT = 3;
41  
42      /**
43       * 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
44       * on return.
45       *
46       * @since 3.4
47       */
48      public static final int PARTIAL = 3;
49  
50      /** The IMAP reply String indicating success of an operation. */
51      private static final String IMAP_OK = "OK";
52  
53      /** The IMAP reply String indicating failure of an operation. */
54      private static final String IMAP_NO = "NO";
55  
56      /** The IMAP reply String indicating command rejection. */
57      private static final String IMAP_BAD = "BAD";
58  
59      // Start of line for untagged replies
60      private static final String IMAP_UNTAGGED_PREFIX = "* ";
61  
62      // Start of line for continuation replies
63      private static final String IMAP_CONTINUATION_PREFIX = "+";
64  
65      /**
66       * Guard against Polynomial regular expression used on uncontrolled data.
67       *
68       * Don't look for more than 80 letters.
69       * Don't look for more than 80 non-whitespace.
70       * Don't look for more than 80 character.
71       */
72      private static final String TAGGED_RESPONSE = "^\\w{1,80} (\\S{1,80}).{0,80}";
73  
74      /**
75       * Tag cannot contain: + ( ) { SP CTL % * " \ ]
76       */
77      private static final Pattern TAGGED_PATTERN = Pattern.compile(TAGGED_RESPONSE);
78  
79      /**
80       * Guard against Polynomial regular expression used on uncontrolled data.
81       *
82       * Don't look for more than 80 backslashes.
83       * Don't look for more than 80 character.
84       */
85      private static final String UNTAGGED_RESPONSE = "^\\* (\\S{1,80}).{0,160}";
86  
87      private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE);
88      private static final Pattern LITERAL_PATTERN = Pattern.compile("\\{(\\d+)\\}$"); // {dd}
89  
90      /**
91       * Interpret the String reply code - OK, NO, BAD - in a tagged response as an integer.
92       *
93       * @param line the tagged line to be checked
94       * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
95       * @throws IOException if the input has an unexpected format
96       */
97      public static int getReplyCode(final String line) throws IOException {
98          return getReplyCode(line, TAGGED_PATTERN);
99      }
100 
101     // Helper method to process both tagged and untagged replies.
102     private static int getReplyCode(final String line, final Pattern pattern) throws IOException {
103         if (isContinuation(line)) {
104             return CONT;
105         }
106         final Matcher m = pattern.matcher(line);
107         if (m.matches()) { // TODO would lookingAt() be more efficient? If so, then drop trailing .* from patterns
108             final String code = m.group(1);
109             if (code.equals(IMAP_OK)) {
110                 return OK;
111             }
112             if (code.equals(IMAP_BAD)) {
113                 return BAD;
114             }
115             if (code.equals(IMAP_NO)) {
116                 return NO;
117             }
118         }
119         throw new MalformedServerReplyException("Received unexpected IMAP protocol response from server: '" + line + "'.");
120     }
121 
122     /**
123      * Interpret the String reply code - OK, NO, BAD - in an untagged response as an integer.
124      *
125      * @param line the untagged line to be checked
126      * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
127      * @throws IOException if the input has an unexpected format
128      */
129     public static int getUntaggedReplyCode(final String line) throws IOException {
130         return getReplyCode(line, UNTAGGED_PATTERN);
131     }
132 
133     /**
134      * Tests whether the reply line is a continuation, i.e. starts with "+"
135      *
136      * @param replyCode the code to be checked
137      * @return {@code true} if the response was a continuation
138      */
139     public static boolean isContinuation(final int replyCode) {
140         return replyCode == CONT;
141     }
142 
143     /**
144      * Tests whether if the reply line is a continuation, i.e. starts with "+"
145      *
146      * @param line the line to be checked
147      * @return {@code true} if the line is a continuation
148      */
149     public static boolean isContinuation(final String line) {
150         return line.startsWith(IMAP_CONTINUATION_PREFIX);
151     }
152 
153     /**
154      * Tests whether whether the reply code indicates success or not
155      *
156      * @param replyCode the code to check
157      * @return {@code true} if the code equals {@link #OK}
158      */
159     public static boolean isSuccess(final int replyCode) {
160         return replyCode == OK;
161     }
162 
163     /**
164      * Tests whether if the reply line is untagged - e.g. "* OK ..."
165      *
166      * @param line to be checked
167      * @return {@code true} if the line is untagged
168      */
169     public static boolean isUntagged(final String line) {
170         return line.startsWith(IMAP_UNTAGGED_PREFIX);
171     }
172 
173     /**
174      * Checks if the line introduces a literal, i.e. ends with {dd}
175      *
176      * @param line the line to check
177      * @return the literal count, or -1 if there was no literal.
178      */
179     public static int literalCount(final String line) {
180         final Matcher m = LITERAL_PATTERN.matcher(line);
181         if (m.find()) {
182             return Integer.parseInt(m.group(1)); // Should always parse because we matched \d+
183         }
184         return -1;
185     }
186 
187     /** Cannot be instantiated. */
188     private IMAPReply() {
189     }
190 
191 }
192