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