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    *      https://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  
31      /** The reply code indicating success of an operation. */
32      public static final int OK = 0;
33  
34      /** The reply code indicating failure of an operation. */
35      public static final int NO = 1;
36  
37      /** The reply code indicating command rejection. */
38      public static final int BAD = 2;
39  
40      /** The reply code indicating command continuation. */
41      public static final int CONT = 3;
42  
43      /**
44       * 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
45       * on return.
46       *
47       * @since 3.4
48       */
49      public static final int PARTIAL = 3;
50  
51      /** The IMAP reply String indicating success of an operation. */
52      private static final String IMAP_OK = "OK";
53  
54      /** The IMAP reply String indicating failure of an operation. */
55      private static final String IMAP_NO = "NO";
56  
57      /** The IMAP reply String indicating command rejection. */
58      private static final String IMAP_BAD = "BAD";
59  
60      // Start of line for untagged replies
61      private static final String IMAP_UNTAGGED_PREFIX = "* ";
62  
63      // Start of line for continuation replies
64      private static final String IMAP_CONTINUATION_PREFIX = "+";
65  
66      /**
67       * Guards against Polynomial regular expression used on uncontrolled data.
68       * <ol>
69       * <li>the start of a line.</li>
70       * <li>letters, up to 80.</li>
71       * <li>a space.</li>
72       * <li>non-whitespace characters, up to 80, for example {@code OK}.</li>
73       * <li>up to 500 extra characters.</li>
74       * </ol>
75       */
76      private static final String TAGGED_RESPONSE = "^\\w{1,80} (\\S{1,80}).{0,500}";
77  
78      /**
79       * Tag cannot contain: + ( ) { SP CTL % * " \ ]
80       */
81      private static final Pattern TAGGED_PATTERN = Pattern.compile(TAGGED_RESPONSE);
82  
83      /**
84       * Guards against Polynomial regular expression used on uncontrolled data.
85       * <ol>
86       * <li>the start of a line, then a star, then a space.</li>
87       * <li>non-whitespace characters, up to 80, for example {@code OK}.</li>
88       * <li>up to 500 extra characters.</li>
89       * </ol>
90       */
91      private static final String UNTAGGED_RESPONSE = "^\\* (\\S{1,80}).{0,500}";
92  
93      private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE);
94      private static final Pattern LITERAL_PATTERN = Pattern.compile("\\{(\\d+)\\}$"); // {dd}
95  
96      /**
97       * Gets the String reply code - OK, NO, BAD - in a tagged response as an integer.
98       *
99       * @param line the tagged line to be checked
100      * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
101      * @throws IOException if the input has an unexpected format
102      */
103     public static int getReplyCode(final String line) throws IOException {
104         return getReplyCode(line, TAGGED_PATTERN);
105     }
106 
107     // Helper method to process both tagged and untagged replies.
108     private static int getReplyCode(final String line, final Pattern pattern) throws IOException {
109         if (isContinuation(line)) {
110             return CONT;
111         }
112         final Matcher m = pattern.matcher(line);
113         if (m.matches()) { // TODO would lookingAt() be more efficient? If so, then drop trailing .* from patterns
114             final String code = m.group(1);
115             switch (code) {
116             case IMAP_OK:
117                 return OK;
118             case IMAP_BAD:
119                 return BAD;
120             case IMAP_NO:
121                 return NO;
122             default:
123                 break;
124             }
125         }
126         throw new MalformedServerReplyException("Received unexpected IMAP protocol response from server: '" + line + "'.");
127     }
128 
129     /**
130      * Gets the String reply code - OK, NO, BAD - in an untagged response as an integer.
131      *
132      * @param line the untagged line to be checked
133      * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT}
134      * @throws IOException if the input has an unexpected format
135      */
136     public static int getUntaggedReplyCode(final String line) throws IOException {
137         return getReplyCode(line, UNTAGGED_PATTERN);
138     }
139 
140     /**
141      * Tests whether the reply line is a continuation, i.e. starts with "+"
142      *
143      * @param replyCode the code to be checked
144      * @return {@code true} if the response was a continuation
145      */
146     public static boolean isContinuation(final int replyCode) {
147         return replyCode == CONT;
148     }
149 
150     /**
151      * Tests whether if the reply line is a continuation, i.e. starts with "+"
152      *
153      * @param line the line to be checked
154      * @return {@code true} if the line is a continuation
155      */
156     public static boolean isContinuation(final String line) {
157         return line.startsWith(IMAP_CONTINUATION_PREFIX);
158     }
159 
160     /**
161      * Tests whether whether the reply code indicates success or not
162      *
163      * @param replyCode the code to check
164      * @return {@code true} if the code equals {@link #OK}
165      */
166     public static boolean isSuccess(final int replyCode) {
167         return replyCode == OK;
168     }
169 
170     /**
171      * Tests whether if the reply line is untagged - e.g. "* OK ..."
172      *
173      * @param line to be checked
174      * @return {@code true} if the line is untagged
175      */
176     public static boolean isUntagged(final String line) {
177         return line.startsWith(IMAP_UNTAGGED_PREFIX);
178     }
179 
180     /**
181      * Checks if the line introduces a literal, i.e. ends with {dd}
182      *
183      * @param line the line to check
184      * @return the literal count, or -1 if there was no literal.
185      */
186     public static int literalCount(final String line) {
187         final Matcher m = LITERAL_PATTERN.matcher(line);
188         if (m.find()) {
189             return Integer.parseInt(m.group(1)); // Should always parse because we matched \d+
190         }
191         return -1;
192     }
193 
194     /** Cannot be instantiated. */
195     private IMAPReply() {
196     }
197 
198 }
199