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