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 024import org.apache.commons.net.util.NetConstants; 025 026/** 027 * 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 028 * 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 029 * NNTP and POP3 produce messages of this type. 030 * <p> 031 * 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 032 * message. 033 * <p> 034 * 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. 035 */ 036public final class DotTerminatedMessageReader extends BufferedReader { 037 private static final char LF = '\n'; 038 private static final char CR = '\r'; 039 private static final int DOT = '.'; 040 041 private boolean atBeginning; 042 private boolean eof; 043 private boolean seenCR; // was last character CR? 044 045 /** 046 * Creates a DotTerminatedMessageReader that wraps an existing Reader input source. 047 * 048 * @param reader The Reader input source containing the message. 049 */ 050 public DotTerminatedMessageReader(final Reader reader) { 051 super(reader); 052 // Assumes input is at start of message 053 atBeginning = true; 054 eof = false; 055 } 056 057 /** 058 * Closes the message for reading. This doesn't actually close the underlying stream. The underlying stream may still be used for communicating with the 059 * server and therefore is not closed. 060 * <p> 061 * 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 062 * 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 063 * will likely hang or behave improperly. 064 * 065 * @throws IOException If an error occurs while reading the underlying stream. 066 */ 067 @Override 068 public void close() throws IOException { 069 synchronized (lock) { 070 if (!eof) { 071 while (read() != -1) { 072 // read to EOF 073 } 074 } 075 eof = true; 076 atBeginning = false; 077 } 078 } 079 080 /** 081 * 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 082 * 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 083 * programmer and is only mentioned for completeness. 084 * 085 * @return The next character in the message. Returns -1 if the end of the message has been reached. 086 * @throws IOException If an error occurs while reading the underlying stream. 087 */ 088 @Override 089 public int read() throws IOException { 090 synchronized (lock) { 091 if (eof) { 092 return NetConstants.EOS; // Don't allow read past EOF 093 } 094 int chint = super.read(); 095 if (chint == NetConstants.EOS) { // True EOF 096 eof = true; 097 return NetConstants.EOS; 098 } 099 if (atBeginning) { 100 atBeginning = false; 101 if (chint == DOT) { // Have DOT 102 mark(2); // need to check for CR LF or DOT 103 chint = super.read(); 104 switch (chint) { 105 case NetConstants.EOS: 106 // new Throwable("Trailing DOT").printStackTrace(); 107 eof = true; 108 return DOT; // return the trailing DOT 109 case DOT: 110 // no need to reset as we want to lose the first DOT 111 return chint; // i.e. DOT 112 case CR: 113 chint = super.read(); 114 if (chint == NetConstants.EOS) { // Still only DOT CR - should not happen 115 // new Throwable("Trailing DOT CR").printStackTrace(); 116 reset(); // So CR is picked up next time 117 return DOT; // return the trailing DOT 118 } 119 if (chint == LF) { // DOT CR LF 120 atBeginning = true; 121 eof = true; 122 // Do we need to clear the mark somehow? 123 return NetConstants.EOS; 124 } 125 break; 126 default: 127 break; 128 } 129 // Should not happen - lone DOT at beginning 130 // new Throwable("Lone DOT followed by "+(char)chint).printStackTrace(); 131 reset(); 132 return DOT; 133 } // have DOT 134 } // atBeginning 135 136 // Handle CRLF in normal flow 137 if (seenCR) { 138 seenCR = false; 139 if (chint == LF) { 140 atBeginning = true; 141 } 142 } 143 if (chint == CR) { 144 seenCR = true; 145 } 146 return chint; 147 } 148 } 149 150 /** 151 * 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 152 * reached. 153 * 154 * @param buffer The character array in which to store the characters. 155 * @return The number of characters read. Returns -1 if the end of the message has been reached. 156 * @throws IOException If an error occurs in reading the underlying stream. 157 */ 158 @Override 159 public int read(final char[] buffer) throws IOException { 160 return read(buffer, 0, buffer.length); 161 } 162 163 /** 164 * 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 165 * reached. The characters are stored in the array starting from the given offset and up to the length specified. 166 * 167 * @param buffer The character array in which to store the characters. 168 * @param offset The offset into the array at which to start storing characters. 169 * @param length The number of characters to read. 170 * @return The number of characters read. Returns -1 if the end of the message has been reached. 171 * @throws IOException If an error occurs in reading the underlying stream. 172 */ 173 @Override 174 public int read(final char[] buffer, int offset, int length) throws IOException { 175 if (length < 1) { 176 return 0; 177 } 178 int ch; 179 synchronized (lock) { 180 if ((ch = read()) == -1) { 181 return NetConstants.EOS; 182 } 183 184 final int off = offset; 185 186 do { 187 buffer[offset++] = (char) ch; 188 } while (--length > 0 && (ch = read()) != -1); 189 190 return offset - off; 191 } 192 } 193 194 /** 195 * Read a line of text. A line is considered to be terminated by carriage return followed immediately by a linefeed. This contrasts with BufferedReader 196 * which also allows other combinations. 197 * 198 * @since 3.0 199 */ 200 @Override 201 public String readLine() throws IOException { 202 final StringBuilder sb = new StringBuilder(); 203 int intch; 204 synchronized (lock) { // make thread-safe (hopefully!) 205 while ((intch = read()) != NetConstants.EOS) { 206 if (intch == LF && atBeginning) { 207 return sb.substring(0, sb.length() - 1); 208 } 209 sb.append((char) intch); 210 } 211 } 212 final String string = sb.toString(); 213 if (string.isEmpty()) { // immediate EOF 214 return null; 215 } 216 // Should not happen - EOF without CRLF 217 // new Throwable(string).printStackTrace(); 218 return string; 219 } 220}