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