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.BufferedReader; 021import java.io.BufferedWriter; 022import java.io.EOFException; 023import java.io.InputStreamReader; 024import java.io.IOException; 025import java.io.OutputStreamWriter; 026import java.util.ArrayList; 027import java.util.List; 028 029import org.apache.commons.net.SocketClient; 030import 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 */ 037public 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 109 _replyLines.add(line); 110 111 if (wantTag) { 112 while(IMAPReply.isUntagged(line)) { 113 line = _reader.readLine(); 114 if (line == null) { 115 throw new EOFException("Connection closed without indication."); 116 } 117 _replyLines.add(line); 118 } 119 // check the response code on the last line 120 _replyCode = IMAPReply.getReplyCode(line); 121 } else { 122 _replyCode = IMAPReply.getUntaggedReplyCode(line); 123 } 124 125 fireReplyReceived(_replyCode, getReplyString()); 126 } 127 128 /** 129 * Performs connection initialization and sets state to 130 * {@link IMAPState#NOT_AUTH_STATE}. 131 */ 132 @Override 133 protected void _connectAction_() throws IOException 134 { 135 super._connectAction_(); 136 _reader = 137 new CRLFLineReader(new InputStreamReader(_input_, 138 __DEFAULT_ENCODING)); 139 __writer = 140 new BufferedWriter(new OutputStreamWriter(_output_, 141 __DEFAULT_ENCODING)); 142 int tmo = getSoTimeout(); 143 if (tmo <= 0) { // none set currently 144 setSoTimeout(connectTimeout); // use connect timeout to ensure we don't block forever 145 } 146 __getReply(false); // untagged response 147 if (tmo <= 0) { 148 setSoTimeout(tmo); // restore the original value 149 } 150 setState(IMAPState.NOT_AUTH_STATE); 151 } 152 153 /** 154 * Sets IMAP client state. This must be one of the 155 * <code>_STATE</code> constants. 156 * <p> 157 * @param state The new state. 158 */ 159 protected void setState(IMAP.IMAPState state) 160 { 161 __state = state; 162 } 163 164 165 /** 166 * Returns the current IMAP client state. 167 * <p> 168 * @return The current IMAP client state. 169 */ 170 public IMAP.IMAPState getState() 171 { 172 return __state; 173 } 174 175 /** 176 * Disconnects the client from the server, and sets the state to 177 * <code> DISCONNECTED_STATE </code>. The reply text information 178 * from the last issued command is voided to allow garbage collection 179 * of the memory used to store that information. 180 * <p> 181 * @exception IOException If there is an error in disconnecting. 182 */ 183 @Override 184 public void disconnect() throws IOException 185 { 186 super.disconnect(); 187 _reader = null; 188 __writer = null; 189 _replyLines.clear(); 190 setState(IMAPState.DISCONNECTED_STATE); 191 } 192 193 194 /** 195 * Sends a command an arguments to the server and returns the reply code. 196 * <p> 197 * @param commandID The ID (tag) of the command. 198 * @param command The IMAP command to send. 199 * @param args The command arguments. 200 * @return The server reply code (either IMAPReply.OK, IMAPReply.NO or IMAPReply.BAD). 201 */ 202 private int sendCommandWithID(String commandID, String command, String args) throws IOException 203 { 204 StringBuilder __commandBuffer = new StringBuilder(); 205 if (commandID != null) 206 { 207 __commandBuffer.append(commandID); 208 __commandBuffer.append(' '); 209 } 210 __commandBuffer.append(command); 211 212 if (args != null) 213 { 214 __commandBuffer.append(' '); 215 __commandBuffer.append(args); 216 } 217 __commandBuffer.append(SocketClient.NETASCII_EOL); 218 219 String message = __commandBuffer.toString(); 220 __writer.write(message); 221 __writer.flush(); 222 223 fireCommandSent(command, message); 224 225 __getReply(); 226 return _replyCode; 227 } 228 229 /** 230 * Sends a command an arguments to the server and returns the reply code. 231 * <p> 232 * @param command The IMAP command to send. 233 * @param args The command arguments. 234 * @return The server reply code (see IMAPReply). 235 */ 236 public int sendCommand(String command, String args) throws IOException 237 { 238 return sendCommandWithID(generateCommandID(), command, args); 239 } 240 241 /** 242 * Sends a command with no arguments to the server and returns the 243 * reply code. 244 * <p> 245 * @param command The IMAP command to send. 246 * @return The server reply code (see IMAPReply). 247 */ 248 public int sendCommand(String command) throws IOException 249 { 250 return sendCommand(command, null); 251 } 252 253 /** 254 * Sends a command and arguments to the server and returns the reply code. 255 * <p> 256 * @param command The IMAP command to send 257 * (one of the IMAPCommand constants). 258 * @param args The command arguments. 259 * @return The server reply code (see IMAPReply). 260 */ 261 public int sendCommand(IMAPCommand command, String args) throws IOException 262 { 263 return sendCommand(command.getIMAPCommand(), args); 264 } 265 266 /** 267 * Sends a command and arguments to the server and return whether successful. 268 * <p> 269 * @param command The IMAP command to send 270 * (one of the IMAPCommand constants). 271 * @param args The command arguments. 272 * @return {@code true} if the command was successful 273 */ 274 public boolean doCommand(IMAPCommand command, String args) throws IOException 275 { 276 return IMAPReply.isSuccess(sendCommand(command, args)); 277 } 278 279 /** 280 * Sends a command with no arguments to the server and returns the 281 * reply code. 282 * 283 * @param command The IMAP command to send 284 * (one of the IMAPCommand constants). 285 * @return The server reply code (see IMAPReply). 286 **/ 287 public int sendCommand(IMAPCommand command) throws IOException 288 { 289 return sendCommand(command, null); 290 } 291 292 /** 293 * Sends a command to the server and return whether successful. 294 * 295 * @param command The IMAP command to send 296 * (one of the IMAPCommand constants). 297 * @return {@code true} if the command was successful 298 */ 299 public boolean doCommand(IMAPCommand command) throws IOException 300 { 301 return IMAPReply.isSuccess(sendCommand(command)); 302 } 303 304 /** 305 * Sends data to the server and returns the reply code. 306 * <p> 307 * @param command The IMAP command to send. 308 * @return The server reply code (see IMAPReply). 309 */ 310 public int sendData(String command) throws IOException 311 { 312 return sendCommandWithID(null, command, null); 313 } 314 315 /** 316 * Returns an array of lines received as a reply to the last command 317 * sent to the server. The lines have end of lines truncated. 318 * @return The last server response. 319 */ 320 public String[] getReplyStrings() 321 { 322 return _replyLines.toArray(new String[_replyLines.size()]); 323 } 324 325 /** 326 * Returns the reply to the last command sent to the server. 327 * The value is a single string containing all the reply lines including 328 * newlines. 329 * <p> 330 * @return The last server response. 331 */ 332 public String getReplyString() 333 { 334 StringBuilder buffer = new StringBuilder(256); 335 for (String s : _replyLines) 336 { 337 buffer.append(s); 338 buffer.append(SocketClient.NETASCII_EOL); 339 } 340 341 return buffer.toString(); 342 } 343 344 /** 345 * Generates a new command ID (tag) for a command. 346 * @return a new command ID (tag) for an IMAP command. 347 */ 348 protected String generateCommandID() 349 { 350 String res = new String (_initialID); 351 // "increase" the ID for the next call 352 boolean carry = true; // want to increment initially 353 for (int i = _initialID.length-1; carry && i>=0; i--) 354 { 355 if (_initialID[i] == 'Z') 356 { 357 _initialID[i] = 'A'; 358 } 359 else 360 { 361 _initialID[i]++; 362 carry = false; // did not wrap round 363 } 364 } 365 return res; 366 } 367} 368/* kate: indent-width 4; replace-tabs on; */