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