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 }