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 * https://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 018package org.apache.commons.net.imap; 019 020import java.io.IOException; 021import java.util.regex.Matcher; 022import java.util.regex.Pattern; 023 024import org.apache.commons.net.MalformedServerReplyException; 025 026/** 027 * Stores IMAP reply code constants. 028 */ 029public final class IMAPReply { 030 /** The reply code indicating success of an operation. */ 031 public static final int OK = 0; 032 033 /** The reply code indicating failure of an operation. */ 034 public static final int NO = 1; 035 036 /** The reply code indicating command rejection. */ 037 public static final int BAD = 2; 038 039 /** The reply code indicating command continuation. */ 040 public static final int CONT = 3; 041 042 /** 043 * 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 044 * on return. 045 * 046 * @since 3.4 047 */ 048 public static final int PARTIAL = 3; 049 050 /** The IMAP reply String indicating success of an operation. */ 051 private static final String IMAP_OK = "OK"; 052 053 /** The IMAP reply String indicating failure of an operation. */ 054 private static final String IMAP_NO = "NO"; 055 056 /** The IMAP reply String indicating command rejection. */ 057 private static final String IMAP_BAD = "BAD"; 058 059 // Start of line for untagged replies 060 private static final String IMAP_UNTAGGED_PREFIX = "* "; 061 062 // Start of line for continuation replies 063 private static final String IMAP_CONTINUATION_PREFIX = "+"; 064 065 /** 066 * Guards against Polynomial regular expression used on uncontrolled data. 067 * <ol> 068 * <li>the start of a line.</li> 069 * <li>letters, up to 80.</li> 070 * <li>a space.</li> 071 * <li>non-whitespace characters, up to 80, for example {@code OK}.</li> 072 * <li>up to 500 extra characters.</li> 073 * </ol> 074 */ 075 private static final String TAGGED_RESPONSE = "^\\w{1,80} (\\S{1,80}).{0,500}"; 076 077 /** 078 * Tag cannot contain: + ( ) { SP CTL % * " \ ] 079 */ 080 private static final Pattern TAGGED_PATTERN = Pattern.compile(TAGGED_RESPONSE); 081 082 /** 083 * Guards against Polynomial regular expression used on uncontrolled data. 084 * <ol> 085 * <li>the start of a line, then a star, then a space.</li> 086 * <li>non-whitespace characters, up to 80, for example {@code OK}.</li> 087 * <li>up to 500 extra characters.</li> 088 * </ol> 089 */ 090 private static final String UNTAGGED_RESPONSE = "^\\* (\\S{1,80}).{0,500}"; 091 092 private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE); 093 private static final Pattern LITERAL_PATTERN = Pattern.compile("\\{(\\d+)\\}$"); // {dd} 094 095 /** 096 * Gets the String reply code - OK, NO, BAD - in a tagged response as an integer. 097 * 098 * @param line the tagged line to be checked 099 * @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