package org.mailster.smtp.core; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import javax.mail.util.SharedByteArrayInputStream; import org.apache.mina.core.buffer.IoBuffer; import org.mailster.smtp.util.SharedTmpFileInputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * The SMTP protocol decoder context is used when a client * command is split among multiple network packets. * * @author De Oliveira Edouard <doe_wanted@yahoo.fr> */ public class SMTPDecoderContext { private static final Logger LOG = LoggerFactory.getLogger(SMTPDecoderContext.class); private final CharsetDecoder charsetDecoder; private IoBuffer buf; private int matchCount = 0; private int overflowPosition = 0; private boolean thresholdReached = false; private boolean dataMode = false; /** If we switch to file output, this is the file. */ private File outFile; /** If we switch to file output, this is the stream to write to the file. */ private FileOutputStream stream; private SMTPDecoder decoder; protected SMTPDecoderContext(SMTPDecoder decoder) { this.decoder = decoder; charsetDecoder = decoder.getCharset().newDecoder(); buf = IoBuffer.allocate(decoder.getMaxLineLength()).setAutoExpand(true); } /** */ private static byte[] asArray(IoBuffer b) { int len = b.remaining(); byte[] array = new byte[len]; b.get(array, 0, len); return array; } /** */ protected CharsetDecoder getDecoder() { return charsetDecoder; } /** */ protected IoBuffer getBuffer() { return buf; } /** */ private void compactBuffer() { if (dataMode && buf.capacity() > decoder.getMaxLineLength()) buf = IoBuffer.allocate(decoder.getMaxLineLength()).setAutoExpand(true); else buf.clear(); } /** */ protected int getOverflowPosition() { return overflowPosition; } /** */ protected int getMatchCount() { return matchCount; } /** */ protected void setMatchCount(int matchCount) { this.matchCount = matchCount; } /** */ protected void reset() throws IOException { overflowPosition = 0; matchCount = 0; charsetDecoder.reset(); if (thresholdReached) { thresholdReached = false; compactBuffer(); closeOutputStream(); } } /** */ protected void write(IoBuffer b) throws IOException { if (dataMode) write(asArray(b)); else append(b); } /** */ private void write(byte[] src) throws IOException { int predicted = this.thresholdReached ? 0 : this.buf.position() + src.length; // Checks whether reading count bytes would cross the limit. if (this.thresholdReached || predicted > decoder.getThreshold()) { // If previously hit, then use the stream. if (!this.thresholdReached) thresholdReached(); this.stream.write(src); } else this.buf.put(src); } /** * Called when the threshold is about to be exceeded. Once called, it * won't be called again for the current data transfer. */ private void thresholdReached() throws IOException { this.outFile = File.createTempFile(SMTPDecoder.TMPFILE_PREFIX, SMTPDecoder.TMPFILE_SUFFIX); LOG.debug("Writing message to file : {}", outFile.getAbsolutePath()); this.stream = new FileOutputStream(this.outFile); this.buf.flip(); this.stream.write(asArray(this.buf)); this.thresholdReached = true; this.buf.clear(); LOG.debug("ByteBuffer written to stream"); } /** */ protected void closeOutputStream() throws IOException { if (this.stream != null) { this.stream.flush(); this.stream.close(); LOG.debug("Temp file writing achieved - closing stream"); } } /** */ protected InputStream getNewInputStream() throws IOException { if (this.thresholdReached) return new SharedTmpFileInputStream(this.outFile); else return new SharedByteArrayInputStream(asArray(this.buf)); } /** */ private void append(IoBuffer in) throws CharacterCodingException { if (overflowPosition != 0) discard(in); else { int pos = buf.position(); if ((pos + in.remaining()) > decoder.getMaxLineLength()) { overflowPosition = pos; buf.clear(); discard(in); } else this.buf.put(in); } } /** */ private void discard(IoBuffer in) { if (Integer.MAX_VALUE < (overflowPosition + in.remaining()) ) overflowPosition = Integer.MAX_VALUE; else overflowPosition += in.remaining(); in.position(in.limit()); } /** */ public void setDataMode(boolean dataMode) { this.dataMode = dataMode; } }