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 }