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    }