1. /*
  2. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  3. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  4. */
  5. package javax.mail.internet;
  6. import javax.mail.*;
  7. import javax.activation.*;
  8. import java.util.*;
  9. import java.io.*;
  10. import com.sun.mail.util.LineOutputStream;
  11. import com.sun.mail.util.LineInputStream;
  12. /**
  13. * The MimeMultipart class is an implementation of the abstract Multipart
  14. * class that uses MIME conventions for the multipart data. <p>
  15. *
  16. * A MimeMultipart is obtained from a MimePart whose primary type
  17. * is "multipart" (by invoking the part's <code>getContent()</code> method)
  18. * or it can be created by a client as part of creating a new MimeMessage. <p>
  19. *
  20. * The default multipart subtype is "mixed". The other multipart
  21. * subtypes, such as "alternative", "related", and so on, can be
  22. * implemented as subclasses of MimeMultipart with additional methods
  23. * to implement the additional semantics of that type of multipart
  24. * content. The intent is that service providers, mail JavaBean writers
  25. * and mail clients will write many such subclasses and their Command
  26. * Beans, and will install them into the JavaBeans Activation
  27. * Framework, so that any JavaMail implementation and its clients can
  28. * transparently find and use these classes. Thus, a MIME multipart
  29. * handler is treated just like any other type handler, thereby
  30. * decoupling the process of providing multipart handlers from the
  31. * JavaMail API. Lacking these additional MimeMultipart subclasses,
  32. * all subtypes of MIME multipart data appear as MimeMultipart objects. <p>
  33. *
  34. * An application can directly construct a MIME multipart object of any
  35. * subtype by using the <code>MimeMultipart(String subtype)</code>
  36. * constructor. For example, to create a "multipart/alternative" object,
  37. * use <code>new MimeMultipart("alternative")</code>.
  38. *
  39. * @version 1.26, 00/09/18
  40. * @author John Mani
  41. * @author Bill Shannon
  42. * @author Max Spivak
  43. */
  44. public class MimeMultipart extends Multipart {
  45. /**
  46. * The DataSource supplying our InputStream.
  47. */
  48. protected DataSource ds = null;
  49. /**
  50. * Have we parsed the data from our InputStream yet?
  51. * Defaults to true; set to false when our constructor is
  52. * given a DataSource with an InputStream that we need to
  53. * parse.
  54. */
  55. protected boolean parsed = true;
  56. /**
  57. * Default constructor. An empty MimeMultipart object
  58. * is created. Its content type is set to "multipart/mixed".
  59. * A unique boundary string is generated and this string is
  60. * setup as the "boundary" parameter for the
  61. * <code>contentType</code> field. <p>
  62. *
  63. * MimeBodyParts may be added later.
  64. */
  65. public MimeMultipart() {
  66. this("mixed");
  67. }
  68. /**
  69. * Construct a MimeMultipart object of the given subtype.
  70. * A unique boundary string is generated and this string is
  71. * setup as the "boundary" parameter for the
  72. * <code>contentType</code> field. <p>
  73. *
  74. * MimeBodyParts may be added later.
  75. */
  76. public MimeMultipart(String subtype) {
  77. super();
  78. /*
  79. * Compute a boundary string.
  80. */
  81. String boundary = UniqueValue.getUniqueBoundaryValue();
  82. ContentType cType = new ContentType("multipart", subtype, null);
  83. cType.setParameter("boundary", boundary);
  84. contentType = cType.toString();
  85. }
  86. /**
  87. * Constructs a MimeMultipart object and its bodyparts from the
  88. * given DataSource. <p>
  89. *
  90. * This constructor handles as a special case the situation where the
  91. * given DataSource is a MultipartDataSource object. In this case, this
  92. * method just invokes the superclass (i.e., Multipart) constructor
  93. * that takes a MultipartDataSource object. <p>
  94. *
  95. * Otherwise, the DataSource is assumed to provide a MIME multipart
  96. * byte stream. The <code>parsed</code> flag is set to false. When
  97. * the data for the body parts are needed, the parser extracts the
  98. * "boundary" parameter from the content type of this DataSource,
  99. * skips the 'preamble' and reads bytes till the terminating
  100. * boundary and creates MimeBodyParts for each part of the stream.
  101. *
  102. * @param ds DataSource, can be a MultipartDataSource
  103. */
  104. public MimeMultipart(DataSource ds) throws MessagingException {
  105. super();
  106. if (ds instanceof MessageAware) {
  107. MessageContext mc = ((MessageAware)ds).getMessageContext();
  108. setParent(mc.getPart());
  109. }
  110. if (ds instanceof MultipartDataSource) {
  111. // ask super to do this for us.
  112. setMultipartDataSource((MultipartDataSource)ds);
  113. return;
  114. }
  115. // 'ds' was not a MultipartDataSource, we have
  116. // to parse this ourself.
  117. parsed = false;
  118. this.ds = ds;
  119. contentType = ds.getContentType();
  120. }
  121. /**
  122. * Set the subtype. This method should be invoked only on a new
  123. * MimeMultipart object created by the client. The default subtype
  124. * of such a multipart object is "mixed". <p>
  125. *
  126. * @param subtype Subtype
  127. */
  128. public synchronized void setSubType(String subtype)
  129. throws MessagingException {
  130. ContentType cType = new ContentType(contentType);
  131. cType.setSubType(subtype);
  132. contentType = cType.toString();
  133. }
  134. /**
  135. * Return the number of enclosed BodyPart objects.
  136. *
  137. * @return number of parts
  138. */
  139. public synchronized int getCount() throws MessagingException {
  140. parse();
  141. return super.getCount();
  142. }
  143. /**
  144. * Get the specified BodyPart. BodyParts are numbered starting at 0.
  145. *
  146. * @param index the index of the desired BodyPart
  147. * @return the Part
  148. * @exception MessagingException if no such BodyPart exists
  149. */
  150. public synchronized BodyPart getBodyPart(int index)
  151. throws MessagingException {
  152. parse();
  153. return super.getBodyPart(index);
  154. }
  155. /**
  156. * Get the MimeBodyPart referred to by the given ContentID (CID).
  157. * Returns null if the part is not found.
  158. *
  159. * @param CID the ContentID of the desired part
  160. * @return the Part
  161. */
  162. public synchronized BodyPart getBodyPart(String CID)
  163. throws MessagingException {
  164. parse();
  165. int count = getCount();
  166. for (int i = 0; i < count; i++) {
  167. MimeBodyPart part = (MimeBodyPart)getBodyPart(i);
  168. String s = part.getContentID();
  169. if (s != null && s.equals(CID))
  170. return part;
  171. }
  172. return null;
  173. }
  174. /**
  175. * Update headers. The default implementation here just
  176. * calls the <code>updateHeaders</code> method on each of its
  177. * children BodyParts. <p>
  178. *
  179. * Note that the boundary parameter is already set up when
  180. * a new and empty MimeMultipart object is created. <p>
  181. *
  182. * This method is called when the <code>saveChanges</code>
  183. * method is invoked on the Message object containing this
  184. * Multipart. This is typically done as part of the Message
  185. * send process, however note that a client is free to call
  186. * it any number of times. So if the header updating process is
  187. * expensive for a specific MimeMultipart subclass, then it
  188. * might itself want to track whether its internal state actually
  189. * did change, and do the header updating only if necessary.
  190. */
  191. protected void updateHeaders() throws MessagingException {
  192. for (int i = 0; i < parts.size(); i++)
  193. ((MimeBodyPart)parts.elementAt(i)).updateHeaders();
  194. }
  195. /**
  196. * Iterates through all the parts and outputs each Mime part
  197. * separated by a boundary.
  198. */
  199. public void writeTo(OutputStream os)
  200. throws IOException, MessagingException {
  201. parse();
  202. String boundary = "--" +
  203. (new ContentType(contentType)).getParameter("boundary");
  204. LineOutputStream los = new LineOutputStream(os);
  205. for (int i = 0; i < parts.size(); i++) {
  206. los.writeln(boundary); // put out boundary
  207. ((MimeBodyPart)parts.elementAt(i)).writeTo(os);
  208. los.writeln(); // put out empty line
  209. }
  210. // put out last boundary
  211. los.writeln(boundary + "--");
  212. }
  213. /**
  214. * Parse the InputStream from our DataSource, constructing the
  215. * appropriate MimeBodyParts. The <code>parsed</code> flag is
  216. * set to true, and if true on entry nothing is done. This
  217. * method is called by all other methods that need data for
  218. * the body parts, to make sure the data has been parsed.
  219. *
  220. * @since JavaMail 1.2
  221. */
  222. protected synchronized void parse() throws MessagingException {
  223. if (parsed)
  224. return;
  225. InputStream in = null;
  226. SharedInputStream sin = null;
  227. long start = 0, end = 0;
  228. try {
  229. in = ds.getInputStream();
  230. if (!(in instanceof ByteArrayInputStream) &&
  231. !(in instanceof BufferedInputStream))
  232. in = new BufferedInputStream(in);
  233. } catch (Exception ex) {
  234. throw new MessagingException("No inputstream from datasource");
  235. }
  236. if (in instanceof SharedInputStream)
  237. sin = (SharedInputStream)in;
  238. ContentType cType = new ContentType(contentType);
  239. String boundary = "--" + cType.getParameter("boundary");
  240. int bl = boundary.length();
  241. byte[] bndbytes = new byte[bl];
  242. boundary.getBytes(0, bl, bndbytes, 0);
  243. try {
  244. // Skip the preamble
  245. LineInputStream lin = new LineInputStream(in);
  246. String line;
  247. while ((line = lin.readLine()) != null) {
  248. if (line.trim().equals(boundary))
  249. break;
  250. }
  251. if (line == null)
  252. throw new MessagingException("Missing start boundary");
  253. /*
  254. * Read and process body parts until we see the
  255. * terminating boundary line (or EOF).
  256. */
  257. boolean done = false;
  258. while (!done) {
  259. InternetHeaders headers = null;
  260. if (sin != null) {
  261. start = sin.getPosition();
  262. // skip headers
  263. while ((line = lin.readLine()) != null && line.length() > 0)
  264. ;
  265. if (line == null)
  266. throw new MessagingException("EOF skipping headers");
  267. } else {
  268. // collect the headers for this body part
  269. headers = createInternetHeaders(in);
  270. }
  271. if (!in.markSupported())
  272. throw new MessagingException("Stream doesn't support mark");
  273. ByteArrayOutputStream buf = null;
  274. // if we don't have a shared input stream, we copy the data
  275. if (sin == null)
  276. buf = new ByteArrayOutputStream();
  277. int b;
  278. boolean bol = true; // beginning of line flag
  279. // the two possible end of line characters
  280. int eol1 = -1, eol2 = -1;
  281. /*
  282. * Read and save the content bytes in buf.
  283. */
  284. for (;;) {
  285. if (bol) {
  286. /*
  287. * At the beginning of a line, check whether the
  288. * next line is a boundary.
  289. */
  290. int i;
  291. in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
  292. // read bytes, matching against the boundary
  293. for (i = 0; i < bl; i++)
  294. if (in.read() != bndbytes[i])
  295. break;
  296. if (i == bl) {
  297. // matched the boundary, check for last boundary
  298. int b2 = in.read();
  299. if (b2 == '-') {
  300. if (in.read() == '-') {
  301. done = true;
  302. break; // ignore trailing text
  303. }
  304. }
  305. // skip linear whitespace
  306. while (b2 == ' ' || b2 == '\t')
  307. b2 = in.read();
  308. // check for end of line
  309. if (b2 == '\n')
  310. break; // got it! break out of the loop
  311. if (b2 == '\r') {
  312. in.mark(1);
  313. if (in.read() != '\n')
  314. in.reset();
  315. break; // got it! break out of the loop
  316. }
  317. }
  318. // failed to match, reset and proceed normally
  319. in.reset();
  320. // if this is not the first line, write out the
  321. // end of line characters from the previous line
  322. if (buf != null && eol1 != -1) {
  323. buf.write(eol1);
  324. if (eol2 != -1)
  325. buf.write(eol2);
  326. eol1 = eol2 = -1;
  327. }
  328. }
  329. // read the next byte
  330. if ((b = in.read()) < 0) {
  331. done = true;
  332. break;
  333. }
  334. /*
  335. * If we're at the end of the line, save the eol characters
  336. * to be written out before the beginning of the next line.
  337. */
  338. if (b == '\r' || b == '\n') {
  339. bol = true;
  340. if (sin != null)
  341. end = sin.getPosition() - 1;
  342. eol1 = b;
  343. if (b == '\r') {
  344. in.mark(1);
  345. if ((b = in.read()) == '\n')
  346. eol2 = b;
  347. else
  348. in.reset();
  349. }
  350. } else {
  351. bol = false;
  352. if (buf != null)
  353. buf.write(b);
  354. }
  355. }
  356. /*
  357. * Create a MimeBody element to represent this body part.
  358. */
  359. MimeBodyPart part;
  360. if (sin != null)
  361. part = createMimeBodyPart(sin.newStream(start, end));
  362. else
  363. part = createMimeBodyPart(headers, buf.toByteArray());
  364. addBodyPart(part);
  365. }
  366. } catch (IOException ioex) {
  367. throw new MessagingException("IO Error", ioex);
  368. }
  369. parsed = true;
  370. }
  371. /**
  372. * Create and return an InternetHeaders object that loads the
  373. * headers from the given InputStream. Subclasses can override
  374. * this method to return a subclass of InternetHeaders, if
  375. * necessary. This implementation simply constructs and returns
  376. * an InternetHeaders object.
  377. *
  378. * @param is the InputStream to read the headers from
  379. * @exception MessagingException
  380. * @since JavaMail 1.2
  381. */
  382. protected InternetHeaders createInternetHeaders(InputStream is)
  383. throws MessagingException {
  384. return new InternetHeaders(is);
  385. }
  386. /**
  387. * Create and return a MimeBodyPart object to represent a
  388. * body part parsed from the InputStream. Subclasses can override
  389. * this method to return a subclass of MimeBodyPart, if
  390. * necessary. This implementation simply constructs and returns
  391. * a MimeBodyPart object.
  392. *
  393. * @param headers the headers for the body part
  394. * @param content the content of the body part
  395. * @exception MessagingException
  396. * @since JavaMail 1.2
  397. */
  398. protected MimeBodyPart createMimeBodyPart(InternetHeaders headers,
  399. byte[] content) throws MessagingException {
  400. return new MimeBodyPart(headers, content);
  401. }
  402. /**
  403. * Create and return a MimeBodyPart object to represent a
  404. * body part parsed from the InputStream. Subclasses can override
  405. * this method to return a subclass of MimeBodyPart, if
  406. * necessary. This implementation simply constructs and returns
  407. * a MimeBodyPart object.
  408. *
  409. * @param is InputStream containing the body part
  410. * @exception MessagingException
  411. * @since JavaMail 1.2
  412. */
  413. protected MimeBodyPart createMimeBodyPart(InputStream is)
  414. throws MessagingException {
  415. return new MimeBodyPart(is);
  416. }
  417. }