All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.subethamail.smtp.io.CRLFOutputStream Maven / Gradle / Ivy

There is a newer version: 3.1.7
Show newest version
/****************************************************************
 * Licensed to the Apache Software Foundation (ASF) under one   *
 * or more contributor license agreements.  See the NOTICE file *
 * distributed with this work for additional information        *
 * regarding copyright ownership.  The ASF licenses this file   *
 * to you under the Apache License, Version 2.0 (the            *
 * "License"); you may not use this file except in compliance   *
 * with the License.  You may obtain a copy of the License at   *
 *                                                              *
 *   http://www.apache.org/licenses/LICENSE-2.0                 *
 *                                                              *
 * Unless required by applicable law or agreed to in writing,   *
 * software distributed under the License is distributed on an  *
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
 * KIND, either express or implied.  See the License for the    *
 * specific language governing permissions and limitations      *
 * under the License.                                           *
 ****************************************************************/

package org.subethamail.smtp.io;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

/**
 * A Filter for use with SMTP or other protocols in which lines must end with
 * CRLF. Converts every "isolated" occourency of \r or \n with \r\n
 *
 * RFC 2821 #2.3.7 mandates that line termination is CRLF, and that CR and LF
 * must not be transmitted except in that pairing. If we get a naked LF, convert
 * to CRLF.
 *
 */
public class CRLFOutputStream extends FilterOutputStream {

	/**
	 * Counter for number of last (0A or 0D).
	 */
	protected int statusLast;

	protected final static int LAST_WAS_OTHER = 0;

	protected final static int LAST_WAS_CR = 1;

	protected final static int LAST_WAS_LF = 2;

	protected boolean startOfLine = true;

	/**
	 * Constructor that wraps an OutputStream.
	 *
	 * @param out
	 *                the OutputStream to be wrapped
	 */
	public CRLFOutputStream(OutputStream out) {
		super(out);
		this.statusLast = LAST_WAS_LF; // we already assume a CRLF at beginning
									// (otherwise TOP would not work correctly
									// !)
	}

	/**
	 * Writes a byte to the stream Fixes any naked CR or LF to the RFC 2821
	 * mandated CFLF pairing.
	 *
	 * @param b
	 *                the byte to write
	 *
	 * @throws IOException
	 *                 if an error occurs writing the byte
	 */
	@Override
	public void write(int b) throws IOException {
		switch (b) {
			case '\r':
				this.out.write('\r');
				this.out.write('\n');
				this.startOfLine = true;
				this.statusLast = LAST_WAS_CR;
				break;
			case '\n':
				if (this.statusLast != LAST_WAS_CR) {
					this.out.write('\r');
					this.out.write('\n');
					this.startOfLine = true;
				}
				this.statusLast = LAST_WAS_LF;
				break;
			default:
				// we're no longer at the start of a line
				this.out.write(b);
				this.startOfLine = false;
				this.statusLast = LAST_WAS_OTHER;
				break;
		}
	}

	/**
	 * Provides an extension point for ExtraDotOutputStream to be able to add dots
	 * at the beginning of new lines.
	 *
	 * @see java.io.FilterOutputStream#write(byte[], int, int)
	 */
	protected void writeChunk(byte buffer[], int offset, int length) throws IOException {
		this.out.write(buffer, offset, length);
	}

	/**
	 * @see java.io.FilterOutputStream#write(byte[], int, int)
	 */
	@Override
	public synchronized void write(byte buffer[], int offset, int length)
			throws IOException {
		/* optimized */
		int lineStart = offset;
		for (int i = offset; i < length + offset; i++) {
			switch(buffer[i]) {
			case '\r':
				// CR case. Write down the last line
				// and position the new lineStart at the next char
				this.writeChunk(buffer, lineStart, i - lineStart);
				this.out.write('\r');
				this.out.write('\n');
				this.startOfLine = true;
				lineStart = i + 1;
				this.statusLast = LAST_WAS_CR;
				break;
			case '\n':
				if (this.statusLast != LAST_WAS_CR) {
					this.writeChunk(buffer, lineStart, i - lineStart);
					this.out.write('\r');
					this.out.write('\n');
					this.startOfLine = true;
				}
				lineStart = i + 1;
				this.statusLast = LAST_WAS_LF;
				break;
			default:
				this.statusLast = LAST_WAS_OTHER;
			}
		}
		if (length + offset > lineStart) {
			this.writeChunk(buffer, lineStart, length + offset - lineStart);
			this.startOfLine = false;
		}
	}


	/**
	 * Ensure that the stream is CRLF terminated.
	 *
	 * @throws IOException
	 *                 if an error occurs writing the byte
	 */
	public void checkCRLFTerminator() throws IOException {
		if (this.statusLast == LAST_WAS_OTHER) {
			this.out.write('\r');
			this.out.write('\n');
			this.statusLast = LAST_WAS_CR;
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy