package mireka.pop.command; import java.io.IOException; import java.io.InputStream; import org.apache.james.mime4j.io.MaxLineLimitException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.subethamail.smtp.io.CRLFTerminatedReader.MaxLineLengthException; /** * CrLfInputStream recognizes CR LF line endings but otherwise assumes arbitrary * binary content. Mail content is special, because it is considered to be an * octet stream, although usually it contains text. MIME mail headers can be * encoded in either US-ASCII or UTF-8, but the mail body can be in any charset, * moreover it can be arbitrary binary data encoded using the MIME binary * method, so the result does not even similar to text. The POP3 TOP command * needs to return mail headers and a few lines of the body. The latter is * obsolete functionality, there may be no lines in the body, but we have to * deal with this situation. */ public class CrLfInputStream { private final Logger logger = LoggerFactory .getLogger(CrLfInputStream.class); private final InputStream in; private int pushBackChar; private boolean isPushBackFilled = false; private boolean isInvalidLineEndingLogged = false; public CrLfInputStream(InputStream in) { this.in = in; } /** * Reads a line without end-of-line characters. It recognizes but logs line * endings other then CR LF. * * @throws MaxLineLengthException * if the line is longer than the length of the buffer */ public int readLineWithEol(byte[] buffer) throws MaxLineLengthException, IOException { int i = 0; while (true) { int octet = readInput(); if (octet == -1) { return i == 0 ? -1 : i; } else if (octet == '\r') { buffer[i++] = (byte) octet; octet = readInput(); if (octet == '\n') { if (i >= buffer.length) throw new MaxLineLimitException( "Input line length is too long!"); buffer[i++] = (byte) octet; return i; } else { logInvalidLineEnding(); pushBack(octet); return i; } } else if (octet == '\n') { logInvalidLineEnding(); buffer[i++] = (byte) octet; return i; } else { buffer[i++] = (byte) octet; } if (i >= buffer.length) throw new MaxLineLimitException( "Input line length is too long!"); } } private int readInput() throws IOException { if (isPushBackFilled) { isPushBackFilled = false; return pushBackChar; } else { return in.read(); } } private void pushBack(int ch) { if (isPushBackFilled) throw new RuntimeException("Assertion failed"); isPushBackFilled = true; pushBackChar = ch; } private void logInvalidLineEnding() { if (isInvalidLineEndingLogged) return; logger.debug("Invalid line ending in input stream"); isInvalidLineEndingLogged = true; } }