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    import org.apache.geronimo.javamail.util.ResponseFormatException;
031    
032    
033    public class IMAPBodyStructure extends IMAPFetchDataItem {
034    
035        // the MIME type information
036        public ContentType mimeType = new ContentType();
037        // the content disposition info
038        public ContentDisposition disposition = null;
039        // the message ID
040        public String contentID;
041        public String contentDescription;
042        public String transferEncoding;
043        // size of the message 
044        public int bodySize;
045        // number of lines, which only applies to text types.
046        public int lines = -1;
047    
048        // "parts is parts".  If this is a multipart message, we have a body structure item for each subpart.
049        public IMAPBodyStructure[] parts;
050        // optional dispostiion parameters
051        public Map dispositionParameters;
052        // language parameters
053        public List languages;
054        // the MD5 hash
055        public String md5Hash;
056    
057        // references to nested message information.
058        public IMAPEnvelope nestedEnvelope;
059        public IMAPBodyStructure nestedBody;
060    
061    
062        public IMAPBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
063            super(BODYSTRUCTURE);
064            parseBodyStructure(source);
065        }
066    
067    
068        protected void parseBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
069            // the body structure needs to start with a left paren
070            source.checkLeftParen();
071    
072            // if we start with a parentized item, we have a multipart content type.  We need to
073            // recurse on each of those as appropriate
074            if (source.peek().getType() == '(') {
075                parseMultipartBodyStructure(source);
076            }
077            else {
078                parseSinglepartBodyStructure(source);
079            }
080        }
081    
082    
083        protected void parseMultipartBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
084            mimeType.setPrimaryType("multipart");
085            ArrayList partList = new ArrayList();
086    
087            do {
088                // parse the subpiece (which might also be a multipart).
089                IMAPBodyStructure part = new IMAPBodyStructure(source);
090                partList.add(part);
091                // we keep doing this as long as we seen parenthized items.
092            } while (source.peek().getType() == '(');
093            
094            parts = (IMAPBodyStructure[])partList.toArray(new IMAPBodyStructure[partList.size()]); 
095    
096            // get the subtype (required)
097            mimeType.setSubType(source.readString());
098    
099            // if the next token is the list terminator, we're done.  Otherwise, we need to read extension
100            // data.
101            if (source.checkListEnd()) {
102                return;
103            }
104            // read the content parameter information and copy into the ContentType.
105            mimeType.setParameterList(source.readParameterList());
106    
107            // more optional stuff
108            if (source.checkListEnd()) {
109                return;
110            }
111    
112            // go parse the extensions that are common to both single- and multi-part messages.
113            parseMessageExtensions(source);
114        }
115    
116    
117        protected void parseSinglepartBodyStructure(IMAPResponseTokenizer source) throws MessagingException {
118            // get the primary and secondary types.
119            mimeType.setPrimaryType(source.readString());
120            mimeType.setSubType(source.readString());
121    
122            // read the parameters associated with the content type.
123            mimeType.setParameterList(source.readParameterList());
124    
125            // now a bunch of string value parameters
126            contentID = source.readStringOrNil();
127            contentDescription = source.readStringOrNil();
128            transferEncoding = source.readStringOrNil();
129            bodySize = source.readInteger();
130    
131            // is this an embedded message type?  Embedded messages include envelope and body structure
132            // information for the embedded message next.
133            if (mimeType.match("message/rfc822")) {
134                // parse the nested information
135                nestedEnvelope = new IMAPEnvelope(source);
136                nestedBody = new IMAPBodyStructure(source);
137                lines = source.readInteger();
138            }
139            // text types include a line count
140            else if (mimeType.match("text/*")) {
141                lines = source.readInteger();
142            }
143    
144            // now the optional extension data.  All of these are optional, but must be in the specified order.
145            if (source.checkListEnd()) {
146                return;
147            }
148    
149            md5Hash = source.readString();
150    
151            // go parse the extensions that are common to both single- and multi-part messages.
152            parseMessageExtensions(source);
153        }
154    
155        /**
156         * Parse common message extension information shared between
157         * single part and multi part messages.
158         *
159         * @param source The source tokenizer..
160         */
161        protected void parseMessageExtensions(IMAPResponseTokenizer source) throws MessagingException {
162    
163            // now the optional extension data.  All of these are optional, but must be in the specified order.
164            if (source.checkListEnd()) {
165                return;
166            }
167    
168            disposition = new ContentDisposition();
169            // now the dispostion.  This is a string, followed by a parameter list.
170            if (source.peek(true).getType() == '(') {
171                source.checkLeftParen();
172                disposition.setDisposition(source.readString());
173                disposition.setParameterList(source.readParameterList());
174                source.checkRightParen();
175            } else if (source.peek(true) == IMAPResponseTokenizer.NIL) {
176                source.next();
177            } else {
178                throw new ResponseFormatException("Expecting NIL or '(' in response");
179            }
180    
181            // once more
182            if (source.checkListEnd()) {
183                return;
184            }
185            // read the language info.
186            languages = source.readStringList();
187            // next is the body location information.  The Javamail APIs don't really expose that, so
188            // we'll just skip over that.
189    
190            // once more
191            if (source.checkListEnd()) {
192                return;
193            }
194            // read the location info.
195            source.readStringList();
196    
197            // we don't recognize any other forms of extension, so just skip over these.
198            while (source.notListEnd()) {
199                source.skipExtensionItem();
200            }
201    
202            // step over the closing paren
203            source.next();
204        }
205    
206    
207        /**
208         * Tests if a body structure is for a multipart body.
209         *
210         * @return true if this is a multipart body part, false for a single part.
211         */
212        public boolean isMultipart() {
213            return parts != null;
214        }
215        
216        
217        /**
218         * Test if this body structure represents an attached message.  If it's a
219         * message, this will be a single part of MIME type message/rfc822. 
220         * 
221         * @return True if this is a nested message type, false for either a multipart or 
222         *         a single part of another type.
223         */
224        public boolean isAttachedMessage() {
225            return !isMultipart() && mimeType.match("message/rfc822"); 
226        }
227    }
228