1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.net.io;
19
20 import java.io.IOException;
21 import java.io.PushbackReader;
22 import java.io.Reader;
23
24 /**
25 * DotTerminatedMessageReader is a class used to read messages from a
26 * server that are terminated by a single dot followed by a
27 * <CR><LF>
28 * sequence and with double dots appearing at the begining of lines which
29 * do not signal end of message yet start with a dot. Various Internet
30 * protocols such as NNTP and POP3 produce messages of this type.
31 * <p>
32 * This class handles stripping of the duplicate period at the beginning
33 * of lines starting with a period, converts NETASCII newlines to the
34 * local line separator format, truncates the end of message indicator,
35 * and ensures you cannot read past the end of the message.
36 * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a>
37 * @version $Id: DotTerminatedMessageReader.java 636825 2008-03-13 18:34:52Z sebb $
38 */
39 public final class DotTerminatedMessageReader extends Reader
40 {
41 private static final String LS;
42 private static final char[] LS_CHARS;
43
44 static
45 {
46 LS = System.getProperty("line.separator");
47 LS_CHARS = LS.toCharArray();
48 }
49
50 private boolean atBeginning;
51 private boolean eof;
52 private int pos;
53 private char[] internalBuffer;
54 private PushbackReader internalReader;
55
56 /**
57 * Creates a DotTerminatedMessageReader that wraps an existing Reader
58 * input source.
59 * @param reader The Reader input source containing the message.
60 */
61 public DotTerminatedMessageReader(Reader reader)
62 {
63 super(reader);
64 internalBuffer = new char[LS_CHARS.length + 3];
65 pos = internalBuffer.length;
66 // Assumes input is at start of message
67 atBeginning = true;
68 eof = false;
69 internalReader = new PushbackReader(reader);
70 }
71
72 /**
73 * Reads and returns the next character in the message. If the end of the
74 * message has been reached, returns -1. Note that a call to this method
75 * may result in multiple reads from the underlying input stream to decode
76 * the message properly (removing doubled dots and so on). All of
77 * this is transparent to the programmer and is only mentioned for
78 * completeness.
79 * @return The next character in the message. Returns -1 if the end of the
80 * message has been reached.
81 * @exception IOException If an error occurs while reading the underlying
82 * stream.
83 */
84 @Override
85 public int read() throws IOException
86 {
87 int ch;
88
89 synchronized (lock)
90 {
91 if (pos < internalBuffer.length)
92 {
93 return internalBuffer[pos++];
94 }
95
96 if (eof)
97 {
98 return -1;
99 }
100
101 if ((ch = internalReader.read()) == -1)
102 {
103 eof = true;
104 return -1;
105 }
106
107 if (atBeginning)
108 {
109 atBeginning = false;
110 if (ch == '.')
111 {
112 ch = internalReader.read();
113
114 if (ch != '.')
115 {
116 // read newline
117 eof = true;
118 internalReader.read();
119 return -1;
120 }
121 else
122 {
123 return '.';
124 }
125 }
126 }
127
128 if (ch == '\r')
129 {
130 ch = internalReader.read();
131
132 if (ch == '\n')
133 {
134 ch = internalReader.read();
135
136 if (ch == '.')
137 {
138 ch = internalReader.read();
139
140 if (ch != '.')
141 {
142 // read newline and indicate end of file
143 internalReader.read();
144 eof = true;
145 }
146 else
147 {
148 internalBuffer[--pos] = (char) ch;
149 }
150 }
151 else
152 {
153 internalReader.unread(ch);
154 }
155
156 pos -= LS_CHARS.length;
157 System.arraycopy(LS_CHARS, 0, internalBuffer, pos,
158 LS_CHARS.length);
159 ch = internalBuffer[pos++];
160 }
161 else
162 {
163 internalBuffer[--pos] = (char) ch;
164 return '\r';
165 }
166 }
167
168 return ch;
169 }
170 }
171
172 /**
173 * Reads the next characters from the message into an array and
174 * returns the number of characters read. Returns -1 if the end of the
175 * message has been reached.
176 * @param buffer The character array in which to store the characters.
177 * @return The number of characters read. Returns -1 if the
178 * end of the message has been reached.
179 * @exception IOException If an error occurs in reading the underlying
180 * stream.
181 */
182 @Override
183 public int read(char[] buffer) throws IOException
184 {
185 return read(buffer, 0, buffer.length);
186 }
187
188 /**
189 * Reads the next characters from the message into an array and
190 * returns the number of characters read. Returns -1 if the end of the
191 * message has been reached. The characters are stored in the array
192 * starting from the given offset and up to the length specified.
193 * @param buffer The character array in which to store the characters.
194 * @param offset The offset into the array at which to start storing
195 * characters.
196 * @param length The number of characters to read.
197 * @return The number of characters read. Returns -1 if the
198 * end of the message has been reached.
199 * @exception IOException If an error occurs in reading the underlying
200 * stream.
201 */
202 @Override
203 public int read(char[] buffer, int offset, int length) throws IOException
204 {
205 int ch, off;
206 synchronized (lock)
207 {
208 if (length < 1)
209 {
210 return 0;
211 }
212 if ((ch = read()) == -1)
213 {
214 return -1;
215 }
216 off = offset;
217
218 do
219 {
220 buffer[offset++] = (char) ch;
221 }
222 while (--length > 0 && (ch = read()) != -1);
223
224 return (offset - off);
225 }
226 }
227
228 /**
229 * Determines if the message is ready to be read.
230 * @return True if the message is ready to be read, false if not.
231 * @exception IOException If an error occurs while checking the underlying
232 * stream.
233 */
234 @Override
235 public boolean ready() throws IOException
236 {
237 synchronized (lock)
238 {
239 return (pos < internalBuffer.length || internalReader.ready());
240 }
241 }
242
243 /**
244 * Closes the message for reading. This doesn't actually close the
245 * underlying stream. The underlying stream may still be used for
246 * communicating with the server and therefore is not closed.
247 * <p>
248 * If the end of the message has not yet been reached, this method
249 * will read the remainder of the message until it reaches the end,
250 * so that the underlying stream may continue to be used properly
251 * for communicating with the server. If you do not fully read
252 * a message, you MUST close it, otherwise your program will likely
253 * hang or behave improperly.
254 * @exception IOException If an error occurs while reading the
255 * underlying stream.
256 */
257 @Override
258 public void close() throws IOException
259 {
260 synchronized (lock)
261 {
262 if (internalReader == null)
263 {
264 return;
265 }
266
267 if (!eof)
268 {
269 while (read() != -1)
270 {
271 ;
272 }
273 }
274 eof = true;
275 atBeginning = false;
276 pos = internalBuffer.length;
277 internalReader = null;
278 }
279 }
280 }