File: MimeMessage.java

package info (click to toggle)
libgnumail-java 1.0-5
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 3,620 kB
  • ctags: 2,193
  • sloc: java: 17,470; sh: 9,912; makefile: 432; xml: 159
file content (2065 lines) | stat: -rw-r--r-- 68,570 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
/*
 * MimeMessage.java
 * Copyright (C) 2002, 2004 The Free Software Foundation
 * 
 * This file is part of GNU JavaMail, a library.
 * 
 * GNU JavaMail is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * GNU JavaMail is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * As a special exception, if you link this library with other files to
 * produce an executable, this library does not by itself cause the
 * resulting executable to be covered by the GNU General Public License.
 * This exception does not however invalidate any other reasons why the
 * executable file might be covered by the GNU General Public License.
 */

package javax.mail.internet;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.ObjectStreamException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import javax.activation.DataHandler;
import javax.mail.Address;
import javax.mail.Flags;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.Session;

import gnu.mail.util.RFC2822OutputStream;

/**
 * This class represents a MIME style email message.
 * It implements the Message abstract class and the MimePart interface.
 * <p>
 * Clients wanting to create new MIME style messages will instantiate an empty
 * MimeMessage object and then fill it with appropriate attributes and content.
 * <p>
 * Service providers that implement MIME compliant backend stores may want to
 * subclass MimeMessage and override certain methods to provide specific
 * implementations. The simplest case is probably a provider that generates 
 * a MIME style input stream and leaves the parsing of the stream to this 
 * class.
 * <p>
 * MimeMessage uses the InternetHeaders class to parse and store the toplevel 
 * RFC 822 headers of a message.
 * <p>
 * <hr>
 * A note on RFC 822 and MIME headers
 * <p>
 * RFC 822 header fields must contain only US-ASCII characters. MIME allows 
 * non ASCII characters to be present in certain portions of certain headers,
 * by encoding those characters. RFC 2047 specifies the rules for doing this.
 * The MimeUtility class provided in this package can be used to to achieve 
 * this.
 * Callers of the <code>setHeader</code>, <code>addHeader</code>, and 
 * <code>addHeaderLine</code> methods are responsible for enforcing the MIME
 * requirements for the specified headers. In addition, these header fields
 * must be folded (wrapped) before being sent if they exceed the line length
 * limitation for the transport (1000 bytes for SMTP). Received headers may
 * have been folded. The application is responsible for folding and unfolding
 * headers as appropriate.
 *
 * @author <a href="mailto:dog@gnu.org">Chris Burdess</a>
 * @version 1.3
 */
