001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.net.io; 019 020import java.io.BufferedReader; 021import java.io.IOException; 022import java.io.Reader; 023 024/** 025 * DotTerminatedMessageReader is a class used to read messages from a 026 * server that are terminated by a single dot followed by a 027 * <CR><LF> 028 * sequence and with double dots appearing at the begining of lines which 029 * do not signal end of message yet start with a dot. Various Internet 030 * protocols such as NNTP and POP3 produce messages of this type. 031 * <p> 032 * This class handles stripping of the duplicate period at the beginning 033 * of lines starting with a period, and ensures you cannot read past the end of the message. 034 * <p> 035 * Note: versions since 3.0 extend BufferedReader rather than Reader, 036 * and no longer change the CRLF into the local EOL. Also only DOT CR LF 037 * acts as EOF. 038 */ 039public final class DotTerminatedMessageReader extends BufferedReader 040{ 041 private static final char LF = '\n'; 042 private static final char CR = '\r'; 043 private static final int DOT = '.'; 044 045 private boolean atBeginning; 046 private boolean eof; 047 private boolean seenCR; // was last character CR? 048 049 /** 050 * Creates a DotTerminatedMessageReader that wraps an existing Reader 051 * input source. 052 * @param reader The Reader input source containing the message. 053 */ 054 public DotTerminatedMessageReader(final Reader reader) 055 { 056 super(reader); 057 // Assumes input is at start of message 058 atBeginning = true; 059 eof = false; 060 } 061 062 /** 063 * Reads and returns the next character in the message. If the end of the 064 * message has been reached, returns -1. Note that a call to this method 065 * may result in multiple reads from the underlying input stream to decode 066 * the message properly (removing doubled dots and so on). All of 067 * this is transparent to the programmer and is only mentioned for 068 * completeness. 069 * @return The next character in the message. Returns -1 if the end of the 070 * message has been reached. 071 * @throws IOException If an error occurs while reading the underlying 072 * stream. 073 */ 074 @Override 075 public int read() throws IOException { 076 synchronized (lock) { 077 if (eof) { 078 return -1; // Don't allow read past EOF 079 } 080 int chint = super.read(); 081 if (chint == -1) { // True EOF 082 eof = true; 083 return -1; 084 } 085 if (atBeginning) { 086 atBeginning = false; 087 if (chint == DOT) { // Have DOT 088 mark(2); // need to check for CR LF or DOT 089 chint = super.read(); 090 if (chint == -1) { // Should not happen 091 // new Throwable("Trailing DOT").printStackTrace(); 092 eof = true; 093 return DOT; // return the trailing DOT 094 } 095 if (chint == DOT) { // Have DOT DOT 096 // no need to reset as we want to lose the first DOT 097 return chint; // i.e. DOT 098 } 099 if (chint == CR) { // Have DOT CR 100 chint = super.read(); 101 if (chint == -1) { // Still only DOT CR - should not happen 102 //new Throwable("Trailing DOT CR").printStackTrace(); 103 reset(); // So CR is picked up next time 104 return DOT; // return the trailing DOT 105 } 106 if (chint == LF) { // DOT CR LF 107 atBeginning = true; 108 eof = true; 109 // Do we need to clear the mark somehow? 110 return -1; 111 } 112 } 113 // Should not happen - lone DOT at beginning 114 //new Throwable("Lone DOT followed by "+(char)chint).printStackTrace(); 115 reset(); 116 return DOT; 117 } // have DOT 118 } // atBeginning 119 120 // Handle CRLF in normal flow 121 if (seenCR) { 122 seenCR = false; 123 if (chint == LF) { 124 atBeginning = true; 125 } 126 } 127 if (chint == CR) { 128 seenCR = true; 129 } 130 return chint; 131 } 132 } 133 134 135 /** 136 * Reads the next characters from the message into an array and 137 * returns the number of characters read. Returns -1 if the end of the 138 * message has been reached. 139 * @param buffer The character array in which to store the characters. 140 * @return The number of characters read. Returns -1 if the 141 * end of the message has been reached. 142 * @throws IOException If an error occurs in reading the underlying 143 * stream. 144 */ 145 @Override 146 public int read(final char[] buffer) throws IOException 147 { 148 return read(buffer, 0, buffer.length); 149 } 150 151 /** 152 * Reads the next characters from the message into an array and 153 * returns the number of characters read. Returns -1 if the end of the 154 * message has been reached. The characters are stored in the array 155 * starting from the given offset and up to the length specified. 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 158 * characters. 159 * @param length The number of characters to read. 160 * @return The number of characters read. Returns -1 if the 161 * end of the message has been reached. 162 * @throws IOException If an error occurs in reading the underlying 163 * stream. 164 */ 165 @Override 166 public int read(final char[] buffer, int offset, int length) throws IOException 167 { 168 if (length < 1) 169 { 170 return 0; 171 } 172 int ch; 173 synchronized (lock) 174 { 175 if ((ch = read()) == -1) 176 { 177 return -1; 178 } 179 180 final int off = offset; 181 182 do 183 { 184 buffer[offset++] = (char) ch; 185 } 186 while (--length > 0 && (ch = read()) != -1); 187 188 return offset - off; 189 } 190 } 191 192 /** 193 * Closes the message for reading. This doesn't actually close the 194 * underlying stream. The underlying stream may still be used for 195 * communicating with the server and therefore is not closed. 196 * <p> 197 * If the end of the message has not yet been reached, this method 198 * will read the remainder of the message until it reaches the end, 199 * so that the underlying stream may continue to be used properly 200 * for communicating with the server. If you do not fully read 201 * a message, you MUST close it, otherwise your program will likely 202 * hang or behave improperly. 203 * @throws IOException If an error occurs while reading the 204 * underlying stream. 205 */ 206 @Override 207 public void close() throws IOException 208 { 209 synchronized (lock) 210 { 211 if (!eof) 212 { 213 while (read() != -1) 214 { 215 // read to EOF 216 } 217 } 218 eof = true; 219 atBeginning = false; 220 } 221 } 222 223 /** 224 * Read a line of text. 225 * A line is considered to be terminated by carriage return followed immediately by a linefeed. 226 * This contrasts with BufferedReader which also allows other combinations. 227 * @since 3.0 228 */ 229 @Override 230 public String readLine() throws IOException { 231 final StringBuilder sb = new StringBuilder(); 232 int intch; 233 synchronized(lock) { // make thread-safe (hopefully!) 234 while((intch = read()) != -1) 235 { 236 if (intch == LF && atBeginning) { 237 return sb.substring(0, sb.length()-1); 238 } 239 sb.append((char) intch); 240 } 241 } 242 final String string = sb.toString(); 243 if (string.isEmpty()) { // immediate EOF 244 return null; 245 } 246 // Should not happen - EOF without CRLF 247 //new Throwable(string).printStackTrace(); 248 return string; 249 } 250}