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.BufferedReader;
021 import java.io.IOException;
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, and ensures you cannot read past the end of the message.
034 * <p>
035 * Note: versions since 3.0 extend BufferedReader rather than Reader,
036 * and no longer change the CRLF into the local EOL. Also only DOT CR LF
037 * acts as EOF.
038 * @version $Id: DotTerminatedMessageReader.java 1301276 2012-03-15 23:51:31Z sebb $
039 */
040 public final class DotTerminatedMessageReader extends BufferedReader
041 {
042 private static final char LF = '\n';
043 private static final char CR = '\r';
044 private static final int DOT = '.';
045
046 private boolean atBeginning;
047 private boolean eof;
048 private boolean seenCR; // was last character CR?
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 // Assumes input is at start of message
059 atBeginning = true;
060 eof = false;
061 }
062
063 /**
064 * Reads and returns the next character in the message. If the end of the
065 * message has been reached, returns -1. Note that a call to this method
066 * may result in multiple reads from the underlying input stream to decode
067 * the message properly (removing doubled dots and so on). All of
068 * this is transparent to the programmer and is only mentioned for
069 * completeness.
070 * @return The next character in the message. Returns -1 if the end of the
071 * message has been reached.
072 * @exception IOException If an error occurs while reading the underlying
073 * stream.
074 */
075 @Override
076 public int read() throws IOException {
077 synchronized (lock) {
078 if (eof) {
079 return -1; // Don't allow read past EOF
080 }
081 int chint = super.read();
082 if (chint == -1) { // True EOF
083 eof = true;
084 return -1;
085 }
086 if (atBeginning) {
087 atBeginning = false;
088 if (chint == DOT) { // Have DOT
089 mark(2); // need to check for CR LF or DOT
090 chint = super.read();
091 if (chint == -1) { // Should not happen
092 // new Throwable("Trailing DOT").printStackTrace();
093 eof = true;
094 return DOT; // return the trailing DOT
095 }
096 if (chint == DOT) { // Have DOT DOT
097 // no need to reset as we want to lose the first DOT
098 return chint; // i.e. DOT
099 }
100 if (chint == CR) { // Have DOT CR
101 chint = super.read();
102 if (chint == -1) { // Still only DOT CR - should not happen
103 //new Throwable("Trailing DOT CR").printStackTrace();
104 reset(); // So CR is picked up next time
105 return DOT; // return the trailing DOT
106 }
107 if (chint == LF) { // DOT CR LF
108 atBeginning = true;
109 eof = true;
110 // Do we need to clear the mark somehow?
111 return -1;
112 }
113 }
114 // Should not happen - lone DOT at beginning
115 //new Throwable("Lone DOT followed by "+(char)chint).printStackTrace();
116 reset();
117 return DOT;
118 } // have DOT
119 } // atBeginning
120
121 // Handle CRLF in normal flow
122 if (seenCR) {
123 seenCR = false;
124 if (chint == LF) {
125 atBeginning = true;
126 }
127 }
128 if (chint == CR) {
129 seenCR = true;
130 }
131 return chint;
132 }
133 }
134
135
136 /**
137 * Reads the next characters from the message into an array and
138 * returns the number of characters read. Returns -1 if the end of the
139 * message has been reached.
140 * @param buffer The character array in which to store the characters.
141 * @return The number of characters read. Returns -1 if the
142 * end of the message has been reached.
143 * @exception IOException If an error occurs in reading the underlying
144 * stream.
145 */
146 @Override
147 public int read(char[] buffer) throws IOException
148 {
149 return read(buffer, 0, buffer.length);
150 }
151
152 /**
153 * Reads the next characters from the message into an array and
154 * returns the number of characters read. Returns -1 if the end of the
155 * message has been reached. The characters are stored in the array
156 * starting from the given offset and up to the length specified.
157 * @param buffer The character array in which to store the characters.
158 * @param offset The offset into the array at which to start storing
159 * characters.
160 * @param length The number of characters to read.
161 * @return The number of characters read. Returns -1 if the
162 * end of the message has been reached.
163 * @exception IOException If an error occurs in reading the underlying
164 * stream.
165 */
166 @Override
167 public int read(char[] buffer, int offset, int length) throws IOException
168 {
169 if (length < 1)
170 {
171 return 0;
172 }
173 int ch;
174 synchronized (lock)
175 {
176 if ((ch = read()) == -1)
177 {
178 return -1;
179 }
180
181 int off = offset;
182
183 do
184 {
185 buffer[offset++] = (char) ch;
186 }
187 while (--length > 0 && (ch = read()) != -1);
188
189 return (offset - off);
190 }
191 }
192
193 /**
194 * Closes the message for reading. This doesn't actually close the
195 * underlying stream. The underlying stream may still be used for
196 * communicating with the server and therefore is not closed.
197 * <p>
198 * If the end of the message has not yet been reached, this method
199 * will read the remainder of the message until it reaches the end,
200 * so that the underlying stream may continue to be used properly
201 * for communicating with the server. If you do not fully read
202 * a message, you MUST close it, otherwise your program will likely
203 * hang or behave improperly.
204 * @exception IOException If an error occurs while reading the
205 * underlying stream.
206 */
207 @Override
208 public void close() throws IOException
209 {
210 synchronized (lock)
211 {
212 if (!eof)
213 {
214 while (read() != -1)
215 {
216 // read to EOF
217 }
218 }
219 eof = true;
220 atBeginning = false;
221 }
222 }
223
224 /**
225 * Read a line of text.
226 * A line is considered to be terminated by carriage return followed immediately by a linefeed.
227 * This contrasts with BufferedReader which also allows other combinations.
228 * @since 3.0
229 */
230 @Override
231 public String readLine() throws IOException {
232 StringBuilder sb = new StringBuilder();
233 int intch;
234 synchronized(lock) { // make thread-safe (hopefully!)
235 while((intch = read()) != -1)
236 {
237 if (intch == LF && atBeginning) {
238 return sb.substring(0, sb.length()-1);
239 }
240 sb.append((char) intch);
241 }
242 }
243 String string = sb.toString();
244 if (string.length() == 0) { // immediate EOF
245 return null;
246 }
247 // Should not happen - EOF without CRLF
248 //new Throwable(string).printStackTrace();
249 return string;
250 }
251 }