001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.geronimo.javamail.store.imap.connection;
019    
020    import java.util.ArrayList;
021    import java.util.Date;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    
026    import javax.mail.MessagingException;
027    import javax.mail.internet.ContentDisposition;
028    import javax.mail.internet.ContentType;
029    
030    
031    public class IMAPBodyStructure extends IMAPFetchDataItem {
032    
033        // the MIME type information
034        public ContentType mimeType = new ContentType();
035        // the content disposition info
036        public ContentDisposition disposition = null;
037        // the message ID
038        public String contentID;
039        public String contentDescription;
040        public String transferEncoding;
041        // size of the message 
042        public int bodySize;
043        // number of lines, which only applies to text types.
044        public int lines = -1;
045    
046        // "parts is parts".  If this is a multipart message, we have a body structure item for each subpart.
047        public IMAPBodyStructure[] parts;
048        // optional dispostiion parameters
049        public Map dispositionParameters;
050        // language parameters
051        public List languages;
052        // the MD5 hash
053        public String md5Hash;
054    
055        // references to nested message information.
056        public IMAPEnvelope nestedEnvelope;
057        public IMAPBodyStructure nestedBody;
058    
059    
060        public IMAPBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
061            super(BODYSTRUCTURE);
062            parseBodyStructure(source);
063        }
064    
065    
066        protected void parseBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
067            // the body structure needs to start with a left paren
068            source.checkLeftParen();
069    
070            // if we start with a parentized item, we have a multipart content type.  We need to
071            // recurse on each of those as appropriate
072            if (source.peek().getType() == '(') {
073                parseMultipartBodyStructure(source);
074            }
075            else {
076                parseSinglepartBodyStructure(source);
077            }
078        }
079    
080    
081        protected void parseMultipartBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
082            mimeType.setPrimaryType("multipart");
083            ArrayList partList = new ArrayList();
084    
085            do {
086                // parse the subpiece (which might also be a multipart).
087                IMAPBodyStructure part = new IMAPBodyStructure(source);
088                partList.add(part);
089                // we keep doing this as long as we seen parenthized items.
090            } while (source.peek().getType() == '(');
091            
092            parts = (IMAPBodyStructure[])partList.toArray(new IMAPBodyStructure[partList.size()]); 
093    
094            // get the subtype (required)
095            mimeType.setSubType(source.readString());
096    
097            if (source.checkListEnd()) {
098                return;
099            }
100            // if the next token is the list terminator, we're done.  Otherwise, we need to read extension
101            // data.
102            if (source.checkListEnd()) {
103                return;
104            }
105            // read the content parameter information and copy into the ContentType.
106            mimeType.setParameterList(source.readParameterList());
107    
108            // more optional stuff
109            if (source.checkListEnd()) {
110                return;
111            }
112    
113            // go parse the extensions that are common to both single- and multi-part messages.
114            parseMessageExtensions(source);
115        }
116    
117    
118        protected void parseSinglepartBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
119            // get the primary and secondary types.
120            mimeType.setPrimaryType(source.readString());
121            mimeType.setSubType(source.readString());
122    
123            // read the parameters associated with the content type.
124            mimeType.setParameterList(source.readParameterList());
125    
126            // now a bunch of string value parameters
127            contentID = source.readStringOrNil();
128            contentDescription = source.readStringOrNil();
129            transferEncoding = source.readStringOrNil();
130            bodySize = source.readInteger();
131    
132            // is this an embedded message type?  Embedded messages include envelope and body structure
133            // information for the embedded message next.
134            if (mimeType.match("message/rfc822")) {
135                // parse the nested information
136                nestedEnvelope = new IMAPEnvelope(source);
137                nestedBody = new IMAPBodyStructure(source);
138                lines = source.readInteger();
139            }
140            // text types include a line count
141            else if (mimeType.match("text/*")) {
142                lines = source.readInteger();
143            }
144    
145            // now the optional extension data.  All of these are optional, but must be in the specified order.
146            if (source.checkListEnd()) {
147                return;
148            }
149    
150            md5Hash = source.readString();
151    
152            // go parse the extensions that are common to both single- and multi-part messages.
153            parseMessageExtensions(source);
154        }
155    
156        /**
157         * Parse common message extension information shared between
158         * single part and multi part messages.
159         *
160         * @param source The source tokenizer..
161         */
162        protected void parseMessageExtensions(IMAPResponseTokenizer source) throws MessagingException {
163    
164            // now the optional extension data.  All of these are optional, but must be in the specified order.
165            if (source.checkListEnd()) {
166                return;
167            }
168    
169            disposition = new ContentDisposition();
170            // now the dispostion.  This is a string, followed by a parameter list.
171            disposition.setDisposition(source.readString());
172            disposition.setParameterList(source.readParameterList());
173    
174            // once more
175            if (source.checkListEnd()) {
176                return;
177            }
178            // read the language info.
179            languages = source.readStringList();
180            // next is the body location information.  The Javamail APIs don't really expose that, so
181            // we'll just skip over that.
182    
183            // once more
184            if (source.checkListEnd()) {
185                return;
186            }
187            // read the location info.
188            source.readStringList();
189    
190    
191            // we don't recognize any other forms of extension, so just skip over these.
192            while (source.notListEnd()) {
193                source.skipExtensionItem();
194            }
195        }
196    
197    
198        /**
199         * Tests if a body structure is for a multipart body.
200         *
201         * @return true if this is a multipart body part, false for a single part.
202         */
203        public boolean isMultipart() {
204            return parts != null;
205        }
206        
207        
208        /**
209         * Test if this body structure represents an attached message.  If it's a
210         * message, this will be a single part of MIME type message/rfc822. 
211         * 
212         * @return True if this is a nested message type, false for either a multipart or 
213         *         a single part of another type.
214         */
215        public boolean isAttachedMessage() {
216            return !isMultipart() && mimeType.match("message/rfc822"); 
217        }
218    }
219