org.subethamail.smtp.io.CRLFOutputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of subethasmtp Show documentation
Show all versions of subethasmtp Show documentation
SubEtha SMTP is an easy-to-use server-side SMTP library for Java.
/****************************************************************
* 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;
}
}
}