src/org/sonews/util/io/SMTPInputStream.java
author František Kučera <franta-hg@frantovo.cz>
Sat Nov 05 00:06:09 2011 +0100 (2011-11-05)
changeset 115 e5bfc969d41f
permissions -rw-r--r--
SMTP: correct unescaping of posted messages containing lines with single dot.
     1 /*
     2  *   SONEWS News Server
     3  *   see AUTHORS for the list of contributors
     4  *
     5  *   This program is free software: you can redistribute it and/or modify
     6  *   it under the terms of the GNU General Public License as published by
     7  *   the Free Software Foundation, either version 3 of the License, or
     8  *   (at your option) any later version.
     9  *
    10  *   This program is distributed in the hope that it will be useful,
    11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  *   GNU General Public License for more details.
    14  *
    15  *   You should have received a copy of the GNU General Public License
    16  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  */
    18 package org.sonews.util.io;
    19 
    20 import java.io.FilterInputStream;
    21 import java.io.IOException;
    22 import java.io.InputStream;
    23 
    24 /**
    25  * Filter input stream for reading from SMTP (or NNTP or similar) socket
    26  * where lines containing single dot have special meaning – end of message.
    27  * 
    28  * @author František Kučera (frantovo.cz)
    29  */
    30 public class SMTPInputStream extends FilterInputStream {
    31 
    32 	public static final int CR = 0x0d;
    33 	public static final int LF = 0x0a;
    34 	public static final int DOT = 0x2e;
    35 	protected int last;
    36 
    37 	public SMTPInputStream(InputStream in) {
    38 		super(in);
    39 	}
    40 
    41 	/**
    42 	 * @return one byte as expected 
    43 	 * or -2 if there was line with single dot (which means end of message)
    44 	 * @throws IOException 
    45 	 */
    46 	@Override
    47 	public int read() throws IOException {
    48 		// read current character
    49 		int ch = super.read();
    50 
    51 		if (ch == DOT) {
    52 			if (last == LF) {
    53 				int next = super.read();
    54 
    55 				if (next == CR || next == LF) { // There should be CRLF, but we may accept also just LF or CR with missing LF. Or should we be more strict?
    56 					// <CRLF>.<CRLF> → end of current message
    57 					ch = -2;
    58 				} else {
    59 					// <CRLF>.… → eat one dot and return next character
    60 					ch = next;
    61 				}
    62 			}
    63 		}
    64 
    65 		last = ch;
    66 		return ch;
    67 	}
    68 
    69 	/**
    70 	 * @param buffer
    71 	 * @param offset
    72 	 * @param length
    73 	 * @return See {@link FilterInputStream#read(byte[], int, int)} or -2 (then see {@link #read(byte[])})
    74 	 * @throws IOException 
    75 	 */
    76 	@Override
    77 	public int read(byte[] buffer, int offset, int length) throws IOException {
    78 		if (buffer == null) {
    79 			throw new NullPointerException("Byte array should not be null.");
    80 		} else if ((offset < 0) || (offset > buffer.length) || (length < 0) || ((offset + length) > buffer.length) || ((offset + length) < 0)) {
    81 			throw new IndexOutOfBoundsException("Invalid offset or length.");
    82 		} else if (length == 0) {
    83 			return 0;
    84 		}
    85 
    86 		int ch = read();
    87 
    88 		if (ch == -1 || ch == -2) {
    89 			return ch;
    90 		}
    91 
    92 		buffer[offset] = (byte) ch;
    93 
    94 		int readCounter = 1;
    95 
    96 		for (; readCounter < length; readCounter++) {
    97 			ch = read();
    98 
    99 			if (ch == -1 || ch == -2) {
   100 				break;
   101 			}
   102 
   103 			if (buffer != null) {
   104 				buffer[offset + readCounter] = (byte) ch;
   105 			}
   106 		}
   107 
   108 		return readCounter;
   109 	}
   110 }