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.  *      https://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.  * <p>
  31.  * 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.
  32.  * </p>
  33.  */
  34. public final class DotTerminatedMessageReader extends BufferedReader {
  35.     private static final char LF = '\n';
  36.     private static final char CR = '\r';
  37.     private static final int DOT = '.';

  38.     private boolean atBeginning;
  39.     private boolean eof;
  40.     private boolean seenCR; // was last character CR?

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

  52.     /**
  53.      * Closes the message for reading. This doesn't actually close the underlying stream. The underlying stream may still be used for communicating with the
  54.      * server and therefore is not closed.
  55.      * <p>
  56.      * 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
  57.      * 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
  58.      * will likely hang or behave improperly.
  59.      * </p>
  60.      *
  61.      * @throws IOException If an error occurs while reading the underlying stream.
  62.      */
  63.     @Override
  64.     public void close() throws IOException {
  65.         synchronized (lock) {
  66.             if (!eof) {
  67.                 while (read() != -1) {
  68.                     // read to EOF
  69.                 }
  70.             }
  71.             eof = true;
  72.             atBeginning = false;
  73.         }
  74.     }

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

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

  143.     /**
  144.      * 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
  145.      * reached.
  146.      *
  147.      * @param buffer The character array in which to store the characters.
  148.      * @return The number of characters read. Returns -1 if the end of the message has been reached.
  149.      * @throws IOException If an error occurs in reading the underlying stream.
  150.      */
  151.     @Override
  152.     public int read(final char[] buffer) throws IOException {
  153.         return read(buffer, 0, buffer.length);
  154.     }

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

  175.             final int off = offset;

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

  179.             return offset - off;
  180.         }
  181.     }

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