1 /*
   2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.org.jvnet.mimepull;
  27 
  28 import java.io.File;
  29 import java.io.InputStream;
  30 import java.nio.ByteBuffer;
  31 import java.util.List;
  32 import java.util.logging.Level;
  33 import java.util.logging.Logger;
  34 
  35 /**
  36  * Represents an attachment part in a MIME message. MIME message parsing is done
  37  * lazily using a pull parser, so the part may not have all the data. {@link #read}
  38  * and {@link #readOnce} may trigger the actual parsing the message. In fact,
  39  * parsing of an attachment part may be triggered by calling {@link #read} methods
  40  * on some other attachment parts. All this happens behind the scenes so the
  41  * application developer need not worry about these details.
  42  *
  43  * @author Jitendra Kotamraju, Martin Grebac
  44  */
  45 public class MIMEPart {
  46 
  47     private static final Logger LOGGER = Logger.getLogger(MIMEPart.class.getName());
  48 
  49     private volatile InternetHeaders headers;
  50     private volatile String contentId;
  51     private String contentType;
  52     private String contentTransferEncoding;
  53 
  54     volatile boolean parsed;    // part is parsed or not
  55     final MIMEMessage msg;
  56     private final DataHead dataHead;
  57 
  58     MIMEPart(MIMEMessage msg) {
  59         this.msg = msg;
  60         this.dataHead = new DataHead(this);
  61     }
  62 
  63     MIMEPart(MIMEMessage msg, String contentId) {
  64         this(msg);
  65         this.contentId = contentId;
  66     }
  67 
  68     /**
  69      * Can get the attachment part's content multiple times. That means
  70      * the full content needs to be there in memory or on the file system.
  71      * Calling this method would trigger parsing for the part's data. So
  72      * do not call this unless it is required(otherwise, just wrap MIMEPart
  73      * into a object that returns InputStream for e.g DataHandler)
  74      *
  75      * @return data for the part's content
  76      */
  77     public InputStream read() {
  78         InputStream is = null;
  79         try {
  80             is = MimeUtility.decode(dataHead.read(), contentTransferEncoding);
  81         } catch (DecodingException ex) { //ignore
  82             if (LOGGER.isLoggable(Level.WARNING)) {
  83                 LOGGER.log(Level.WARNING, null, ex);
  84             }
  85         }
  86         return is;
  87     }
  88 
  89     /**
  90      * Cleans up any resources that are held by this part (for e.g. deletes
  91      * the temp file that is used to serve this part's content). After
  92      * calling this, one shouldn't call {@link #read()} or {@link #readOnce()}
  93      */
  94     public void close() {
  95         dataHead.close();
  96     }
  97 
  98     /**
  99      * Can get the attachment part's content only once. The content
 100      * will be lost after the method. Content data is not be stored
 101      * on the file system or is not kept in the memory for the
 102      * following case:
 103      *   - Attachement parts contents are accessed sequentially
 104      *
 105      * In general, take advantage of this when the data is used only
 106      * once.
 107      *
 108      * @return data for the part's content
 109      */
 110     public InputStream readOnce() {
 111         InputStream is = null;
 112         try {
 113             is = MimeUtility.decode(dataHead.readOnce(), contentTransferEncoding);
 114         } catch (DecodingException ex) { //ignore
 115             if (LOGGER.isLoggable(Level.WARNING)) {
 116                 LOGGER.log(Level.WARNING, null, ex);
 117             }
 118         }
 119         return is;
 120     }
 121 
 122     public void moveTo(File f) {
 123         dataHead.moveTo(f);
 124     }
 125 
 126     /**
 127      * Returns Content-ID MIME header for this attachment part
 128      *
 129      * @return Content-ID of the part
 130      */
 131     public String getContentId() {
 132         if (contentId == null) {
 133             getHeaders();
 134         }
 135         return contentId;
 136     }
 137 
 138     /**
 139      * Returns Content-Transfer-Encoding MIME header for this attachment part
 140      *
 141      * @return Content-Transfer-Encoding of the part
 142      */
 143     public String getContentTransferEncoding() {
 144         if (contentTransferEncoding == null) {
 145             getHeaders();
 146         }
 147         return contentTransferEncoding;
 148     }
 149 
 150     /**
 151      * Returns Content-Type MIME header for this attachment part
 152      *
 153      * @return Content-Type of the part
 154      */
 155     public String getContentType() {
 156         if (contentType == null) {
 157             getHeaders();
 158         }
 159         return contentType;
 160     }
 161 
 162     private void getHeaders() {
 163         // Trigger parsing for the part headers
 164         while(headers == null) {
 165             if (!msg.makeProgress()) {
 166                 if (headers == null) {
 167                     throw new IllegalStateException("Internal Error. Didn't get Headers even after complete parsing.");
 168                 }
 169             }
 170         }
 171     }
 172 
 173     /**
 174      * Return all the values for the specified header.
 175      * Returns <code>null</code> if no headers with the
 176      * specified name exist.
 177      *
 178      * @param   name header name
 179      * @return  list of header values, or null if none
 180      */
 181     public List<String> getHeader(String name) {
 182         getHeaders();
 183         assert headers != null;
 184         return headers.getHeader(name);
 185     }
 186 
 187     /**
 188      * Return all the headers
 189      *
 190      * @return list of Header objects
 191      */
 192     public List<? extends Header> getAllHeaders() {
 193         getHeaders();
 194         assert headers != null;
 195         return headers.getAllHeaders();
 196     }
 197 
 198     /**
 199      * Callback to set headers
 200      *
 201      * @param headers MIME headers for the part
 202      */
 203     void setHeaders(InternetHeaders headers) {
 204         this.headers = headers;
 205         List<String> ct = getHeader("Content-Type");
 206         this.contentType = (ct == null) ? "application/octet-stream" : ct.get(0);
 207         List<String> cte = getHeader("Content-Transfer-Encoding");
 208         this.contentTransferEncoding = (cte == null) ? "binary" : cte.get(0);
 209     }
 210 
 211     /**
 212      * Callback to notify that there is a partial content for the part
 213      *
 214      * @param buf content data for the part
 215      */
 216     void addBody(ByteBuffer buf) {
 217         dataHead.addBody(buf);
 218     }
 219 
 220     /**
 221      * Callback to indicate that parsing is done for this part
 222      * (no more update events for this part)
 223      */
 224     void doneParsing() {
 225         parsed = true;
 226         dataHead.doneParsing();
 227     }
 228 
 229     /**
 230      * Callback to set Content-ID for this part
 231      * @param cid Content-ID of the part
 232      */
 233     void setContentId(String cid) {
 234         this.contentId = cid;
 235     }
 236 
 237     /**
 238      * Callback to set Content-Transfer-Encoding for this part
 239      * @param cte Content-Transfer-Encoding of the part
 240      */
 241     void setContentTransferEncoding(String cte) {
 242         this.contentTransferEncoding = cte;
 243     }
 244 
 245     @Override
 246     public String toString() {
 247         return "Part="+contentId+":"+contentTransferEncoding;
 248     }
 249 
 250 }