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 }