/* * 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.mailster.smtp.core; import java.io.IOException; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; import org.apache.mina.core.buffer.BufferDataException; import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.session.IoSession; import org.apache.mina.filter.codec.ProtocolDecoder; import org.apache.mina.filter.codec.ProtocolDecoderOutput; /** * A {@link ProtocolDecoder} which decodes incoming SMTP data based on session context. * * @author De Oliveira Edouard <doe_wanted@yahoo.fr> */ public class SMTPDecoder implements ProtocolDecoder { private final static String CONTEXT = SMTPDecoder.class.getName()+ ".context"; protected final static String TMPFILE_PREFIX = "mailsterSmtp"; protected final static String TMPFILE_SUFFIX = ".eml"; private final static byte[] SMTP_CMD_DELIMITER = new byte[] {'\r','\n'}; private final static byte[] SMTP_DATA_DELIMITER = new byte[] {'\r','\n', '.', '\r','\n'}; private Charset charset; /** * <a href="http://rfc.net/rfc2822.html#s2.1.1.">RFC 2822</a> */ private int maxLineLength = 998; /** When to trigger */ private int threshold; /** * Creates a new instance with the specified <tt>charset</tt> and the * specified <tt>thresholdBytes</tt> deferring size. */ public SMTPDecoder(Charset charset, int thresholdBytes) { setup(charset, thresholdBytes); } public void setup(Charset charset, int thresholdBytes) { if (charset == null) { throw new NullPointerException("charset"); } this.charset = charset; this.threshold = thresholdBytes; } /** */ public void setDataDeferredSize(int dataDeferredSize) { this.threshold = dataDeferredSize; } /** * Returns the allowed maximum size of the line to be decoded. If the size * of the line to be decoded exceeds this value, the decoder will throw a * {@link BufferDataException}. The default value is <tt>998</tt> bytes. */ public int getMaxLineLength() { return maxLineLength; } /** * Sets the allowed maximum size of the line to be decoded. If the size of * the line to be decoded exceeds this value, the decoder will throw a * {@link BufferDataException}. The default value is <tt>998</tt> bytes. */ public void setMaxLineLength(int maxLineLength) { if (maxLineLength <= 0) { throw new IllegalArgumentException("maxLineLength: "+ maxLineLength); } this.maxLineLength = maxLineLength; } /** */ private SMTPDecoderContext getContext(IoSession session) { SMTPDecoderContext ctx = (SMTPDecoderContext) session.getAttribute(CONTEXT); if (ctx == null) { ctx = new SMTPDecoderContext(this); session.setAttribute(CONTEXT, ctx); } return ctx; } /** */ public void finishDecode(IoSession session, ProtocolDecoderOutput out) throws Exception { } /** */ public void dispose(IoSession session) throws Exception { SMTPDecoderContext ctx = (SMTPDecoderContext) session.getAttribute(CONTEXT); if (ctx != null) { ctx.getBuffer().free(); ctx.closeOutputStream(); session.removeAttribute(CONTEXT); } } /** */ public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { SMTPDecoderContext ctx = getContext(session); int matchCount = ctx.getMatchCount(); SMTPContext minaCtx = (SMTPContext) session.getAttribute(SMTPConnectionHandler.CONTEXT_ATTRIBUTE); boolean dataMode = minaCtx.getSMTPState().isDataMode(); ctx.setDataMode(dataMode); byte[] delimBuf = dataMode ? SMTP_DATA_DELIMITER : SMTP_CMD_DELIMITER; // Try to find a match int oldPos = in.position(); int oldLimit = in.limit(); if (matchCount == delimBuf.length) matchCount = 0; while (in.remaining() > 0) { byte b = in.get(); if (delimBuf[matchCount] == b) { matchCount++; if (matchCount == delimBuf.length) { // Found a match. int pos = in.position(); in.limit(pos); in.position(oldPos); ctx.write(in); in.limit(oldLimit); in.position(pos); if (ctx.getOverflowPosition() == 0) { IoBuffer buf = ctx.getBuffer(); buf.flip(); try { if (dataMode) { delimBuf = SMTP_CMD_DELIMITER; out.write(ctx.getNewInputStream()); } else { buf.limit(buf.limit() - matchCount); out.write(buf.getString(ctx.getDecoder())); } } catch (IOException ioex) { throw new CharacterCodingException(); } finally { ctx.reset(); buf.clear(); } } else { String msg = "Line is too long: " + ctx.getOverflowPosition(); ctx.reset(); throw new BufferDataException(msg); } oldPos = pos; matchCount = 0; } } else { // fix for DIRMINA-506 in.position(Math.max(0, in.position() - matchCount)); matchCount = 0; } } // Put remainder to buf. in.position(oldPos); ctx.write(in); ctx.setMatchCount(matchCount); } public Charset getCharset() { return charset; } public int getThreshold() { return threshold; } }