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 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 import org.apache.commons.net.util.NetConstants; 25 26 /** 27 * 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 28 * 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 29 * NNTP and POP3 produce messages of this type. 30 * <p> 31 * 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 32 * message. 33 * </p> 34 * <p> 35 * 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. 36 * </p> 37 */ 38 public final class DotTerminatedMessageReader extends BufferedReader { 39 private static final char LF = '\n'; 40 private static final char CR = '\r'; 41 private static final int DOT = '.'; 42 43 private boolean atBeginning; 44 private boolean eof; 45 private boolean seenCR; // was last character CR? 46 47 /** 48 * Creates a DotTerminatedMessageReader that wraps an existing Reader input source. 49 * 50 * @param reader The Reader input source containing the message. 51 */ 52 public DotTerminatedMessageReader(final Reader reader) { 53 super(reader); 54 // Assumes input is at start of message 55 atBeginning = true; 56 eof = false; 57 } 58 59 /** 60 * Closes the message for reading. This doesn't actually close the underlying stream. The underlying stream may still be used for communicating with the 61 * server and therefore is not closed. 62 * <p> 63 * 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 64 * 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 65 * will likely hang or behave improperly. 66 * </p> 67 * 68 * @throws IOException If an error occurs while reading the underlying stream. 69 */ 70 @Override 71 public void close() throws IOException { 72 synchronized (lock) { 73 if (!eof) { 74 while (read() != -1) { 75 // read to EOF 76 } 77 } 78 eof = true; 79 atBeginning = false; 80 } 81 } 82 83 /** 84 * 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 85 * 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 86 * programmer and is only mentioned for completeness. 87 * 88 * @return The next character in the message. Returns -1 if the end of the message has been reached. 89 * @throws IOException If an error occurs while reading the underlying stream. 90 */ 91 @Override 92 public int read() throws IOException { 93 synchronized (lock) { 94 if (eof) { 95 return NetConstants.EOS; // Don't allow read past EOF 96 } 97 int chint = super.read(); 98 if (chint == NetConstants.EOS) { // True EOF 99 eof = true; 100 return NetConstants.EOS; 101 } 102 if (atBeginning) { 103 atBeginning = false; 104 if (chint == DOT) { // Have DOT 105 mark(2); // need to check for CR LF or DOT 106 chint = super.read(); 107 switch (chint) { 108 case NetConstants.EOS: 109 // new Throwable("Trailing DOT").printStackTrace(); 110 eof = true; 111 return DOT; // return the trailing DOT 112 case DOT: 113 // no need to reset as we want to lose the first DOT 114 return chint; // i.e. DOT 115 case CR: 116 chint = super.read(); 117 if (chint == NetConstants.EOS) { // Still only DOT CR - should not happen 118 // new Throwable("Trailing DOT CR").printStackTrace(); 119 reset(); // So CR is picked up next time 120 return DOT; // return the trailing DOT 121 } 122 if (chint == LF) { // DOT CR LF 123 atBeginning = true; 124 eof = true; 125 // Do we need to clear the mark somehow? 126 return NetConstants.EOS; 127 } 128 break; 129 default: 130 break; 131 } 132 // Should not happen - lone DOT at beginning 133 // new Throwable("Lone DOT followed by "+(char)chint).printStackTrace(); 134 reset(); 135 return DOT; 136 } // have DOT 137 } // atBeginning 138 139 // Handle CRLF in normal flow 140 if (seenCR) { 141 seenCR = false; 142 if (chint == LF) { 143 atBeginning = true; 144 } 145 } 146 if (chint == CR) { 147 seenCR = true; 148 } 149 return chint; 150 } 151 } 152 153 /** 154 * 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 155 * reached. 156 * 157 * @param buffer The character array in which to store the characters. 158 * @return The number of characters read. Returns -1 if the end of the message has been reached. 159 * @throws IOException If an error occurs in reading the underlying stream. 160 */ 161 @Override 162 public int read(final char[] buffer) throws IOException { 163 return read(buffer, 0, buffer.length); 164 } 165 166 /** 167 * 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 168 * reached. The characters are stored in the array starting from the given offset and up to the length specified. 169 * 170 * @param buffer The character array in which to store the characters. 171 * @param offset The offset into the array at which to start storing characters. 172 * @param length The number of characters to read. 173 * @return The number of characters read. Returns -1 if the end of the message has been reached. 174 * @throws IOException If an error occurs in reading the underlying stream. 175 */ 176 @Override 177 public int read(final char[] buffer, int offset, int length) throws IOException { 178 if (length < 1) { 179 return 0; 180 } 181 int ch; 182 synchronized (lock) { 183 if ((ch = read()) == -1) { 184 return NetConstants.EOS; 185 } 186 187 final int off = offset; 188 189 do { 190 buffer[offset++] = (char) ch; 191 } while (--length > 0 && (ch = read()) != -1); 192 193 return offset - off; 194 } 195 } 196 197 /** 198 * Reads a line of text. A line is considered to be terminated by carriage return followed immediately by a linefeed. This contrasts with BufferedReader 199 * which also allows other combinations. 200 * 201 * @since 3.0 202 */ 203 @Override 204 public String readLine() throws IOException { 205 final StringBuilder sb = new StringBuilder(); 206 int intch; 207 synchronized (lock) { // make thread-safe (hopefully!) 208 while ((intch = read()) != NetConstants.EOS) { 209 if (intch == LF && atBeginning) { 210 return sb.substring(0, sb.length() - 1); 211 } 212 sb.append((char) intch); 213 } 214 } 215 final String string = sb.toString(); 216 if (string.isEmpty()) { // immediate EOF 217 return null; 218 } 219 // Should not happen - EOF without CRLF 220 // new Throwable(string).printStackTrace(); 221 return string; 222 } 223 }