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.io; 019 020 import java.io.IOException; 021 import java.io.PushbackReader; 022 import java.io.Reader; 023 024 /** 025 * DotTerminatedMessageReader is a class used to read messages from a 026 * server that are terminated by a single dot followed by a 027 * <CR><LF> 028 * sequence and with double dots appearing at the begining of lines which 029 * do not signal end of message yet start with a dot. Various Internet 030 * protocols such as NNTP and POP3 produce messages of this type. 031 * <p> 032 * This class handles stripping of the duplicate period at the beginning 033 * of lines starting with a period, converts NETASCII newlines to the 034 * local line separator format, truncates the end of message indicator, 035 * and ensures you cannot read past the end of the message. 036 * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a> 037 * @version $Id: DotTerminatedMessageReader.java 922748 2010-03-14 03:23:18Z sebb $ 038 */ 039 public final class DotTerminatedMessageReader extends Reader 040 { 041 private static final String LS = System.getProperty("line.separator"); 042 char[] LS_CHARS; 043 044 private boolean atBeginning; 045 private boolean eof; 046 private int pos; 047 private char[] internalBuffer; 048 private PushbackReader internalReader; 049 050 /** 051 * Creates a DotTerminatedMessageReader that wraps an existing Reader 052 * input source. 053 * @param reader The Reader input source containing the message. 054 */ 055 public DotTerminatedMessageReader(Reader reader) 056 { 057 super(reader); 058 LS_CHARS = LS.toCharArray(); 059 internalBuffer = new char[LS_CHARS.length + 3]; 060 pos = internalBuffer.length; 061 // Assumes input is at start of message 062 atBeginning = true; 063 eof = false; 064 internalReader = new PushbackReader(reader); 065 } 066 067 /** 068 * Reads and returns the next character in the message. If the end of the 069 * message has been reached, returns -1. Note that a call to this method 070 * may result in multiple reads from the underlying input stream to decode 071 * the message properly (removing doubled dots and so on). All of 072 * this is transparent to the programmer and is only mentioned for 073 * completeness. 074 * @return The next character in the message. Returns -1 if the end of the 075 * message has been reached. 076 * @exception IOException If an error occurs while reading the underlying 077 * stream. 078 */ 079 @Override 080 public int read() throws IOException 081 { 082 int ch; 083 084 synchronized (lock) 085 { 086 if (pos < internalBuffer.length) 087 { 088 return internalBuffer[pos++]; 089 } 090 091 if (eof) 092 { 093 return -1; 094 } 095 096 if ((ch = internalReader.read()) == -1) 097 { 098 eof = true; 099 return -1; 100 } 101 102 if (atBeginning) 103 { 104 atBeginning = false; 105 if (ch == '.') 106 { 107 ch = internalReader.read(); 108 109 if (ch != '.') 110 { 111 // read newline 112 eof = true; 113 internalReader.read(); 114 return -1; 115 } 116 else 117 { 118 return '.'; 119 } 120 } 121 } 122 123 if (ch == '\r') 124 { 125 ch = internalReader.read(); 126 127 if (ch == '\n') 128 { 129 ch = internalReader.read(); 130 131 if (ch == '.') 132 { 133 ch = internalReader.read(); 134 135 if (ch != '.') 136 { 137 // read newline and indicate end of file 138 internalReader.read(); 139 eof = true; 140 } 141 else 142 { 143 internalBuffer[--pos] = (char) ch; 144 } 145 } 146 else 147 { 148 internalReader.unread(ch); 149 } 150 151 pos -= LS_CHARS.length; 152 System.arraycopy(LS_CHARS, 0, internalBuffer, pos, 153 LS_CHARS.length); 154 ch = internalBuffer[pos++]; 155 } 156 else if (ch == '\r') { 157 internalReader.unread(ch); 158 } 159 else 160 { 161 internalBuffer[--pos] = (char) ch; 162 return '\r'; 163 } 164 } 165 166 return ch; 167 } 168 } 169 170 /** 171 * Reads the next characters from the message into an array and 172 * returns the number of characters read. Returns -1 if the end of the 173 * message has been reached. 174 * @param buffer The character array in which to store the characters. 175 * @return The number of characters read. Returns -1 if the 176 * end of the message has been reached. 177 * @exception IOException If an error occurs in reading the underlying 178 * stream. 179 */ 180 @Override 181 public int read(char[] buffer) throws IOException 182 { 183 return read(buffer, 0, buffer.length); 184 } 185 186 /** 187 * Reads the next characters from the message into an array and 188 * returns the number of characters read. Returns -1 if the end of the 189 * message has been reached. The characters are stored in the array 190 * starting from the given offset and up to the length specified. 191 * @param buffer The character array in which to store the characters. 192 * @param offset The offset into the array at which to start storing 193 * characters. 194 * @param length The number of characters to read. 195 * @return The number of characters read. Returns -1 if the 196 * end of the message has been reached. 197 * @exception IOException If an error occurs in reading the underlying 198 * stream. 199 */ 200 @Override 201 public int read(char[] buffer, int offset, int length) throws IOException 202 { 203 int ch, off; 204 synchronized (lock) 205 { 206 if (length < 1) 207 { 208 return 0; 209 } 210 if ((ch = read()) == -1) 211 { 212 return -1; 213 } 214 off = offset; 215 216 do 217 { 218 buffer[offset++] = (char) ch; 219 } 220 while (--length > 0 && (ch = read()) != -1); 221 222 return (offset - off); 223 } 224 } 225 226 /** 227 * Determines if the message is ready to be read. 228 * @return True if the message is ready to be read, false if not. 229 * @exception IOException If an error occurs while checking the underlying 230 * stream. 231 */ 232 @Override 233 public boolean ready() throws IOException 234 { 235 synchronized (lock) 236 { 237 return (pos < internalBuffer.length || internalReader.ready()); 238 } 239 } 240 241 /** 242 * Closes the message for reading. This doesn't actually close the 243 * underlying stream. The underlying stream may still be used for 244 * communicating with the server and therefore is not closed. 245 * <p> 246 * If the end of the message has not yet been reached, this method 247 * will read the remainder of the message until it reaches the end, 248 * so that the underlying stream may continue to be used properly 249 * for communicating with the server. If you do not fully read 250 * a message, you MUST close it, otherwise your program will likely 251 * hang or behave improperly. 252 * @exception IOException If an error occurs while reading the 253 * underlying stream. 254 */ 255 @Override 256 public void close() throws IOException 257 { 258 synchronized (lock) 259 { 260 if (internalReader == null) 261 { 262 return; 263 } 264 265 if (!eof) 266 { 267 while (read() != -1) 268 { 269 // read to EOF 270 } 271 } 272 eof = true; 273 atBeginning = false; 274 pos = internalBuffer.length; 275 internalReader = null; 276 } 277 } 278 }