public class MimeMessage
  extends Message
  implements MimePart
{

  /**
   * This inner class extends the javax.mail.Message.RecipientType class 
   * to add additional RecipientTypes.
   * The one additional RecipientType currently defined here is NEWSGROUPS.
   */
  public static class RecipientType
    extends Message.RecipientType
  {

    /**
     * The "Newsgroup" (Usenet news) recipients.
     */
    public static final RecipientType NEWSGROUPS =
      new RecipientType("Newsgroups");

    /**
     * When deserializing a RecipientType, we need to make sure to return
     * only one of the known static final instances defined in this class.
     * Subclasses must implement their own readResolve method that checks
     * for their known instances before calling this super method.
     */
    protected Object readResolve()
      throws ObjectStreamException
    {
      if (type.equals("Newsgroups"))
        return NEWSGROUPS;
      else
        return super.readResolve();
    }

    // super :-)
    protected RecipientType(String type)
    {
      super(type);
    }
    
  }

  /**
   * The DataHandler object representing this Message's content.
   */
  protected DataHandler dh;

  /**
   * Byte array that holds the bytes of this Message's content.
   */
  protected byte content[];

  /**
   * If the data for this message was supplied by an InputStream 
   * that implements the SharedInputStream interface, contentStream is 
   * another such stream representing the content of this message.
   * In this case, content will be null.
   */
  protected InputStream contentStream;

  /**
   * The InternetHeaders object that stores the header of this message.
   */
  protected InternetHeaders headers;

  /**
   * The Flags for this message.
   */
  protected Flags flags;

  /**
   * A flag indicating whether the message has been modified.
   * If the message has not been modified, any data in the content array
   * is assumed to be valid and is used directly in the <code>writeTo</code>
   * method. This flag is set to true when an empty message is created 
   * or when the <code>saveChanges</code> method is called.
   */
  protected boolean modified;

  /**
   * Does the saveChanges method need to be called on this message?
   * This flag is set to false by the public constructor and set to true 
   * by the <code>saveChanges</code> method.
   * The <code>writeTo</code> method checks this flag and calls the 
   * <code>saveChanges</code> method as necessary.
   * This avoids the common mistake of forgetting to call the 
   * <code>saveChanges</code> method on a newly constructed message.
   */
  protected boolean saved;

  /*
   * This is used to parse and format values for the RFC822 Date header.
   */
  private static MailDateFormat dateFormat = new MailDateFormat();

  // Header constants.
  static final String TO_NAME = "To";
  static final String CC_NAME = "Cc";
  static final String BCC_NAME = "Bcc";
  static final String NEWSGROUPS_NAME = "Newsgroups";
  static final String FROM_NAME = "From";
  static final String SENDER_NAME = "Sender";
  static final String REPLY_TO_NAME = "Reply-To";
  static final String SUBJECT_NAME = "Subject";
  static final String DATE_NAME = "Date";
  static final String MESSAGE_ID_NAME = "Message-ID";
  
  /**
   * Default constructor.
   * An empty message object is created.
   * The headers field is set to an empty InternetHeaders object.
   * The flags field is set to an empty Flags object.
   * The <code>modified</code> flag is set to true.
   */
  public MimeMessage(Session session)
  {
    super(session);
    headers = new InternetHeaders();
    flags = new Flags();
    modified = true;
  }

  /**
   * Constructs a MimeMessage by reading and parsing the data 
   * from the specified MIME InputStream.
   * The InputStream will be left positioned at the end of the data 
   * for the message. Note that the input stream parse is done within this
   * constructor itself.
   * @param session Session object for this message
   * @param is the message input stream
   */
  public MimeMessage(Session session, InputStream is)
    throws MessagingException
  {
    super(session);
    flags = new Flags();
    parse(is);
    saved = true;
  }

  /**
   * Constructs a new MimeMessage with content initialized from the source
   * MimeMessage.
   * The new message is independent of the original.
   * <p>
   * Note: The current implementation is rather inefficient, copying the data
   * more times than strictly necessary.
   * @param source the message to copy content from
   */
  public MimeMessage(MimeMessage source)
    throws MessagingException
  {
    super(source.session);
    // Use a byte array for temporary storage
    try
    {
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      source.writeTo(bos);
      bos.close();
      ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
      parse(bis);
      bis.close();
      saved = true;
    }
    catch (IOException e)
    {
      throw new MessagingException("I/O error", e);
    }
  }

  /**
   * Constructs an empty MimeMessage object with the given Folder and message
   * number.
   * <p>
   * This method is for providers subclassing MimeMessage.
   */
  protected MimeMessage(Folder folder, int msgnum)
  {
    super(folder, msgnum);
    flags = new Flags();
    saved = true;
  }

  /**
   * Constructs a MimeMessage by reading and parsing the data from the 
   * specified MIME InputStream.
   * The InputStream will be left positioned at the end of the data
   * for the message. Note that the input stream parse is done within this
   * constructor itself.
   * <p>
   * This method is for providers subclassing MimeMessage.
   * @param folder The containing folder.
   * @param is the message input stream
   * @param msgnum Message number of this message within its folder
   */
  protected MimeMessage(Folder folder, InputStream is, int msgnum)
    throws MessagingException
  {
    this(folder, msgnum);
    parse(is);
  }

  /**
   * Constructs a MimeMessage from the given InternetHeaders object and 
   * content.
   * This method is for providers subclassing MimeMessage.
   * @param folder The containing folder.
   * @param headers The message headers.
   * @param content the content as an array of bytes
   * @param msgnum Message number of this message within its folder
   */
  protected MimeMessage(Folder folder, InternetHeaders headers, 
      byte[] content, int msgnum)
    throws MessagingException
  {
    this(folder, msgnum);
    this.headers = headers;
    this.content = content;
  }

  /**
   * Parse the InputStream setting the headers and content fields 
   * appropriately.
   * Also resets the modified flag.
   * <p>
   * This method is intended for use by subclasses that need to control 
   * when the InputStream is parsed.
   * @param is The message input stream
   */
  protected void parse(InputStream is)
    throws MessagingException
  {
    if (is instanceof SharedInputStream)
    {
      headers = createInternetHeaders(is);
      SharedInputStream sis = (SharedInputStream)is;
      contentStream = sis.newStream(sis.getPosition(), -1L);
    }
    else
    {
      // buffer it
      if (!(is instanceof ByteArrayInputStream) && 
          !(is instanceof BufferedInputStream))
        is = new BufferedInputStream(is);
      // headers
      headers = createInternetHeaders(is);
      // Read stream into byte array
      try
      {
        // TODO Make buffer size configurable
        int len = 1024;
        if (is instanceof ByteArrayInputStream)
        {
          len = is.available();
          content = new byte[len];
          is.read(content, 0, len);
        }
        else
        {
          ByteArrayOutputStream bos = new ByteArrayOutputStream(len);
          content = new byte[len]; // it's just a buffer!
          for (int l = is.read(content, 0, len); 
              l!=-1;
              l = is.read(content, 0, len)) 
            bos.write(content, 0, l);
          content = bos.toByteArray();
        }
      }
      catch (IOException e)
      {
        throw new MessagingException("I/O error", e);
      }
    }
    modified = false;
  }

  // -- From --
  
  /**
   * Returns the value of the RFC 822 "From" header fields.
   * If this header field is absent, the "Sender" header field is used.
   * If the "Sender" header field is also absent, null is returned.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   */
  public Address[] getFrom()
    throws MessagingException
  {
    Address[] from = getInternetAddresses(FROM_NAME);
    if (from==null)
      from = getInternetAddresses(SENDER_NAME);
    return from;
  }

  /**
   * Set the RFC 822 "From" header field.
   * Any existing values are replaced with the given address.
   * If address is null, this header is removed.
   * @param address the sender of this message
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setFrom(Address address)
    throws MessagingException
  {
    if (address==null)
      removeHeader(FROM_NAME);
    else
      setHeader(FROM_NAME, address.toString());
  }

  /**
   * Set the RFC 822 "From" header field using the value of the
   * <code>InternetAddress.getLocalAddress</code> method.
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setFrom()
    throws MessagingException
  {
    InternetAddress localAddress = 
      InternetAddress.getLocalAddress(session);
    if (localAddress!=null)
      setFrom(localAddress);
    else
      throw new MessagingException("No local address");
  }

  /**
   * Add the specified addresses to the existing "From" field.
   * If the "From" field does not already exist, it is created.
   * @param addresses the senders of this message
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void addFrom(Address[] addresses)
    throws MessagingException
  {
    addInternetAddresses(FROM_NAME, addresses);
  }

  /**
   * Returns the value of the RFC 822 "Sender" header field.
   * If the "Sender" header field is absent, <code>null</code> is returned.
   * <p>
   * This implementation uses the <code>getHeader</code> method to obtain
   * the required header field.
   * @since JavaMail 1.3
   */
  public Address getSender()
    throws MessagingException
  {
    Address[] sender = getInternetAddresses(SENDER_NAME);
    if (sender != null && sender.length > 0)
      return sender[0];
    else
      return null;
  }

  /**
   * Set the RFC 822 "Sender header field. Any existing values are replaced
   * with the given address. If address is <code>null</code>, this header is
   * removed.
   * @param address the sener of this message
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @since JavaMail 1.3
   */
  public void setSender(Address address)
    throws MessagingException
  {
    Address[] addresses = new Address[] { address };
    addInternetAddresses(SENDER_NAME, addresses);
  }

  // -- To --
  
  /**
   * Returns the recipients specified by the type.
   * The mapping between the type and the corresponding RFC 822 header 
   * is as follows:
   * <dl>
   * <dt>Message.RecipientType.TO<dd>"To"
   * <dt>Message.RecipientType.CC<dd>"Cc"
   * <dt>Message.RecipientType.BCC<dd>"Bcc"
   * <dt>MimeMessage.RecipientType.NEWSGROUPS<dd>"Newsgroups"
   * </dl>
   * <p>
   * Returns null if the header specified by the type is not found or if its
   * value is empty.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   * @param type the type of recipient
   */
  public Address[] getRecipients(Message.RecipientType type)
    throws MessagingException
  {
    if (type==RecipientType.NEWSGROUPS)
    {
      // Can't use getInternetAddresses here
      // and it's not worth a getNewsAddresses method
      String header = getHeader(NEWSGROUPS_NAME, ",");
      return (header!=null) ? NewsAddress.parse(header) : null;
    }
    return getInternetAddresses(getHeader(type));
  }

  /**
   * Get all the recipient addresses for the message.
   * Extracts the TO, CC, BCC, and NEWSGROUPS recipients.
   */
  public Address[] getAllRecipients()
    throws MessagingException
  {
    Address[] recipients = super.getAllRecipients();
    Address[] newsgroups = getRecipients(RecipientType.NEWSGROUPS);
    if (newsgroups==null)
      return recipients;
    else if (recipients==null)
      return newsgroups;
    else
    {
      Address[] both = new Address[recipients.length+newsgroups.length];
      System.arraycopy(recipients, 0, both, 0, recipients.length);
      System.arraycopy(newsgroups, 0, both, recipients.length, 
          newsgroups.length);
      return both;
    }
  }

  /**
   * Set the specified recipient type to the given addresses.
   * If the address parameter is null, the corresponding recipient field 
   * is removed.
   * @param type Recipient type
   * @param addresses Addresses
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setRecipients(Message.RecipientType type, Address[] addresses)
    throws MessagingException
  {
    if (type==RecipientType.NEWSGROUPS)
    {
      if (addresses==null || addresses.length==0)
        removeHeader(NEWSGROUPS_NAME);
      else
        setHeader(NEWSGROUPS_NAME, NewsAddress.toString(addresses));
    }
    else
      setInternetAddresses(getHeader(type), addresses);
  }

  /**
   * Set the specified recipient type to the given addresses.
   * If the address parameter is null, the corresponding recipient field
   * is removed.
   * @param type Recipient type
   * @param addresses Addresses
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setRecipients(Message.RecipientType type, String addresses)
    throws MessagingException
  {
    if (type==RecipientType.NEWSGROUPS)
    {
      if (addresses==null || addresses.length()==0)
        removeHeader(NEWSGROUPS_NAME);
      else
        setHeader(NEWSGROUPS_NAME, addresses);
    }
    else
      setInternetAddresses(getHeader(type),
          InternetAddress.parse(addresses));
  }

  /**
   * Add the given addresses to the specified recipient type.
   * @param type Recipient type
   * @param addresses Addresses
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void addRecipients(Message.RecipientType type, Address[] addresses)
    throws MessagingException
  {
    if (type==RecipientType.NEWSGROUPS)
    {
      String value = NewsAddress.toString(addresses);
      if (value!=null)
        addHeader(NEWSGROUPS_NAME, value);
    }
    else
      addInternetAddresses(getHeader(type), addresses);
  }

  /**
   * Add the given addresses to the specified recipient type.
   * @param type Recipient type
   * @param addresses Addresses
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void addRecipients(Message.RecipientType type, String addresses)
    throws MessagingException
  {
    if (type==RecipientType.NEWSGROUPS)
    {
      if (addresses!=null && addresses.length()!=0)
        addHeader(NEWSGROUPS_NAME, addresses);
    }
    else
      addInternetAddresses(getHeader(type),
          InternetAddress.parse(addresses));
  }

  /**
   * Return the value of the RFC 822 "Reply-To" header field.
   * If this header is unavailable or its value is absent,
   * then the <code>getFrom</code> method is called and its value is returned.
   * This implementation uses the <code>getHeader</code> method
   * to obtain the requisite header field.
   */
  public Address[] getReplyTo()
    throws MessagingException
  {
    Address[] replyTo = getInternetAddresses(REPLY_TO_NAME);
    if (replyTo==null)
      replyTo = getFrom();
    return replyTo;
  }

  /**
   * Set the RFC 822 "Reply-To" header field.
   * If the address parameter is null, this header is removed.
   * @param addresses Addresses
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setReplyTo(Address[] addresses)
    throws MessagingException
  {
    setInternetAddresses(REPLY_TO_NAME, addresses);
  }

  // convenience method
  private Address[] getInternetAddresses(String name)
    throws MessagingException
  {
    String value = getHeader(name, ",");
    // Use InternetAddress.parseHeader since 1.3
    String s = session.getProperty("mail.mime.address.strict");
    boolean strict = (s == null) || Boolean.valueOf(s).booleanValue();
    return (value!=null) ? InternetAddress.parseHeader(value, strict) : null;
  }

  // convenience method
  private void setInternetAddresses(String name, Address[] addresses)
    throws MessagingException
  {
    String line = InternetAddress.toString(addresses);
    if (line==null)
      removeHeader(line);
    else
      setHeader(name, line);
  }

  // convenience method
  private void addInternetAddresses(String name, Address[] addresses)
    throws MessagingException
  {
    String line = InternetAddress.toString(addresses);
    if (line!=null)
      addHeader(name, line);
  }

  /*
   * Convenience method to return the header name for a given recipient
   * type. This should be faster than keeping a hash of recipient types to
   * names.
   */
  private String getHeader(Message.RecipientType type)
    throws MessagingException
  {
    if (type==Message.RecipientType.TO)
      return TO_NAME;
    if (type==Message.RecipientType.CC)
      return CC_NAME;
    if (type==Message.RecipientType.BCC)
      return BCC_NAME;
    if (type==RecipientType.NEWSGROUPS)
      return NEWSGROUPS_NAME;
    throw new MessagingException("Invalid recipient type");
  }

  /**
   * Returns the value of the "Subject" header field.
   * Returns null if the subject field is unavailable or its value is absent.
   * <p>
   * If the subject is encoded as per RFC 2047, it is decoded and converted 
   * into Unicode. If the decoding or conversion fails, 
   * the raw data is returned as is.
   * <p>
   * This implementation uses the <code>getHeader</code> method
   * to obtain the requisite header field.
   */
  public String getSubject()
    throws MessagingException
  {
    String subject = getHeader(SUBJECT_NAME, null);
    if (subject==null)
      return null;
    try
    {
      subject = MimeUtility.decodeText(subject);
    }
    catch (UnsupportedEncodingException e)
    {
    }
    return subject;
  }

  /**
   * Set the "Subject" header field. 
   * If the subject contains non US-ASCII characters, it will be encoded 
   * using the platform's default charset. If the subject contains only 
   * US-ASCII characters, no encoding is done and it is used as-is.
   * If the subject is null, the existing "Subject" field is removed.
   * <p>
   * Note that if the charset encoding process fails, a MessagingException is
   * thrown, and an UnsupportedEncodingException is included in the chain of
   * nested exceptions within the MessagingException.
   * @param subject the subject
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setSubject(String subject)
    throws MessagingException
  {
    setSubject(subject, null);
  }

  /**
   * Set the "Subject" header field. 
   * If the subject contains non US-ASCII characters, it will be encoded 
   * using the specified charset. If the subject contains only 
   * US-ASCII characters, no encoding is done and it is used as-is.
   * If the subject is null, the existing "Subject" field is removed.
   * <p>
   * Note that if the charset encoding process fails, a MessagingException is
   * thrown, and an UnsupportedEncodingException is included in the chain of
   * nested exceptions within the MessagingException.
   * @param subject the subject
   * @param charset the charset
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setSubject(String subject, String charset)
    throws MessagingException
  {
    if (subject==null)
      removeHeader(SUBJECT_NAME);
    try
    {
      setHeader(SUBJECT_NAME, MimeUtility.encodeText(subject, charset, null));
    }
    catch (UnsupportedEncodingException e)
    {
      throw new MessagingException("Encoding error", e);
    }
  }

  /**
   * Returns the value of the RFC 822 "Date" field. This is the date on which
   * this message was sent. Returns null if this field is unavailable or its
   * value is absent.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   */
  public Date getSentDate()
    throws MessagingException
  {
    String value = getHeader(DATE_NAME, null);
    if (value!=null)
    {
      try
      {
        return dateFormat.parse(value);
      }
      catch (ParseException e)
      {
      }
    }
    return null;
  }

  /**
   * Set the RFC 822 "Date" header field.
   * This is the date on which the creator of the message indicates that 
   * the message is complete and ready for delivery.
   * If the <code>date</code> parameter is null, the existing "Date" field 
   * is removed.
   * @param date the date value to set, or null to remove
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setSentDate(Date date)
    throws MessagingException
  {
    if (date==null)
      removeHeader(DATE_NAME);
    else
      setHeader(DATE_NAME, dateFormat.format(date));
  }

  /**
   * Returns the Date on this message was received.
   * Returns null if this date cannot be obtained.
   * <p>
   * Note that RFC 822 does not define a field for the received date.
   * Hence only implementations that can provide this date need return 
   * a valid value.
   */
  public Date getReceivedDate()
    throws MessagingException
  {
    // hence...
    return null;
  }

  /**
   * Return the size of the content of this message in bytes.
   * Return -1 if the size cannot be determined.
   * <p>
   * Note that this number may not be an exact measure of the content size 
   * and may or may not account for any transfer encoding of the content.
   * <p>
   * This implementation returns the size of the <code>content</code> array
   * (if not null), or, if <code>contentStream</code> is not null, and the 
   * <code>available</code> method returns a positive number, it returns 
   * that number as the size. Otherwise, it returns -1.
   */
  public int getSize()
    throws MessagingException
  {
    if (content!=null)
      return content.length;
    if (contentStream!=null)
    {
      try
      {
        int available = contentStream.available();
        if (available>0)
          return available;
      }
      catch (IOException e)
      {
      }
    }
    return -1;
  }

  /**
   * Return the number of lines for the content of this message.
   * Return -1 if this number cannot be determined.
   * <p>
   * Note that this number may not be an exact measure of the content length 
   * and may or may not account for any transfer encoding of the content.
   * <p>
   * This implementation returns -1.
   */
  public int getLineCount()
    throws MessagingException
  {
    return -1;
  }

  /**
   * Returns the value of the RFC 822 "Content-Type" header field.
   * This represents the content-type of the content of this message.
   * This value must not be null. If this field is unavailable, 
   * "text/plain" should be returned.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   */
  public String getContentType()
    throws MessagingException
  {
    String contentType = getHeader(MimeBodyPart.CONTENT_TYPE_NAME, null);
    if (contentType==null)
      return MimeBodyPart.TEXT_PLAIN;
    return contentType;
  }

  /**
   * Is this Part of the specified MIME type? This method compares only the
   * primaryType and subType.
   * The parameters of the content types are ignored.
   * <p>
   * For example, this method will return true when comparing a Part 
   * of content type "text/plain" with "text/plain; charset=foobar".
   * <p>
   * If the subType of mimeType is the special character '*', then 
   * the subtype is ignored during the comparison.
   * @see MimeBodyPart#isMimeType
   */
  public boolean isMimeType(String mimeType)
    throws MessagingException
  {
    return (new ContentType(getContentType()).match(mimeType));
  }

  /**
   * Returns the value of the "Content-Disposition" header field.
   * This represents the disposition of this part.
   * The disposition describes how the part should be presented to the user.
   * <p>
   * If the Content-Disposition field is unavailable, null is returned.
   * <p>
   * This implementation uses the <code>getHeader</code> method
   * to obtain the requisite header field.
   * @see MimeBodyPart#getDisposition
   */
  public String getDisposition()
    throws MessagingException
  {
    String disposition = 
      getHeader(MimeBodyPart.CONTENT_DISPOSITION_NAME, null);
    if (disposition!=null)
      return new ContentDisposition(disposition).getDisposition();
    return null;
  }

  /**
   * Set the "Content-Disposition" header field of this Message.
   * If <code>disposition</code> is null,
   * any existing "Content-Disposition" header field is removed.
   * @param disposition the disposition value to set, or null to remove
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#setDisposition
   */
  public void setDisposition(String disposition)
    throws MessagingException
  {
    if (disposition==null)
      removeHeader(MimeBodyPart.CONTENT_DISPOSITION_NAME);
    else
    {
      String value = getHeader(MimeBodyPart.CONTENT_DISPOSITION_NAME, null);
      if (value!=null)
      {
        ContentDisposition cd = new ContentDisposition(value);
        cd.setDisposition(disposition);
        disposition = cd.toString();
      }
      setHeader(MimeBodyPart.CONTENT_DISPOSITION_NAME, disposition);
    }
  }

  /**
   * Returns the content transfer encoding from the "Content-Transfer-Encoding"
   * header field.
   * Returns null if the header is unavailable or its value is absent.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   * @see MimeBodyPart#getEncoding
   */
  public String getEncoding()
    throws MessagingException
  {
    String encoding = 
      getHeader(MimeBodyPart.CONTENT_TRANSFER_ENCODING_NAME, null);
    if (encoding!=null)
    {
      encoding = encoding.trim();
      if (encoding.equalsIgnoreCase("7bit") || 
          encoding.equalsIgnoreCase("8bit") || 
          encoding.equalsIgnoreCase("quoted-printable") ||
          encoding.equalsIgnoreCase("base64"))
        return encoding;
      HeaderTokenizer ht = new HeaderTokenizer(encoding, HeaderTokenizer.MIME);
      for (boolean done = false; !done; )
      {
        HeaderTokenizer.Token token = ht.next();
        switch (token.getType())
        {
          case HeaderTokenizer.Token.EOF:
            done = true;
            break;
          case HeaderTokenizer.Token.ATOM:
            return token.getValue();
        }
      }
      return encoding;
    }
    return null;
  }

  /**
   * Returns the value of the "Content-ID" header field.
   * Returns null if the field is unavailable or its value is absent.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   * @see MimeBodyPart#getContentID
   */
  public String getContentID()
    throws MessagingException
  {
    return getHeader(MimeBodyPart.CONTENT_ID_NAME, null);
  }

  /**
   * Set the "Content-ID" header field of this Message.
   * If the cid parameter is null, any existing "Content-ID" is removed.
   * @param cid the content-id value to set, or null to remove
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setContentID(String cid)
    throws MessagingException
  {
    if (cid==null)
      removeHeader(MimeBodyPart.CONTENT_ID_NAME);
    else
      setHeader(MimeBodyPart.CONTENT_ID_NAME, cid);
  }

  /**
   * Return the value of the "Content-MD5" header field.
   * Returns null if this field is unavailable or its value is absent.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   * @see MimeBodyPart#getContentMD5
   */
  public String getContentMD5()
    throws MessagingException
  {
    return getHeader(MimeBodyPart.CONTENT_MD5_NAME, null);
  }

  /**
   * Set the "Content-MD5" header field of this Message.
   * @param md5 the content-md5 value to set
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#setContentMD5
   */
  public void setContentMD5(String md5)
    throws MessagingException
  {
    setHeader(MimeBodyPart.CONTENT_MD5_NAME, md5);
  }

  /**
   * Returns the "Content-Description" header field of this Message.
   * This typically associates some descriptive information with this part.
   * Returns null if this field is unavailable or its value is absent.
   * <p>
   * If the Content-Description field is encoded as per RFC 2047,
   * it is decoded and converted into Unicode.
   * If the decoding or conversion fails, the raw data is returned as-is.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   * @see MimeBodyPart#getDescription
   */
  public String getDescription()
    throws MessagingException
  {
    String header = getHeader(MimeBodyPart.CONTENT_DESCRIPTION_NAME, null);
    if (header!=null)
    {
      try
      {
        return MimeUtility.decodeText(header);
      }
      catch (UnsupportedEncodingException e)
      {
        return header;
      }
    }
    return null;
  }

  /**
   * Set the "Content-Description" header field for this Message.
   * If the <code>description</code> parameter is null,
   * then any existing "Content-Description" fields are removed.
   * <p>
   * If the description contains non US-ASCII characters, it will be encoded
   * using the platform's default charset. If the description contains only
   * US-ASCII characters, no encoding is done and it is used as-is.
   * <p>
   * Note that if the charset encoding process fails, a MessagingException is
   * thrown, and an UnsupportedEncodingException is included in the chain of
   * nested exceptions within the MessagingException.
   * @param description content-description
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#setDescription
   */
  public void setDescription(String description)
    throws MessagingException
  {
    setDescription(description, null);
  }

  /**
   * Set the "Content-Description" header field for this Message.
   * If the <code>description</code> parameter is null,
   * then any existing "Content-Description" fields are removed.
   * <p>
   * If the description contains non US-ASCII characters, it will be encoded
   * using the specified charset. If the description contains only
   * US-ASCII characters, no encoding is done and it is used as-is.
   * <p>
   * Note that if the charset encoding process fails, a MessagingException is
   * thrown, and an UnsupportedEncodingException is included in the chain of
   * nested exceptions within the MessagingException.
   * @param description content-description
   * @param charset the charset to use
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#setDescription
   */
  public void setDescription(String description, String charset)
    throws MessagingException
  {
    if (description!=null)
    {
      try
      {
        setHeader(MimeBodyPart.CONTENT_DESCRIPTION_NAME,
            MimeUtility.encodeText(description, charset, null));
      }
      catch (UnsupportedEncodingException e)
      {
        throw new MessagingException("Encode error", e);
      }
    }
    else
      removeHeader(MimeBodyPart.CONTENT_DESCRIPTION_NAME);
  }

  /**
   * Get the languages specified in the "Content-Language" header field 
   * of this message.
   * The Content-Language header is defined by RFC 1766.
   * Returns null if this field is unavailable or its value is absent.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   * @see MimeBodyPart#getContentLanguage
   */
  public String[] getContentLanguage()
    throws MessagingException
  {
    String header = getHeader(MimeBodyPart.CONTENT_LANGUAGE_NAME, null);
    if (header!=null)
    {
      HeaderTokenizer ht = new HeaderTokenizer(header, HeaderTokenizer.MIME);
      ArrayList acc = new ArrayList();
      for (boolean done = false; !done; )
      {
        HeaderTokenizer.Token token = ht.next();
        switch (token.getType())
        {
          case HeaderTokenizer.Token.EOF:
            done = true;
            break;
          case HeaderTokenizer.Token.ATOM:
            acc.add(token.getValue());
            break;
        }
      } 
      if (acc.size()>0)
      {
        String[] languages = new String[acc.size()];
        acc.toArray(languages);
        return languages;
      }
    }
    return null;
  }

  /**
   * Set the "Content-Language" header of this MimePart.
   * The Content-Language header is defined by RFC 1766.
   * @param languages array of language tags
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#setContentLanguage
   */
  public void setContentLanguage(String[] languages)
    throws MessagingException
  {
    if (languages!=null && languages.length>0)
    {
      StringBuffer buffer = new StringBuffer();
      buffer.append(languages[0]);
      for (int i = 1; i<languages.length; i++)
      {
        buffer.append(',');
        buffer.append(languages[i]);
      }
      setHeader(MimeBodyPart.CONTENT_LANGUAGE_NAME, buffer.toString());
    }
    else
      setHeader(MimeBodyPart.CONTENT_LANGUAGE_NAME, null);
  }

  /**
   * Returns the value of the "Message-ID" header field.
   * Returns null if this field is unavailable or its value is absent.
   * <p>
   * This implementation uses the <code>getHeader</code> method 
   * to obtain the requisite header field.
   */
  public String getMessageID()
    throws MessagingException
  {
    return getHeader(MESSAGE_ID_NAME, null);
  }

  /**
   * Get the filename associated with this Message.
   * <p>
   * Returns the value of the "filename" parameter from the
   * "Content-Disposition" header field of this message.
   * If it's not available, returns the value of the "name" parameter 
   * from the "Content-Type" header field of this BodyPart.
   * Returns null if both are absent.
   * @see MimeBodyPart#getFileName
   */
  public String getFileName()
    throws MessagingException
  {
    String filename = null;
    String header = getHeader(MimeBodyPart.CONTENT_DISPOSITION_NAME, null);
    if (header!=null)
    {
      ContentDisposition cd = new ContentDisposition(header);
      filename = cd.getParameter("filename");
    }
    if (filename==null)
    {
      header = getHeader(MimeBodyPart.CONTENT_TYPE_NAME, null);
      if (header!=null)
      {
        ContentType contentType = new ContentType(header);
        filename = contentType.getParameter("name");
      }
    }
    return filename;
  }

  /**
   * Set the filename associated with this part, if possible.
   * <p>
   * Sets the "filename" parameter of the "Content-Disposition" 
   * header field of this message.
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#setFileName
   */
  public void setFileName(String filename)
    throws MessagingException
  {
    String header = getHeader(MimeBodyPart.CONTENT_DISPOSITION_NAME, null);
    if (header==null)
      header = "attachment";
    ContentDisposition cd = new ContentDisposition(header);
    cd.setParameter("filename", filename);
    setHeader(MimeBodyPart.CONTENT_DISPOSITION_NAME, cd.toString());

    // We will also set the "name" parameter of the Content-Type field
    // to preserve compatibility with nonconformant MUAs
    header = getContentType(); // not valid for this to be null
    ContentType contentType = new ContentType(header);
    contentType.setParameter("name", filename);
    setHeader(MimeBodyPart.CONTENT_TYPE_NAME, contentType.toString());
  }

  /**
   * Return a decoded input stream for this Message's "content".
   * <p>
   * This implementation obtains the input stream from the DataHandler,
   * that is, it invokes <code>getDataHandler().getInputStream()</code>.
   * @exception IOException this is typically thrown by the DataHandler.
   * Refer to the documentation for javax.activation.DataHandler for more
   * details.
   * @see MimeBodyPart#getInputStream
   */
  public InputStream getInputStream()
    throws IOException, MessagingException
  {
    return getDataHandler().getInputStream();
  }

  /**
   * Produce the raw bytes of the content.
   * This method is used during parsing, to create a DataHandler object 
   * for the content. Subclasses that can provide a separate input stream 
   * for just the message content might want to override this method.
   * <p>
   * This implementation just returns a ByteArrayInputStream constructed 
   * out of the content byte array.
   * @see MimeBodyPart#getContentStream
   */
  protected InputStream getContentStream()
    throws MessagingException
  {
    if (contentStream!=null)
      return ((SharedInputStream)contentStream).newStream(0L, -1L);
    if (content!=null)
      return new ByteArrayInputStream(content);
    else
      throw new MessagingException("No content");
  }

  /**
   * Return an InputStream to the raw data with any Content-Transfer-Encoding
   * intact.
   * This method is useful if the "Content-Transfer-Encoding" header is
   * incorrect or corrupt, which would prevent the <code>getInputStream</code>
   * method or <code>getContent</code> method from returning the correct data.
   * In such a case the application may use this method and attempt to decode
   * the raw data itself.
   * <p>
   * This implementation simply calls the <code>getContentStream</code>
   * method.
   * @see MimeBodyPart#getRawInputStream
   */
  public InputStream getRawInputStream()
    throws MessagingException
  {
    return getContentStream();
  }

  /**
   * Return a DataHandler for this Message's content.
   * <p>
   * The implementation provided here works as follows. Note the use of the
   * <code>getContentStream</code> method to generate the byte stream for 
   * the content. Also note that any transfer-decoding is done automatically
   * within this method.
   * <pre>
    getDataHandler() {
        if (dh == null) {
            dh = new DataHandler(new MimePartDataSource(this));
        }
        return dh;
    }

    class MimePartDataSource implements DataSource {
        public getInputStream() {
            return MimeUtility.decode(
               getContentStream(), getEncoding());
        }

          ....
    }
    </pre>
   */
  public synchronized DataHandler getDataHandler()
    throws MessagingException
  {
    if (dh==null)
      dh = new DataHandler(new MimePartDataSource(this));
    return dh;
  }

  /**
   * Return the content as a Java object.
   * The type of this object is dependent on the content itself.
   * For example, the native format of a "text/plain" content is usually 
   * a String object. The native format for a "multipart" message is always 
   * a Multipart subclass. For content types that are unknown to the 
   * DataHandler system, an input stream is returned as the content.
   * <p>
   * This implementation obtains the content from the DataHandler,
   * that is, it invokes <code>getDataHandler().getContent()</code>.
   * @exception IOException this is typically thrown by the DataHandler.
   * Refer to the documentation for javax.activation.DataHandler for more
   * details.
   */
  public Object getContent()
    throws IOException, MessagingException
  {
    return getDataHandler().getContent();
  }

  /**
   * This method provides the mechanism to set this part's content.
   * The given DataHandler object should wrap the actual content.
   * @param dh The DataHandler for the content.
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#setDataHandler
   */
  public void setDataHandler(DataHandler datahandler)
    throws MessagingException
  {
    dh = datahandler;
    // The Content-Type and Content-Transfer-Encoding headers may need to be
    // recalculated by the new DataHandler - see updateHeaders()
    removeHeader(MimeBodyPart.CONTENT_TYPE_NAME);
    removeHeader(MimeBodyPart.CONTENT_TRANSFER_ENCODING_NAME);
  }

  /**
   * A convenience method for setting this Message's content.
   * <p>
   * The content is wrapped in a DataHandler object. Note that a
   * DataContentHandler class for the specified type should be available 
   * to the JavaMail implementation for this to work right. i.e., to do
   * <code>setContent(foobar, "application/x-foobar")</code>, a 
   * DataContentHandler for "application/x-foobar" should be installed.
   * Refer to the Java Activation Framework for more information.
   * @param o the content object
   * @param type Mime type of the object
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#setContent
   */
  public void setContent(Object o, String type)
    throws MessagingException
  {
    setDataHandler(new DataHandler(o, type));
  }

  /**
   * Convenience method that sets the given String as this part's content,
   * with a MIME type of "text/plain".
   * If the string contains non US-ASCII characters, it will be encoded 
   * using the platform's default charset. The charset is also used to set 
   * the "charset" parameter.
   * <p>
   * Note that there may be a performance penalty if text is large, since 
   * this method may have to scan all the characters to determine what 
   * charset to use.
   * <p>
   * If the charset is already known, use the <code>setText</code> method
   * that takes the <code>charset</code> parameter.
   * @see MimeBodyPart#setText
   */
  public void setText(String text)
    throws MessagingException
  {
    setText(text, null);
  }

  /**
   * Convenience method that sets the given String as this part's content,
   * with a MIME type of "text/plain" and the specified charset.
   * The given Unicode string will be charset-encoded using the specified 
   * charset. The charset is also used to set the "charset" parameter.
   */
  public void setText(String text, String charset)
    throws MessagingException
  {
    if (charset==null)
    {
      // According to the API doc for getText(String), we may have to scan
      // the characters to determine the charset.
      // However this should work just as well and is hopefully relatively
      // cheap.
      charset = MimeUtility.mimeCharset(MimeUtility.getDefaultJavaCharset());
    }
    StringBuffer buffer = new StringBuffer();
    buffer.append("text/plain; charset=");
    buffer.append(MimeUtility.quote(charset, HeaderTokenizer.MIME));
    setContent(text, buffer.toString());
  }

  /**
   * This method sets the Message's content to a Multipart object.
   * @param mp The multipart object that is the Message's content
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#setContent(Multipart)
   */
  public void setContent(Multipart mp)
    throws MessagingException
  {
    setDataHandler(new DataHandler(mp, mp.getContentType()));
    // Ensure component hierarchy
    mp.setParent(this);
  }

  /**
   * Get a new Message suitable for a reply to this message.
   * The new Message will have its attributes and headers set up 
   * appropriately. Note that this new message object will be empty, 
   * i.e., it will not have a "content". These will have to be suitably 
   * filled in by the client.
   * <p>
   * If <code>replyToAll</code> is set, the new Message will be addressed 
   * to all recipients of this message. Otherwise, the reply will be 
   * addressed to only the sender of this message (using the value of 
   * the <code>getReplyTo</code> method).
   * <p>
   * The "Subject" field is filled in with the original subject prefixed 
   * with "Re:" (unless it already starts with "Re:"). The "In-Reply-To"
   * header is set in the new message if this message has a "Message-Id"
   * header. The ANSWERED flag is set in this message.
   * @param replyToAll reply should be sent to all recipients of this message
   * @return the reply Message
   */
  public Message reply(boolean replyToAll)
    throws MessagingException
  {
    MimeMessage message = new MimeMessage(session);
    String subject = getHeader(SUBJECT_NAME, null);
    if (subject!=null)
    {
      if (!subject.startsWith("Re: "))
        subject = "Re: "+subject;
      message.setHeader(SUBJECT_NAME, subject);
    }
    Address[] addresses = getReplyTo();
    message.setRecipients(Message.RecipientType.TO, addresses);
    if (replyToAll)
    {
      // We use a Set to store the addresses in order to ensure no address
      // duplication.
      HashSet set = new HashSet();
      set.addAll(Arrays.asList(addresses));

      InternetAddress localAddress = InternetAddress.getLocalAddress(session);
      if (localAddress!=null)
        set.add(localAddress);
      String alternates = session.getProperty("mail.alternates");
      if (alternates!=null)
        set.addAll(Arrays.asList(InternetAddress.parse(alternates, false)));
      
      set.addAll(Arrays.asList(getRecipients(Message.RecipientType.TO)));
      addresses = new Address[set.size()];
      set.toArray(addresses);
      
      boolean replyAllCC = 
        new Boolean(session.getProperty("mail.replyallcc")).booleanValue();
      if (addresses.length>0)
        if (replyAllCC)
          message.addRecipients(Message.RecipientType.CC, addresses);
        else
          message.addRecipients(Message.RecipientType.TO, addresses);
      
      set.clear();
      set.addAll(Arrays.asList(getRecipients(Message.RecipientType.CC)));
      addresses = new Address[set.size()];
      set.toArray(addresses);
      
      if (addresses!=null && addresses.length>0)
        message.addRecipients(Message.RecipientType.CC, addresses);
      
      addresses = getRecipients(RecipientType.NEWSGROUPS);
      if (addresses!=null && addresses.length>0)
        message.setRecipients(RecipientType.NEWSGROUPS, addresses);
    }

    // Set In-Reply-To (will be replaced by References for NNTP)
    String mid = getHeader(MESSAGE_ID_NAME, null);
    if (mid!=null)
      message.setHeader("In-Reply-To", mid);
    try
    {
      setFlag(Flags.Flag.ANSWERED, true);
    }
    catch (MessagingException e)
    {
    }
    return message;
  }

  /**
   * Output the message as an RFC 822 format stream.
   * <p>
   * Note that, depending on how the message was constructed, it may use a
   * variety of line termination conventions. Generally the output should be
   * sent through an appropriate FilterOutputStream that converts the line
   * terminators to the desired form, either CRLF for MIME compatibility and 
   * for use in Internet protocols, or the local platform's line terminator 
   * for storage in a local text file.
   * <p>
   * This implementation calls the 
   * <code>writeTo(OutputStream, String[])</code> method with a null ignore 
   * list.
   * @exception IOException if an error occurs writing to the stream or if an
   * error is generated by the javax.activation layer.
   */
  public void writeTo(OutputStream os)
    throws IOException, MessagingException
  {
    writeTo(os, null);
  }

  /**
   * Output the message as an RFC 822 format stream, without specified 
   * headers. If the saved flag is not set, the <code>saveChanges</code>
   * method is called. If the <code>modified</code> flag is not set and 
   * the <code>content</code> array is not null, the <code>content</code>
   * array is written directly, after writing the appropriate message headers.
   * @exception IOException if an error occurs writing to the stream or if an
   * error is generated by the javax.activation layer.
   */
  public void writeTo(OutputStream os, String[] ignoreList)
    throws IOException, MessagingException
  {
    if (!saved)
      saveChanges();

    String charset = "UTF-8"; // TODO default charset?
    byte[] sep = new byte[] { 0x0d, 0x0a };

    // Write the headers
    for (Enumeration e = getNonMatchingHeaderLines(ignoreList);
        e.hasMoreElements(); )
    {
      String line = (String)e.nextElement();
      /*
       * RFC 2822, section 2.1 states that each line should be no more
       * than 998 characters. Ensure that any headers we emit have no lines
       * longer than this by folding the line.
       */
      int max = 998;
      while (line.length()>max)
      {
        String left = line.substring(0, max);
        byte[] bytes = left.getBytes(charset);
        os.write(bytes);
        os.write(sep);
        line = line.substring(max);
        max = 997; // make space for the tab
      }
			byte[] bytes = line.getBytes(charset);
      os.write(bytes);
      os.write(sep);
    }
    os.write(sep);
    os.flush();

    /*
     * Implement the no-CR-without-LF and len(line)<=998 RFC2822 rules
     * (section 2.3).
     * We do this by wrapping in an RFC2822OutputStream.
     */
    RFC2822OutputStream rfc2822os = new RFC2822OutputStream(os);
    if (modified || content==null && contentStream==null)
    {
      // use datahandler
      os = MimeUtility.encode(rfc2822os, getEncoding());
      getDataHandler().writeTo(os);
    }
    else
    {
      // write content directly
      if (contentStream!=null)
      {
        InputStream is = ((SharedInputStream)contentStream).newStream(0L, -1L);
        // TODO make buffer size configurable
        int len = 8192;
        byte[] bytes = new byte[len];
        while ((len = is.read(bytes))>-1) 
          rfc2822os.write(bytes, 0, len);
        is.close();
      }
      else
        rfc2822os.write(content);
    }
    rfc2822os.flush();
  }

  static int fc = 1;

  /**
   * Get all the headers for this header_name.
   * Note that certain headers may be encoded as per RFC 2047 if they 
   * contain non US-ASCII characters and these should be decoded.
   * <p>
   * This implementation obtains the headers from the <code>headers</code>
   * InternetHeaders object.
   * @param name name of header
   * @return array of headers
   */
  public String[] getHeader(String name)
    throws MessagingException
  {
    return headers.getHeader(name);
  }

  /**
   * Get all the headers for this header name, returned as a single String,
   * with headers separated by the delimiter.
   * If the delimiter is null, only the first header is returned.
   * @param name the name of the header
   * @param delimiter the delimiter
   * @return the value fields for all headers with this name
   */
  public String getHeader(String name, String delimiter)
    throws MessagingException
  {
    return headers.getHeader(name, delimiter);
  }

  /**
   * Set the value for this header_name.
   * Replaces all existing header values with this new value.
   * Note that RFC 822 headers must contain only US-ASCII characters,
   * so a header that contains non US-ASCII characters must have been 
   * encoded by the caller as per the rules of RFC 2047.
   * @param name header name
   * @param value header value
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setHeader(String name, String value)
    throws MessagingException
  {
    headers.setHeader(name, value);
  }

  /**
   * Add this value to the existing values for this header_name.
   * Note that RFC 822 headers must contain only US-ASCII characters,
   * so a header that contains non US-ASCII characters must have been
   * encoded as per the rules of RFC 2047.
   * @param name header name
   * @param value header value
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void addHeader(String name, String value)
    throws MessagingException
  {
    headers.addHeader(name, value);
  }

  /**
   * Remove all headers with this name.
   * @param name header name
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void removeHeader(String name)
    throws MessagingException
  {
    headers.removeHeader(name);
  }

  /**
   * Return all the headers from this Message as an enumeration of Header
   * objects.
   * <p>
   * Note that certain headers may be encoded as per RFC 2047 if they contain
   * non US-ASCII characters and these should be decoded.
   * <p>
   * This implementation obtains the headers from the <code>headers</code>
   * InternetHeaders object.
   * @return array of header objects
   */
  public Enumeration getAllHeaders()
    throws MessagingException
  {
    return headers.getAllHeaders();
  }

  /**
   * Return matching headers from this Message as an Enumeration of Header
   * objects.
   * <p>
   * This implementation obtains the headers from the <code>headers</code>
   * InternetHeaders object.
   */
  public Enumeration getMatchingHeaders(String[] names)
    throws MessagingException
  {
    return headers.getMatchingHeaders(names);
  }

  /**
   * Return non-matching headers from this Message as an Enumeration of Header
   * objects.
   * <p>
   * This implementation obtains the headers from the <code>headers</code>
   * InternetHeaders object.
   */
  public Enumeration getNonMatchingHeaders(String[] names)
    throws MessagingException
  {
    return headers.getNonMatchingHeaders(names);
  }

  /**
   * Add a raw RFC 822 header-line.
   * @param line the line to add
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void addHeaderLine(String line)
    throws MessagingException
  {
    headers.addHeaderLine(line);
  }

  /**
   * Get all header lines as an Enumeration of Strings.
   * A Header line is a raw RFC 822 header-line, containing both the "name"
   * and "value" field.
   */
  public Enumeration getAllHeaderLines()
    throws MessagingException
  {
    return headers.getAllHeaderLines();
  }

  /**
   * Get matching header lines as an Enumeration of Strings.
   * A Header line is a raw RFC 822 header-line, containing both the "name"
   * and "value" field.
   */
  public Enumeration getMatchingHeaderLines(String[] names)
    throws MessagingException
  {
    return headers.getMatchingHeaderLines(names);
  }

  /**
   * Get non-matching header lines as an Enumeration of Strings.
   * A Header line is a raw RFC 822 header-line, containing both the "name"
   * and "value" field.
   */
  public Enumeration getNonMatchingHeaderLines(String[] names)
    throws MessagingException
  {
    return headers.getNonMatchingHeaderLines(names);
  }

  /**
   * Return a Flags object containing the flags for this message.
   * <p>
   * Note that a clone of the internal Flags object is returned, so modifying
   * the returned Flags object will not affect the flags of this message.
   * @return Flags object containing the flags for this message
   */
  public Flags getFlags()
    throws MessagingException
  {
    return (Flags)flags.clone();
  }

  /**
   * Check whether the flag specified in the flag argument is set in this
   * message.
   * <p>
   * This implementation checks this message's internal flags object.
   * @param flag - the flag
   * @return value of the specified flag for this message
   */
  public boolean isSet(Flags.Flag flag)
    throws MessagingException
  {
    return flags.contains(flag);
  }

  /**
   * Set the flags for this message.
   * <p>
   * This implementation modifies the flags field.
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void setFlags(Flags flag, boolean set)
    throws MessagingException
  {
    if (set)
      flags.add(flag);
    else
      flags.remove(flag);
  }

  /**
   * Updates the appropriate header fields of this message to be consistent
   * with the message's contents.
   * If this message is contained in a Folder, any changes made to this 
   * message are committed to the containing folder.
   * <p>
   * If any part of a message's headers or contents are changed, 
   * <code>saveChanges</code> must be called to ensure that those changes 
   * are permanent. Otherwise, any such modifications may or may not be 
   * saved, depending on the folder implementation.
   * <p>
   * Messages obtained from folders opened READ_ONLY should not be modified 
   * and <code>saveChanges</code> should not be called on such messages.
   * <p>
   * This method sets the <code>modified</code> flag to true,
   * the <code>save</code> flag to true, and then calls the 
   * <code>updateHeaders</code> method.
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   */
  public void saveChanges()
    throws MessagingException
  {
    modified = true;
    saved = true;
    updateHeaders();
  }

  /**
   * Called by the <code>saveChanges</code> method to actually update 
   * the MIME headers.
   * The implementation here sets the Content-Transfer-Encoding header
   * (if needed and not already set), the Mime-Version header
   * and the Message-ID header.
   * Also, if the content of this message is a MimeMultipart,
   * its <code>updateHeaders</code> method is called.
   * @exception IllegalWriteException if the underlying implementation 
   * does not support modification of existing values
   * @exception IllegalStateException if this message is obtained from 
   * a READ_ONLY folder.
   * @see MimeBodyPart#updateHeaders
   */
  protected void updateHeaders()
    throws MessagingException
  {
    // This code is from MimeBodyPart
    if (getDataHandler()!=null)
    {
      try
      {
        String contentType = dh.getContentType();
        ContentType ct = new ContentType(contentType);
        if (ct.match("multipart/*"))
        {
          MimeMultipart mmp = (MimeMultipart)dh.getContent();
          mmp.updateHeaders();
        } 
        else if (ct.match("message/rfc822"))
        {
        }
        else
        {
          // Update Content-Transfer-Encoding
          if (getHeader(MimeBodyPart.CONTENT_TRANSFER_ENCODING_NAME)==null)
          {
            setHeader(MimeBodyPart.CONTENT_TRANSFER_ENCODING_NAME,
                MimeUtility.getEncoding(dh));
          }
        }

        // Update Content-Type if nonexistent,
        // and Content-Type "name" with Content-Disposition "filename"
        // parameter (see setFilename())
        if (getHeader(MimeBodyPart.CONTENT_TYPE_NAME)==null)
        {
          String disposition =
            getHeader(MimeBodyPart.CONTENT_DISPOSITION_NAME, null);
          if (disposition!=null)
          {
            ContentDisposition cd = new ContentDisposition(disposition);
            String filename = cd.getParameter("filename");
            if (filename!=null)
            {
              ct.setParameter("name", filename);
              contentType = ct.toString();
            }
          }
          setHeader(MimeBodyPart.CONTENT_TYPE_NAME, contentType);
        }
      }
      catch (IOException e)
      {
        throw new MessagingException("I/O error", e);
      }
    }
    
    // Below is MimeMessage-specific.
    // set mime version
    setHeader("Mime-Version", "1.0");
    // set new message-id if necessary
    String mid = getHeader(MESSAGE_ID_NAME, null);
    if (mid==null)
    {
      StringBuffer buffer = new StringBuffer();
      buffer.append('<');
      buffer.append(MimeUtility.getUniqueMessageIDValue(session));
      buffer.append('>');
      mid = buffer.toString();
      setHeader(MESSAGE_ID_NAME, mid);
    }
  }

  /**
   * Create and return an InternetHeaders object that loads the headers 
   * from the given InputStream.
   * Subclasses can override this method to return a subclass of
   * InternetHeaders, if necessary.
   * This implementation simply constructs and returns an InternetHeaders
   * object.
   * @param is the InputStream to read the headers from
   */
  protected InternetHeaders createInternetHeaders(InputStream is)
    throws MessagingException
  {
    return new InternetHeaders(is);
  }

}