001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    
020    package org.apache.james.mime4j.descriptor;
021    
022    import java.util.HashMap;
023    import java.util.Map;
024    
025    import org.apache.commons.logging.Log;
026    import org.apache.commons.logging.LogFactory;
027    import org.apache.james.mime4j.parser.Field;
028    import org.apache.james.mime4j.util.MimeUtil;
029    
030    /**
031     * Encapsulates the values of the MIME-specific header fields 
032     * (which starts with <code>Content-</code>). 
033     */
034    public class DefaultBodyDescriptor implements MutableBodyDescriptor {
035        private static final String US_ASCII = "us-ascii";
036    
037        private static final String SUB_TYPE_EMAIL = "rfc822";
038    
039        private static final String MEDIA_TYPE_TEXT = "text";
040    
041        private static final String MEDIA_TYPE_MESSAGE = "message";
042    
043        private static final String EMAIL_MESSAGE_MIME_TYPE = MEDIA_TYPE_MESSAGE + "/" + SUB_TYPE_EMAIL;
044    
045        private static final String DEFAULT_SUB_TYPE = "plain";
046    
047        private static final String DEFAULT_MEDIA_TYPE = MEDIA_TYPE_TEXT;
048    
049        private static final String DEFAULT_MIME_TYPE = DEFAULT_MEDIA_TYPE + "/" + DEFAULT_SUB_TYPE;
050    
051        private static Log log = LogFactory.getLog(DefaultBodyDescriptor.class);
052        
053        private String mediaType = DEFAULT_MEDIA_TYPE;
054        private String subType = DEFAULT_SUB_TYPE;
055        private String mimeType = DEFAULT_MIME_TYPE;
056        private String boundary = null;
057        private String charset = US_ASCII;
058        private String transferEncoding = "7bit";
059        private Map<String, String> parameters = new HashMap<String, String>();
060        private boolean contentTypeSet;
061        private boolean contentTransferEncSet;
062        private long contentLength = -1;
063        
064        /**
065         * Creates a new root <code>BodyDescriptor</code> instance.
066         */
067        public DefaultBodyDescriptor() {
068            this(null);
069        }
070    
071        /**
072         * Creates a new <code>BodyDescriptor</code> instance.
073         * 
074         * @param parent the descriptor of the parent or <code>null</code> if this
075         *        is the root descriptor.
076         */
077        public DefaultBodyDescriptor(BodyDescriptor parent) {
078            if (parent != null && MimeUtil.isSameMimeType("multipart/digest", parent.getMimeType())) {
079                mimeType = EMAIL_MESSAGE_MIME_TYPE;
080                subType = SUB_TYPE_EMAIL;
081                mediaType = MEDIA_TYPE_MESSAGE;
082            } else {
083                mimeType = DEFAULT_MIME_TYPE;
084                subType = DEFAULT_SUB_TYPE;
085                mediaType = DEFAULT_MEDIA_TYPE;
086            }
087        }
088        
089        /**
090         * Should be called for each <code>Content-</code> header field of 
091         * a MIME message or part.
092         * 
093         * @param field the MIME field.
094         */
095        public void addField(Field field) {
096            String name = field.getName();
097            String value = field.getBody();
098    
099            name = name.trim().toLowerCase();
100            
101            if (name.equals("content-transfer-encoding") && !contentTransferEncSet) {
102                contentTransferEncSet = true;
103                
104                value = value.trim().toLowerCase();
105                if (value.length() > 0) {
106                    transferEncoding = value;
107                }
108                
109            } else if (name.equals("content-length") && contentLength == -1) {
110                try {
111                    contentLength = Long.parseLong(value.trim());
112                } catch (NumberFormatException e) {
113                    log.error("Invalid content-length: " + value);
114                }
115            } else if (name.equals("content-type") && !contentTypeSet) {
116                parseContentType(value);
117            }
118        }
119    
120        private void parseContentType(String value) {
121            contentTypeSet = true;
122            
123            Map<String, String> params = MimeUtil.getHeaderParams(value);
124            
125            String main = params.get("");
126            String type = null;
127            String subtype = null;
128            if (main != null) {
129                main = main.toLowerCase().trim();
130                int index = main.indexOf('/');
131                boolean valid = false;
132                if (index != -1) {
133                    type = main.substring(0, index).trim();
134                    subtype = main.substring(index + 1).trim();
135                    if (type.length() > 0 && subtype.length() > 0) {
136                        main = type + "/" + subtype;
137                        valid = true;
138                    }
139                }
140                
141                if (!valid) {
142                    main = null;
143                    type = null;
144                    subtype = null;
145                }
146            }
147            String b = params.get("boundary");
148            
149            if (main != null 
150                    && ((main.startsWith("multipart/") && b != null) 
151                            || !main.startsWith("multipart/"))) {
152                
153                mimeType = main;
154                this.subType = subtype;
155                this.mediaType = type;
156            }
157            
158            if (MimeUtil.isMultipart(mimeType)) {
159                boundary = b;
160            }
161            
162            String c = params.get("charset");
163            charset = null;
164            if (c != null) {
165                c = c.trim();
166                if (c.length() > 0) {
167                    charset = c.toLowerCase();
168                }
169            }
170            if (charset == null && MEDIA_TYPE_TEXT.equals(mediaType)) {
171                charset = US_ASCII;
172            }
173            
174            /*
175             * Add all other parameters to parameters.
176             */
177            parameters.putAll(params);
178            parameters.remove("");
179            parameters.remove("boundary");
180            parameters.remove("charset");
181        }
182    
183        /**
184         * Return the MimeType 
185         * 
186         * @return mimeType
187         */
188        public String getMimeType() {
189            return mimeType;
190        }
191        
192        /**
193         * Return the boundary
194         * 
195         * @return boundary
196         */
197        public String getBoundary() {
198            return boundary;
199        }
200        
201        /**
202         * Return the charset
203         * 
204         * @return charset
205         */
206        public String getCharset() {
207            return charset;
208        }
209        
210        /**
211         * Return all parameters for the BodyDescriptor
212         * 
213         * @return parameters
214         */
215        public Map<String, String> getContentTypeParameters() {
216            return parameters;
217        }
218        
219        /**
220         * Return the TransferEncoding
221         * 
222         * @return transferEncoding
223         */
224        public String getTransferEncoding() {
225            return transferEncoding;
226        }
227        
228        @Override
229        public String toString() {
230            return mimeType;
231        }
232    
233        public long getContentLength() {
234            return contentLength;
235        }
236    
237        public String getMediaType() {
238            return mediaType;
239        }
240    
241        public String getSubType() {
242            return subType;
243        }
244    }