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 018 package org.apache.commons.net.io; 019 020 import java.io.IOException; 021 import java.io.PushbackReader; 022 import 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, converts NETASCII newlines to the 034 * local line separator format, truncates the end of message indicator, 035 * and ensures you cannot read past the end of the message. 036 * @author <a href="mailto:savarese@apache.org">Daniel F. Savarese</a> 037 * @version $Id: DotTerminatedMessageReader.java 636825 2008-03-13 18:34:52Z sebb $ 038 */ 039 public final class DotTerminatedMessageReader extends Reader 040 { 041 private static final String LS; 042 private static final char[] LS_CHARS; 043 044 static 045 { 046 LS = System.getProperty("line.separator"); 047 LS_CHARS = LS.toCharArray(); 048 } 049 050 private boolean atBeginning; 051 private boolean eof; 052 private int pos; 053 private char[] internalBuffer; 054 private PushbackReader internalReader; 055 056 /** 057 * Creates a DotTerminatedMessageReader that wraps an existing Reader 058 * input source. 059 * @param reader The Reader input source containing the message. 060 */ 061 public DotTerminatedMessageReader(Reader reader) 062 { 063 super(reader); 064 internalBuffer = new char[LS_CHARS.length + 3]; 065 pos = internalBuffer.length; 066 // Assumes input is at start of message 067 atBeginning = true; 068 eof = false; 069 internalReader = new PushbackReader(reader); 070 } 071 072 /** 073 * Reads and returns the next character in the message. If the end of the 074 * message has been reached, returns -1. Note that a call to this method 075 * may result in multiple reads from the underlying input stream to decode 076 * the message properly (removing doubled dots and so on). All of 077 * this is transparent to the programmer and is only mentioned for 078 * completeness. 079 * @return The next character in the message. Returns -1 if the end of the 080 * message has been reached. 081 * @exception IOException If an error occurs while reading the underlying 082 * stream. 083 */ 084 @Override 085 public int read() throws IOException 086 { 087 int ch; 088 089 synchronized (lock) 090 { 091 if (pos < internalBuffer.length) 092 { 093 return internalBuffer[pos++]; 094 } 095 096 if (eof) 097 { 098 return -1; 099 } 100 101 if ((ch = internalReader.read()) == -1) 102 { 103 eof = true; 104 return -1; 105 } 106 107 if (atBeginning) 108 { 109 atBeginning = false; 110 if (ch == '.') 111 { 112 ch = internalReader.read(); 113 114 if (ch != '.') 115 { 116 // read newline 117 eof = true; 118 internalReader.read(); 119 return -1; 120 } 121 else 122 { 123 return '.'; 124 } 125 } 126 } 127 128 if (ch == '\r') 129 { 130 ch = internalReader.read(); 131 132 if (ch == '\n') 133 { 134 ch = internalReader.read(); 135 136 if (ch == '.') 137 { 138 ch = internalReader.read(); 139 140 if (ch != '.') 141 { 142 // read newline and indicate end of file 143 internalReader.read(); 144 eof = true; 145 } 146 else 147 { 148 internalBuffer[--pos] = (char) ch; 149 } 150 } 151 else 152 { 153 internalReader.unread(ch); 154 } 155 156 pos -= LS_CHARS.length; 157 System.arraycopy(LS_CHARS, 0, internalBuffer, pos, 158 LS_CHARS.length); 159 ch = internalBuffer[pos++]; 160 } 161 else 162 { 163 internalBuffer[--pos] = (char) ch; 164 return '\r'; 165 } 166 } 167 168 return ch; 169 } 170 } 171 172 /** 173 * Reads the next characters from the message into an array and 174 * returns the number of characters read. Returns -1 if the end of the 175 * message has been reached. 176 * @param buffer The character array in which to store the characters. 177 * @return The number of characters read. Returns -1 if the 178 * end of the message has been reached. 179 * @exception IOException If an error occurs in reading the underlying 180 * stream. 181 */ 182 @Override 183 public int read(char[] buffer) throws IOException 184 { 185 return read(buffer, 0, buffer.length); 186 } 187 188 /** 189 * Reads the next characters from the message into an array and 190 * returns the number of characters read. Returns -1 if the end of the 191 * message has been reached. The characters are stored in the array 192 * starting from the given offset and up to the length specified. 193 * @param buffer The character array in which to store the characters. 194 * @param offset The offset into the array at which to start storing 195 * characters. 196 * @param length The number of characters to read. 197 * @return The number of characters read. Returns -1 if the 198 * end of the message has been reached. 199 * @exception IOException If an error occurs in reading the underlying 200 * stream. 201 */ 202 @Override 203 public int read(char[] buffer, int offset, int length) throws IOException 204 { 205 int ch, off; 206 synchronized (lock) 207 { 208 if (length < 1) 209 { 210 return 0; 211 } 212 if ((ch = read()) == -1) 213 { 214 return -1; 215 } 216 off = offset; 217 218 do 219 { 220 buffer[offset++] = (char) ch; 221 } 222 while (--length > 0 && (ch = read()) != -1); 223 224 return (offset - off); 225 } 226 } 227 228 /** 229 * Determines if the message is ready to be read. 230 * @return True if the message is ready to be read, false if not. 231 * @exception IOException If an error occurs while checking the underlying 232 * stream. 233 */ 234 @Override 235 public boolean ready() throws IOException 236 { 237 synchronized (lock) 238 { 239 return (pos < internalBuffer.length || internalReader.ready()); 240 } 241 } 242 243 /** 244 * Closes the message for reading. This doesn't actually close the 245 * underlying stream. The underlying stream may still be used for 246 * communicating with the server and therefore is not closed. 247 * <p> 248 * If the end of the message has not yet been reached, this method 249 * will read the remainder of the message until it reaches the end, 250 * so that the underlying stream may continue to be used properly 251 * for communicating with the server. If you do not fully read 252 * a message, you MUST close it, otherwise your program will likely 253 * hang or behave improperly. 254 * @exception IOException If an error occurs while reading the 255 * underlying stream. 256 */ 257 @Override 258 public void close() throws IOException 259 { 260 synchronized (lock) 261 { 262 if (internalReader == null) 263 { 264 return; 265 } 266 267 if (!eof) 268 { 269 while (read() != -1) 270 { 271 ; 272 } 273 } 274 eof = true; 275 atBeginning = false; 276 pos = internalBuffer.length; 277 internalReader = null; 278 } 279 } 280 }