001 /* 002 * Copyright 2010 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package com.hs.mail.imap.server.codec; 017 018 import java.io.UnsupportedEncodingException; 019 020 import org.jboss.netty.buffer.ChannelBuffer; 021 import org.jboss.netty.channel.Channel; 022 import org.jboss.netty.channel.ChannelHandlerContext; 023 import org.jboss.netty.handler.codec.frame.TooLongFrameException; 024 import org.jboss.netty.handler.codec.replay.ReplayingDecoder; 025 026 /** 027 * Decodes <code>ChannelBuffer</code> into {@link ImapMessage}. 028 * 029 * @author Won Chul Doh 030 * @since Jan 22, 2010 031 * 032 */ 033 public abstract class ImapMessageDecoder extends 034 ReplayingDecoder<ImapMessageDecoder.State> { 035 036 private final int maxLineLength; 037 protected volatile ImapMessage message; 038 private String request; 039 private volatile ChannelBuffer content; 040 041 /** 042 * The internal state of <code>ImapMessageDecoder</code>. 043 * <em>Internal use only</em>. 044 */ 045 protected enum State { 046 READ_COMMAND, 047 READ_LITERAL, 048 READ_REMAINDER 049 } 050 051 /** 052 * Creates a new instance with the default. 053 * {@code maxLineLength (8192)} 054 */ 055 protected ImapMessageDecoder() { 056 this(8192); 057 } 058 059 /** 060 * Creates a new instance with the specific parameter. 061 */ 062 protected ImapMessageDecoder(int maxLineLength) { 063 super(State.READ_COMMAND, true); 064 065 if (maxLineLength <= 0) { 066 throw new IllegalArgumentException( 067 "maxLineLength must be a positive integer: " 068 + maxLineLength); 069 } 070 this.maxLineLength = maxLineLength; 071 } 072 073 @Override 074 protected Object decode(ChannelHandlerContext ctx, Channel channel, 075 ChannelBuffer buffer, State state) throws Exception { 076 switch (state) { 077 case READ_COMMAND: { 078 request = readLine(buffer, maxLineLength); 079 message = createMessage(request); 080 if (message.getLiteralLength() != -1) { 081 checkpoint(State.READ_LITERAL); 082 if (message.isNeedContinuationRequest()) { 083 channel.write("+ OK\r\n"); 084 } 085 } else { 086 return message; 087 } 088 } 089 case READ_LITERAL: { 090 // we have a content-length so we just read the correct number of 091 // bytes 092 readFixedLengthContent(buffer); 093 checkpoint(State.READ_REMAINDER); 094 } 095 case READ_REMAINDER: { 096 // FIXME - RFC 3501 7.5 097 String remainder = readLine(buffer, maxLineLength); 098 return reset(remainder); 099 } 100 default: 101 throw new Error("Shouldn't reach here."); 102 } 103 } 104 105 private Object reset(String remainder) throws Exception { 106 ImapMessage message = this.message; 107 ChannelBuffer content = this.content; 108 109 if (content != null) { 110 if ("APPEND".equalsIgnoreCase(message.getCommand())) { 111 message.setLiteral(content); 112 } else { 113 request = request.substring(0, request.lastIndexOf('{')) 114 + toString(content) + remainder; 115 message = createMessage(request); 116 } 117 this.content = null; 118 } 119 this.message = null; 120 121 checkpoint(State.READ_COMMAND); 122 return message; 123 } 124 125 private void readFixedLengthContent(ChannelBuffer buffer) { 126 long length = message.getLiteralLength(); 127 if (content == null) { 128 content = buffer.readBytes((int) length); 129 } else { 130 content.writeBytes(buffer.readBytes((int) length)); 131 } 132 } 133 134 protected abstract ImapMessage createMessage(String line) throws Exception; 135 136 private String readLine(ChannelBuffer buffer, int maxLineLength) 137 throws TooLongFrameException { 138 StringBuilder sb = new StringBuilder(128); 139 int lineLength = 0; 140 while (true) { 141 byte nextByte = buffer.readByte(); 142 if (nextByte == ImapCodecUtil.CR) { 143 nextByte = buffer.readByte(); 144 if (nextByte == ImapCodecUtil.LF) { 145 sb.append(ImapCodecUtil.CRLF); 146 return sb.toString(); 147 } 148 } else if (nextByte == ImapCodecUtil.LF) { 149 sb.append((char) ImapCodecUtil.LF); 150 return sb.toString(); 151 } else { 152 if (lineLength >= maxLineLength) { 153 throw new TooLongFrameException( 154 "An IMAP command is larger than " + maxLineLength 155 + " bytes."); 156 } 157 lineLength++; 158 sb.append((char) nextByte); 159 } 160 } 161 } 162 163 private String toString(ChannelBuffer buffer) { 164 byte[] dst = new byte[buffer.readableBytes()]; 165 buffer.getBytes(buffer.readerIndex(), dst); 166 try { 167 return new String(dst, "ISO8859_1"); 168 } catch (UnsupportedEncodingException e) { 169 return new String(dst); 170 } 171 } 172 173 }