View Javadoc

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.BufferedReader;
21  import java.io.IOException;
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, and ensures you cannot read past the end of the message.
34   * <p>
35   * Note: versions since 3.0 extend BufferedReader rather than Reader,
36   * and no longer change the CRLF into the local EOL. Also only DOT CR LF
37   * acts as EOF.
38   * @version $Id: DotTerminatedMessageReader.java 1301276 2012-03-15 23:51:31Z sebb $
39   */
40  public final class DotTerminatedMessageReader extends BufferedReader
41  {
42      private static final char LF = '\n';
43      private static final char CR = '\r';
44      private static final int DOT = '.';
45  
46      private boolean atBeginning;
47      private boolean eof;
48      private boolean seenCR; // was last character CR?
49  
50      /**
51       * Creates a DotTerminatedMessageReader that wraps an existing Reader
52       * input source.
53       * @param reader  The Reader input source containing the message.
54       */
55      public DotTerminatedMessageReader(Reader reader)
56      {
57          super(reader);
58          // Assumes input is at start of message
59          atBeginning = true;
60          eof = false;
61      }
62  
63      /**
64       * Reads and returns the next character in the message.  If the end of the
65       * message has been reached, returns -1.  Note that a call to this method
66       * may result in multiple reads from the underlying input stream to decode
67       * the message properly (removing doubled dots and so on).  All of
68       * this is transparent to the programmer and is only mentioned for
69       * completeness.
70       * @return The next character in the message. Returns -1 if the end of the
71       *          message has been reached.
72       * @exception IOException If an error occurs while reading the underlying
73       *            stream.
74       */
75      @Override
76      public int read() throws IOException {
77          synchronized (lock) {
78              if (eof) {
79                  return -1; // Don't allow read past EOF
80              }
81              int chint = super.read();
82              if (chint == -1) { // True EOF
83                  eof = true;
84                  return -1;
85              }
86              if (atBeginning) {
87                  atBeginning = false;
88                  if (chint == DOT) { // Have DOT
89                      mark(2); // need to check for CR LF or DOT
90                      chint = super.read();
91                      if (chint == -1) { // Should not happen
92                          // new Throwable("Trailing DOT").printStackTrace();
93                          eof = true;
94                          return DOT; // return the trailing DOT
95                      }
96                      if (chint == DOT) { // Have DOT DOT
97                          // no need to reset as we want to lose the first DOT
98                          return chint; // i.e. DOT
99                      }
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 }