]> git.argeo.org Git - gpl/argeo-slc.git/blob - ext/javax.mail.mbox/src/com/sun/mail/mbox/MboxMessage.java
Make logging synchronous during native image build
[gpl/argeo-slc.git] / ext / javax.mail.mbox / src / com / sun / mail / mbox / MboxMessage.java
1 /*
2 * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
3 *
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v. 2.0, which is available at
6 * http://www.eclipse.org/legal/epl-2.0.
7 *
8 * This Source Code may also be made available under the following Secondary
9 * Licenses when the conditions for such availability set forth in the
10 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
11 * version 2 with the GNU Classpath Exception, which is available at
12 * https://www.gnu.org/software/classpath/license.html.
13 *
14 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15 */
16
17 package com.sun.mail.mbox;
18
19 import java.io.*;
20 import java.util.StringTokenizer;
21 import java.util.Date;
22 import java.text.SimpleDateFormat;
23 import javax.activation.*;
24 import javax.mail.*;
25 import javax.mail.internet.*;
26 import javax.mail.event.MessageChangedEvent;
27 import com.sun.mail.util.LineInputStream;
28
29 /**
30 * This class represents an RFC822 style email message that resides in a file.
31 *
32 * @author Bill Shannon
33 */
34
35 public class MboxMessage extends MimeMessage {
36
37 boolean writable = false;
38 // original msg flags, used by MboxFolder to detect modification
39 Flags origFlags;
40 /*
41 * A UNIX From line looks like:
42 * From address Day Mon DD HH:MM:SS YYYY
43 */
44 String unix_from;
45 InternetAddress unix_from_user;
46 Date rcvDate;
47 int lineCount = -1;
48 private static OutputStream nullOutputStream = new OutputStream() {
49 public void write(int b) { }
50 public void write(byte[] b, int off, int len) { }
51 };
52
53 /**
54 * Construct an MboxMessage from the InputStream.
55 */
56 public MboxMessage(Session session, InputStream is)
57 throws MessagingException, IOException {
58 super(session);
59 BufferedInputStream bis;
60 if (is instanceof BufferedInputStream)
61 bis = (BufferedInputStream)is;
62 else
63 bis = new BufferedInputStream(is);
64 LineInputStream dis = new LineInputStream(bis);
65 bis.mark(1024);
66 String line = dis.readLine();
67 if (line != null && line.startsWith("From "))
68 this.unix_from = line;
69 else
70 bis.reset();
71 parse(bis);
72 saved = true;
73 }
74
75 /**
76 * Construct an MboxMessage using the given InternetHeaders object
77 * and content from an InputStream.
78 */
79 public MboxMessage(MboxFolder folder, InternetHeaders hdrs, InputStream is,
80 int msgno, String unix_from, boolean writable)
81 throws MessagingException {
82 super(folder, hdrs, null, msgno);
83 setFlagsFromHeaders();
84 origFlags = getFlags();
85 this.unix_from = unix_from;
86 this.writable = writable;
87 this.contentStream = is;
88 }
89
90 /**
91 * Returns the "From" attribute. The "From" attribute contains
92 * the identity of the person(s) who wished this message to
93 * be sent. <p>
94 *
95 * If our superclass doesn't have a value, we return the address
96 * from the UNIX From line.
97 *
98 * @return array of Address objects
99 * @exception MessagingException
100 */
101 public Address[] getFrom() throws MessagingException {
102 Address[] ret = super.getFrom();
103 if (ret == null) {
104 InternetAddress ia = getUnixFrom();
105 if (ia != null)
106 ret = new InternetAddress[] { ia };
107 }
108 return ret;
109 }
110
111 /**
112 * Returns the address from the UNIX "From" line.
113 *
114 * @return UNIX From address
115 * @exception MessagingException
116 */
117 public synchronized InternetAddress getUnixFrom()
118 throws MessagingException {
119 if (unix_from_user == null && unix_from != null) {
120 int i;
121 // find the space after the address, before the date
122 i = unix_from.indexOf(' ', 5);
123 if (i > 5) {
124 try {
125 unix_from_user =
126 new InternetAddress(unix_from.substring(5, i));
127 } catch (AddressException e) {
128 // ignore it
129 }
130 }
131 }
132 return unix_from_user != null ?
133 (InternetAddress)unix_from_user.clone() : null;
134 }
135
136 private String getUnixFromLine() {
137 if (unix_from != null)
138 return unix_from;
139 String from = "unknown";
140 try {
141 Address[] froma = getFrom();
142 if (froma != null && froma.length > 0 &&
143 froma[0] instanceof InternetAddress)
144 from = ((InternetAddress)froma[0]).getAddress();
145 } catch (MessagingException ex) { }
146 Date d = null;
147 try {
148 d = getSentDate();
149 } catch (MessagingException ex) {
150 // ignore
151 }
152 if (d == null)
153 d = new Date();
154 // From shannon Mon Jun 10 12:06:52 2002
155 SimpleDateFormat fmt = new SimpleDateFormat("EEE LLL dd HH:mm:ss yyyy");
156 return "From " + from + " " + fmt.format(d);
157 }
158
159 /**
160 * Get the date this message was received, from the UNIX From line.
161 *
162 * @return the date this message was received
163 * @exception MessagingException
164 */
165 @SuppressWarnings("deprecation") // for Date constructor
166 public Date getReceivedDate() throws MessagingException {
167 if (rcvDate == null && unix_from != null) {
168 int i;
169 // find the space after the address, before the date
170 i = unix_from.indexOf(' ', 5);
171 if (i > 5) {
172 try {
173 rcvDate = new Date(unix_from.substring(i));
174 } catch (IllegalArgumentException iae) {
175 // ignore it
176 }
177 }
178 }
179 return rcvDate == null ? null : new Date(rcvDate.getTime());
180 }
181
182 /**
183 * Return the number of lines for the content of this message.
184 * Return -1 if this number cannot be determined. <p>
185 *
186 * Note that this number may not be an exact measure of the
187 * content length and may or may not account for any transfer
188 * encoding of the content. <p>
189 *
190 * This implementation returns -1.
191 *
192 * @return number of lines in the content.
193 * @exception MessagingException
194 */
195 public int getLineCount() throws MessagingException {
196 if (lineCount < 0 && isMimeType("text/plain")) {
197 LineCounter lc = null;
198 // writeTo will set the SEEN flag, remember the original state
199 boolean seen = isSet(Flags.Flag.SEEN);
200 try {
201 lc = new LineCounter(nullOutputStream);
202 getDataHandler().writeTo(lc);
203 lineCount = lc.getLineCount();
204 } catch (IOException ex) {
205 // ignore it, can't happen
206 } finally {
207 try {
208 if (lc != null)
209 lc.close();
210 } catch (IOException ex) {
211 // can't happen
212 }
213 }
214 if (!seen)
215 setFlag(Flags.Flag.SEEN, false);
216 }
217 return lineCount;
218 }
219
220 /**
221 * Set the specified flags on this message to the specified value.
222 *
223 * @param flags the flags to be set
224 * @param set the value to be set
225 */
226 public void setFlags(Flags newFlags, boolean set)
227 throws MessagingException {
228 Flags oldFlags = (Flags)flags.clone();
229 super.setFlags(newFlags, set);
230 if (!flags.equals(oldFlags)) {
231 setHeadersFromFlags(this);
232 if (folder != null)
233 ((MboxFolder)folder).notifyMessageChangedListeners(
234 MessageChangedEvent.FLAGS_CHANGED, this);
235 }
236 }
237
238 /**
239 * Return the content type, mapping from SunV3 types to MIME types
240 * as necessary.
241 */
242 public String getContentType() throws MessagingException {
243 String ct = super.getContentType();
244 if (ct.indexOf('/') < 0)
245 ct = SunV3BodyPart.MimeV3Map.toMime(ct);
246 return ct;
247 }
248
249 /**
250 * Produce the raw bytes of the content. This method is used during
251 * parsing, to create a DataHandler object for the content. Subclasses
252 * that can provide a separate input stream for just the message
253 * content might want to override this method. <p>
254 *
255 * This implementation just returns a ByteArrayInputStream constructed
256 * out of the <code>content</code> byte array.
257 *
258 * @see #content
259 */
260 protected InputStream getContentStream() throws MessagingException {
261 if (folder != null)
262 ((MboxFolder)folder).checkOpen();
263 if (isExpunged())
264 throw new MessageRemovedException("mbox message expunged");
265 if (!isSet(Flags.Flag.SEEN))
266 setFlag(Flags.Flag.SEEN, true);
267 return super.getContentStream();
268 }
269
270 /**
271 * Return a DataHandler for this Message's content.
272 * If this is a SunV3 multipart message, handle it specially.
273 *
274 * @exception MessagingException
275 */
276 public synchronized DataHandler getDataHandler()
277 throws MessagingException {
278 if (dh == null) {
279 // XXX - Following is a kludge to avoid having to register
280 // the "multipart/x-sun-attachment" data type with the JAF.
281 String ct = getContentType();
282 if (ct.equalsIgnoreCase("multipart/x-sun-attachment"))
283 dh = new DataHandler(
284 new SunV3Multipart(new MimePartDataSource(this)), ct);
285 else
286 return super.getDataHandler(); // will set "dh"
287 }
288 return dh;
289 }
290
291 // here only to allow package private access from MboxFolder
292 protected void setMessageNumber(int msgno) {
293 super.setMessageNumber(msgno);
294 }
295
296 // here to synchronize access to expunged field
297 public synchronized boolean isExpunged() {
298 return super.isExpunged();
299 }
300
301 // here to synchronize and to allow access from MboxFolder
302 protected synchronized void setExpunged(boolean expunged) {
303 super.setExpunged(expunged);
304 }
305
306 // XXX - We assume that only body parts that are part of a SunV3
307 // multipart will use the SunV3 headers (X-Sun-Content-Length,
308 // X-Sun-Content-Lines, X-Sun-Data-Type, X-Sun-Encoding-Info,
309 // X-Sun-Data-Description, X-Sun-Data-Name) so we don't handle
310 // them here.
311
312 /**
313 * Set the flags for this message based on the Status,
314 * X-Status, and X-Dt-Delete-Time headers.
315 *
316 * SIMS 2.0:
317 * "X-Status: DFAT", deleted, flagged, answered, draft.
318 * Unset flags represented as "$".
319 * User flags not supported.
320 *
321 * University of Washington IMAP server:
322 * "X-Status: DFAT", deleted, flagged, answered, draft.
323 * Unset flags not present.
324 * "X-Keywords: userflag1 userflag2"
325 */
326 private synchronized void setFlagsFromHeaders() {
327 flags = new Flags(Flags.Flag.RECENT);
328 try {
329 String s = getHeader("Status", null);
330 if (s != null) {
331 if (s.indexOf('R') >= 0)
332 flags.add(Flags.Flag.SEEN);
333 if (s.indexOf('O') >= 0)
334 flags.remove(Flags.Flag.RECENT);
335 }
336 s = getHeader("X-Dt-Delete-Time", null); // set by dtmail
337 if (s != null)
338 flags.add(Flags.Flag.DELETED);
339 s = getHeader("X-Status", null); // set by IMAP server
340 if (s != null) {
341 if (s.indexOf('D') >= 0)
342 flags.add(Flags.Flag.DELETED);
343 if (s.indexOf('F') >= 0)
344 flags.add(Flags.Flag.FLAGGED);
345 if (s.indexOf('A') >= 0)
346 flags.add(Flags.Flag.ANSWERED);
347 if (s.indexOf('T') >= 0)
348 flags.add(Flags.Flag.DRAFT);
349 }
350 s = getHeader("X-Keywords", null); // set by IMAP server
351 if (s != null) {
352 StringTokenizer st = new StringTokenizer(s);
353 while (st.hasMoreTokens())
354 flags.add(st.nextToken());
355 }
356 } catch (MessagingException e) {
357 // ignore it
358 }
359 }
360
361 /**
362 * Set the various header fields that represent the message flags.
363 */
364 static void setHeadersFromFlags(MimeMessage msg) {
365 try {
366 Flags flags = msg.getFlags();
367 StringBuilder status = new StringBuilder();
368 if (flags.contains(Flags.Flag.SEEN))
369 status.append('R');
370 if (!flags.contains(Flags.Flag.RECENT))
371 status.append('O');
372 if (status.length() > 0)
373 msg.setHeader("Status", status.toString());
374 else
375 msg.removeHeader("Status");
376
377 boolean sims = false;
378 String s = msg.getHeader("X-Status", null);
379 // is it a SIMS 2.0 format X-Status header?
380 sims = s != null && s.length() == 4 && s.indexOf('$') >= 0;
381 status.setLength(0);
382 if (flags.contains(Flags.Flag.DELETED))
383 status.append('D');
384 else if (sims)
385 status.append('$');
386 if (flags.contains(Flags.Flag.FLAGGED))
387 status.append('F');
388 else if (sims)
389 status.append('$');
390 if (flags.contains(Flags.Flag.ANSWERED))
391 status.append('A');
392 else if (sims)
393 status.append('$');
394 if (flags.contains(Flags.Flag.DRAFT))
395 status.append('T');
396 else if (sims)
397 status.append('$');
398 if (status.length() > 0)
399 msg.setHeader("X-Status", status.toString());
400 else
401 msg.removeHeader("X-Status");
402
403 String[] userFlags = flags.getUserFlags();
404 if (userFlags.length > 0) {
405 status.setLength(0);
406 for (int i = 0; i < userFlags.length; i++)
407 status.append(userFlags[i]).append(' ');
408 status.setLength(status.length() - 1); // smash trailing space
409 msg.setHeader("X-Keywords", status.toString());
410 }
411 if (flags.contains(Flags.Flag.DELETED)) {
412 s = msg.getHeader("X-Dt-Delete-Time", null);
413 if (s == null)
414 // XXX - should be time
415 msg.setHeader("X-Dt-Delete-Time", "1");
416 }
417 } catch (MessagingException e) {
418 // ignore it
419 }
420 }
421
422 protected void updateHeaders() throws MessagingException {
423 super.updateHeaders();
424 setHeadersFromFlags(this);
425 }
426
427 /**
428 * Save any changes made to this message.
429 */
430 public void saveChanges() throws MessagingException {
431 if (folder != null)
432 ((MboxFolder)folder).checkOpen();
433 if (isExpunged())
434 throw new MessageRemovedException("mbox message expunged");
435 if (!writable)
436 throw new MessagingException("Message is read-only");
437
438 super.saveChanges();
439
440 try {
441 /*
442 * Count the size of the body, in order to set the Content-Length
443 * header. (Should we only do this to update an existing
444 * Content-Length header?)
445 * XXX - We could cache the content bytes here, for use later
446 * in writeTo.
447 */
448 ContentLengthCounter cos = new ContentLengthCounter();
449 OutputStream os = new NewlineOutputStream(cos);
450 super.writeTo(os);
451 os.flush();
452 setHeader("Content-Length", String.valueOf(cos.getSize()));
453 // setContentSize((int)cos.getSize());
454 } catch (MessagingException e) {
455 throw e;
456 } catch (Exception e) {
457 throw new MessagingException("unexpected exception " + e);
458 }
459 }
460
461 /**
462 * Expose modified flag to MboxFolder.
463 */
464 boolean isModified() {
465 return modified;
466 }
467
468 /**
469 * Put out a byte stream suitable for saving to a file.
470 * XXX - ultimately implement "ignore headers" here?
471 */
472 public void writeToFile(OutputStream os) throws IOException {
473 try {
474 if (getHeader("Content-Length") == null) {
475 /*
476 * Count the size of the body, in order to set the
477 * Content-Length header.
478 */
479 ContentLengthCounter cos = new ContentLengthCounter();
480 OutputStream oos = new NewlineOutputStream(cos);
481 super.writeTo(oos, null);
482 oos.flush();
483 setHeader("Content-Length", String.valueOf(cos.getSize()));
484 // setContentSize((int)cos.getSize());
485 }
486
487 os = new NewlineOutputStream(os, true);
488 PrintStream pos = new PrintStream(os, false, "iso-8859-1");
489
490 pos.println(getUnixFromLine());
491 super.writeTo(pos, null);
492 pos.flush();
493 } catch (MessagingException e) {
494 throw new IOException("unexpected exception " + e);
495 }
496 }
497
498 public void writeTo(OutputStream os, String[] ignoreList)
499 throws IOException, MessagingException {
500 // set the SEEN flag now, which will normally be set by
501 // getContentStream, so it will show up in our headers
502 if (!isSet(Flags.Flag.SEEN))
503 setFlag(Flags.Flag.SEEN, true);
504 super.writeTo(os, ignoreList);
505 }
506
507 /**
508 * Interpose on superclass method to make sure folder is still open
509 * and message hasn't been expunged.
510 */
511 public String[] getHeader(String name)
512 throws MessagingException {
513 if (folder != null)
514 ((MboxFolder)folder).checkOpen();
515 if (isExpunged())
516 throw new MessageRemovedException("mbox message expunged");
517 return super.getHeader(name);
518 }
519
520 /**
521 * Interpose on superclass method to make sure folder is still open
522 * and message hasn't been expunged.
523 */
524 public String getHeader(String name, String delimiter)
525 throws MessagingException {
526 if (folder != null)
527 ((MboxFolder)folder).checkOpen();
528 if (isExpunged())
529 throw new MessageRemovedException("mbox message expunged");
530 return super.getHeader(name, delimiter);
531 }
532 }