DotTerminatedMessageReader.java

  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. package org.apache.commons.net.io;

  18. import java.io.BufferedReader;
  19. import java.io.IOException;
  20. import java.io.Reader;

  21. import org.apache.commons.net.util.NetConstants;

  22. /**
  23.  * DotTerminatedMessageReader is a class used to read messages from a server that are terminated by a single dot followed by a <CR><LF> sequence and
  24.  * with double dots appearing at the beginning of lines which do not signal end of message yet start with a dot. Various Internet protocols such as
  25.  * NNTP and POP3 produce messages of this type.
  26.  * <p>
  27.  * This class handles stripping of the duplicate period at the beginning of lines starting with a period, and ensures you cannot read past the end of the
  28.  * message.
  29.  * <p>
  30.  * Note: versions since 3.0 extend BufferedReader rather than Reader, and no longer change the CRLF into the local EOL. Also, only DOT CR LF acts as EOF.
  31.  */
  32. public final class DotTerminatedMessageReader extends BufferedReader {
  33.     private static final char LF = '\n';
  34.     private static final char CR = '\r';
  35.     private static final int DOT = '.';

  36.     private boolean atBeginning;
  37.     private boolean eof;
  38.     private boolean seenCR; // was last character CR?

  39.     /**
  40.      * Creates a DotTerminatedMessageReader that wraps an existing Reader input source.
  41.      *
  42.      * @param reader The Reader input source containing the message.
  43.      */
  44.     public DotTerminatedMessageReader(final Reader reader) {
  45.         super(reader);
  46.         // Assumes input is at start of message
  47.         atBeginning = true;
  48.         eof = false;
  49.     }

  50.     /**
  51.      * Closes the message for reading. This doesn't actually close the underlying stream. The underlying stream may still be used for communicating with the
  52.      * server and therefore is not closed.
  53.      * <p>
  54.      * If the end of the message has not yet been reached, this method will read the remainder of the message until it reaches the end, so that the underlying
  55.      * stream may continue to be used properly for communicating with the server. If you do not fully read a message, you MUST close it, otherwise your program
  56.      * will likely hang or behave improperly.
  57.      *
  58.      * @throws IOException If an error occurs while reading the underlying stream.
  59.      */
  60.     @Override
  61.     public void close() throws IOException {
  62.         synchronized (lock) {
  63.             if (!eof) {
  64.                 while (read() != -1) {
  65.                     // read to EOF
  66.                 }
  67.             }
  68.             eof = true;
  69.             atBeginning = false;
  70.         }
  71.     }

  72.     /**
  73.      * Reads and returns the next character in the message. If the end of the message has been reached, returns -1. Note that a call to this method may result
  74.      * in multiple reads from the underlying input stream to decode the message properly (removing doubled dots and so on). All of this is transparent to the
  75.      * programmer and is only mentioned for completeness.
  76.      *
  77.      * @return The next character in the message. Returns -1 if the end of the message has been reached.
  78.      * @throws IOException If an error occurs while reading the underlying stream.
  79.      */
  80.     @Override
  81.     public int read() throws IOException {
  82.         synchronized (lock) {
  83.             if (eof) {
  84.                 return NetConstants.EOS; // Don't allow read past EOF
  85.             }
  86.             int chint = super.read();
  87.             if (chint == NetConstants.EOS) { // True EOF
  88.                 eof = true;
  89.                 return NetConstants.EOS;
  90.             }
  91.             if (atBeginning) {
  92.                 atBeginning = false;
  93.                 if (chint == DOT) { // Have DOT
  94.                     mark(2); // need to check for CR LF or DOT
  95.                     chint = super.read();
  96.                     switch (chint) {
  97.                     case NetConstants.EOS:
  98.                         // new Throwable("Trailing DOT").printStackTrace();
  99.                         eof = true;
  100.                         return DOT; // return the trailing DOT
  101.                     case DOT:
  102.                         // no need to reset as we want to lose the first DOT
  103.                         return chint; // i.e. DOT
  104.                     case CR:
  105.                         chint = super.read();
  106.                         if (chint == NetConstants.EOS) { // Still only DOT CR - should not happen
  107.                             // new Throwable("Trailing DOT CR").printStackTrace();
  108.                             reset(); // So CR is picked up next time
  109.                             return DOT; // return the trailing DOT
  110.                         }
  111.                         if (chint == LF) { // DOT CR LF
  112.                             atBeginning = true;
  113.                             eof = true;
  114.                             // Do we need to clear the mark somehow?
  115.                             return NetConstants.EOS;
  116.                         }
  117.                         break;
  118.                     default:
  119.                         break;
  120.                     }
  121.                     // Should not happen - lone DOT at beginning
  122.                     // new Throwable("Lone DOT followed by "+(char)chint).printStackTrace();
  123.                     reset();
  124.                     return DOT;
  125.                 } // have DOT
  126.             } // atBeginning

  127.             // Handle CRLF in normal flow
  128.             if (seenCR) {
  129.                 seenCR = false;
  130.                 if (chint == LF) {
  131.                     atBeginning = true;
  132.                 }
  133.             }
  134.             if (chint == CR) {
  135.                 seenCR = true;
  136.             }
  137.             return chint;
  138.         }
  139.     }

  140.     /**
  141.      * Reads the next characters from the message into an array and returns the number of characters read. Returns -1 if the end of the message has been
  142.      * reached.
  143.      *
  144.      * @param buffer The character array in which to store the characters.
  145.      * @return The number of characters read. Returns -1 if the end of the message has been reached.
  146.      * @throws IOException If an error occurs in reading the underlying stream.
  147.      */
  148.     @Override
  149.     public int read(final char[] buffer) throws IOException {
  150.         return read(buffer, 0, buffer.length);
  151.     }

  152.     /**
  153.      * Reads the next characters from the message into an array and returns the number of characters read. Returns -1 if the end of the message has been
  154.      * reached. The characters are stored in the array starting from the given offset and up to the length specified.
  155.      *
  156.      * @param buffer The character array in which to store the characters.
  157.      * @param offset The offset into the array at which to start storing characters.
  158.      * @param length The number of characters to read.
  159.      * @return The number of characters read. Returns -1 if the end of the message has been reached.
  160.      * @throws IOException If an error occurs in reading the underlying stream.
  161.      */
  162.     @Override
  163.     public int read(final char[] buffer, int offset, int length) throws IOException {
  164.         if (length < 1) {
  165.             return 0;
  166.         }
  167.         int ch;
  168.         synchronized (lock) {
  169.             if ((ch = read()) == -1) {
  170.                 return NetConstants.EOS;
  171.             }

  172.             final int off = offset;

  173.             do {
  174.                 buffer[offset++] = (char) ch;
  175.             } while (--length > 0 && (ch = read()) != -1);

  176.             return offset - off;
  177.         }
  178.     }

  179.     /**
  180.      * Read a line of text. A line is considered to be terminated by carriage return followed immediately by a linefeed. This contrasts with BufferedReader
  181.      * which also allows other combinations.
  182.      *
  183.      * @since 3.0
  184.      */
  185.     @Override
  186.     public String readLine() throws IOException {
  187.         final StringBuilder sb = new StringBuilder();
  188.         int intch;
  189.         synchronized (lock) { // make thread-safe (hopefully!)
  190.             while ((intch = read()) != NetConstants.EOS) {
  191.                 if (intch == LF && atBeginning) {
  192.                     return sb.substring(0, sb.length() - 1);
  193.                 }
  194.                 sb.append((char) intch);
  195.             }
  196.         }
  197.         final String string = sb.toString();
  198.         if (string.isEmpty()) { // immediate EOF
  199.             return null;
  200.         }
  201.         // Should not happen - EOF without CRLF
  202.         // new Throwable(string).printStackTrace();
  203.         return string;
  204.     }
  205. }