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
018package org.apache.commons.net.io;
019
020import java.io.BufferedReader;
021import java.io.IOException;
022import 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 1747119 2016-06-07 02:22:24Z ggregory $
039 */
040public 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     * @throws 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     * @throws 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     * @throws 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     * @throws 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}