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 * http://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 * Guard against Polynomial regular expression used on uncontrolled data. 067 * 068 * Don't look for more than 80 letters. 069 * Don't look for more than 80 non-whitespace. 070 * Don't look for more than 80 character. 071 */ 072 private static final String TAGGED_RESPONSE = "^\\w{1,80} (\\S{1,80}).{0,80}"; 073 074 /** 075 * Tag cannot contain: + ( ) { SP CTL % * " \ ] 076 */ 077 private static final Pattern TAGGED_PATTERN = Pattern.compile(TAGGED_RESPONSE); 078 079 /** 080 * Guard against Polynomial regular expression used on uncontrolled data. 081 * 082 * Don't look for more than 80 backslashes. 083 * Don't look for more than 80 character. 084 */ 085 private static final String UNTAGGED_RESPONSE = "^\\* (\\S{1,80}).{0,160}"; 086 087 private static final Pattern UNTAGGED_PATTERN = Pattern.compile(UNTAGGED_RESPONSE); 088 private static final Pattern LITERAL_PATTERN = Pattern.compile("\\{(\\d+)\\}$"); // {dd} 089 090 /** 091 * Interpret the String reply code - OK, NO, BAD - in a tagged response as an integer. 092 * 093 * @param line the tagged line to be checked 094 * @return {@link #OK} or {@link #NO} or {@link #BAD} or {@link #CONT} 095 * @throws IOException if the input has an unexpected format 096 */ 097 public static int getReplyCode(final String line) throws IOException { 098 return getReplyCode(line, TAGGED_PATTERN); 099 } 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