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