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