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 018 package org.apache.commons.net.imap; 019 020 import java.io.BufferedReader; 021 import java.io.BufferedWriter; 022 import java.io.EOFException; 023 import java.io.InputStreamReader; 024 import java.io.IOException; 025 import java.io.OutputStreamWriter; 026 import java.util.ArrayList; 027 import java.util.List; 028 029 import org.apache.commons.net.SocketClient; 030 import org.apache.commons.net.io.CRLFLineReader; 031 032 033 /** 034 * The IMAP class provides the basic the functionality necessary to implement your 035 * own IMAP client. 036 */ 037 public class IMAP extends SocketClient 038 { 039 /** The default IMAP port (RFC 3501). */ 040 public static final int DEFAULT_PORT = 143; 041 042 public enum IMAPState 043 { 044 /** A constant representing the state where the client is not yet connected to a server. */ 045 DISCONNECTED_STATE, 046 /** A constant representing the "not authenticated" state. */ 047 NOT_AUTH_STATE, 048 /** A constant representing the "authenticated" state. */ 049 AUTH_STATE, 050 /** A constant representing the "logout" state. */ 051 LOGOUT_STATE; 052 } 053 054 // RFC 3501, section 5.1.3. It should be "modified UTF-7". 055 /** 056 * The default control socket ecoding. 057 */ 058 protected static final String __DEFAULT_ENCODING = "ISO-8859-1"; 059 060 private IMAPState __state; 061 protected BufferedWriter __writer; 062 063 protected BufferedReader _reader; 064 private int _replyCode; 065 private List<String> _replyLines; 066 067 private char[] _initialID = { 'A', 'A', 'A', 'A' }; 068 069 /** 070 * The default IMAPClient constructor. Initializes the state 071 * to <code>DISCONNECTED_STATE</code>. 072 */ 073 public IMAP() 074 { 075 setDefaultPort(DEFAULT_PORT); 076 __state = IMAPState.DISCONNECTED_STATE; 077 _reader = null; 078 __writer = null; 079 _replyLines = new ArrayList<String>(); 080 createCommandSupport(); 081 } 082 083 /** 084 * Get the reply for a command that expects a tagged response. 085 * 086 * @throws IOException 087 */ 088 private void __getReply() throws IOException 089 { 090 __getReply(true); // tagged response 091 } 092 093 /** 094 * Get the reply for a command, reading the response until the 095 * reply is found. 096 * 097 * @param wantTag {@code true} if the command expects a tagged response. 098 * @throws IOException 099 */ 100 private void __getReply(boolean wantTag) throws IOException 101 { 102 _replyLines.clear(); 103 String line = _reader.readLine(); 104 105 if (line == null) 106 throw new EOFException("Connection closed without indication."); 107 108 _replyLines.add(line); 109 110 if (wantTag) { 111 while(IMAPReply.isUntagged(line)) { 112 line = _reader.readLine(); 113 if (line == null) { 114 throw new EOFException("Connection closed without indication."); 115 } 116 _replyLines.add(line); 117 } 118 // check the response code on the last line 119 _replyCode = IMAPReply.getReplyCode(line); 120 } else { 121 _replyCode = IMAPReply.getUntaggedReplyCode(line); 122 } 123 124 fireReplyReceived(_replyCode, getReplyString()); 125 } 126 127 /** 128 * Performs connection initialization and sets state to 129 * {@link IMAPState#NOT_AUTH_STATE}. 130 */ 131 @Override 132 protected void _connectAction_() throws IOException 133 { 134 super._connectAction_(); 135 _reader = 136 new CRLFLineReader(new InputStreamReader(_input_, 137 __DEFAULT_ENCODING)); 138 __writer = 139 new BufferedWriter(new OutputStreamWriter(_output_, 140 __DEFAULT_ENCODING)); 141 int tmo = getSoTimeout(); 142 if (tmo <= 0) { // none set currently 143 setSoTimeout(connectTimeout); // use connect timeout to ensure we don't block forever 144 } 145 __getReply(false); // untagged response 146 if (tmo <= 0) { 147 setSoTimeout(tmo); // restore the original value 148 } 149 setState(IMAPState.NOT_AUTH_STATE); 150 } 151 152 /** 153 * Sets IMAP client state. This must be one of the 154 * <code>_STATE</code> constants. 155 * <p> 156 * @param state The new state. 157 */ 158 protected void setState(IMAP.IMAPState state) 159 { 160 __state = state; 161 } 162 163 164 /** 165 * Returns the current IMAP client state. 166 * <p> 167 * @return The current IMAP client state. 168 */ 169 public IMAP.IMAPState getState() 170 { 171 return __state; 172 } 173 174 /** 175 * Disconnects the client from the server, and sets the state to 176 * <code> DISCONNECTED_STATE </code>. The reply text information 177 * from the last issued command is voided to allow garbage collection 178 * of the memory used to store that information. 179 * <p> 180 * @exception IOException If there is an error in disconnecting. 181 */ 182 @Override 183 public void disconnect() throws IOException 184 { 185 super.disconnect(); 186 _reader = null; 187 __writer = null; 188 _replyLines.clear(); 189 setState(IMAPState.DISCONNECTED_STATE); 190 } 191 192 193 /** 194 * Sends a command an arguments to the server and returns the reply code. 195 * <p> 196 * @param commandID The ID (tag) of the command. 197 * @param command The IMAP command to send. 198 * @param args The command arguments. 199 * @return The server reply code (either IMAPReply.OK, IMAPReply.NO or IMAPReply.BAD). 200 */ 201 private int sendCommandWithID(String commandID, String command, String args) throws IOException 202 { 203 StringBuilder __commandBuffer = new StringBuilder(); 204 if (commandID != null) 205 { 206 __commandBuffer.append(commandID); 207 __commandBuffer.append(' '); 208 } 209 __commandBuffer.append(command); 210 211 if (args != null) 212 { 213 __commandBuffer.append(' '); 214 __commandBuffer.append(args); 215 } 216 __commandBuffer.append(SocketClient.NETASCII_EOL); 217 218 String message = __commandBuffer.toString(); 219 __writer.write(message); 220 __writer.flush(); 221 222 fireCommandSent(command, message); 223 224 __getReply(); 225 return _replyCode; 226 } 227 228 /** 229 * Sends a command an arguments to the server and returns the reply code. 230 * <p> 231 * @param command The IMAP command to send. 232 * @param args The command arguments. 233 * @return The server reply code (see IMAPReply). 234 */ 235 public int sendCommand(String command, String args) throws IOException 236 { 237 return sendCommandWithID(generateCommandID(), command, args); 238 } 239 240 /** 241 * Sends a command with no arguments to the server and returns the 242 * reply code. 243 * <p> 244 * @param command The IMAP command to send. 245 * @return The server reply code (see IMAPReply). 246 */ 247 public int sendCommand(String command) throws IOException 248 { 249 return sendCommand(command, null); 250 } 251 252 /** 253 * Sends a command and arguments to the server and returns the reply code. 254 * <p> 255 * @param command The IMAP command to send 256 * (one of the IMAPCommand constants). 257 * @param args The command arguments. 258 * @return The server reply code (see IMAPReply). 259 */ 260 public int sendCommand(IMAPCommand command, String args) throws IOException 261 { 262 return sendCommand(command.getIMAPCommand(), args); 263 } 264 265 /** 266 * Sends a command and arguments to the server and return whether successful. 267 * <p> 268 * @param command The IMAP command to send 269 * (one of the IMAPCommand constants). 270 * @param args The command arguments. 271 * @return {@code true} if the command was successful 272 */ 273 public boolean doCommand(IMAPCommand command, String args) throws IOException 274 { 275 return IMAPReply.isSuccess(sendCommand(command, args)); 276 } 277 278 /** 279 * Sends a command with no arguments to the server and returns the 280 * reply code. 281 * 282 * @param command The IMAP command to send 283 * (one of the IMAPCommand constants). 284 * @return The server reply code (see IMAPReply). 285 **/ 286 public int sendCommand(IMAPCommand command) throws IOException 287 { 288 return sendCommand(command, null); 289 } 290 291 /** 292 * Sends a command to the server and return whether successful. 293 * 294 * @param command The IMAP command to send 295 * (one of the IMAPCommand constants). 296 * @return {@code true} if the command was successful 297 */ 298 public boolean doCommand(IMAPCommand command) throws IOException 299 { 300 return IMAPReply.isSuccess(sendCommand(command)); 301 } 302 303 /** 304 * Sends data to the server and returns the reply code. 305 * <p> 306 * @param command The IMAP command to send. 307 * @return The server reply code (see IMAPReply). 308 */ 309 public int sendData(String command) throws IOException 310 { 311 return sendCommandWithID(null, command, null); 312 } 313 314 /** 315 * Returns an array of lines received as a reply to the last command 316 * sent to the server. The lines have end of lines truncated. 317 * @return The last server response. 318 */ 319 public String[] getReplyStrings() 320 { 321 return _replyLines.toArray(new String[_replyLines.size()]); 322 } 323 324 /** 325 * Returns the reply to the last command sent to the server. 326 * The value is a single string containing all the reply lines including 327 * newlines. 328 * <p> 329 * @return The last server response. 330 */ 331 public String getReplyString() 332 { 333 StringBuilder buffer = new StringBuilder(256); 334 for (String s : _replyLines) 335 { 336 buffer.append(s); 337 buffer.append(SocketClient.NETASCII_EOL); 338 } 339 340 return buffer.toString(); 341 } 342 343 /** 344 * Generates a new command ID (tag) for a command. 345 * @return a new command ID (tag) for an IMAP command. 346 */ 347 protected String generateCommandID() 348 { 349 String res = new String (_initialID); 350 // "increase" the ID for the next call 351 boolean carry = true; // want to increment initially 352 for (int i = _initialID.length-1; carry && i>=0; i--) 353 { 354 if (_initialID[i] == 'Z') 355 { 356 _initialID[i] = 'A'; 357 } 358 else 359 { 360 _initialID[i]++; 361 carry = false; // did not wrap round 362 } 363 } 364 return res; 365 } 366 } 367 /* kate: indent-width 4; replace-tabs on; */