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