Copybara import of the project:

  - beab9265aa39a4c3c4a2e220b1e4c198060803a5 Initial Contribution by Jayasheelan Kumar <jayasheelan.kumar@oracle.com>
  - 6adf99452b0aef38e6b31f885f7e4bbcc2e59b55 https://github.com/eclipse-ee4j/javamail/issues/336 relea... by Scott Marlow <smarlow@redhat.com>
  - af160fc3ef126bda42001b04c4ab5157ed084b97 Changed groupId and artifactId. Fixes #338 (#340) by Guillermo González de Agüero <ggam@users.noreply.github.com>
  - cb505e119af47844efd5a3c1860d7df9e8db60de Update to newest parent pom. (#341) by Bill Shannon <bill.shannon@oracle.com>
  - 22daa79f6b516a23840636ee28c401ae35b31771 Switch to jakarta coordinates. by Bill Shannon <bill.shannon@oracle.com>
  - f8f6ee8b62198ba758b2e2289591b3407f6eae22 Update parent pom version and update JAF version. by Bill Shannon <bill.shannon@oracle.com>
  - ecc40b842820f62347938635cb0da764c4f6ccec Update documentation for the move to Eclipse - fix #348 by Bill Shannon <bill.shannon@oracle.com>
  - 0977a71f1dd44b00a05cdbe8fd2a15e6b7b7591b JavaMail 1.6.3 Final Release. by Bill Shannon <bill.shannon@oracle.com>
  - fc43f4d6679ec43cbabb75c31b3af863fd0bff9e Update dependency javax.mail -> jakarta.mail. by Bill Shannon <bill.shannon@oracle.com>
  - d08a9e505bd1382ef32fbe69cfc18d02756d877a Change Java platform module system name to jakarta.mail. by Bill Shannon <bill.shannon@oracle.com>
  - 7be4f44afd920e35ede59aa06221cedc7b9bd12c Don't include a version in the OSGi javax.activation depe... by Bill Shannon <bill.shannon@oracle.com>

GitOrigin-RevId: 7be4f44afd920e35ede59aa06221cedc7b9bd12c
Change-Id: I310600fedd79de9ce07c9dd07d3de3bd2503ff34
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cb3fd73
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+target/
+*/target/
+nbproject/private/
+.m2/
+webrev
+src/main/java/javax/mail/Version\.java
diff --git a/.hgignore b/.hgignore
new file mode 100644
index 0000000..e774dec
--- /dev/null
+++ b/.hgignore
@@ -0,0 +1,6 @@
+^target/
+/target/
+^nbproject/private/
+^\.m2/
+^webrev
+^src/main/java/javax/mail/Version\.java$
diff --git a/.hgtags b/.hgtags
new file mode 100644
index 0000000..4fcda51
--- /dev/null
+++ b/.hgtags
@@ -0,0 +1,33 @@
+da19cbb2014590348273983815b35ac8a48717c5 JAVAMAIL-1_4_1
+dfb709919353eec53e9dca61b379163186f4d154 1.4.2-beta
+24c89d103755d49d62c67368cea5c13b39c2b399 JAVAMAIL-1_4_2
+dfaa56e47386544f4002ade511f2cb0207107734 JAVAMAIL-1_4_3-RC1
+200b5a729cff286d1288b64287dc339233cd5040 JAVAMAIL-1_4_3
+200b5a729cff286d1288b64287dc339233cd5040 JAVAMAIL-1_4_3
+1f3f699ed17aa2c740eb09def5d83dd80671748a JAVAMAIL-1_4_3
+0a99728ee1d1d3d2b27ab76e0db05d9652ee1063 JAVAMAIL-1_4_4-RC1
+9cff25c6d73c8f86ff27f4cb39f7c3c092c34dd7 JAVAMAIL-1_4_4
+ce00d5900757397cbc7943b686035e8b27aa9449 JAVAMAIL-1_4_5-RC1
+e0ece38db9a0a6b46fe03575bffba6605603761d JAVAMAIL-1_4_5
+da6e25608ec175cf0556c6b7b7d1fee40cbe09c5 JAVAMAIL-1_4_6-RC1
+b9c00b0cf3cf560077cd3c393c597e4ed9f1bf14 JAVAMAIL-1_4_6
+b9c00b0cf3cf560077cd3c393c597e4ed9f1bf14 JAVAMAIL-1_4_6
+3dba8c78634fcb1ffd9a32e840bcfab91a83a551 JAVAMAIL-1_4_6
+ba4e734fdfa9916c0e8867b8ea8b4218e276be0e JAVAMAIL-1_4_7
+fa1edb81787d0b26828cee438a6d54df619fa6da JAVAMAIL-1_5_0
+f6c4d3181f06de080bb3b107382464fad875dfdb JAVAMAIL-1_5_1
+1cbe387fd6841ae0f1c5c6da1e7f872c49255f1d JAVAMAIL-1_5_2
+a63663b1bb07ae812f9cf13b03fbcf3e8e129521 JAVAMAIL-1_5_3
+15b5ba9b58712d064178b2530a2b7f89e1d2ebc1 JAVAMAIL-1_5_4
+6f854b7031906581f1c91bf70a13734cbec18e0a JAVAMAIL-1_5_5
+ddcb8608cc654e273da1cd11969a41d60c7c07db JAVAMAIL-1_5_6
+d90b0230ffde36e71d06cc8efc35c1bc662d3731 JAVAMAIL-1_6_0-RC1
+946643021f7a4facdbf1d98238b2cfee28e656ef JAVAMAIL-1_6_0-RC2
+e4cb3b3d3aeefd1afab7e965317604bcd6644700 JAVAMAIL-1_6_0
+dfb709919353eec53e9dca61b379163186f4d154 1.4.2-beta
+0000000000000000000000000000000000000000 1.4.2-beta
+48f8142d2dc471a93866f1da6c13436371d9024c JAVAMAIL-1_6_1
+d8410291f8ed108e94ce0664e1f1a01d9a52e14a JAVAMAIL_1_6_2
+d8410291f8ed108e94ce0664e1f1a01d9a52e14a JAVAMAIL_1_6_2
+0000000000000000000000000000000000000000 JAVAMAIL_1_6_2
+3e658a0fb7b8d36912df0b29f9868946fbffc19c JAVAMAIL-1_6_2
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..db1f146
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,45 @@
+# Contributing to Eclipse Project for JavaMail
+
+Thanks for your interest in this project.
+
+## Project description
+
+The JavaMail API provides a platform-independent and protocol-independent
+framework to build mail and messaging applications. The JavaMail API is
+available as an optional package for use with the Java SE platform and is also
+included in the Java EE platform.
+
+* https://projects.eclipse.org/projects/ee4j.javamail
+
+## Developer resources
+
+Information regarding source code management, builds, coding standards, and
+more.
+
+* https://projects.eclipse.org/projects/ee4j.javamail/developer
+
+The project maintains the following source code repositories
+
+* https://github.com/eclipse-ee4j/javamail
+
+## Eclipse Contributor Agreement
+
+Before your contribution can be accepted by the project team contributors must
+electronically sign the Eclipse Contributor Agreement (ECA).
+
+* http://www.eclipse.org/legal/ECA.php
+
+Commits that are provided by non-committers must have a Signed-off-by field in
+the footer indicating that the author is aware of the terms by which the
+contribution has been provided to the project. The non-committer must
+additionally have an Eclipse Foundation account and must have a signed Eclipse
+Contributor Agreement (ECA) on file.
+
+For more information, please see the Eclipse Committer Handbook:
+https://www.eclipse.org/projects/handbook/#resources-commit
+
+## Contact
+
+Contact the project developers via the project's "dev" list.
+
+* https://accounts.eclipse.org/mailing-list/javamail-dev
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..5de3d1b
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,637 @@
+# Eclipse Public License - v 2.0
+
+        THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+        PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
+        OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+    1. DEFINITIONS
+
+    "Contribution" means:
+
+      a) in the case of the initial Contributor, the initial content
+         Distributed under this Agreement, and
+
+      b) in the case of each subsequent Contributor: 
+         i) changes to the Program, and 
+         ii) additions to the Program;
+      where such changes and/or additions to the Program originate from
+      and are Distributed by that particular Contributor. A Contribution
+      "originates" from a Contributor if it was added to the Program by
+      such Contributor itself or anyone acting on such Contributor's behalf.
+      Contributions do not include changes or additions to the Program that
+      are not Modified Works.
+
+    "Contributor" means any person or entity that Distributes the Program.
+
+    "Licensed Patents" mean patent claims licensable by a Contributor which
+    are necessarily infringed by the use or sale of its Contribution alone
+    or when combined with the Program.
+
+    "Program" means the Contributions Distributed in accordance with this
+    Agreement.
+
+    "Recipient" means anyone who receives the Program under this Agreement
+    or any Secondary License (as applicable), including Contributors.
+
+    "Derivative Works" shall mean any work, whether in Source Code or other
+    form, that is based on (or derived from) the Program and for which the
+    editorial revisions, annotations, elaborations, or other modifications
+    represent, as a whole, an original work of authorship.
+
+    "Modified Works" shall mean any work in Source Code or other form that
+    results from an addition to, deletion from, or modification of the
+    contents of the Program, including, for purposes of clarity any new file
+    in Source Code form that contains any contents of the Program. Modified
+    Works shall not include works that contain only declarations,
+    interfaces, types, classes, structures, or files of the Program solely
+    in each case in order to link to, bind by name, or subclass the Program
+    or Modified Works thereof.
+
+    "Distribute" means the acts of a) distributing or b) making available
+    in any manner that enables the transfer of a copy.
+
+    "Source Code" means the form of a Program preferred for making
+    modifications, including but not limited to software source code,
+    documentation source, and configuration files.
+
+    "Secondary License" means either the GNU General Public License,
+    Version 2.0, or any later versions of that license, including any
+    exceptions or additional permissions as identified by the initial
+    Contributor.
+
+    2. GRANT OF RIGHTS
+
+      a) Subject to the terms of this Agreement, each Contributor hereby
+      grants Recipient a non-exclusive, worldwide, royalty-free copyright
+      license to reproduce, prepare Derivative Works of, publicly display,
+      publicly perform, Distribute and sublicense the Contribution of such
+      Contributor, if any, and such Derivative Works.
+
+      b) Subject to the terms of this Agreement, each Contributor hereby
+      grants Recipient a non-exclusive, worldwide, royalty-free patent
+      license under Licensed Patents to make, use, sell, offer to sell,
+      import and otherwise transfer the Contribution of such Contributor,
+      if any, in Source Code or other form. This patent license shall
+      apply to the combination of the Contribution and the Program if, at
+      the time the Contribution is added by the Contributor, such addition
+      of the Contribution causes such combination to be covered by the
+      Licensed Patents. The patent license shall not apply to any other
+      combinations which include the Contribution. No hardware per se is
+      licensed hereunder.
+
+      c) Recipient understands that although each Contributor grants the
+      licenses to its Contributions set forth herein, no assurances are
+      provided by any Contributor that the Program does not infringe the
+      patent or other intellectual property rights of any other entity.
+      Each Contributor disclaims any liability to Recipient for claims
+      brought by any other entity based on infringement of intellectual
+      property rights or otherwise. As a condition to exercising the
+      rights and licenses granted hereunder, each Recipient hereby
+      assumes sole responsibility to secure any other intellectual
+      property rights needed, if any. For example, if a third party
+      patent license is required to allow Recipient to Distribute the
+      Program, it is Recipient's responsibility to acquire that license
+      before distributing the Program.
+
+      d) Each Contributor represents that to its knowledge it has
+      sufficient copyright rights in its Contribution, if any, to grant
+      the copyright license set forth in this Agreement.
+
+      e) Notwithstanding the terms of any Secondary License, no
+      Contributor makes additional grants to any Recipient (other than
+      those set forth in this Agreement) as a result of such Recipient's
+      receipt of the Program under the terms of a Secondary License
+      (if permitted under the terms of Section 3).
+
+    3. REQUIREMENTS
+
+    3.1 If a Contributor Distributes the Program in any form, then:
+
+      a) the Program must also be made available as Source Code, in
+      accordance with section 3.2, and the Contributor must accompany
+      the Program with a statement that the Source Code for the Program
+      is available under this Agreement, and informs Recipients how to
+      obtain it in a reasonable manner on or through a medium customarily
+      used for software exchange; and
+
+      b) the Contributor may Distribute the Program under a license
+      different than this Agreement, provided that such license:
+         i) effectively disclaims on behalf of all other Contributors all
+         warranties and conditions, express and implied, including
+         warranties or conditions of title and non-infringement, and
+         implied warranties or conditions of merchantability and fitness
+         for a particular purpose;
+
+         ii) effectively excludes on behalf of all other Contributors all
+         liability for damages, including direct, indirect, special,
+         incidental and consequential damages, such as lost profits;
+
+         iii) does not attempt to limit or alter the recipients' rights
+         in the Source Code under section 3.2; and
+
+         iv) requires any subsequent distribution of the Program by any
+         party to be under a license that satisfies the requirements
+         of this section 3.
+
+    3.2 When the Program is Distributed as Source Code:
+
+      a) it must be made available under this Agreement, or if the
+      Program (i) is combined with other material in a separate file or
+      files made available under a Secondary License, and (ii) the initial
+      Contributor attached to the Source Code the notice described in
+      Exhibit A of this Agreement, then the Program may be made available
+      under the terms of such Secondary Licenses, and
+
+      b) a copy of this Agreement must be included with each copy of
+      the Program.
+
+    3.3 Contributors may not remove or alter any copyright, patent,
+    trademark, attribution notices, disclaimers of warranty, or limitations
+    of liability ("notices") contained within the Program from any copy of
+    the Program which they Distribute, provided that Contributors may add
+    their own appropriate notices.
+
+    4. COMMERCIAL DISTRIBUTION
+
+    Commercial distributors of software may accept certain responsibilities
+    with respect to end users, business partners and the like. While this
+    license is intended to facilitate the commercial use of the Program,
+    the Contributor who includes the Program in a commercial product
+    offering should do so in a manner which does not create potential
+    liability for other Contributors. Therefore, if a Contributor includes
+    the Program in a commercial product offering, such Contributor
+    ("Commercial Contributor") hereby agrees to defend and indemnify every
+    other Contributor ("Indemnified Contributor") against any losses,
+    damages and costs (collectively "Losses") arising from claims, lawsuits
+    and other legal actions brought by a third party against the Indemnified
+    Contributor to the extent caused by the acts or omissions of such
+    Commercial Contributor in connection with its distribution of the Program
+    in a commercial product offering. The obligations in this section do not
+    apply to any claims or Losses relating to any actual or alleged
+    intellectual property infringement. In order to qualify, an Indemnified
+    Contributor must: a) promptly notify the Commercial Contributor in
+    writing of such claim, and b) allow the Commercial Contributor to control,
+    and cooperate with the Commercial Contributor in, the defense and any
+    related settlement negotiations. The Indemnified Contributor may
+    participate in any such claim at its own expense.
+
+    For example, a Contributor might include the Program in a commercial
+    product offering, Product X. That Contributor is then a Commercial
+    Contributor. If that Commercial Contributor then makes performance
+    claims, or offers warranties related to Product X, those performance
+    claims and warranties are such Commercial Contributor's responsibility
+    alone. Under this section, the Commercial Contributor would have to
+    defend claims against the other Contributors related to those performance
+    claims and warranties, and if a court requires any other Contributor to
+    pay any damages as a result, the Commercial Contributor must pay
+    those damages.
+
+    5. NO WARRANTY
+
+    EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+    PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
+    BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+    IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
+    TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+    PURPOSE. Each Recipient is solely responsible for determining the
+    appropriateness of using and distributing the Program and assumes all
+    risks associated with its exercise of rights under this Agreement,
+    including but not limited to the risks and costs of program errors,
+    compliance with applicable laws, damage to or loss of data, programs
+    or equipment, and unavailability or interruption of operations.
+
+    6. DISCLAIMER OF LIABILITY
+
+    EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+    PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
+    SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+    PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+    ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+    EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+    POSSIBILITY OF SUCH DAMAGES.
+
+    7. GENERAL
+
+    If any provision of this Agreement is invalid or unenforceable under
+    applicable law, it shall not affect the validity or enforceability of
+    the remainder of the terms of this Agreement, and without further
+    action by the parties hereto, such provision shall be reformed to the
+    minimum extent necessary to make such provision valid and enforceable.
+
+    If Recipient institutes patent litigation against any entity
+    (including a cross-claim or counterclaim in a lawsuit) alleging that the
+    Program itself (excluding combinations of the Program with other software
+    or hardware) infringes such Recipient's patent(s), then such Recipient's
+    rights granted under Section 2(b) shall terminate as of the date such
+    litigation is filed.
+
+    All Recipient's rights under this Agreement shall terminate if it
+    fails to comply with any of the material terms or conditions of this
+    Agreement and does not cure such failure in a reasonable period of
+    time after becoming aware of such noncompliance. If all Recipient's
+    rights under this Agreement terminate, Recipient agrees to cease use
+    and distribution of the Program as soon as reasonably practicable.
+    However, Recipient's obligations under this Agreement and any licenses
+    granted by Recipient relating to the Program shall continue and survive.
+
+    Everyone is permitted to copy and distribute copies of this Agreement,
+    but in order to avoid inconsistency the Agreement is copyrighted and
+    may only be modified in the following manner. The Agreement Steward
+    reserves the right to publish new versions (including revisions) of
+    this Agreement from time to time. No one other than the Agreement
+    Steward has the right to modify this Agreement. The Eclipse Foundation
+    is the initial Agreement Steward. The Eclipse Foundation may assign the
+    responsibility to serve as the Agreement Steward to a suitable separate
+    entity. Each new version of the Agreement will be given a distinguishing
+    version number. The Program (including Contributions) may always be
+    Distributed subject to the version of the Agreement under which it was
+    received. In addition, after a new version of the Agreement is published,
+    Contributor may elect to Distribute the Program (including its
+    Contributions) under the new version.
+
+    Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+    receives no rights or licenses to the intellectual property of any
+    Contributor under this Agreement, whether expressly, by implication,
+    estoppel or otherwise. All rights in the Program not expressly granted
+    under this Agreement are reserved. Nothing in this Agreement is intended
+    to be enforceable by any entity that is not a Contributor or Recipient.
+    No third-party beneficiary rights are created under this Agreement.
+
+    Exhibit A - Form of Secondary Licenses Notice
+
+    "This Source Code may also be made available under the following 
+    Secondary Licenses when the conditions for such availability set forth 
+    in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+    version(s), and exceptions or additional permissions here}."
+
+      Simply including a copy of this Agreement, including this Exhibit A
+      is not sufficient to license the Source Code under Secondary Licenses.
+
+      If it is not possible or desirable to put the notice in a particular
+      file, then You may include the notice in a location (such as a LICENSE
+      file in a relevant directory) where a recipient would be likely to
+      look for such a notice.
+
+      You may add additional accurate notices of copyright ownership.
+
+---
+
+##    The GNU General Public License (GPL) Version 2, June 1991
+
+    Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+    51 Franklin Street, Fifth Floor
+    Boston, MA 02110-1335
+    USA
+
+    Everyone is permitted to copy and distribute verbatim copies
+    of this license document, but changing it is not allowed.
+
+    Preamble
+
+    The licenses for most software are designed to take away your freedom to
+    share and change it. By contrast, the GNU General Public License is
+    intended to guarantee your freedom to share and change free software--to
+    make sure the software is free for all its users. This General Public
+    License applies to most of the Free Software Foundation's software and
+    to any other program whose authors commit to using it. (Some other Free
+    Software Foundation software is covered by the GNU Library General
+    Public License instead.) You can apply it to your programs, too.
+
+    When we speak of free software, we are referring to freedom, not price.
+    Our General Public Licenses are designed to make sure that you have the
+    freedom to distribute copies of free software (and charge for this
+    service if you wish), that you receive source code or can get it if you
+    want it, that you can change the software or use pieces of it in new
+    free programs; and that you know you can do these things.
+
+    To protect your rights, we need to make restrictions that forbid anyone
+    to deny you these rights or to ask you to surrender the rights. These
+    restrictions translate to certain responsibilities for you if you
+    distribute copies of the software, or if you modify it.
+
+    For example, if you distribute copies of such a program, whether gratis
+    or for a fee, you must give the recipients all the rights that you have.
+    You must make sure that they, too, receive or can get the source code.
+    And you must show them these terms so they know their rights.
+
+    We protect your rights with two steps: (1) copyright the software, and
+    (2) offer you this license which gives you legal permission to copy,
+    distribute and/or modify the software.
+
+    Also, for each author's protection and ours, we want to make certain
+    that everyone understands that there is no warranty for this free
+    software. If the software is modified by someone else and passed on, we
+    want its recipients to know that what they have is not the original, so
+    that any problems introduced by others will not reflect on the original
+    authors' reputations.
+
+    Finally, any free program is threatened constantly by software patents.
+    We wish to avoid the danger that redistributors of a free program will
+    individually obtain patent licenses, in effect making the program
+    proprietary. To prevent this, we have made it clear that any patent must
+    be licensed for everyone's free use or not licensed at all.
+
+    The precise terms and conditions for copying, distribution and
+    modification follow.
+
+    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+    0. This License applies to any program or other work which contains a
+    notice placed by the copyright holder saying it may be distributed under
+    the terms of this General Public License. The "Program", below, refers
+    to any such program or work, and a "work based on the Program" means
+    either the Program or any derivative work under copyright law: that is
+    to say, a work containing the Program or a portion of it, either
+    verbatim or with modifications and/or translated into another language.
+    (Hereinafter, translation is included without limitation in the term
+    "modification".) Each licensee is addressed as "you".
+
+    Activities other than copying, distribution and modification are not
+    covered by this License; they are outside its scope. The act of running
+    the Program is not restricted, and the output from the Program is
+    covered only if its contents constitute a work based on the Program
+    (independent of having been made by running the Program). Whether that
+    is true depends on what the Program does.
+
+    1. You may copy and distribute verbatim copies of the Program's source
+    code as you receive it, in any medium, provided that you conspicuously
+    and appropriately publish on each copy an appropriate copyright notice
+    and disclaimer of warranty; keep intact all the notices that refer to
+    this License and to the absence of any warranty; and give any other
+    recipients of the Program a copy of this License along with the Program.
+
+    You may charge a fee for the physical act of transferring a copy, and
+    you may at your option offer warranty protection in exchange for a fee.
+
+    2. You may modify your copy or copies of the Program or any portion of
+    it, thus forming a work based on the Program, and copy and distribute
+    such modifications or work under the terms of Section 1 above, provided
+    that you also meet all of these conditions:
+
+        a) You must cause the modified files to carry prominent notices
+        stating that you changed the files and the date of any change.
+
+        b) You must cause any work that you distribute or publish, that in
+        whole or in part contains or is derived from the Program or any part
+        thereof, to be licensed as a whole at no charge to all third parties
+        under the terms of this License.
+
+        c) If the modified program normally reads commands interactively
+        when run, you must cause it, when started running for such
+        interactive use in the most ordinary way, to print or display an
+        announcement including an appropriate copyright notice and a notice
+        that there is no warranty (or else, saying that you provide a
+        warranty) and that users may redistribute the program under these
+        conditions, and telling the user how to view a copy of this License.
+        (Exception: if the Program itself is interactive but does not
+        normally print such an announcement, your work based on the Program
+        is not required to print an announcement.)
+
+    These requirements apply to the modified work as a whole. If
+    identifiable sections of that work are not derived from the Program, and
+    can be reasonably considered independent and separate works in
+    themselves, then this License, and its terms, do not apply to those
+    sections when you distribute them as separate works. But when you
+    distribute the same sections as part of a whole which is a work based on
+    the Program, the distribution of the whole must be on the terms of this
+    License, whose permissions for other licensees extend to the entire
+    whole, and thus to each and every part regardless of who wrote it.
+
+    Thus, it is not the intent of this section to claim rights or contest
+    your rights to work written entirely by you; rather, the intent is to
+    exercise the right to control the distribution of derivative or
+    collective works based on the Program.
+
+    In addition, mere aggregation of another work not based on the Program
+    with the Program (or with a work based on the Program) on a volume of a
+    storage or distribution medium does not bring the other work under the
+    scope of this License.
+
+    3. You may copy and distribute the Program (or a work based on it,
+    under Section 2) in object code or executable form under the terms of
+    Sections 1 and 2 above provided that you also do one of the following:
+
+        a) Accompany it with the complete corresponding machine-readable
+        source code, which must be distributed under the terms of Sections 1
+        and 2 above on a medium customarily used for software interchange; or,
+
+        b) Accompany it with a written offer, valid for at least three
+        years, to give any third party, for a charge no more than your cost
+        of physically performing source distribution, a complete
+        machine-readable copy of the corresponding source code, to be
+        distributed under the terms of Sections 1 and 2 above on a medium
+        customarily used for software interchange; or,
+
+        c) Accompany it with the information you received as to the offer to
+        distribute corresponding source code. (This alternative is allowed
+        only for noncommercial distribution and only if you received the
+        program in object code or executable form with such an offer, in
+        accord with Subsection b above.)
+
+    The source code for a work means the preferred form of the work for
+    making modifications to it. For an executable work, complete source code
+    means all the source code for all modules it contains, plus any
+    associated interface definition files, plus the scripts used to control
+    compilation and installation of the executable. However, as a special
+    exception, the source code distributed need not include anything that is
+    normally distributed (in either source or binary form) with the major
+    components (compiler, kernel, and so on) of the operating system on
+    which the executable runs, unless that component itself accompanies the
+    executable.
+
+    If distribution of executable or object code is made by offering access
+    to copy from a designated place, then offering equivalent access to copy
+    the source code from the same place counts as distribution of the source
+    code, even though third parties are not compelled to copy the source
+    along with the object code.
+
+    4. You may not copy, modify, sublicense, or distribute the Program
+    except as expressly provided under this License. Any attempt otherwise
+    to copy, modify, sublicense or distribute the Program is void, and will
+    automatically terminate your rights under this License. However, parties
+    who have received copies, or rights, from you under this License will
+    not have their licenses terminated so long as such parties remain in
+    full compliance.
+
+    5. You are not required to accept this License, since you have not
+    signed it. However, nothing else grants you permission to modify or
+    distribute the Program or its derivative works. These actions are
+    prohibited by law if you do not accept this License. Therefore, by
+    modifying or distributing the Program (or any work based on the
+    Program), you indicate your acceptance of this License to do so, and all
+    its terms and conditions for copying, distributing or modifying the
+    Program or works based on it.
+
+    6. Each time you redistribute the Program (or any work based on the
+    Program), the recipient automatically receives a license from the
+    original licensor to copy, distribute or modify the Program subject to
+    these terms and conditions. You may not impose any further restrictions
+    on the recipients' exercise of the rights granted herein. You are not
+    responsible for enforcing compliance by third parties to this License.
+
+    7. If, as a consequence of a court judgment or allegation of patent
+    infringement or for any other reason (not limited to patent issues),
+    conditions are imposed on you (whether by court order, agreement or
+    otherwise) that contradict the conditions of this License, they do not
+    excuse you from the conditions of this License. If you cannot distribute
+    so as to satisfy simultaneously your obligations under this License and
+    any other pertinent obligations, then as a consequence you may not
+    distribute the Program at all. For example, if a patent license would
+    not permit royalty-free redistribution of the Program by all those who
+    receive copies directly or indirectly through you, then the only way you
+    could satisfy both it and this License would be to refrain entirely from
+    distribution of the Program.
+
+    If any portion of this section is held invalid or unenforceable under
+    any particular circumstance, the balance of the section is intended to
+    apply and the section as a whole is intended to apply in other
+    circumstances.
+
+    It is not the purpose of this section to induce you to infringe any
+    patents or other property right claims or to contest validity of any
+    such claims; this section has the sole purpose of protecting the
+    integrity of the free software distribution system, which is implemented
+    by public license practices. Many people have made generous
+    contributions to the wide range of software distributed through that
+    system in reliance on consistent application of that system; it is up to
+    the author/donor to decide if he or she is willing to distribute
+    software through any other system and a licensee cannot impose that choice.
+
+    This section is intended to make thoroughly clear what is believed to be
+    a consequence of the rest of this License.
+
+    8. If the distribution and/or use of the Program is restricted in
+    certain countries either by patents or by copyrighted interfaces, the
+    original copyright holder who places the Program under this License may
+    add an explicit geographical distribution limitation excluding those
+    countries, so that distribution is permitted only in or among countries
+    not thus excluded. In such case, this License incorporates the
+    limitation as if written in the body of this License.
+
+    9. The Free Software Foundation may publish revised and/or new
+    versions of the General Public License from time to time. Such new
+    versions will be similar in spirit to the present version, but may
+    differ in detail to address new problems or concerns.
+
+    Each version is given a distinguishing version number. If the Program
+    specifies a version number of this License which applies to it and "any
+    later version", you have the option of following the terms and
+    conditions either of that version or of any later version published by
+    the Free Software Foundation. If the Program does not specify a version
+    number of this License, you may choose any version ever published by the
+    Free Software Foundation.
+
+    10. If you wish to incorporate parts of the Program into other free
+    programs whose distribution conditions are different, write to the
+    author to ask for permission. For software which is copyrighted by the
+    Free Software Foundation, write to the Free Software Foundation; we
+    sometimes make exceptions for this. Our decision will be guided by the
+    two goals of preserving the free status of all derivatives of our free
+    software and of promoting the sharing and reuse of software generally.
+
+    NO WARRANTY
+
+    11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO
+    WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+    EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+    OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND,
+    EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+    ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
+    YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+    NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+    12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+    WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+    AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
+    DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
+    DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+    (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
+    INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
+    THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR
+    OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+    END OF TERMS AND CONDITIONS
+
+    How to Apply These Terms to Your New Programs
+
+    If you develop a new program, and you want it to be of the greatest
+    possible use to the public, the best way to achieve this is to make it
+    free software which everyone can redistribute and change under these terms.
+
+    To do so, attach the following notices to the program. It is safest to
+    attach them to the start of each source file to most effectively convey
+    the exclusion of warranty; and each file should have at least the
+    "copyright" line and a pointer to where the full notice is found.
+
+        One line to give the program's name and a brief idea of what it does.
+        Copyright (C) <year> <name of author>
+
+        This program 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.
+
+        This program 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 program; if not, write to the Free Software
+        Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
+
+    Also add information on how to contact you by electronic and paper mail.
+
+    If the program is interactive, make it output a short notice like this
+    when it starts in an interactive mode:
+
+        Gnomovision version 69, Copyright (C) year name of author
+        Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type
+        `show w'. This is free software, and you are welcome to redistribute
+        it under certain conditions; type `show c' for details.
+
+    The hypothetical commands `show w' and `show c' should show the
+    appropriate parts of the General Public License. Of course, the commands
+    you use may be called something other than `show w' and `show c'; they
+    could even be mouse-clicks or menu items--whatever suits your program.
+
+    You should also get your employer (if you work as a programmer) or your
+    school, if any, to sign a "copyright disclaimer" for the program, if
+    necessary. Here is a sample; alter the names:
+
+        Yoyodyne, Inc., hereby disclaims all copyright interest in the
+        program `Gnomovision' (which makes passes at compilers) written by
+        James Hacker.
+
+        signature of Ty Coon, 1 April 1989
+        Ty Coon, President of Vice
+
+    This General Public License does not permit incorporating your program
+    into proprietary programs. If your program is a subroutine library, you
+    may consider it more useful to permit linking proprietary applications
+    with the library. If this is what you want to do, use the GNU Library
+    General Public License instead of this License.
+
+---
+
+## CLASSPATH EXCEPTION
+
+    Linking this library statically or dynamically with other modules is
+    making a combined work based on this library.  Thus, the terms and
+    conditions of the GNU General Public License version 2 cover the whole
+    combination.
+
+    As a special exception, the copyright holders of this library give you
+    permission to link this library with independent modules to produce an
+    executable, regardless of the license terms of these independent
+    modules, and to copy and distribute the resulting executable under
+    terms of your choice, provided that you also meet, for each linked
+    independent module, the terms and conditions of the license of that
+    module.  An independent module is a module which is not derived from or
+    based on this library.  If you modify this library, you may extend this
+    exception to your version of the library, but you are not obligated to
+    do so.  If you do not wish to do so, delete this exception statement
+    from your version.
diff --git a/NOTICE.md b/NOTICE.md
new file mode 100644
index 0000000..9a5159e
--- /dev/null
+++ b/NOTICE.md
@@ -0,0 +1,50 @@
+# Notices for Eclipse Project for JavaMail
+
+This content is produced and maintained by the Eclipse Project for JavaMail
+project.
+
+* Project home: https://projects.eclipse.org/projects/ee4j.javamail
+
+## Trademarks
+
+Eclipse Project for JavaMail is a trademark of the Eclipse Foundation.
+
+## Copyright
+
+All content is the property of the respective authors or their employers. For
+more information regarding authorship of content, please consult the listed
+source code repository logs.
+
+## Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License v. 2.0 which is available at
+http://www.eclipse.org/legal/epl-2.0. This Source Code may also be made
+available under the following Secondary Licenses when the conditions for such
+availability set forth in the Eclipse Public License v. 2.0 are satisfied: GNU
+General Public License, version 2 with the GNU Classpath Exception which is
+available at https://www.gnu.org/software/classpath/license.html.
+
+SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+## Source Code
+
+The project maintains the following source code repositories:
+
+* https://github.com/eclipse-ee4j/javamail
+
+## Third-party Content
+
+This project leverages the following third party content.
+
+None
+
+## Cryptography
+
+Content may contain encryption software. The country in which you are currently
+may have restrictions on the import, possession, and use, and/or re-export to
+another country, of encryption software. BEFORE using any encryption software,
+please check the country's laws, regulations and policies concerning the import,
+possession, or use, and re-export of encryption software, to see if this is
+permitted.
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..01c8392
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+See the [JavaMail web site](https://javaee.github.io/javamail).
diff --git a/android/activation/exclude.xml b/android/activation/exclude.xml
new file mode 100644
index 0000000..3ca7394
--- /dev/null
+++ b/android/activation/exclude.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<!-- FindBugs exclude list for JAF -->
+
+<FindBugsFilter>
+    <!--
+	We're purposely using the platform default encoding when reading
+	these files.
+    -->
+    <Match>
+	<Or>
+	    <Class name="com.sun.activation.registries.MailcapFile"/>
+	    <Class name="com.sun.activation.registries.MimeTypeFile"/>
+	</Or>
+	<Bug pattern="DM_DEFAULT_ENCODING"/>
+    </Match>
+
+    <!--
+	We're purposely using the platform default encoding when writing
+	a String to an OutputStream.
+    -->
+    <Match>
+	<Class name="javax.activation.ObjectDataContentHandler"/>
+	<Method name="writeTo"/>
+	<Bug pattern="DM_DEFAULT_ENCODING"/>
+    </Match>
+</FindBugsFilter>
diff --git a/android/activation/pom.xml b/android/activation/pom.xml
new file mode 100644
index 0000000..492e698
--- /dev/null
+++ b/android/activation/pom.xml
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>android</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>android-activation</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaBeans Activation Framework fork for Android</name>
+
+    <!--
+	This is a fork of the JavaBeans Activation Framework (JAF) that
+	is sufficient for use by JavaMail when running on Android.
+	Android is not Java Compatible, and is missing the java.awt.datatransfer
+	classes.  This fork of JAF breaks the dependency on the
+	java.awt.datatransfer classes and thus isn't compatible with
+	the JAF specification.  It should not be used by anything other
+	than this Android-specific version of JavaMail.
+    -->
+
+    <properties>
+	<activation.spec.version>1.2</activation.spec.version>
+	<activation.extensionName>
+	    jakarta.activation.android
+	</activation.extensionName>
+	<activation.specificationTitle>
+	    JavaBeans(TM) Activation Framework Specification
+	</activation.specificationTitle>
+	<activation.implementationTitle>
+	    javax.activation
+	</activation.implementationTitle>
+	<activation.bundle.symbolicName>
+	    ${project.groupId}.${project.artifactId}
+	</activation.bundle.symbolicName>
+	<activation.packages.export>
+	    javax.activation.*; version=${activation.spec.version},
+	    com.sun.activation.*; version=${activation.osgiversion}
+	</activation.packages.export>
+	<findbugs.skip>
+	    false
+	</findbugs.skip>
+	<findbugs.exclude>
+	    ${project.basedir}/exclude.xml
+	</findbugs.exclude>
+    </properties>
+
+    <build>
+	<resources>
+	    <resource>
+		<directory>src/main/resources</directory>
+		<filtering>true</filtering>
+	    </resource>
+	</resources>
+	<plugins>
+	    <!--
+		Configure compiler plugin to print lint warnings.
+	    -->
+	    <plugin>
+		<artifactId>maven-compiler-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>default-compile</id>
+			<configuration>
+			    <source>1.7</source>
+			    <target>1.7</target>
+			    <fork>true</fork>
+			    <!--
+				ignore the errors that I don't
+				want to fix now
+			    -->
+			    <compilerArgs>
+				<arg>-Xlint</arg>
+				<arg>-Xlint:-options</arg>
+				<arg>-Xlint:-path</arg>
+				<arg>-Xlint:-rawtypes</arg>
+				<arg>-Xlint:-unchecked</arg>
+				<arg>-Xlint:-finally</arg>
+				<arg>-Xlint:-serial</arg>
+				<arg>-Xlint:-cast</arg>
+				<arg>-Xlint:-deprecation</arg>
+				<arg>-Xlint:-dep-ann</arg>
+				<arg>-Werror</arg>
+			    </compilerArgs>
+			    <showWarnings>true</showWarnings>
+			</configuration>
+		    </execution>
+		    <execution>
+			<id>default-testCompile</id>
+			<configuration>
+			    <source>1.7</source>
+			    <target>1.7</target>
+			</configuration>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Configure test plugin to find *TestSuite classes.
+	    -->
+	    <plugin>
+		<groupId>org.apache.maven.plugins</groupId>
+		<artifactId>maven-surefire-plugin</artifactId>
+		<configuration>
+		    <includes>
+			<include>**/*Test.java</include>
+			<include>**/*TestSuite.java</include>
+		    </includes>
+		</configuration>
+	    </plugin>
+
+	    <plugin>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>maven-bundle-plugin</artifactId>
+		<configuration>
+		    <instructions>
+			<Bundle-SymbolicName>
+			    ${activation.bundle.symbolicName}
+			</Bundle-SymbolicName>
+			<Export-Package>
+			    ${activation.packages.export}
+			</Export-Package>
+			<Import-Package>
+			    ${activation.packages.import}
+			</Import-Package>
+			<Private-Package>
+			    ${activation.packages.private}
+			</Private-Package>
+			<DynamicImport-Package>
+			    *
+			</DynamicImport-Package>
+		    </instructions>
+		</configuration>
+		<!--
+		    Since we don't change the packaging type to bundle, we
+		    need to configure the plugin to execute the manifest goal
+		    during the process-classes phase of the build life cycle.
+		-->
+		<executions>
+		    <execution>
+			<id>osgi-manifest</id>
+			<phase>process-classes</phase>
+			<goals>
+			    <goal>manifest</goal>
+			</goals>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Since we don't want a qualifier like b05 or SNAPSHOT to
+		appear in the OSGi package version attribute, we use
+		the following plugin to populate a project property
+		with an OSGi version that is equivalent to the maven
+		version without the qualifier.
+	    -->
+	    <plugin>
+		<groupId>org.glassfish.hk2</groupId>
+		<artifactId>osgiversion-maven-plugin</artifactId>
+		<version>${hk2.plugin.version}</version>
+		<configuration>
+		    <dropVersionComponent>qualifier</dropVersionComponent>
+		    <versionPropertyName>activation.osgiversion</versionPropertyName>
+		</configuration>
+		<executions>
+		    <execution>
+			<id>compute-osgi-version</id>
+			<goals>
+			    <goal>compute-osgi-version</goal>
+			</goals>
+		    </execution>
+		</executions>
+	    </plugin>
+	</plugins>
+    </build>
+
+    <dependencies>
+	<dependency>
+	    <groupId>junit</groupId>
+	    <artifactId>junit</artifactId>
+	    <version>4.12</version>
+	    <scope>test</scope>
+	    <optional>true</optional>
+	</dependency>
+    </dependencies>
+</project>
diff --git a/android/activation/src/main/java/com/sun/activation/registries/LogSupport.java b/android/activation/src/main/java/com/sun/activation/registries/LogSupport.java
new file mode 100644
index 0000000..3228f3c
--- /dev/null
+++ b/android/activation/src/main/java/com/sun/activation/registries/LogSupport.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.activation.registries;
+
+import java.io.*;
+import java.util.logging.*;
+
+/**
+ * Logging related methods.
+ */
+public class LogSupport {
+    private static boolean debug = false;
+    private static Logger logger;
+    private static final Level level = Level.FINE;
+
+    static {
+	try {
+	    debug = Boolean.getBoolean("javax.activation.debug");
+	} catch (Throwable t) {
+	    // ignore any errors
+	}
+	logger = Logger.getLogger("javax.activation");
+    }
+
+    /**
+     * Constructor.
+     */
+    private LogSupport() {
+	// private constructor, can't create instances
+    }
+
+    public static void log(String msg) {
+	if (debug)
+	    System.out.println(msg);
+	logger.log(level, msg);
+    }
+
+    public static void log(String msg, Throwable t) {
+	if (debug)
+	    System.out.println(msg + "; Exception: " + t);
+	logger.log(level, msg, t);
+    }
+
+    public static boolean isLoggable() {
+	return debug || logger.isLoggable(level);
+    }
+}
diff --git a/android/activation/src/main/java/com/sun/activation/registries/MailcapFile.java b/android/activation/src/main/java/com/sun/activation/registries/MailcapFile.java
new file mode 100644
index 0000000..afcb5d6
--- /dev/null
+++ b/android/activation/src/main/java/com/sun/activation/registries/MailcapFile.java
@@ -0,0 +1,548 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.activation.registries;
+
+import java.io.*;
+import java.util.*;
+
+public class MailcapFile {
+
+    /**
+     * A Map indexed by MIME type (string) that references
+     * a Map of commands for each type.  The comand Map
+     * is indexed by the command name and references a List of
+     * class names (strings) for each command.
+     */
+    private Map type_hash = new HashMap();
+
+    /**
+     * Another Map like above, but for fallback entries.
+     */
+    private Map fallback_hash = new HashMap();
+
+    /**
+     * A Map indexed by MIME type (string) that references
+     * a List of native commands (string) corresponding to the type.
+     */
+    private Map native_commands = new HashMap();
+
+    private static boolean addReverse = false;
+
+    static {
+	try {
+	    addReverse = Boolean.getBoolean("javax.activation.addreverse");
+	} catch (Throwable t) {
+	    // ignore any errors
+	}
+    }
+
+    /**
+     * The constructor that takes a filename as an argument.
+     *
+     * @param new_fname The file name of the mailcap file.
+     */
+    public MailcapFile(String new_fname) throws IOException {
+	if (LogSupport.isLoggable())
+	    LogSupport.log("new MailcapFile: file " + new_fname);
+	FileReader reader = null;
+	try {
+	    reader = new FileReader(new_fname);
+	    parse(new BufferedReader(reader));
+	} finally {
+	    if (reader != null) {
+		try {
+		    reader.close();
+		} catch (IOException ex) { }
+	    }
+	}
+    }
+
+    /**
+     * The constructor that takes an input stream as an argument.
+     *
+     * @param is	the input stream
+     */
+    public MailcapFile(InputStream is) throws IOException {
+	if (LogSupport.isLoggable())
+	    LogSupport.log("new MailcapFile: InputStream");
+	parse(new BufferedReader(new InputStreamReader(is, "iso-8859-1")));
+    }
+
+    /**
+     * Mailcap file default constructor.
+     */
+    public MailcapFile() {
+	if (LogSupport.isLoggable())
+	    LogSupport.log("new MailcapFile: default");
+    }
+
+    /**
+     * Get the Map of MailcapEntries based on the MIME type.
+     *
+     * <p>
+     * <strong>Semantics:</strong> First check for the literal mime type,
+     * if that fails looks for wildcard <i>type</i>/* and return that. Return the
+     * list of all that hit.
+     */
+    public Map getMailcapList(String mime_type) {
+	Map search_result = null;
+	Map wildcard_result = null;
+
+	// first try the literal
+	search_result = (Map)type_hash.get(mime_type);
+
+	// ok, now try the wildcard
+	int separator = mime_type.indexOf('/');
+	String subtype = mime_type.substring(separator + 1);
+	if (!subtype.equals("*")) {
+	    String type = mime_type.substring(0, separator + 1) + "*";
+	    wildcard_result = (Map)type_hash.get(type);
+
+	    if (wildcard_result != null) { // damn, we have to merge!!!
+		if (search_result != null)
+		    search_result =
+			mergeResults(search_result, wildcard_result);
+		else
+		    search_result = wildcard_result;
+	    }
+	}
+	return search_result;
+    }
+
+    /**
+     * Get the Map of fallback MailcapEntries based on the MIME type.
+     *
+     * <p>
+     * <strong>Semantics:</strong> First check for the literal mime type,
+     * if that fails looks for wildcard <i>type</i>/* and return that. Return the
+     * list of all that hit.
+     */
+    public Map getMailcapFallbackList(String mime_type) {
+	Map search_result = null;
+	Map wildcard_result = null;
+
+	// first try the literal
+	search_result = (Map)fallback_hash.get(mime_type);
+
+	// ok, now try the wildcard
+	int separator = mime_type.indexOf('/');
+	String subtype = mime_type.substring(separator + 1);
+	if (!subtype.equals("*")) {
+	    String type = mime_type.substring(0, separator + 1) + "*";
+	    wildcard_result = (Map)fallback_hash.get(type);
+
+	    if (wildcard_result != null) { // damn, we have to merge!!!
+		if (search_result != null)
+		    search_result =
+			mergeResults(search_result, wildcard_result);
+		else
+		    search_result = wildcard_result;
+	    }
+	}
+	return search_result;
+    }
+
+    /**
+     * Return all the MIME types known to this mailcap file.
+     */
+    public String[] getMimeTypes() {
+	Set types = new HashSet(type_hash.keySet());
+	types.addAll(fallback_hash.keySet());
+	types.addAll(native_commands.keySet());
+	String[] mts = new String[types.size()];
+	mts = (String[])types.toArray(mts);
+	return mts;
+    }
+
+    /**
+     * Return all the native comands for the given MIME type.
+     */
+    public String[] getNativeCommands(String mime_type) {
+	String[] cmds = null;
+	List v =
+	    (List)native_commands.get(mime_type.toLowerCase(Locale.ENGLISH));
+	if (v != null) {
+	    cmds = new String[v.size()];
+	    cmds = (String[])v.toArray(cmds);
+	}
+	return cmds;
+    }
+
+    /**
+     * Merge the first hash into the second.
+     * This merge will only effect the hashtable that is
+     * returned, we don't want to touch the one passed in since
+     * its integrity must be maintained.
+     */
+    private Map mergeResults(Map first, Map second) {
+	Iterator verb_enum = second.keySet().iterator();
+	Map clonedHash = new HashMap(first);
+
+	// iterate through the verbs in the second map
+	while (verb_enum.hasNext()) {
+	    String verb = (String)verb_enum.next();
+	    List cmdVector = (List)clonedHash.get(verb);
+	    if (cmdVector == null) {
+		clonedHash.put(verb, second.get(verb));
+	    } else {
+		// merge the two
+		List oldV = (List)second.get(verb);
+		cmdVector = new ArrayList(cmdVector);
+		cmdVector.addAll(oldV);
+		clonedHash.put(verb, cmdVector);
+	    }
+	}
+	return clonedHash;
+    }
+
+    /**
+     * appendToMailcap: Append to this Mailcap DB, use the mailcap
+     * format:
+     * Comment == "# <i>comment string</i>
+     * Entry == "mimetype;        javabeanclass<br>
+     *
+     * Example:
+     * # this is a comment
+     * image/gif       jaf.viewers.ImageViewer
+     */
+    public void appendToMailcap(String mail_cap) {
+	if (LogSupport.isLoggable())
+	    LogSupport.log("appendToMailcap: " + mail_cap);
+	try {
+	    parse(new StringReader(mail_cap));
+	} catch (IOException ex) {
+	    // can't happen
+	}
+    }
+
+    /**
+     * parse file into a hash table of MC Type Entry Obj
+     */
+    private void parse(Reader reader) throws IOException {
+	BufferedReader buf_reader = new BufferedReader(reader);
+	String line = null;
+	String continued = null;
+
+	while ((line = buf_reader.readLine()) != null) {
+	    //    LogSupport.log("parsing line: " + line);
+
+	    line = line.trim();
+
+	    try {
+		if (line.charAt(0) == '#')
+		    continue;
+		if (line.charAt(line.length() - 1) == '\\') {
+		    if (continued != null)
+			continued += line.substring(0, line.length() - 1);
+		    else
+			continued = line.substring(0, line.length() - 1);
+		} else if (continued != null) {
+		    // handle the two strings
+		    continued = continued + line;
+		    //	LogSupport.log("parse: " + continued);
+		    try {
+			parseLine(continued);
+		    } catch (MailcapParseException e) {
+			//e.printStackTrace();
+		    }
+		    continued = null;
+		}
+		else {
+		    //	LogSupport.log("parse: " + line);
+		    try {
+			parseLine(line);
+			// LogSupport.log("hash.size = " + type_hash.size());
+		    } catch (MailcapParseException e) {
+			//e.printStackTrace();
+		    }
+		}
+	    } catch (StringIndexOutOfBoundsException e) {}
+	}
+    }
+
+    /**
+     *	A routine to parse individual entries in a Mailcap file.
+     *
+     *	Note that this routine does not handle line continuations.
+     *	They should have been handled prior to calling this routine.
+     */
+    protected void parseLine(String mailcapEntry)
+				throws MailcapParseException, IOException {
+	MailcapTokenizer tokenizer = new MailcapTokenizer(mailcapEntry);
+	tokenizer.setIsAutoquoting(false);
+
+	if (LogSupport.isLoggable())
+	    LogSupport.log("parse: " + mailcapEntry);
+	//	parse the primary type
+	int currentToken = tokenizer.nextToken();
+	if (currentToken != MailcapTokenizer.STRING_TOKEN) {
+	    reportParseError(MailcapTokenizer.STRING_TOKEN, currentToken,
+					tokenizer.getCurrentTokenValue());
+	}
+	String primaryType =
+	    tokenizer.getCurrentTokenValue().toLowerCase(Locale.ENGLISH);
+	String subType = "*";
+
+	//	parse the '/' between primary and sub
+	//	if it's not present that's ok, we just don't have a subtype
+	currentToken = tokenizer.nextToken();
+	if ((currentToken != MailcapTokenizer.SLASH_TOKEN) &&
+			(currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) {
+	    reportParseError(MailcapTokenizer.SLASH_TOKEN,
+				MailcapTokenizer.SEMICOLON_TOKEN, currentToken,
+				tokenizer.getCurrentTokenValue());
+	}
+
+	//	only need to look for a sub type if we got a '/'
+	if (currentToken == MailcapTokenizer.SLASH_TOKEN) {
+	    //	parse the sub type
+	    currentToken = tokenizer.nextToken();
+	    if (currentToken != MailcapTokenizer.STRING_TOKEN) {
+		reportParseError(MailcapTokenizer.STRING_TOKEN,
+			    currentToken, tokenizer.getCurrentTokenValue());
+	    }
+	    subType =
+		tokenizer.getCurrentTokenValue().toLowerCase(Locale.ENGLISH);
+
+	    //	get the next token to simplify the next step
+	    currentToken = tokenizer.nextToken();
+	}
+
+	String mimeType = primaryType + "/" + subType;
+
+	if (LogSupport.isLoggable())
+	    LogSupport.log("  Type: " + mimeType);
+
+	//	now setup the commands hashtable
+	Map commands = new LinkedHashMap();	// keep commands in order found
+
+	//	parse the ';' that separates the type from the parameters
+	if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) {
+	    reportParseError(MailcapTokenizer.SEMICOLON_TOKEN,
+			    currentToken, tokenizer.getCurrentTokenValue());
+	}
+	//	eat it
+
+	//	parse the required view command
+	tokenizer.setIsAutoquoting(true);
+	currentToken = tokenizer.nextToken();
+	tokenizer.setIsAutoquoting(false);
+	if ((currentToken != MailcapTokenizer.STRING_TOKEN) &&
+		    (currentToken != MailcapTokenizer.SEMICOLON_TOKEN)) {
+	    reportParseError(MailcapTokenizer.STRING_TOKEN,
+			    MailcapTokenizer.SEMICOLON_TOKEN, currentToken,
+			    tokenizer.getCurrentTokenValue());
+	}
+
+	if (currentToken == MailcapTokenizer.STRING_TOKEN) {
+	    // have a native comand, save the entire mailcap entry
+	    //String nativeCommand = tokenizer.getCurrentTokenValue();
+	    List v = (List)native_commands.get(mimeType);
+	    if (v == null) {
+		v = new ArrayList();
+		v.add(mailcapEntry);
+		native_commands.put(mimeType, v);
+	    } else {
+		// XXX - check for duplicates?
+		v.add(mailcapEntry);
+	    }
+	}
+
+	//	only have to get the next token if the current one isn't a ';'
+	if (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) {
+	    currentToken = tokenizer.nextToken();
+	}
+
+	// look for a ';' which will indicate whether
+	// a parameter list is present or not
+	if (currentToken == MailcapTokenizer.SEMICOLON_TOKEN) {
+	    boolean isFallback = false;
+	    do {
+		//	eat the ';'
+
+		//	parse the parameter name
+		currentToken = tokenizer.nextToken();
+		if (currentToken != MailcapTokenizer.STRING_TOKEN) {
+		    reportParseError(MailcapTokenizer.STRING_TOKEN,
+			    currentToken, tokenizer.getCurrentTokenValue());
+		}
+		String paramName = tokenizer.getCurrentTokenValue().
+						toLowerCase(Locale.ENGLISH);
+
+		//	parse the '=' which separates the name from the value
+		currentToken = tokenizer.nextToken();
+		if ((currentToken != MailcapTokenizer.EQUALS_TOKEN) &&
+		    (currentToken != MailcapTokenizer.SEMICOLON_TOKEN) &&
+		    (currentToken != MailcapTokenizer.EOI_TOKEN)) {
+		    reportParseError(MailcapTokenizer.EQUALS_TOKEN,
+			    MailcapTokenizer.SEMICOLON_TOKEN,
+			    MailcapTokenizer.EOI_TOKEN,
+			    currentToken, tokenizer.getCurrentTokenValue());
+		}
+
+		//	we only have a useful command if it is named
+		if (currentToken == MailcapTokenizer.EQUALS_TOKEN) {
+		    //	eat it
+
+		    //	parse the parameter value (which is autoquoted)
+		    tokenizer.setIsAutoquoting(true);
+		    currentToken = tokenizer.nextToken();
+		    tokenizer.setIsAutoquoting(false);
+		    if (currentToken != MailcapTokenizer.STRING_TOKEN) {
+			reportParseError(MailcapTokenizer.STRING_TOKEN,
+			currentToken, tokenizer.getCurrentTokenValue());
+		    }
+		    String paramValue =
+				tokenizer.getCurrentTokenValue();
+
+		    // add the class to the list iff it is one we care about
+		    if (paramName.startsWith("x-java-")) {
+			String commandName = paramName.substring(7);
+			//	7 == "x-java-".length
+
+			if (commandName.equals("fallback-entry") &&
+			    paramValue.equalsIgnoreCase("true")) {
+			    isFallback = true;
+			} else {
+
+			    //	setup the class entry list
+			    if (LogSupport.isLoggable())
+				LogSupport.log("    Command: " + commandName +
+						    ", Class: " + paramValue);
+			    List classes = (List)commands.get(commandName);
+			    if (classes == null) {
+				classes = new ArrayList();
+				commands.put(commandName, classes);
+			    }
+			    if (addReverse)
+				classes.add(0, paramValue);
+			    else
+				classes.add(paramValue);
+			}
+		    }
+
+		    //	set up the next iteration
+		    currentToken = tokenizer.nextToken();
+		}
+	    } while (currentToken == MailcapTokenizer.SEMICOLON_TOKEN);
+
+	    Map masterHash = isFallback ? fallback_hash : type_hash;
+	    Map curcommands =
+		(Map)masterHash.get(mimeType);
+	    if (curcommands == null) {
+		masterHash.put(mimeType, commands);
+	    } else {
+		if (LogSupport.isLoggable())
+		    LogSupport.log("Merging commands for type " + mimeType);
+		// have to merge current and new commands
+		// first, merge list of classes for commands already known
+		Iterator cn = curcommands.keySet().iterator();
+		while (cn.hasNext()) {
+		    String cmdName = (String)cn.next();
+		    List ccv = (List)curcommands.get(cmdName);
+		    List cv = (List)commands.get(cmdName);
+		    if (cv == null)
+			continue;
+		    // add everything in cv to ccv, if it's not already there
+		    Iterator cvn = cv.iterator();
+		    while (cvn.hasNext()) {
+			String clazz = (String)cvn.next();
+			if (!ccv.contains(clazz))
+			    if (addReverse)
+				ccv.add(0, clazz);
+			    else
+				ccv.add(clazz);
+		    }
+		}
+		// now, add commands not previously known
+		cn = commands.keySet().iterator();
+		while (cn.hasNext()) {
+		    String cmdName = (String)cn.next();
+		    if (curcommands.containsKey(cmdName))
+			continue;
+		    List cv = (List)commands.get(cmdName);
+		    curcommands.put(cmdName, cv);
+		}
+	    }
+	} else if (currentToken != MailcapTokenizer.EOI_TOKEN) {
+	    reportParseError(MailcapTokenizer.EOI_TOKEN,
+		MailcapTokenizer.SEMICOLON_TOKEN,
+		currentToken, tokenizer.getCurrentTokenValue());
+	}
+     }
+
+     protected static void reportParseError(int expectedToken, int actualToken,
+		String actualTokenValue) throws MailcapParseException {
+     	throw new MailcapParseException("Encountered a " +
+		MailcapTokenizer.nameForToken(actualToken) + " token (" +
+		actualTokenValue + ") while expecting a " +
+		MailcapTokenizer.nameForToken(expectedToken) + " token.");
+     }
+
+     protected static void reportParseError(int expectedToken,
+	int otherExpectedToken, int actualToken, String actualTokenValue)
+					throws MailcapParseException {
+     	throw new MailcapParseException("Encountered a " +
+		MailcapTokenizer.nameForToken(actualToken) + " token (" +
+		actualTokenValue + ") while expecting a " +
+		MailcapTokenizer.nameForToken(expectedToken) + " or a " +
+		MailcapTokenizer.nameForToken(otherExpectedToken) + " token.");
+     }
+
+     protected static void reportParseError(int expectedToken,
+	    int otherExpectedToken, int anotherExpectedToken, int actualToken,
+	    String actualTokenValue) throws MailcapParseException {
+	if (LogSupport.isLoggable())
+	    LogSupport.log("PARSE ERROR: " + "Encountered a " +
+		MailcapTokenizer.nameForToken(actualToken) + " token (" +
+		actualTokenValue + ") while expecting a " +
+		MailcapTokenizer.nameForToken(expectedToken) + ", a " +
+		MailcapTokenizer.nameForToken(otherExpectedToken) + ", or a " +
+		MailcapTokenizer.nameForToken(anotherExpectedToken) + " token.");
+     	throw new MailcapParseException("Encountered a " +
+		MailcapTokenizer.nameForToken(actualToken) + " token (" +
+		actualTokenValue + ") while expecting a " +
+		MailcapTokenizer.nameForToken(expectedToken) + ", a " +
+		MailcapTokenizer.nameForToken(otherExpectedToken) + ", or a " +
+		MailcapTokenizer.nameForToken(anotherExpectedToken) + " token.");
+     }
+
+     /** for debugging
+     public static void	main(String[] args) throws Exception {
+     	Map masterHash = new HashMap();
+     	for (int i = 0; i < args.length; ++i) {
+	    System.out.println("Entry " + i + ": " + args[i]);
+	    parseLine(args[i], masterHash);
+     	}
+
+     	Enumeration types = masterHash.keys();
+     	while (types.hasMoreElements()) {
+	    String key = (String)types.nextElement();
+	    System.out.println("MIME Type: " + key);
+
+	    Map commandHash = (Map)masterHash.get(key);
+	    Enumeration commands = commandHash.keys();
+	    while (commands.hasMoreElements()) {
+		String command = (String)commands.nextElement();
+		System.out.println("    Command: " + command);
+
+		Vector classes = (Vector)commandHash.get(command);
+		for (int i = 0; i < classes.size(); ++i) {
+			System.out.println("        Class: " +
+					    (String)classes.elementAt(i));
+		}
+	    }
+
+	    System.out.println("");
+	}
+    }
+    */
+}
diff --git a/android/activation/src/main/java/com/sun/activation/registries/MailcapParseException.java b/android/activation/src/main/java/com/sun/activation/registries/MailcapParseException.java
new file mode 100644
index 0000000..754c405
--- /dev/null
+++ b/android/activation/src/main/java/com/sun/activation/registries/MailcapParseException.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package	com.sun.activation.registries;
+
+/**
+ *	A class to encapsulate Mailcap parsing related exceptions
+ */
+public class MailcapParseException extends Exception {
+
+    public MailcapParseException() {
+	super();
+    }
+
+    public MailcapParseException(String inInfo) {
+	super(inInfo);
+    }
+}
diff --git a/android/activation/src/main/java/com/sun/activation/registries/MailcapTokenizer.java b/android/activation/src/main/java/com/sun/activation/registries/MailcapTokenizer.java
new file mode 100644
index 0000000..6437930
--- /dev/null
+++ b/android/activation/src/main/java/com/sun/activation/registries/MailcapTokenizer.java
@@ -0,0 +1,307 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package	com.sun.activation.registries;
+
+/**
+ *	A tokenizer for strings in the form of "foo/bar; prop1=val1; ... ".
+ *	Useful for parsing MIME content types.
+ */
+public class MailcapTokenizer {
+
+    public static final int UNKNOWN_TOKEN = 0;
+    public static final int START_TOKEN = 1;
+    public static final int STRING_TOKEN = 2;
+    public static final int EOI_TOKEN = 5;
+    public static final int SLASH_TOKEN = '/';
+    public static final int SEMICOLON_TOKEN = ';';
+    public static final int EQUALS_TOKEN = '=';
+
+    /**
+     *  Constructor
+     *
+     *  @param  inputString the string to tokenize
+     */
+    public MailcapTokenizer(String inputString) {
+	data = inputString;
+	dataIndex = 0;
+	dataLength = inputString.length();
+
+	currentToken = START_TOKEN;
+	currentTokenValue = "";
+
+	isAutoquoting = false;
+	autoquoteChar = ';';
+    }
+
+    /**
+     *  Set whether auto-quoting is on or off.
+     *
+     *  Auto-quoting means that all characters after the first
+     *  non-whitespace, non-control character up to the auto-quote
+     *  terminator character or EOI (minus any whitespace immediatley
+     *  preceeding it) is considered a token.
+     *
+     *  This is required for handling command strings in a mailcap entry.
+     */
+    public void setIsAutoquoting(boolean value) {
+	isAutoquoting = value;
+    }
+
+    /**
+     *  Retrieve current token.
+     *
+     *  @return    The current token value
+     */
+    public int getCurrentToken() {
+	return currentToken;
+    }
+
+    /*
+     *  Get a String that describes the given token.
+     */
+    public static String nameForToken(int token) {
+	String name = "really unknown";
+
+	switch(token) {
+	    case UNKNOWN_TOKEN:
+		name = "unknown";
+		break;
+	    case START_TOKEN:
+		name = "start";
+		break;
+	    case STRING_TOKEN:
+		name = "string";
+		break;
+	    case EOI_TOKEN:
+		name = "EOI";
+		break;
+	    case SLASH_TOKEN:
+		name = "'/'";
+		break;
+	    case SEMICOLON_TOKEN:
+		name = "';'";
+		break;
+	    case EQUALS_TOKEN:
+		name = "'='";
+		break;
+	}
+
+	return name;
+    }
+
+    /*
+     *  Retrieve current token value.
+     *
+     *  @return    A String containing the current token value
+     */
+    public String getCurrentTokenValue() {
+	return currentTokenValue;
+    }
+
+    /*
+     *  Process the next token.
+     *
+     *  @return    the next token
+     */
+    public int nextToken() {
+	if (dataIndex < dataLength) {
+	    //  skip white space
+	    while ((dataIndex < dataLength) &&
+		    (isWhiteSpaceChar(data.charAt(dataIndex)))) {
+		++dataIndex;
+	    }
+
+	    if (dataIndex < dataLength) {
+		//  examine the current character and see what kind of token we have
+		char c = data.charAt(dataIndex);
+		if (isAutoquoting) {
+		    if (c == ';' || c == '=') {
+			currentToken = c;
+			currentTokenValue = new Character(c).toString();
+			++dataIndex;
+		    } else {
+			processAutoquoteToken();
+		    }
+		} else {
+		    if (isStringTokenChar(c)) {
+			processStringToken();
+		    } else if ((c == '/') || (c == ';') || (c == '=')) {
+			currentToken = c;
+			currentTokenValue = new Character(c).toString();
+			++dataIndex;
+		    } else {
+			currentToken = UNKNOWN_TOKEN;
+			currentTokenValue = new Character(c).toString();
+			++dataIndex;
+		    }
+		}
+	    } else {
+		currentToken = EOI_TOKEN;
+		currentTokenValue = null;
+	    }
+	} else {
+	    currentToken = EOI_TOKEN;
+	    currentTokenValue = null;
+	}
+
+	return currentToken;
+    }
+
+    private void processStringToken() {
+	//  capture the initial index
+	int initialIndex = dataIndex;
+
+	//  skip to 1st non string token character
+	while ((dataIndex < dataLength) &&
+		isStringTokenChar(data.charAt(dataIndex))) {
+	    ++dataIndex;
+	}
+
+	currentToken = STRING_TOKEN;
+	currentTokenValue = data.substring(initialIndex, dataIndex);
+    }
+
+    private void processAutoquoteToken() {
+	//  capture the initial index
+	int initialIndex = dataIndex;
+
+	//  now skip to the 1st non-escaped autoquote termination character
+	//  XXX - doesn't actually consider escaping
+	boolean foundTerminator = false;
+	while ((dataIndex < dataLength) && !foundTerminator) {
+	    char c = data.charAt(dataIndex);
+	    if (c != autoquoteChar) {
+		++dataIndex;
+	    } else {
+		foundTerminator = true;
+	    }
+	}
+
+	currentToken = STRING_TOKEN;
+	currentTokenValue =
+	    fixEscapeSequences(data.substring(initialIndex, dataIndex));
+    }
+
+    private static boolean isSpecialChar(char c) {
+	boolean lAnswer = false;
+
+	switch(c) {
+	    case '(':
+	    case ')':
+	    case '<':
+	    case '>':
+	    case '@':
+	    case ',':
+	    case ';':
+	    case ':':
+	    case '\\':
+	    case '"':
+	    case '/':
+	    case '[':
+	    case ']':
+	    case '?':
+	    case '=':
+		lAnswer = true;
+		break;
+	}
+
+	return lAnswer;
+    }
+
+    private static boolean isControlChar(char c) {
+	return Character.isISOControl(c);
+    }
+
+    private static boolean isWhiteSpaceChar(char c) {
+	return Character.isWhitespace(c);
+    }
+
+    private static boolean isStringTokenChar(char c) {
+	return !isSpecialChar(c) && !isControlChar(c) && !isWhiteSpaceChar(c);
+    }
+
+    private static String fixEscapeSequences(String inputString) {
+	int inputLength = inputString.length();
+	StringBuffer buffer = new StringBuffer();
+	buffer.ensureCapacity(inputLength);
+
+	for (int i = 0; i < inputLength; ++i) {
+	    char currentChar = inputString.charAt(i);
+	    if (currentChar != '\\') {
+		buffer.append(currentChar);
+	    } else {
+		if (i < inputLength - 1) {
+		    char nextChar = inputString.charAt(i + 1);
+		    buffer.append(nextChar);
+
+		    //  force a skip over the next character too
+		    ++i;
+		} else {
+		    buffer.append(currentChar);
+		}
+	    }
+	}
+
+	return buffer.toString();
+    }
+
+    private String  data;
+    private int     dataIndex;
+    private int     dataLength;
+    private int     currentToken;
+    private String  currentTokenValue;
+    private boolean isAutoquoting;
+    private char    autoquoteChar;
+
+    /*
+    public static void main(String[] args) {
+	for (int i = 0; i < args.length; ++i) {
+	    MailcapTokenizer tokenizer = new MailcapTokenizer(args[i]);
+
+	    System.out.println("Original: |" + args[i] + "|");
+
+	    int currentToken = tokenizer.nextToken();
+	    while (currentToken != EOI_TOKEN) {
+		switch(currentToken) {
+		    case UNKNOWN_TOKEN:
+			System.out.println("  Unknown Token:           |" + tokenizer.getCurrentTokenValue() + "|");
+			break;
+		    case START_TOKEN:
+			System.out.println("  Start Token:             |" + tokenizer.getCurrentTokenValue() + "|");
+			break;
+		    case STRING_TOKEN:
+			System.out.println("  String Token:            |" + tokenizer.getCurrentTokenValue() + "|");
+			break;
+		    case EOI_TOKEN:
+			System.out.println("  EOI Token:               |" + tokenizer.getCurrentTokenValue() + "|");
+			break;
+		    case SLASH_TOKEN:
+			System.out.println("  Slash Token:             |" + tokenizer.getCurrentTokenValue() + "|");
+			break;
+		    case SEMICOLON_TOKEN:
+			System.out.println("  Semicolon Token:         |" + tokenizer.getCurrentTokenValue() + "|");
+			break;
+		    case EQUALS_TOKEN:
+			System.out.println("  Equals Token:            |" + tokenizer.getCurrentTokenValue() + "|");
+			break;
+		    default:
+			System.out.println("  Really Unknown Token:    |" + tokenizer.getCurrentTokenValue() + "|");
+			break;
+		}
+
+		currentToken = tokenizer.nextToken();
+	    }
+
+	    System.out.println("");
+	}
+    }
+    */
+}
diff --git a/android/activation/src/main/java/com/sun/activation/registries/MimeTypeEntry.java b/android/activation/src/main/java/com/sun/activation/registries/MimeTypeEntry.java
new file mode 100644
index 0000000..3582f77
--- /dev/null
+++ b/android/activation/src/main/java/com/sun/activation/registries/MimeTypeEntry.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.activation.registries;
+
+import java.lang.*;
+
+public class MimeTypeEntry {
+    private String type;
+    private String extension;
+
+    public MimeTypeEntry(String mime_type, String file_ext) {
+	type = mime_type;
+	extension = file_ext;
+    }
+
+    public String getMIMEType() {
+	return type;
+    }
+
+    public String getFileExtension() {
+	return extension;
+    }
+
+    public String toString() {
+	return "MIMETypeEntry: " + type + ", " + extension;
+    }
+}
diff --git a/android/activation/src/main/java/com/sun/activation/registries/MimeTypeFile.java b/android/activation/src/main/java/com/sun/activation/registries/MimeTypeFile.java
new file mode 100644
index 0000000..4692791
--- /dev/null
+++ b/android/activation/src/main/java/com/sun/activation/registries/MimeTypeFile.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package com.sun.activation.registries;
+
+import java.io.*;
+import java.util.*;
+
+public class MimeTypeFile {
+    private String fname = null;
+    private Hashtable type_hash = new Hashtable();
+
+    /**
+     * The construtor that takes a filename as an argument.
+     *
+     * @param new_fname The file name of the mime types file.
+     */
+    public MimeTypeFile(String new_fname) throws IOException {
+	File mime_file = null;
+	FileReader fr = null;
+
+	fname = new_fname; // remember the file name
+
+	mime_file = new File(fname); // get a file object
+
+	fr = new FileReader(mime_file);
+
+	try {
+	    parse(new BufferedReader(fr));
+	} finally {
+	    try {
+		fr.close(); // close it
+	    } catch (IOException e) {
+		// ignore it
+	    }
+	}
+    }
+
+    public MimeTypeFile(InputStream is) throws IOException {
+	parse(new BufferedReader(new InputStreamReader(is, "iso-8859-1")));
+    }
+
+    /**
+     * Creates an empty DB.
+     */
+    public MimeTypeFile() {
+    }
+
+    /**
+     * get the MimeTypeEntry based on the file extension
+     */
+    public MimeTypeEntry getMimeTypeEntry(String file_ext) {
+	return (MimeTypeEntry)type_hash.get((Object)file_ext);
+    }
+
+    /**
+     * Get the MIME type string corresponding to the file extension.
+     */
+    public String getMIMETypeString(String file_ext) {
+	MimeTypeEntry entry = this.getMimeTypeEntry(file_ext);
+
+	if (entry != null)
+	    return entry.getMIMEType();
+	else
+	    return null;
+    }
+
+    /**
+     * Appends string of entries to the types registry, must be valid
+     * .mime.types format.
+     * A mime.types entry is one of two forms:
+     *
+     *	type/subtype	ext1 ext2 ...
+     * or
+     *	type=type/subtype desc="description of type" exts=ext1,ext2,...
+     *
+     * Example:
+     * # this is a test
+     * audio/basic            au
+     * text/plain             txt text
+     * type=application/postscript exts=ps,eps
+     */
+    public void appendToRegistry(String mime_types) {
+	try {
+	    parse(new BufferedReader(new StringReader(mime_types)));
+	} catch (IOException ex) {
+	    // can't happen
+	}
+    }
+
+    /**
+     * Parse a stream of mime.types entries.
+     */
+    private void parse(BufferedReader buf_reader) throws IOException {
+	String line = null, prev = null;
+
+	while ((line = buf_reader.readLine()) != null) {
+	    if (prev == null)
+		prev = line;
+	    else
+		prev += line;
+	    int end = prev.length();
+	    if (prev.length() > 0 && prev.charAt(end - 1) == '\\') {
+		prev = prev.substring(0, end - 1);
+		continue;
+	    }
+	    this.parseEntry(prev);
+	    prev = null;
+	}
+	if (prev != null)
+	    this.parseEntry(prev);
+    }
+
+    /**
+     * Parse single mime.types entry.
+     */
+    private void parseEntry(String line) {
+	String mime_type = null;
+	String file_ext = null;
+	line = line.trim();
+
+	if (line.length() == 0) // empty line...
+	    return; // BAIL!
+
+	// check to see if this is a comment line?
+	if (line.charAt(0) == '#')
+	    return; // then we are done!
+
+	// is it a new format line or old format?
+	if (line.indexOf('=') > 0) {
+	    // new format
+	    LineTokenizer lt = new LineTokenizer(line);
+	    while (lt.hasMoreTokens()) {
+		String name = lt.nextToken();
+		String value = null;
+		if (lt.hasMoreTokens() && lt.nextToken().equals("=") &&
+							lt.hasMoreTokens())
+		    value = lt.nextToken();
+		if (value == null) {
+		    if (LogSupport.isLoggable())
+			LogSupport.log("Bad .mime.types entry: " + line);
+		    return;
+		}
+		if (name.equals("type"))
+		    mime_type = value;
+		else if (name.equals("exts")) {
+		    StringTokenizer st = new StringTokenizer(value, ",");
+		    while (st.hasMoreTokens()) {
+			file_ext = st.nextToken();
+			MimeTypeEntry entry =
+				new MimeTypeEntry(mime_type, file_ext);
+			type_hash.put(file_ext, entry);
+			if (LogSupport.isLoggable())
+			    LogSupport.log("Added: " + entry.toString());
+		    }
+		}
+	    }
+	} else {
+	    // old format
+	    // count the tokens
+	    StringTokenizer strtok = new StringTokenizer(line);
+	    int num_tok = strtok.countTokens();
+
+	    if (num_tok == 0) // empty line
+		return;
+
+	    mime_type = strtok.nextToken(); // get the MIME type
+
+	    while (strtok.hasMoreTokens()) {
+		MimeTypeEntry entry = null;
+
+		file_ext = strtok.nextToken();
+		entry = new MimeTypeEntry(mime_type, file_ext);
+		type_hash.put(file_ext, entry);
+		if (LogSupport.isLoggable())
+		    LogSupport.log("Added: " + entry.toString());
+	    }
+	}
+    }
+
+    // for debugging
+    /*
+    public static void main(String[] argv) throws Exception {
+	MimeTypeFile mf = new MimeTypeFile(argv[0]);
+	System.out.println("ext " + argv[1] + " type " +
+						mf.getMIMETypeString(argv[1]));
+	System.exit(0);
+    }
+    */
+}
+
+class LineTokenizer {
+    private int currentPosition;
+    private int maxPosition;
+    private String str;
+    private Vector stack = new Vector();
+    private static final String singles = "=";	// single character tokens
+
+    /**
+     * Constructs a tokenizer for the specified string.
+     * <p>
+     *
+     * @param   str            a string to be parsed.
+     */
+    public LineTokenizer(String str) {
+	currentPosition = 0;
+	this.str = str;
+	maxPosition = str.length();
+    }
+
+    /**
+     * Skips white space.
+     */
+    private void skipWhiteSpace() {
+	while ((currentPosition < maxPosition) &&
+	       Character.isWhitespace(str.charAt(currentPosition))) {
+	    currentPosition++;
+	}
+    }
+
+    /**
+     * Tests if there are more tokens available from this tokenizer's string.
+     *
+     * @return  <code>true</code> if there are more tokens available from this
+     *          tokenizer's string; <code>false</code> otherwise.
+     */
+    public boolean hasMoreTokens() {
+	if (stack.size() > 0)
+	    return true;
+	skipWhiteSpace();
+	return (currentPosition < maxPosition);
+    }
+
+    /**
+     * Returns the next token from this tokenizer.
+     *
+     * @return     the next token from this tokenizer.
+     * @exception  NoSuchElementException  if there are no more tokens in this
+     *               tokenizer's string.
+     */
+    public String nextToken() {
+	int size = stack.size();
+	if (size > 0) {
+	    String t = (String)stack.elementAt(size - 1);
+	    stack.removeElementAt(size - 1);
+	    return t;
+	}
+	skipWhiteSpace();
+
+	if (currentPosition >= maxPosition) {
+	    throw new NoSuchElementException();
+	}
+
+	int start = currentPosition;
+	char c = str.charAt(start);
+	if (c == '"') {
+	    currentPosition++;
+	    boolean filter = false;
+	    while (currentPosition < maxPosition) {
+		c = str.charAt(currentPosition++);
+		if (c == '\\') {
+		    currentPosition++;
+		    filter = true;
+		} else if (c == '"') {
+		    String s;
+
+		    if (filter) {
+			StringBuffer sb = new StringBuffer();
+			for (int i = start + 1; i < currentPosition - 1; i++) {
+			    c = str.charAt(i);
+			    if (c != '\\')
+				sb.append(c);
+			}
+			s = sb.toString();
+		    } else
+			s = str.substring(start + 1, currentPosition - 1);
+		    return s;
+		}
+	    }
+	} else if (singles.indexOf(c) >= 0) {
+	    currentPosition++;
+	} else {
+	    while ((currentPosition < maxPosition) && 
+		   singles.indexOf(str.charAt(currentPosition)) < 0 &&
+		   !Character.isWhitespace(str.charAt(currentPosition))) {
+		currentPosition++;
+	    }
+	}
+	return str.substring(start, currentPosition);
+    }
+
+    public void pushToken(String token) {
+	stack.addElement(token);
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/ActivationDataFlavor.java b/android/activation/src/main/java/javax/activation/ActivationDataFlavor.java
new file mode 100644
index 0000000..64b73f0
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/ActivationDataFlavor.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+//import java.awt.datatransfer.DataFlavor;
+import java.io.IOException;
+import javax.activation.MimeType;
+
+/**
+ * The ActivationDataFlavor class is a special subclass of
+ * <code>java.awt.datatransfer.DataFlavor</code>. It allows the JAF to
+ * set all three values stored by the DataFlavor class via a new
+ * constructor. It also contains improved MIME parsing in the <code>equals
+ * </code> method. Except for the improved parsing, its semantics are
+ * identical to that of the JDK's DataFlavor class.
+ */
+
+public class ActivationDataFlavor /*extends DataFlavor*/ {
+
+    /*
+     * Raison d'etre:
+     *
+     * The DataFlavor class included in JDK 1.1 has several limitations
+     * including piss poor MIME type parsing, and the limitation of
+     * only supporting serialized objects and InputStreams as
+     * representation objects. This class 'fixes' that.
+     */
+
+    // I think for now I'll keep copies of all the variables and
+    // then later I may choose try to better coexist with the base
+    // class *sigh*
+    private String mimeType = null;
+    private MimeType mimeObject = null;
+    private String humanPresentableName = null;
+    private Class representationClass = null;
+
+    /**
+     * Construct a DataFlavor that represents an arbitrary
+     * Java object. This constructor is an extension of the
+     * JDK's DataFlavor in that it allows the explicit setting
+     * of all three DataFlavor attributes.
+     * <p>
+     * The returned DataFlavor will have the following characteristics:
+     * <p>
+     * representationClass = representationClass<br>
+     * mimeType            = mimeType<br>
+     * humanName           = humanName
+     * <p>
+     *
+     * @param representationClass the class used in this DataFlavor
+     * @param mimeType the MIME type of the data represented by this class
+     * @param humanPresentableName the human presentable name of the flavor
+     */
+    public ActivationDataFlavor(Class representationClass,
+		      String mimeType, String humanPresentableName) {
+	//super(mimeType, humanPresentableName); // need to call super
+
+	// init private variables:
+	this.mimeType = mimeType;
+	this.humanPresentableName = humanPresentableName;
+	this.representationClass = representationClass;
+    }
+
+    /**
+     * Construct a DataFlavor that represents a MimeType.
+     * <p>
+     * The returned DataFlavor will have the following characteristics:
+     * <p>
+     * If the mimeType is "application/x-java-serialized-object;
+     * class=", the result is the same as calling new
+     * DataFlavor(Class.forName()) as above.
+     * <p>
+     * otherwise:
+     * <p>
+     * representationClass = InputStream<p>
+     * mimeType = mimeType<p>
+     *
+     * @param representationClass the class used in this DataFlavor
+     * @param humanPresentableName the human presentable name of the flavor
+     */
+    public ActivationDataFlavor(Class representationClass,
+				String humanPresentableName) {
+	//super(representationClass, humanPresentableName);
+	//this.mimeType = super.getMimeType();
+	this.mimeType = "application/x-java-serialized-object";
+	this.representationClass = representationClass;
+      	this.humanPresentableName = humanPresentableName;
+    }
+
+    /**
+     * Construct a DataFlavor that represents a MimeType.
+     * <p>
+     * The returned DataFlavor will have the following characteristics:
+     * <p>
+     * If the mimeType is "application/x-java-serialized-object; class=",
+     * the result is the same as calling new DataFlavor(Class.forName()) as
+     * above, otherwise:
+     * <p>
+     * representationClass = InputStream<p>
+     * mimeType = mimeType
+     *
+     * @param mimeType the MIME type of the data represented by this class
+     * @param humanPresentableName the human presentable name of the flavor
+     */
+    public ActivationDataFlavor(String mimeType, String humanPresentableName) {
+	//super(mimeType, humanPresentableName);
+	this.mimeType = mimeType;
+	try {
+	    this.representationClass = Class.forName("java.io.InputStream");
+	} catch (ClassNotFoundException ex) {
+	    // XXX - should never happen, ignore it
+	}
+      	this.humanPresentableName = humanPresentableName;
+    }
+
+    /**
+     * Return the MIME type for this DataFlavor.
+     *
+     * @return	the MIME type
+     */
+    public String getMimeType() {
+	return mimeType;
+    }
+
+    /**
+     * Return the representation class.
+     *
+     * @return	the representation class
+     */
+    public Class getRepresentationClass() {
+	return representationClass;
+    }
+
+    /**
+     * Return the Human Presentable name.
+     *
+     * @return	the human presentable name
+     */
+    public String getHumanPresentableName() {
+	return humanPresentableName;
+    }
+
+    /**
+     * Set the human presentable name.
+     *
+     * @param humanPresentableName	the name to set
+     */
+    public void setHumanPresentableName(String humanPresentableName) {
+	this.humanPresentableName = humanPresentableName;
+    }
+
+    /**
+     * Compares the DataFlavor passed in with this DataFlavor; calls
+     * the <code>isMimeTypeEqual</code> method.
+     *
+     * @param dataFlavor	the DataFlavor to compare with
+     * @return			true if the MIME type and representation class
+     *				are the same
+     */
+    public boolean equals(ActivationDataFlavor dataFlavor) {
+	return (isMimeTypeEqual(dataFlavor.mimeType) &&
+	 	dataFlavor.getRepresentationClass() == representationClass);
+    }
+
+    /**
+     * Is the string representation of the MIME type passed in equivalent
+     * to the MIME type of this DataFlavor. <p>
+     *
+     * ActivationDataFlavor delegates the comparison of MIME types to
+     * the MimeType class included as part of the JavaBeans Activation
+     * Framework. This provides a more robust comparison than is normally
+     * available in the DataFlavor class.
+     *
+     * @param mimeType	the MIME type
+     * @return		true if the same MIME type
+     */
+    public boolean isMimeTypeEqual(String mimeType) {
+	MimeType mt = null;
+	try {
+	    if (mimeObject == null)
+		mimeObject = new MimeType(this.mimeType);
+	    mt = new MimeType(mimeType);
+	} catch (MimeTypeParseException e) {
+	    // something didn't parse, do a crude comparison
+	    return this.mimeType.equalsIgnoreCase(mimeType);
+	}
+
+	return mimeObject.match(mt);
+    }
+
+    /**
+     * Called on DataFlavor for every MIME Type parameter to allow DataFlavor
+     * subclasses to handle special parameters like the text/plain charset
+     * parameters, whose values are case insensitive.  (MIME type parameter
+     * values are supposed to be case sensitive).
+     * <p>
+     * This method is called for each parameter name/value pair and should
+     * return the normalized representation of the parameterValue.
+     * This method is never invoked by this implementation.
+     *
+     * @param parameterName	the parameter name
+     * @param parameterValue	the parameter value
+     * @return			the normalized parameter value
+     * @deprecated
+     */
+    protected String normalizeMimeTypeParameter(String parameterName,
+						String parameterValue) {
+	return parameterValue;
+    }
+
+    /**
+     * Called for each MIME type string to give DataFlavor subtypes the
+     * opportunity to change how the normalization of MIME types is
+     * accomplished.
+     * One possible use would be to add default parameter/value pairs in cases
+     * where none are present in the MIME type string passed in.
+     * This method is never invoked by this implementation.
+     *
+     * @param mimeType	the MIME type
+     * @return		the normalized MIME type
+     * @deprecated
+     */
+    protected String normalizeMimeType(String mimeType) {
+	return mimeType;
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/CommandInfo.java b/android/activation/src/main/java/javax/activation/CommandInfo.java
new file mode 100644
index 0000000..792c582
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/CommandInfo.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+/**
+ * The CommandInfo class is used by CommandMap implementations to
+ * describe the results of command requests. It provides the requestor
+ * with both the verb requested, as well as an instance of the
+ * bean. There is also a method that will return the name of the
+ * class that implements the command but <i>it is not guaranteed to
+ * return a valid value</i>. The reason for this is to allow CommandMap
+ * implmentations that subclass CommandInfo to provide special
+ * behavior. For example a CommandMap could dynamically generate
+ * JavaBeans. In this case, it might not be possible to create an
+ * object with all the correct state information solely from the class
+ * name.
+ */
+
+public class CommandInfo {
+    private String verb;
+    private String className;
+
+    /**
+     * The Constructor for CommandInfo.
+     * @param verb The command verb this CommandInfo decribes.
+     * @param className The command's fully qualified class name.
+     */
+    public CommandInfo(String verb, String className) {
+	this.verb = verb;
+	this.className = className;
+    }
+
+    /**
+     * Return the command verb.
+     *
+     * @return the command verb.
+     */
+    public String getCommandName() {
+	return verb;
+    }
+
+    /**
+     * Return the command's class name. <i>This method MAY return null in
+     * cases where a CommandMap subclassed CommandInfo for its
+     * own purposes.</i> In other words, it might not be possible to
+     * create the correct state in the command by merely knowing
+     * its class name. <b>DO NOT DEPEND ON THIS METHOD RETURNING
+     * A VALID VALUE!</b>
+     *
+     * @return The class name of the command, or <i>null</i>
+     */
+    public String getCommandClass() {
+	return className;
+    }
+
+    /**
+     * Return the instantiated JavaBean component.
+     * <p>
+     * If the current runtime environment supports
+     * {@link java.beans.Beans#instantiate Beans.instantiate},
+     * use it to instantiate the JavaBeans component.  Otherwise, use
+     * {@link java.lang.Class#forName Class.forName}.
+     * <p>
+     * The component class needs to be public.
+     * On Java SE 9 and newer, if the component class is in a named module,
+     * it needs to be in an exported package.
+     * <p>
+     * If the bean implements the <code>javax.activation.CommandObject</code>
+     * interface, call its <code>setCommandContext</code> method.
+     * <p>
+     * If the DataHandler parameter is null, then the bean is
+     * instantiated with no data. NOTE: this may be useful
+     * if for some reason the DataHandler that is passed in
+     * throws IOExceptions when this method attempts to
+     * access its InputStream. It will allow the caller to
+     * retrieve a reference to the bean if it can be
+     * instantiated.
+     * <p>
+     * If the bean does NOT implement the CommandObject interface,
+     * this method will check if it implements the
+     * java.io.Externalizable interface. If it does, the bean's
+     * readExternal method will be called if an InputStream
+     * can be acquired from the DataHandler.<p>
+     *
+     * @param dh	The DataHandler that describes the data to be
+     *			passed to the command.
+     * @param loader	The ClassLoader to be used to instantiate the bean.
+     * @return The bean
+     * @exception	IOException	for failures reading data
+     * @exception	ClassNotFoundException	if command object class can't
+     *						be found
+     * @see java.beans.Beans#instantiate
+     * @see javax.activation.CommandObject
+     */
+    public Object getCommandObject(DataHandler dh, ClassLoader loader)
+			throws IOException, ClassNotFoundException {
+	Object new_bean = null;
+
+	// try to instantiate the bean
+	new_bean = Beans.instantiate(loader, className);
+
+	// if we got one and it is a CommandObject
+	if (new_bean != null) {
+	    if (new_bean instanceof CommandObject) {
+		((CommandObject)new_bean).setCommandContext(verb, dh);
+	    } else if (new_bean instanceof Externalizable) {
+		if (dh != null) {
+		    InputStream is = dh.getInputStream();
+		    if (is != null) {
+			((Externalizable)new_bean).readExternal(
+					       new ObjectInputStream(is));
+		    }
+		}
+	    }
+	}
+
+	return new_bean;
+    }
+
+    /**
+     * Helper class to invoke Beans.instantiate reflectively or the equivalent
+     * with core reflection when module java.desktop is not readable.
+     */
+    private static final class Beans {
+        static final Method instantiateMethod;
+
+        static {
+            Method m;
+            try {
+                Class<?> c = Class.forName("java.beans.Beans");
+                m = c.getDeclaredMethod("instantiate", ClassLoader.class, String.class);
+            } catch (ClassNotFoundException e) {
+                m = null;
+            } catch (NoSuchMethodException e) {
+                m = null;
+            }
+            instantiateMethod = m;
+        }
+
+        /**
+         * Equivalent to invoking java.beans.Beans.instantiate(loader, cn)
+         */
+        static Object instantiate(ClassLoader loader, String cn)
+                throws IOException, ClassNotFoundException {
+
+            Exception exception;
+
+            if (instantiateMethod != null) {
+
+                // invoke Beans.instantiate
+                try {
+                    return instantiateMethod.invoke(null, loader, cn);
+                } catch (InvocationTargetException e) {
+                    exception = e;
+                } catch (IllegalAccessException e) {
+                    exception = e;
+                }
+
+            } else {
+
+		SecurityManager security = System.getSecurityManager();
+		if (security != null) {
+		    // if it's ok with the SecurityManager, it's ok with me.
+		    String cname = cn.replace('/', '.');
+		    if (cname.startsWith("[")) {
+			int b = cname.lastIndexOf('[') + 2;
+			if (b > 1 && b < cname.length()) {
+			    cname = cname.substring(b);
+			}
+		    }
+		    int i = cname.lastIndexOf('.');
+		    if (i != -1) {
+			security.checkPackageAccess(cname.substring(0, i));
+		    }
+		}
+
+                // Beans.instantiate specified to use SCL when loader is null
+                if (loader == null) {
+                    loader = (ClassLoader)
+		        AccessController.doPrivileged(new PrivilegedAction() {
+			    public Object run() {
+				ClassLoader cl = null;
+				try {
+				    cl = ClassLoader.getSystemClassLoader();
+				} catch (SecurityException ex) { }
+				return cl;
+			    }
+			});
+                }
+                Class<?> beanClass = Class.forName(cn, true, loader);
+                try {
+                    return beanClass.newInstance();
+                } catch (Exception ex) {
+                    throw new ClassNotFoundException(beanClass + ": " + ex, ex);
+                }
+
+            }
+            return null;
+        }
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/CommandMap.java b/android/activation/src/main/java/javax/activation/CommandMap.java
new file mode 100644
index 0000000..69f4910
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/CommandMap.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.util.Map;
+import java.util.WeakHashMap;
+
+
+/**
+ * The CommandMap class provides an interface to a registry of
+ * command objects available in the system.
+ * Developers are expected to either use the CommandMap
+ * implementation included with this package (MailcapCommandMap) or
+ * develop their own. Note that some of the methods in this class are
+ * abstract.
+ */
+public abstract class CommandMap {
+    private static CommandMap defaultCommandMap = null;
+    private static Map<ClassLoader,CommandMap> map =
+				new WeakHashMap<ClassLoader,CommandMap>();
+
+
+    /**
+     * Get the default CommandMap.
+     *
+     * <ul>
+     * <li> In cases where a CommandMap instance has been previously set
+     *      to some value (via <i>setDefaultCommandMap</i>)
+     *  return the CommandMap.
+     * <li>
+     *  In cases where no CommandMap has been set, the CommandMap
+     *       creates an instance of <code>MailcapCommandMap</code> and
+     *       set that to the default, returning its value.
+     *
+     * </ul>
+     *
+     * @return the CommandMap
+     */
+    public static synchronized CommandMap getDefaultCommandMap() {
+	if (defaultCommandMap != null)
+	    return defaultCommandMap;
+
+	// fetch per-thread-context-class-loader default
+	ClassLoader tccl = SecuritySupport.getContextClassLoader();
+	CommandMap def = map.get(tccl);
+	if (def == null) {
+	    def = new MailcapCommandMap();
+	    map.put(tccl, def);
+	}
+	return def;
+    }
+
+    /**
+     * Set the default CommandMap. Reset the CommandMap to the default by
+     * calling this method with <code>null</code>.
+     *
+     * @param commandMap The new default CommandMap.
+     * @exception SecurityException if the caller doesn't have permission
+     *					to change the default
+     */
+    public static synchronized void setDefaultCommandMap(CommandMap commandMap) {
+	SecurityManager security = System.getSecurityManager();
+	if (security != null) {
+	    try {
+		// if it's ok with the SecurityManager, it's ok with me...
+		security.checkSetFactory();
+	    } catch (SecurityException ex) {
+		// otherwise, we also allow it if this code and the
+		// factory come from the same (non-system) class loader (e.g.,
+		// the JAF classes were loaded with the applet classes).
+		ClassLoader cl = CommandMap.class.getClassLoader();
+		if (cl == null || cl.getParent() == null ||
+		    cl != commandMap.getClass().getClassLoader()) {
+		    throw ex;
+		}
+	    }
+	}
+	// remove any per-thread-context-class-loader CommandMap
+	map.remove(SecuritySupport.getContextClassLoader());
+	defaultCommandMap = commandMap;
+    }
+
+    /**
+     * Get the preferred command list from a MIME Type. The actual semantics
+     * are determined by the implementation of the CommandMap.
+     *
+     * @param mimeType	the MIME type
+     * @return the CommandInfo classes that represent the command Beans.
+     */
+    abstract public  CommandInfo[] getPreferredCommands(String mimeType);
+
+    /**
+     * Get the preferred command list from a MIME Type. The actual semantics
+     * are determined by the implementation of the CommandMap. <p>
+     *
+     * The <code>DataSource</code> provides extra information, such as
+     * the file name, that a CommandMap implementation may use to further
+     * refine the list of commands that are returned.  The implementation
+     * in this class simply calls the <code>getPreferredCommands</code>
+     * method that ignores this argument.
+     *
+     * @param mimeType	the MIME type
+     * @param ds	a DataSource for the data
+     * @return the CommandInfo classes that represent the command Beans.
+     * @since	JAF 1.1
+     */
+    public CommandInfo[] getPreferredCommands(String mimeType, DataSource ds) {
+	return getPreferredCommands(mimeType);
+    }
+
+    /**
+     * Get all the available commands for this type. This method
+     * should return all the possible commands for this MIME type.
+     *
+     * @param mimeType	the MIME type
+     * @return the CommandInfo objects representing all the commands.
+     */
+    abstract public CommandInfo[] getAllCommands(String mimeType);
+
+    /**
+     * Get all the available commands for this type. This method
+     * should return all the possible commands for this MIME type. <p>
+     *
+     * The <code>DataSource</code> provides extra information, such as
+     * the file name, that a CommandMap implementation may use to further
+     * refine the list of commands that are returned.  The implementation
+     * in this class simply calls the <code>getAllCommands</code>
+     * method that ignores this argument.
+     *
+     * @param mimeType	the MIME type
+     * @param ds	a DataSource for the data
+     * @return the CommandInfo objects representing all the commands.
+     * @since	JAF 1.1
+     */
+    public CommandInfo[] getAllCommands(String mimeType, DataSource ds) {
+	return getAllCommands(mimeType);
+    }
+
+    /**
+     * Get the default command corresponding to the MIME type.
+     *
+     * @param mimeType	the MIME type
+     * @param cmdName	the command name
+     * @return the CommandInfo corresponding to the command.
+     */
+    abstract public CommandInfo getCommand(String mimeType, String cmdName);
+
+    /**
+     * Get the default command corresponding to the MIME type. <p>
+     *
+     * The <code>DataSource</code> provides extra information, such as
+     * the file name, that a CommandMap implementation may use to further
+     * refine the command that is chosen.  The implementation
+     * in this class simply calls the <code>getCommand</code>
+     * method that ignores this argument.
+     *
+     * @param mimeType	the MIME type
+     * @param cmdName	the command name
+     * @param ds	a DataSource for the data
+     * @return the CommandInfo corresponding to the command.
+     * @since	JAF 1.1
+     */
+    public CommandInfo getCommand(String mimeType, String cmdName,
+				DataSource ds) {
+	return getCommand(mimeType, cmdName);
+    }
+
+    /**
+     * Locate a DataContentHandler that corresponds to the MIME type.
+     * The mechanism and semantics for determining this are determined
+     * by the implementation of the particular CommandMap.
+     *
+     * @param mimeType	the MIME type
+     * @return		the DataContentHandler for the MIME type
+     */
+    abstract public DataContentHandler createDataContentHandler(String
+								mimeType);
+
+    /**
+     * Locate a DataContentHandler that corresponds to the MIME type.
+     * The mechanism and semantics for determining this are determined
+     * by the implementation of the particular CommandMap. <p>
+     *
+     * The <code>DataSource</code> provides extra information, such as
+     * the file name, that a CommandMap implementation may use to further
+     * refine the choice of DataContentHandler.  The implementation
+     * in this class simply calls the <code>createDataContentHandler</code>
+     * method that ignores this argument.
+     *
+     * @param mimeType	the MIME type
+     * @param ds	a DataSource for the data
+     * @return		the DataContentHandler for the MIME type
+     * @since	JAF 1.1
+     */
+    public DataContentHandler createDataContentHandler(String mimeType,
+				DataSource ds) {
+	return createDataContentHandler(mimeType);
+    }
+
+    /**
+     * Get all the MIME types known to this command map.
+     * If the command map doesn't support this operation,
+     * null is returned.
+     *
+     * @return		array of MIME types as strings, or null if not supported
+     * @since	JAF 1.1
+     */
+    public String[] getMimeTypes() {
+	return null;
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/CommandObject.java b/android/activation/src/main/java/javax/activation/CommandObject.java
new file mode 100644
index 0000000..548c731
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/CommandObject.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.io.IOException;
+
+/**
+ * JavaBeans components that are Activation Framework aware implement
+ * this interface to find out which command verb they're being asked
+ * to perform, and to obtain the DataHandler representing the
+ * data they should operate on.  JavaBeans that don't implement
+ * this interface may be used as well.  Such commands may obtain
+ * the data using the Externalizable interface, or using an
+ * application-specific method.
+ */
+public interface CommandObject {
+
+    /**
+     * Initialize the Command with the verb it is requested to handle
+     * and the DataHandler that describes the data it will
+     * operate on. <b>NOTE:</b> it is acceptable for the caller
+     * to pass <i>null</i> as the value for <code>DataHandler</code>.
+     *
+     * @param verb The Command Verb this object refers to.
+     * @param dh The DataHandler.
+     * @exception	IOException	for failures accessing data
+     */
+    public void setCommandContext(String verb, DataHandler dh)
+						throws IOException;
+}
diff --git a/android/activation/src/main/java/javax/activation/DataContentHandler.java b/android/activation/src/main/java/javax/activation/DataContentHandler.java
new file mode 100644
index 0000000..ae78653
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/DataContentHandler.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+//import java.awt.datatransfer.DataFlavor;
+//import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import javax.activation.DataSource;
+
+/**
+ * The DataContentHandler interface is implemented by objects that can
+ * be used to extend the capabilities of the DataHandler's implementation
+ * of the Transferable interface. Through <code>DataContentHandlers</code>
+ * the framework can be extended to convert streams in to objects, and
+ * to write objects to streams. <p>
+ *
+ * Applications don't generally call the methods in DataContentHandlers
+ * directly. Instead, an application calls the equivalent methods in
+ * DataHandler. The DataHandler will attempt to find an appropriate
+ * DataContentHandler that corresponds to its MIME type using the
+ * current DataContentHandlerFactory. The DataHandler then calls
+ * through to the methods in the DataContentHandler.
+ */
+
+public interface DataContentHandler {
+    /**
+     * Returns an array of DataFlavor objects indicating the flavors the
+     * data can be provided in. The array should be ordered according to
+     * preference for providing the data (from most richly descriptive to
+     * least descriptive).
+     *
+     * @return The DataFlavors.
+     */
+    public ActivationDataFlavor[] getTransferDataFlavors();
+
+    /**
+     * Returns an object which represents the data to be transferred.
+     * The class of the object returned is defined by the representation class
+     * of the flavor.
+     *
+     * @param df The DataFlavor representing the requested type.
+     * @param ds The DataSource representing the data to be converted.
+     * @return The constructed Object.
+     * @exception IOException	if the data can't be accessed
+     */
+    public Object getTransferData(ActivationDataFlavor df, DataSource ds)
+				throws /*UnsupportedFlavorException,*/ IOException;
+
+    /**
+     * Return an object representing the data in its most preferred form.
+     * Generally this will be the form described by the first DataFlavor
+     * returned by the <code>getTransferDataFlavors</code> method.
+     *
+     * @param ds The DataSource representing the data to be converted.
+     * @return The constructed Object.
+     * @exception IOException	if the data can't be accessed
+     */
+    public Object getContent(DataSource ds) throws IOException;
+
+    /**
+     * Convert the object to a byte stream of the specified MIME type
+     * and write it to the output stream.
+     *
+     * @param obj	The object to be converted.
+     * @param mimeType	The requested MIME type of the resulting byte stream.
+     * @param os	The output stream into which to write the converted
+     *			byte stream.
+     * @exception IOException	errors writing to the stream
+     */
+    public void writeTo(Object obj, String mimeType, OutputStream os)
+	                                               throws IOException;
+}
diff --git a/android/activation/src/main/java/javax/activation/DataContentHandlerFactory.java b/android/activation/src/main/java/javax/activation/DataContentHandlerFactory.java
new file mode 100644
index 0000000..0cd0f5c
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/DataContentHandlerFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+/**
+ * This interface defines a factory for <code>DataContentHandlers</code>. An
+ * implementation of this interface should map a MIME type into an
+ * instance of DataContentHandler. The design pattern for classes implementing
+ * this interface is the same as for the ContentHandler mechanism used in
+ * <code>java.net.URL</code>.
+ */
+
+public interface DataContentHandlerFactory {
+
+    /**
+     * Creates a new DataContentHandler object for the MIME type.
+     *
+     * @param mimeType the MIME type to create the DataContentHandler for.
+     * @return The new <code>DataContentHandler</code>, or <i>null</i>
+     * if none are found.
+     */
+    public DataContentHandler createDataContentHandler(String mimeType);
+}
diff --git a/android/activation/src/main/java/javax/activation/DataHandler.java b/android/activation/src/main/java/javax/activation/DataHandler.java
new file mode 100644
index 0000000..40a842d
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/DataHandler.java
@@ -0,0 +1,881 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.OutputStreamWriter;
+import java.net.URL;
+//import java.awt.datatransfer.Transferable;
+//import java.awt.datatransfer.DataFlavor;
+//import java.awt.datatransfer.UnsupportedFlavorException;
+
+/**
+ * The DataHandler class provides a consistent interface to data
+ * available in many different sources and formats.
+ * It manages simple stream to string conversions and related operations
+ * using DataContentHandlers.
+ * It provides access to commands that can operate on the data.
+ * The commands are found using a CommandMap. <p>
+ *
+ * <b>DataHandler and the Transferable Interface</b><p>
+ * DataHandler implements the Transferable interface so that data can
+ * be used in AWT data transfer operations, such as cut and paste and
+ * drag and drop. The implementation of the Transferable interface
+ * relies on the availability of an installed DataContentHandler
+ * object corresponding to the MIME type of the data represented in
+ * the specific instance of the DataHandler.<p>
+ *
+ * <b>DataHandler and CommandMaps</b><p>
+ * The DataHandler keeps track of the current CommandMap that it uses to
+ * service requests for commands (<code>getCommand</code>,
+ * <code>getAllCommands</code>, <code>getPreferredCommands</code>).
+ * Each instance of a DataHandler may have a CommandMap associated with
+ * it using the <code>setCommandMap</code> method.  If a CommandMap was
+ * not set, DataHandler calls the <code>getDefaultCommandMap</code>
+ * method in CommandMap and uses the value it returns. See
+ * <i>CommandMap</i> for more information. <p>
+ *
+ * <b>DataHandler and URLs</b><p>
+ * The current DataHandler implementation creates a private
+ * instance of URLDataSource when it is constructed with a URL.
+ *
+ * @see javax.activation.CommandMap
+ * @see javax.activation.DataContentHandler
+ * @see javax.activation.DataSource
+ * @see javax.activation.URLDataSource
+ */
+
+public class DataHandler /*implements Transferable*/ {
+
+    // Use the datasource to indicate whether we were started via the
+    // DataSource constructor or the object constructor.
+    private DataSource dataSource = null;
+    private DataSource objDataSource = null;
+
+    // The Object and mimetype from the constructor (if passed in).
+    // object remains null if it was instantiated with a
+    // DataSource.
+    private Object object = null;
+    private String objectMimeType = null;
+
+    // Keep track of the CommandMap
+    private CommandMap currentCommandMap = null;
+
+    // our transfer flavors
+    private static final ActivationDataFlavor emptyFlavors[] = new ActivationDataFlavor[0];
+    private ActivationDataFlavor transferFlavors[] = emptyFlavors;
+
+    // our DataContentHandler
+    private DataContentHandler dataContentHandler = null;
+    private DataContentHandler factoryDCH = null;
+
+    // our DataContentHandlerFactory
+    private static DataContentHandlerFactory factory = null;
+    private DataContentHandlerFactory oldFactory = null;
+    // the short representation of the ContentType (sans params)
+    private String shortType = null;
+
+    /**
+     * Create a <code>DataHandler</code> instance referencing the
+     * specified DataSource.  The data exists in a byte stream form.
+     * The DataSource will provide an InputStream to access the data.
+     *
+     * @param ds	the DataSource
+     */
+    public DataHandler(DataSource ds) {
+	// save a reference to the incoming DS
+	dataSource = ds;
+	oldFactory = factory; // keep track of the factory
+    }
+
+    /**
+     * Create a <code>DataHandler</code> instance representing an object
+     * of this MIME type.  This constructor is
+     * used when the application already has an in-memory representation
+     * of the data in the form of a Java Object.
+     *
+     * @param obj	the Java Object
+     * @param mimeType	the MIME type of the object
+     */
+    public DataHandler(Object obj, String mimeType) {
+	object = obj;
+	objectMimeType = mimeType;
+	oldFactory = factory; // keep track of the factory
+    }
+
+    /**
+     * Create a <code>DataHandler</code> instance referencing a URL.
+     * The DataHandler internally creates a <code>URLDataSource</code>
+     * instance to represent the URL.
+     *
+     * @param url	a URL object
+     */
+    public DataHandler(URL url) {
+	dataSource = new URLDataSource(url);
+	oldFactory = factory; // keep track of the factory
+    }
+
+    /**
+     * Return the CommandMap for this instance of DataHandler.
+     */
+    private synchronized CommandMap getCommandMap() {
+	if (currentCommandMap != null)
+	    return currentCommandMap;
+	else
+	    return CommandMap.getDefaultCommandMap();
+    }
+
+    /**
+     * Return the DataSource associated with this instance
+     * of DataHandler.
+     * <p>
+     * For DataHandlers that have been instantiated with a DataSource,
+     * this method returns the DataSource that was used to create the
+     * DataHandler object. In other cases the DataHandler
+     * constructs a DataSource from the data used to construct
+     * the DataHandler. DataSources created for DataHandlers <b>not</b>
+     * instantiated with a DataSource are cached for performance
+     * reasons.
+     *
+     * @return	a valid DataSource object for this DataHandler
+     */
+    public DataSource getDataSource() {
+	if (dataSource == null) {
+	    // create one on the fly
+	    if (objDataSource == null)
+		objDataSource = new DataHandlerDataSource(this);
+	    return objDataSource;
+	}
+	return dataSource;
+    }
+
+    /**
+     * Return the name of the data object. If this DataHandler
+     * was created with a DataSource, this method calls through
+     * to the <code>DataSource.getName</code> method, otherwise it
+     * returns <i>null</i>.
+     *
+     * @return	the name of the object
+     */
+    public String getName() {
+	if (dataSource != null)
+	    return dataSource.getName();
+	else
+	    return null;
+    }
+
+    /**
+     * Return the MIME type of this object as retrieved from
+     * the source object. Note that this is the <i>full</i>
+     * type with parameters.
+     *
+     * @return	the MIME type
+     */
+    public String getContentType() {
+	if (dataSource != null) // data source case
+	    return dataSource.getContentType();
+	else
+	    return objectMimeType; // obj/type case
+    }
+
+    /**
+     * Get the InputStream for this object. <p>
+     *
+     * For DataHandlers instantiated with a DataSource, the DataHandler
+     * calls the <code>DataSource.getInputStream</code> method and
+     * returns the result to the caller.
+     * <p>
+     * For DataHandlers instantiated with an Object, the DataHandler
+     * first attempts to find a DataContentHandler for the Object. If
+     * the DataHandler can not find a DataContentHandler for this MIME
+     * type, it throws an UnsupportedDataTypeException.  If it is
+     * successful, it creates a pipe and a thread.  The thread uses the
+     * DataContentHandler's <code>writeTo</code> method to write the
+     * stream data into one end of the pipe.  The other end of the pipe
+     * is returned to the caller.  Because a thread is created to copy
+     * the data, IOExceptions that may occur during the copy can not be
+     * propagated back to the caller. The result is an empty stream.<p>
+     *
+     * @return	the InputStream representing this data
+     * @exception IOException	if an I/O error occurs
+     *
+     * @see javax.activation.DataContentHandler#writeTo
+     * @see javax.activation.UnsupportedDataTypeException
+     */
+    public InputStream getInputStream() throws IOException {
+	InputStream ins = null;
+
+	if (dataSource != null) {
+	    ins = dataSource.getInputStream();
+	} else {
+	    DataContentHandler dch = getDataContentHandler();
+	    // we won't even try if we can't get a dch
+	    if (dch == null)
+		throw new UnsupportedDataTypeException(
+				"no DCH for MIME type " + getBaseType());
+
+	    if (dch instanceof ObjectDataContentHandler) {
+		if (((ObjectDataContentHandler)dch).getDCH() == null)
+		    throw new UnsupportedDataTypeException(
+				"no object DCH for MIME type " + getBaseType());
+	    }
+	    // there is none but the default^^^^^^^^^^^^^^^^
+	    final DataContentHandler fdch = dch;
+
+	    // from bill s.
+	    // ce n'est pas une pipe!
+	    //
+	    // NOTE: This block of code needs to throw exceptions, but
+	    // can't because it is in another thread!!! ARG!
+	    //
+	    final PipedOutputStream pos = new PipedOutputStream();
+	    PipedInputStream pin = new PipedInputStream(pos);
+	    new Thread(
+		       new Runnable() {
+		public void run() {
+		    try {
+			fdch.writeTo(object, objectMimeType, pos);
+		    } catch (IOException e) {
+
+		    } finally {
+			try {
+			    pos.close();
+			} catch (IOException ie) { }
+		    }
+		}
+	    },
+		      "DataHandler.getInputStream").start();
+	    ins = pin;
+	}
+
+	return ins;
+    }
+
+    /**
+     * Write the data to an <code>OutputStream</code>.<p>
+     *
+     * If the DataHandler was created with a DataSource, writeTo
+     * retrieves the InputStream and copies the bytes from the
+     * InputStream to the OutputStream passed in.
+     * <p>
+     * If the DataHandler was created with an object, writeTo
+     * retrieves the DataContentHandler for the object's type.
+     * If the DataContentHandler was found, it calls the
+     * <code>writeTo</code> method on the <code>DataContentHandler</code>.
+     *
+     * @param os	the OutputStream to write to
+     * @exception IOException	if an I/O error occurs
+     */
+    public void writeTo(OutputStream os) throws IOException {
+	// for the DataSource case
+	if (dataSource != null) {
+	    InputStream is = null;
+	    byte data[] = new byte[8*1024];
+	    int bytes_read;
+
+	    is = dataSource.getInputStream();
+
+	    try {
+		while ((bytes_read = is.read(data)) > 0) {
+		    os.write(data, 0, bytes_read);
+		}
+	    } finally {
+		is.close();
+		is = null;
+	    }
+	} else { // for the Object case
+	    DataContentHandler dch = getDataContentHandler();
+	    dch.writeTo(object, objectMimeType, os);
+	}
+    }
+
+    /**
+     * Get an OutputStream for this DataHandler to allow overwriting
+     * the underlying data.
+     * If the DataHandler was created with a DataSource, the
+     * DataSource's <code>getOutputStream</code> method is called.
+     * Otherwise, <code>null</code> is returned.
+     *
+     * @return the OutputStream
+     *
+     * @see javax.activation.DataSource#getOutputStream
+     * @see javax.activation.URLDataSource
+     */
+    public OutputStream getOutputStream() throws IOException {
+	if (dataSource != null)
+	    return dataSource.getOutputStream();
+	else
+	    return null;
+    }
+
+    /**
+     * Return the DataFlavors in which this data is available. <p>
+     *
+     * Returns an array of DataFlavor objects indicating the flavors
+     * the data can be provided in. The array is usually ordered
+     * according to preference for providing the data, from most
+     * richly descriptive to least richly descriptive.<p>
+     *
+     * The DataHandler attempts to find a DataContentHandler that
+     * corresponds to the MIME type of the data. If one is located,
+     * the DataHandler calls the DataContentHandler's
+     * <code>getTransferDataFlavors</code> method. <p>
+     *
+     * If a DataContentHandler can <i>not</i> be located, and if the
+     * DataHandler was created with a DataSource (or URL), one
+     * DataFlavor is returned that represents this object's MIME type
+     * and the <code>java.io.InputStream</code> class.  If the
+     * DataHandler was created with an object and a MIME type,
+     * getTransferDataFlavors returns one DataFlavor that represents
+     * this object's MIME type and the object's class.
+     *
+     * @return	an array of data flavors in which this data can be transferred
+     * @see javax.activation.DataContentHandler#getTransferDataFlavors
+     */
+    public synchronized ActivationDataFlavor[] getTransferDataFlavors() {
+	if (factory != oldFactory) // if the factory has changed, clear cache
+	    transferFlavors = emptyFlavors;
+
+	// if it's not set, set it...
+	if (transferFlavors == emptyFlavors)
+	    transferFlavors = getDataContentHandler().getTransferDataFlavors();
+	if (transferFlavors == emptyFlavors)
+	    return transferFlavors;	// no need to clone an empty array
+	else
+	    return transferFlavors.clone();
+    }
+
+    /**
+     * Returns whether the specified data flavor is supported
+     * for this object.<p>
+     *
+     * This method iterates through the DataFlavors returned from
+     * <code>getTransferDataFlavors</code>, comparing each with
+     * the specified flavor.
+     *
+     * @param flavor	the requested flavor for the data
+     * @return		true if the data flavor is supported
+     * @see javax.activation.DataHandler#getTransferDataFlavors
+     */
+    public boolean isDataFlavorSupported(ActivationDataFlavor flavor) {
+	ActivationDataFlavor[] lFlavors = getTransferDataFlavors();
+
+	for (int i = 0; i < lFlavors.length; i++) {
+	    if (lFlavors[i].equals(flavor))
+		return true;
+	}
+	return false;
+    }
+
+    /**
+     * Returns an object that represents the data to be
+     * transferred. The class of the object returned is defined by the
+     * representation class of the data flavor.<p>
+     *
+     * <b>For DataHandler's created with DataSources or URLs:</b><p>
+     *
+     * The DataHandler attempts to locate a DataContentHandler
+     * for this MIME type. If one is found, the passed in DataFlavor
+     * and the type of the data are passed to its <code>getTransferData</code>
+     * method. If the DataHandler fails to locate a DataContentHandler
+     * and the flavor specifies this object's MIME type and the
+     * <code>java.io.InputStream</code> class, this object's InputStream
+     * is returned.
+     * Otherwise it throws an UnsupportedFlavorException. <p>
+     *
+     * <b>For DataHandler's created with Objects:</b><p>
+     *
+     * The DataHandler attempts to locate a DataContentHandler
+     * for this MIME type. If one is found, the passed in DataFlavor
+     * and the type of the data are passed to its getTransferData
+     * method. If the DataHandler fails to locate a DataContentHandler
+     * and the flavor specifies this object's MIME type and its class,
+     * this DataHandler's referenced object is returned.  
+     * Otherwise it throws an UnsupportedFlavorException.
+     *
+     * @param flavor	the requested flavor for the data
+     * @return		the object
+     * @exception IOException	if an I/O error occurs
+     * @see javax.activation.ActivationDataFlavor
+     */
+    public Object getTransferData(ActivationDataFlavor flavor)
+				throws /*UnsupportedFlavorException,*/ IOException {
+	return getDataContentHandler().getTransferData(flavor, dataSource);
+    }
+
+    /**
+     * Set the CommandMap for use by this DataHandler.
+     * Setting it to <code>null</code> causes the CommandMap to revert
+     * to the CommandMap returned by the
+     * <code>CommandMap.getDefaultCommandMap</code> method.
+     * Changing the CommandMap, or setting it to <code>null</code>,
+     * clears out any data cached from the previous CommandMap.
+     *
+     * @param commandMap	the CommandMap to use in this DataHandler
+     *
+     * @see javax.activation.CommandMap#setDefaultCommandMap
+     */
+    public synchronized void setCommandMap(CommandMap commandMap) {
+	if (commandMap != currentCommandMap || commandMap == null) {
+	    // clear cached values...
+	    transferFlavors = emptyFlavors;
+	    dataContentHandler = null;
+
+	    currentCommandMap = commandMap;
+	}
+    }
+
+    /**
+     * Return the <i>preferred</i> commands for this type of data.
+     * This method calls the <code>getPreferredCommands</code> method
+     * in the CommandMap associated with this instance of DataHandler.
+     * This method returns an array that represents a subset of
+     * available commands. In cases where multiple commands for the
+     * MIME type represented by this DataHandler are present, the
+     * installed CommandMap chooses the appropriate commands.
+     *
+     * @return	the CommandInfo objects representing the preferred commands
+     *
+     * @see javax.activation.CommandMap#getPreferredCommands
+     */
+    public CommandInfo[] getPreferredCommands() {
+	if (dataSource != null)
+	    return getCommandMap().getPreferredCommands(getBaseType(),
+							dataSource);
+	else
+	    return getCommandMap().getPreferredCommands(getBaseType());
+    }
+
+    /**
+     * Return all the commands for this type of data.
+     * This method returns an array containing all commands
+     * for the type of data represented by this DataHandler. The
+     * MIME type for the underlying data represented by this DataHandler
+     * is used to call through to the <code>getAllCommands</code> method
+     * of the CommandMap associated with this DataHandler.
+     *
+     * @return	the CommandInfo objects representing all the commands
+     *
+     * @see javax.activation.CommandMap#getAllCommands
+     */
+    public CommandInfo[] getAllCommands() {
+	if (dataSource != null)
+	    return getCommandMap().getAllCommands(getBaseType(), dataSource);
+	else
+	    return getCommandMap().getAllCommands(getBaseType());
+    }
+
+    /**
+     * Get the command <i>cmdName</i>. Use the search semantics as
+     * defined by the CommandMap installed in this DataHandler. The
+     * MIME type for the underlying data represented by this DataHandler
+     * is used to call through to the <code>getCommand</code> method
+     * of the CommandMap associated with this DataHandler.
+     *
+     * @param cmdName	the command name
+     * @return	the CommandInfo corresponding to the command
+     *
+     * @see javax.activation.CommandMap#getCommand
+     */
+    public CommandInfo getCommand(String cmdName) {
+	if (dataSource != null)
+	    return getCommandMap().getCommand(getBaseType(), cmdName,
+								dataSource);
+	else
+	    return getCommandMap().getCommand(getBaseType(), cmdName);
+    }
+
+    /**
+     * Return the data in its preferred Object form. <p>
+     *
+     * If the DataHandler was instantiated with an object, return
+     * the object. <p>
+     *
+     * If the DataHandler was instantiated with a DataSource,
+     * this method uses a DataContentHandler to return the content
+     * object for the data represented by this DataHandler. If no
+     * <code>DataContentHandler</code> can be found for the
+     * the type of this data, the DataHandler returns an
+     * InputStream for the data.
+     *
+     * @return the content.
+     * @exception IOException if an IOException occurs during
+     *                              this operation.
+     */
+    public Object getContent() throws IOException {
+	if (object != null)
+	    return object;
+	else
+	    return getDataContentHandler().getContent(getDataSource());
+    }
+
+    /**
+     * A convenience method that takes a CommandInfo object
+     * and instantiates the corresponding command, usually
+     * a JavaBean component.
+     * <p>
+     * This method calls the CommandInfo's <code>getCommandObject</code>
+     * method with the <code>ClassLoader</code> used to load
+     * the <code>javax.activation.DataHandler</code> class itself.
+     *
+     * @param cmdinfo	the CommandInfo corresponding to a command
+     * @return	the instantiated command object
+     */
+    public Object getBean(CommandInfo cmdinfo) {
+	Object bean = null;
+
+	try {
+	    // make the bean
+	    ClassLoader cld = null;
+	    // First try the "application's" class loader.
+	    cld = SecuritySupport.getContextClassLoader();
+	    if (cld == null)
+		cld = this.getClass().getClassLoader();
+	    bean = cmdinfo.getCommandObject(this, cld);
+	} catch (IOException e) {
+	} catch (ClassNotFoundException e) { }
+
+	return bean;
+    }
+
+    /**
+     * Get the DataContentHandler for this DataHandler: <p>
+     *
+     * If a DataContentHandlerFactory is set, use it.
+     * Otherwise look for an object to serve DCH in the
+     * following order: <p>
+     *
+     * 1) if a factory is set, use it <p>
+     * 2) if a CommandMap is set, use it <p>
+     * 3) use the default CommandMap <p>
+     *
+     * In any case, wrap the real DataContentHandler with one of our own
+     * to handle any missing cases, fill in defaults, and to ensure that
+     * we always have a non-null DataContentHandler.
+     *
+     * @return	the requested DataContentHandler
+     */
+    private synchronized DataContentHandler getDataContentHandler() {
+
+	// make sure the factory didn't change
+	if (factory != oldFactory) {
+	    oldFactory = factory;
+	    factoryDCH = null;
+	    dataContentHandler = null;
+	    transferFlavors = emptyFlavors;
+	}
+
+ 	if (dataContentHandler != null)
+ 	    return dataContentHandler;
+
+	String simpleMT = getBaseType();
+
+	if (factoryDCH == null && factory != null)
+	    factoryDCH = factory.createDataContentHandler(simpleMT);
+
+ 	if (factoryDCH != null)
+ 	    dataContentHandler = factoryDCH;
+
+	if (dataContentHandler == null) {
+	    if (dataSource != null)
+		dataContentHandler = getCommandMap().
+				createDataContentHandler(simpleMT, dataSource);
+	    else
+		dataContentHandler = getCommandMap().
+				createDataContentHandler(simpleMT);
+	}
+
+	// getDataContentHandler always uses these 'wrapper' handlers
+	// to make sure it returns SOMETHING meaningful...
+	if (dataSource != null)
+	    dataContentHandler = new DataSourceDataContentHandler(
+						      dataContentHandler,
+						      dataSource);
+	else
+	    dataContentHandler = new ObjectDataContentHandler(
+						      dataContentHandler,
+						      object,
+						      objectMimeType);
+	return dataContentHandler;
+    }
+
+    /**
+     * Use the MimeType class to extract the MIME type/subtype,
+     * ignoring the parameters.  The type is cached.
+     */
+    private synchronized String getBaseType() {
+	if (shortType == null) {
+	    String ct = getContentType();
+	    try {
+		MimeType mt = new MimeType(ct);
+		shortType = mt.getBaseType();
+	    } catch (MimeTypeParseException e) {
+		shortType = ct;
+	    }
+	}
+	return shortType;
+    }
+
+    /**
+     * Sets the DataContentHandlerFactory.  The DataContentHandlerFactory
+     * is called first to find DataContentHandlers.
+     * The DataContentHandlerFactory can only be set once.
+     * <p>
+     * If the DataContentHandlerFactory has already been set,
+     * this method throws an Error.
+     *
+     * @param newFactory	the DataContentHandlerFactory
+     * @exception Error	if the factory has already been defined.
+     *
+     * @see javax.activation.DataContentHandlerFactory
+     */
+    public static synchronized void setDataContentHandlerFactory(
+					 DataContentHandlerFactory newFactory) {
+	if (factory != null)
+	    throw new Error("DataContentHandlerFactory already defined");
+
+	SecurityManager security = System.getSecurityManager();
+	if (security != null) {
+	    try {
+		// if it's ok with the SecurityManager, it's ok with me...
+		security.checkSetFactory();
+	    } catch (SecurityException ex) {
+		// otherwise, we also allow it if this code and the
+		// factory come from the same class loader (e.g.,
+		// the JAF classes were loaded with the applet classes).
+		if (DataHandler.class.getClassLoader() !=
+			newFactory.getClass().getClassLoader())
+		    throw ex;
+	    }
+	}
+	factory = newFactory;
+    }
+}
+
+/**
+ * The DataHanderDataSource class implements the
+ * DataSource interface when the DataHandler is constructed
+ * with an Object and a mimeType string.
+ */
+class DataHandlerDataSource implements DataSource {
+    DataHandler dataHandler = null;
+
+    /**
+     * The constructor.
+     */
+    public DataHandlerDataSource(DataHandler dh) {
+	this.dataHandler = dh;
+    }
+
+    /**
+     * Returns an <code>InputStream</code> representing this object.
+     * @return	the <code>InputStream</code>
+     */
+    public InputStream getInputStream() throws IOException {
+	return dataHandler.getInputStream();
+    }
+
+    /**
+     * Returns the <code>OutputStream</code> for this object.
+     * @return	the <code>OutputStream</code>
+     */
+    public OutputStream getOutputStream() throws IOException {
+	return dataHandler.getOutputStream();
+    }
+
+    /**
+     * Returns the MIME type of the data represented by this object.
+     * @return	the MIME type
+     */
+    public String getContentType() {
+	return dataHandler.getContentType();
+    }
+
+    /**
+     * Returns the name of this object.
+     * @return	the name of this object
+     */
+    public String getName() {
+	return dataHandler.getName(); // what else would it be?
+    }
+}
+
+/*
+ * DataSourceDataContentHandler
+ *
+ * This is a <i>private</i> DataContentHandler that wraps the real
+ * DataContentHandler in the case where the DataHandler was instantiated
+ * with a DataSource.
+ */
+class DataSourceDataContentHandler implements DataContentHandler {
+    private DataSource ds = null;
+    private ActivationDataFlavor transferFlavors[] = null;
+    private DataContentHandler dch = null;
+
+    /**
+     * The constructor.
+     */
+    public DataSourceDataContentHandler(DataContentHandler dch, DataSource ds) {
+	this.ds = ds;
+	this.dch = dch;
+    }
+
+    /**
+     * Return the DataFlavors for this <code>DataContentHandler</code>.
+     * @return	the DataFlavors
+     */
+    public ActivationDataFlavor[] getTransferDataFlavors() {
+
+	if (transferFlavors == null) {
+	    if (dch != null) { // is there a dch?
+		transferFlavors = dch.getTransferDataFlavors();
+	    } else {
+		transferFlavors = new ActivationDataFlavor[1];
+		transferFlavors[0] =
+		    new ActivationDataFlavor(ds.getContentType(),
+					     ds.getContentType());
+	    }
+	}
+	return transferFlavors;
+    }
+
+    /**
+     * Return the Transfer Data of type DataFlavor from InputStream.
+     * @param df	the DataFlavor
+     * @param ds	the DataSource
+     * @return		the constructed Object
+     */
+    public Object getTransferData(ActivationDataFlavor df, DataSource ds) throws
+				/*UnsupportedFlavorException,*/ IOException {
+
+	if (dch != null)
+	    return dch.getTransferData(df, ds);
+	else if (df.equals(getTransferDataFlavors()[0])) // only have one now
+	    return ds.getInputStream();
+	else
+	    //throw new UnsupportedFlavorException(df);
+	    throw new IOException("Unsupported DataFlavor: " + df);
+    }
+
+    public Object getContent(DataSource ds) throws IOException {
+
+	if (dch != null)
+	    return dch.getContent(ds);
+	else
+	    return ds.getInputStream();
+    }
+
+    /**
+     * Write the object to the output stream.
+     */
+    public void writeTo(Object obj, String mimeType, OutputStream os)
+						throws IOException {
+	if (dch != null)
+	    dch.writeTo(obj, mimeType, os);
+	else
+	    throw new UnsupportedDataTypeException(
+			"no DCH for content type " + ds.getContentType());
+    }
+}
+
+/*
+ * ObjectDataContentHandler
+ *
+ * This is a <i>private</i> DataContentHandler that wraps the real
+ * DataContentHandler in the case where the DataHandler was instantiated
+ * with an object.
+ */
+class ObjectDataContentHandler implements DataContentHandler {
+    private ActivationDataFlavor transferFlavors[] = null;
+    private Object obj;
+    private String mimeType;
+    private DataContentHandler dch = null;
+
+    /**
+     * The constructor.
+     */
+    public ObjectDataContentHandler(DataContentHandler dch,
+				    Object obj, String mimeType) {
+	this.obj = obj;
+	this.mimeType = mimeType;
+	this.dch = dch;
+    }
+
+    /**
+     * Return the DataContentHandler for this object.
+     * Used only by the DataHandler class.
+     */
+    public DataContentHandler getDCH() {
+	return dch;
+    }
+
+    /**
+     * Return the DataFlavors for this <code>DataContentHandler</code>.
+     * @return	the DataFlavors
+     */
+    public synchronized ActivationDataFlavor[] getTransferDataFlavors() {
+	if (transferFlavors == null) {
+	    if (dch != null) {
+		transferFlavors = dch.getTransferDataFlavors();
+	    } else {
+		transferFlavors = new ActivationDataFlavor[1];
+		transferFlavors[0] = new ActivationDataFlavor(obj.getClass(),
+					     mimeType, mimeType);
+	    }
+	}
+	return transferFlavors;
+    }
+
+    /**
+     * Return the Transfer Data of type DataFlavor from InputStream.
+     * @param df	the DataFlavor
+     * @param ds	the DataSource
+     * @return		the constructed Object
+     */
+    public Object getTransferData(ActivationDataFlavor df, DataSource ds)
+				throws /*UnsupportedFlavorException,*/ IOException {
+
+	if (dch != null)
+	    return dch.getTransferData(df, ds);
+	else if (df.equals(getTransferDataFlavors()[0])) // only have one now
+	    return obj;
+	else
+	    //throw new UnsupportedFlavorException(df);
+	    throw new IOException("Unsupported DataFlavor: " + df);
+
+    }
+
+    public Object getContent(DataSource ds) {
+	return obj;
+    }
+
+    /**
+     * Write the object to the output stream.
+     */
+    public void writeTo(Object obj, String mimeType, OutputStream os)
+						throws IOException {
+	if (dch != null)
+	    dch.writeTo(obj, mimeType, os);
+	else if (obj instanceof byte[])
+	    os.write((byte[])obj);
+	else if (obj instanceof String) {
+	    OutputStreamWriter osw = new OutputStreamWriter(os);
+	    osw.write((String)obj);
+	    osw.flush();
+	} else
+	    throw new UnsupportedDataTypeException(
+				"no object DCH for MIME type " + this.mimeType);
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/DataSource.java b/android/activation/src/main/java/javax/activation/DataSource.java
new file mode 100644
index 0000000..6f9bdb5
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/DataSource.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * The DataSource interface provides the JavaBeans Activation Framework
+ * with an abstraction of an arbitrary collection of data.  It
+ * provides a type for that data as well as access
+ * to it in the form of <code>InputStreams</code> and
+ * <code>OutputStreams</code> where appropriate.
+ */
+
+public interface DataSource {
+
+    /**
+     * This method returns an <code>InputStream</code> representing
+     * the data and throws the appropriate exception if it can
+     * not do so.  Note that a new <code>InputStream</code> object must be
+     * returned each time this method is called, and the stream must be
+     * positioned at the beginning of the data.
+     *
+     * @return an InputStream
+     * @exception	IOException	for failures creating the InputStream
+     */
+    public InputStream getInputStream() throws IOException;
+
+    /**
+     * This method returns an <code>OutputStream</code> where the
+     * data can be written and throws the appropriate exception if it can
+     * not do so.  Note that a new <code>OutputStream</code> object must
+     * be returned each time this method is called, and the stream must
+     * be positioned at the location the data is to be written.
+     *
+     * @return an OutputStream
+     * @exception	IOException	for failures creating the OutputStream
+     */
+    public OutputStream getOutputStream() throws IOException;
+
+    /**
+     * This method returns the MIME type of the data in the form of a
+     * string. It should always return a valid type. It is suggested
+     * that getContentType return "application/octet-stream" if the
+     * DataSource implementation can not determine the data type.
+     *
+     * @return the MIME Type
+     */
+    public String getContentType();
+
+    /**
+     * Return the <i>name</i> of this object where the name of the object
+     * is dependant on the nature of the underlying objects. DataSources
+     * encapsulating files may choose to return the filename of the object.
+     * (Typically this would be the last component of the filename, not an
+     * entire pathname.)
+     *
+     * @return the name of the object.
+     */
+    public String getName();
+}
diff --git a/android/activation/src/main/java/javax/activation/FileDataSource.java b/android/activation/src/main/java/javax/activation/FileDataSource.java
new file mode 100644
index 0000000..3048918
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/FileDataSource.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import com.sun.activation.registries.MimeTypeFile;
+
+/**
+ * The FileDataSource class implements a simple DataSource object
+ * that encapsulates a file. It provides data typing services via
+ * a FileTypeMap object. <p>
+ *
+ * <b>FileDataSource Typing Semantics</b><p>
+ *
+ * The FileDataSource class delegates data typing of files
+ * to an object subclassed from the FileTypeMap class.
+ * The <code>setFileTypeMap</code> method can be used to explicitly
+ * set the FileTypeMap for an instance of FileDataSource. If no
+ * FileTypeMap is set, the FileDataSource will call the FileTypeMap's
+ * getDefaultFileTypeMap method to get the System's default FileTypeMap.
+ *
+ * @see javax.activation.DataSource
+ * @see javax.activation.FileTypeMap
+ * @see javax.activation.MimetypesFileTypeMap
+ */
+public class FileDataSource implements DataSource {
+
+    // keep track of original 'ref' passed in, non-null
+    // one indicated which was passed in:
+    private File _file = null;
+    private FileTypeMap typeMap = null;
+
+    /**
+     * Creates a FileDataSource from a File object. <i>Note:
+     * The file will not actually be opened until a method is
+     * called that requires the file to be opened.</i>
+     *
+     * @param file the file
+     */
+    public FileDataSource(File file) {
+	_file = file;	// save the file Object...
+    }
+
+    /**
+     * Creates a FileDataSource from
+     * the specified path name. <i>Note:
+     * The file will not actually be opened until a method is
+     * called that requires the file to be opened.</i>
+     *
+     * @param name the system-dependent file name.
+     */
+    public FileDataSource(String name) {
+	this(new File(name));	// use the file constructor
+    }
+
+    /**
+     * This method will return an InputStream representing the
+     * the data and will throw an IOException if it can
+     * not do so. This method will return a new
+     * instance of InputStream with each invocation.
+     *
+     * @return an InputStream
+     */
+    public InputStream getInputStream() throws IOException {
+	return new FileInputStream(_file);
+    }
+
+    /**
+     * This method will return an OutputStream representing the
+     * the data and will throw an IOException if it can
+     * not do so. This method will return a new instance of
+     * OutputStream with each invocation.
+     *
+     * @return an OutputStream
+     */
+    public OutputStream getOutputStream() throws IOException {
+	return new FileOutputStream(_file);
+    }
+
+    /**
+     * This method returns the MIME type of the data in the form of a
+     * string. This method uses the currently installed FileTypeMap. If
+     * there is no FileTypeMap explictly set, the FileDataSource will
+     * call the <code>getDefaultFileTypeMap</code> method on
+     * FileTypeMap to acquire a default FileTypeMap. <i>Note: By
+     * default, the FileTypeMap used will be a MimetypesFileTypeMap.</i>
+     *
+     * @return the MIME Type
+     * @see javax.activation.FileTypeMap#getDefaultFileTypeMap
+     */
+    public String getContentType() {
+	// check to see if the type map is null?
+	if (typeMap == null)
+	    return FileTypeMap.getDefaultFileTypeMap().getContentType(_file);
+	else
+	    return typeMap.getContentType(_file);
+    }
+
+    /**
+     * Return the <i>name</i> of this object. The FileDataSource
+     * will return the file name of the object.
+     *
+     * @return the name of the object.
+     * @see javax.activation.DataSource
+     */
+    public String getName() {
+	return _file.getName();
+    }
+
+    /**
+     * Return the File object that corresponds to this FileDataSource.
+     * @return the File object for the file represented by this object.
+     */
+    public File getFile() {
+	return _file;
+    }
+
+    /**
+     * Set the FileTypeMap to use with this FileDataSource
+     *
+     * @param map The FileTypeMap for this object.
+     */
+    public void setFileTypeMap(FileTypeMap map) {
+	typeMap = map;
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/FileTypeMap.java b/android/activation/src/main/java/javax/activation/FileTypeMap.java
new file mode 100644
index 0000000..683f998
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/FileTypeMap.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.io.File;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * The FileTypeMap is an abstract class that provides a data typing
+ * interface for files. Implementations of this class will
+ * implement the getContentType methods which will derive a content
+ * type from a file name or a File object. FileTypeMaps could use any
+ * scheme to determine the data type, from examining the file extension
+ * of a file (like the MimetypesFileTypeMap) to opening the file and
+ * trying to derive its type from the contents of the file. The
+ * FileDataSource class uses the default FileTypeMap (a MimetypesFileTypeMap
+ * unless changed) to determine the content type of files.
+ *
+ * @see javax.activation.FileTypeMap
+ * @see javax.activation.FileDataSource
+ * @see javax.activation.MimetypesFileTypeMap
+ */
+
+public abstract class FileTypeMap {
+
+    private static FileTypeMap defaultMap = null;
+    private static Map<ClassLoader,FileTypeMap> map =
+				new WeakHashMap<ClassLoader,FileTypeMap>();
+
+    /**
+     * The default constructor.
+     */
+    public FileTypeMap() {
+	super();
+    }
+
+    /**
+     * Return the type of the file object. This method should
+     * always return a valid MIME type.
+     *
+     * @param file A file to be typed.
+     * @return The content type.
+     */
+    abstract public String getContentType(File file);
+
+    /**
+     * Return the type of the file passed in.  This method should
+     * always return a valid MIME type.
+     *
+     * @param filename the pathname of the file.
+     * @return The content type.
+     */
+    abstract public String getContentType(String filename);
+
+    /**
+     * Sets the default FileTypeMap for the system. This instance
+     * will be returned to callers of getDefaultFileTypeMap.
+     *
+     * @param fileTypeMap The FileTypeMap.
+     * @exception SecurityException if the caller doesn't have permission
+     *					to change the default
+     */
+    public static synchronized void setDefaultFileTypeMap(FileTypeMap fileTypeMap) {
+	SecurityManager security = System.getSecurityManager();
+	if (security != null) {
+	    try {
+		// if it's ok with the SecurityManager, it's ok with me...
+		security.checkSetFactory();
+	    } catch (SecurityException ex) {
+		// otherwise, we also allow it if this code and the
+		// factory come from the same (non-system) class loader (e.g.,
+		// the JAF classes were loaded with the applet classes).
+		ClassLoader cl = FileTypeMap.class.getClassLoader();
+		if (cl == null || cl.getParent() == null ||
+		    cl != fileTypeMap.getClass().getClassLoader())
+		    throw ex;
+	    }
+	}
+	// remove any per-thread-context-class-loader FileTypeMap
+	map.remove(SecuritySupport.getContextClassLoader());
+	defaultMap = fileTypeMap;	
+    }
+
+    /**
+     * Return the default FileTypeMap for the system.
+     * If setDefaultFileTypeMap was called, return
+     * that instance, otherwise return an instance of
+     * <code>MimetypesFileTypeMap</code>.
+     *
+     * @return The default FileTypeMap
+     * @see javax.activation.FileTypeMap#setDefaultFileTypeMap
+     */
+    public static synchronized FileTypeMap getDefaultFileTypeMap() {
+	if (defaultMap != null)
+	    return defaultMap;
+
+	// fetch per-thread-context-class-loader default
+	ClassLoader tccl = SecuritySupport.getContextClassLoader();
+	FileTypeMap def = map.get(tccl);
+	if (def == null) {
+	    def = new MimetypesFileTypeMap();
+	    map.put(tccl, def);
+	}
+	return def;
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/MailcapCommandMap.java b/android/activation/src/main/java/javax/activation/MailcapCommandMap.java
new file mode 100644
index 0000000..f5e0672
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/MailcapCommandMap.java
@@ -0,0 +1,707 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.util.*;
+import java.io.*;
+import java.net.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import com.sun.activation.registries.MailcapFile;
+import com.sun.activation.registries.LogSupport;
+
+/**
+ * MailcapCommandMap extends the CommandMap
+ * abstract class. It implements a CommandMap whose configuration
+ * is based on mailcap files
+ * (<A HREF="http://www.ietf.org/rfc/rfc1524.txt">RFC 1524</A>).
+ * The MailcapCommandMap can be configured both programmatically
+ * and via configuration files.
+ * <p>
+ * <b>Mailcap file search order:</b><p>
+ * The MailcapCommandMap looks in various places in the user's
+ * system for mailcap file entries. When requests are made
+ * to search for commands in the MailcapCommandMap, it searches  
+ * mailcap files in the following order:
+ * <ol>
+ * <li> Programatically added entries to the MailcapCommandMap instance.
+ * <li> The file <code>.mailcap</code> in the user's home directory.
+ * <li> The file <code>mailcap</code> in the Java runtime.
+ * <li> The file or resources named <code>META-INF/mailcap</code>.
+ * <li> The file or resource named <code>META-INF/mailcap.default</code>
+ * (usually found only in the <code>activation.jar</code> file).
+ * </ol>
+ * <p>
+ * (The current implementation looks for the <code>mailcap</code> file
+ * in the Java runtime in the directory <code><i>java.home</i>/conf</code>
+ * if it exists, and otherwise in the directory
+ * <code><i>java.home</i>/lib</code>, where <i>java.home</i> is the value
+ * of the "java.home" System property.  Note that the "conf" directory was
+ * introduced in JDK 9.)
+ * <p>
+ * <b>Mailcap file format:</b><p>
+ *
+ * Mailcap files must conform to the mailcap
+ * file specification (RFC 1524, <i>A User Agent Configuration Mechanism
+ * For Multimedia Mail Format Information</i>). 
+ * The file format consists of entries corresponding to
+ * particular MIME types. In general, the specification 
+ * specifies <i>applications</i> for clients to use when they
+ * themselves cannot operate on the specified MIME type. The 
+ * MailcapCommandMap extends this specification by using a parameter mechanism
+ * in mailcap files that allows JavaBeans(tm) components to be specified as
+ * corresponding to particular commands for a MIME type.<p>
+ *
+ * When a mailcap file is
+ * parsed, the MailcapCommandMap recognizes certain parameter signatures,
+ * specifically those parameter names that begin with <code>x-java-</code>.
+ * The MailcapCommandMap uses this signature to find
+ * command entries for inclusion into its registries.
+ * Parameter names with the form <code>x-java-&lt;name&gt;</code>
+ * are read by the MailcapCommandMap as identifying a command
+ * with the name <i>name</i>. When the <i>name</i> is <code>
+ * content-handler</code> the MailcapCommandMap recognizes the class
+ * signified by this parameter as a <i>DataContentHandler</i>.
+ * All other commands are handled generically regardless of command 
+ * name. The command implementation is specified by a fully qualified
+ * class name of a JavaBean(tm) component. For example; a command for viewing
+ * some data can be specified as: <code>x-java-view=com.foo.ViewBean</code>.<p>
+ *
+ * When the command name is <code>fallback-entry</code>, the value of
+ * the command may be <code>true</code> or <code>false</code>.  An
+ * entry for a MIME type that includes a parameter of
+ * <code>x-java-fallback-entry=true</code> defines fallback commands
+ * for that MIME type that will only be used if no non-fallback entry
+ * can be found.  For example, an entry of the form <code>text/*; ;
+ * x-java-fallback-entry=true; x-java-view=com.sun.TextViewer</code>
+ * specifies a view command to be used for any text MIME type.  This
+ * view command would only be used if a non-fallback view command for
+ * the MIME type could not be found.<p>
+ * 
+ * MailcapCommandMap aware mailcap files have the 
+ * following general form:<p>
+ * <code>
+ * # Comments begin with a '#' and continue to the end of the line.<br>
+ * &lt;mime type&gt;; ; &lt;parameter list&gt;<br>
+ * # Where a parameter list consists of one or more parameters,<br>
+ * # where parameters look like: x-java-view=com.sun.TextViewer<br>
+ * # and a parameter list looks like: <br>
+ * text/plain; ; x-java-view=com.sun.TextViewer; x-java-edit=com.sun.TextEdit
+ * <br>
+ * # Note that mailcap entries that do not contain 'x-java' parameters<br>
+ * # and comply to RFC 1524 are simply ignored:<br>
+ * image/gif; /usr/dt/bin/sdtimage %s<br>
+ *
+ * </code>
+ * <p>
+ *
+ * @author Bart Calder
+ * @author Bill Shannon
+ */
+
+public class MailcapCommandMap extends CommandMap {
+    /*
+     * We manage a collection of databases, searched in order.
+     */
+    private MailcapFile[] DB;
+    private static final int PROG = 0;	// programmatically added entries
+
+    private static final String confDir;
+
+    static {
+	String dir = null;
+	try {
+	    dir = (String)AccessController.doPrivileged(
+		new PrivilegedAction() {
+		    public Object run() {
+			String home = System.getProperty("java.home");
+			String newdir = home + File.separator + "conf";
+			File conf = new File(newdir);
+			if (conf.exists())
+			    return newdir + File.separator;
+			else
+			    return home + File.separator + "lib" + File.separator;
+		    }
+		});
+	} catch (Exception ex) {
+	    // ignore any exceptions
+	}
+	confDir = dir;
+    }
+
+    /**
+     * The default Constructor.
+     */
+    public MailcapCommandMap() {
+	super();
+	List dbv = new ArrayList(5);	// usually 5 or less databases
+	MailcapFile mf = null;
+	dbv.add(null);		// place holder for PROG entry
+
+	LogSupport.log("MailcapCommandMap: load HOME");
+	try {
+	    String user_home = System.getProperty("user.home");
+
+	    if (user_home != null) {
+		String path = user_home + File.separator + ".mailcap";
+		mf = loadFile(path);
+		if (mf != null)
+		    dbv.add(mf);
+	    }
+	} catch (SecurityException ex) {}
+
+	LogSupport.log("MailcapCommandMap: load SYS");
+	try {
+	    // check system's home
+	    if (confDir != null) {
+		mf = loadFile(confDir + "mailcap");
+		if (mf != null)
+		    dbv.add(mf);
+	    }
+	} catch (SecurityException ex) {}
+
+	LogSupport.log("MailcapCommandMap: load JAR");
+	// load from the app's jar file
+	loadAllResources(dbv, "META-INF/mailcap");
+
+	LogSupport.log("MailcapCommandMap: load DEF");
+	mf = loadResource("/META-INF/mailcap.default");
+
+	if (mf != null)
+	    dbv.add(mf);
+
+	DB = new MailcapFile[dbv.size()];
+	DB = (MailcapFile[])dbv.toArray(DB);
+    }
+
+    /**
+     * Load from the named resource.
+     */
+    private MailcapFile loadResource(String name) {
+	InputStream clis = null;
+	try {
+	    clis = SecuritySupport.getResourceAsStream(this.getClass(), name);
+	    if (clis != null) {
+		MailcapFile mf = new MailcapFile(clis);
+		if (LogSupport.isLoggable())
+		    LogSupport.log("MailcapCommandMap: successfully loaded " +
+			"mailcap file: " + name);
+		return mf;
+	    } else {
+		if (LogSupport.isLoggable())
+		    LogSupport.log("MailcapCommandMap: not loading " +
+			"mailcap file: " + name);
+	    }
+	} catch (IOException e) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("MailcapCommandMap: can't load " + name, e);
+	} catch (SecurityException sex) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("MailcapCommandMap: can't load " + name, sex);
+	} finally {
+	    try {
+		if (clis != null)
+		    clis.close();
+	    } catch (IOException ex) { }	// ignore it
+	}
+	return null;
+    }
+
+    /**
+     * Load all of the named resource.
+     */
+    private void loadAllResources(List v, String name) {
+	boolean anyLoaded = false;
+	try {
+	    URL[] urls;
+	    ClassLoader cld = null;
+	    // First try the "application's" class loader.
+	    cld = SecuritySupport.getContextClassLoader();
+	    if (cld == null)
+		cld = this.getClass().getClassLoader();
+	    if (cld != null)
+		urls = SecuritySupport.getResources(cld, name);
+	    else
+		urls = SecuritySupport.getSystemResources(name);
+	    if (urls != null) {
+		if (LogSupport.isLoggable())
+		    LogSupport.log("MailcapCommandMap: getResources");
+		for (int i = 0; i < urls.length; i++) {
+		    URL url = urls[i];
+		    InputStream clis = null;
+		    if (LogSupport.isLoggable())
+			LogSupport.log("MailcapCommandMap: URL " + url);
+		    try {
+			clis = SecuritySupport.openStream(url);
+			if (clis != null) {
+			    v.add(new MailcapFile(clis));
+			    anyLoaded = true;
+			    if (LogSupport.isLoggable())
+				LogSupport.log("MailcapCommandMap: " +
+				    "successfully loaded " +
+				    "mailcap file from URL: " +
+				    url);
+			} else {
+			    if (LogSupport.isLoggable())
+				LogSupport.log("MailcapCommandMap: " +
+				    "not loading mailcap " +
+				    "file from URL: " + url);
+			}
+		    } catch (IOException ioex) {
+			if (LogSupport.isLoggable())
+			    LogSupport.log("MailcapCommandMap: can't load " +
+						url, ioex);
+		    } catch (SecurityException sex) {
+			if (LogSupport.isLoggable())
+			    LogSupport.log("MailcapCommandMap: can't load " +
+						url, sex);
+		    } finally {
+			try {
+			    if (clis != null)
+				clis.close();
+			} catch (IOException cex) { }
+		    }
+		}
+	    }
+	} catch (Exception ex) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("MailcapCommandMap: can't load " + name, ex);
+	}
+
+	// if failed to load anything, fall back to old technique, just in case
+	if (!anyLoaded) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("MailcapCommandMap: !anyLoaded");
+	    MailcapFile mf = loadResource("/" + name);
+	    if (mf != null)
+		v.add(mf);
+	}
+    }
+
+    /**
+     * Load from the named file.
+     */
+    private MailcapFile loadFile(String name) {
+	MailcapFile mtf = null;
+
+	try {
+	    mtf = new MailcapFile(name);
+	} catch (IOException e) {
+	    //	e.printStackTrace();
+	}
+	return mtf;
+    }
+
+    /**
+     * Constructor that allows the caller to specify the path
+     * of a <i>mailcap</i> file.
+     *
+     * @param fileName The name of the <i>mailcap</i> file to open
+     * @exception	IOException	if the file can't be accessed
+     */
+    public MailcapCommandMap(String fileName) throws IOException {
+	this();
+
+	if (LogSupport.isLoggable())
+	    LogSupport.log("MailcapCommandMap: load PROG from " + fileName);
+	if (DB[PROG] == null) {
+	    DB[PROG] = new MailcapFile(fileName);
+	}
+    }
+
+
+    /**
+     * Constructor that allows the caller to specify an <i>InputStream</i>
+     * containing a mailcap file.
+     *
+     * @param is	InputStream of the <i>mailcap</i> file to open
+     */
+    public MailcapCommandMap(InputStream is) {
+	this();
+
+	LogSupport.log("MailcapCommandMap: load PROG");
+	if (DB[PROG] == null) {
+	    try {
+		DB[PROG] = new MailcapFile(is);
+	    } catch (IOException ex) {
+		// XXX - should throw it
+	    }
+	}
+    }
+
+    /**
+     * Get the preferred command list for a MIME Type. The MailcapCommandMap
+     * searches the mailcap files as described above under
+     * <i>Mailcap file search order</i>.<p>
+     *
+     * The result of the search is a proper subset of available
+     * commands in all mailcap files known to this instance of 
+     * MailcapCommandMap.  The first entry for a particular command
+     * is considered the preferred command.
+     *
+     * @param mimeType	the MIME type
+     * @return the CommandInfo objects representing the preferred commands.
+     */
+    public synchronized CommandInfo[] getPreferredCommands(String mimeType) {
+	List cmdList = new ArrayList();
+	if (mimeType != null)
+	    mimeType = mimeType.toLowerCase(Locale.ENGLISH);
+
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    Map cmdMap = DB[i].getMailcapList(mimeType);
+	    if (cmdMap != null)
+		appendPrefCmdsToList(cmdMap, cmdList);
+	}
+
+	// now add the fallback commands
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    Map cmdMap = DB[i].getMailcapFallbackList(mimeType);
+	    if (cmdMap != null)
+		appendPrefCmdsToList(cmdMap, cmdList);
+	}
+
+	CommandInfo[] cmdInfos = new CommandInfo[cmdList.size()];
+	cmdInfos = (CommandInfo[])cmdList.toArray(cmdInfos);
+
+	return cmdInfos;
+    }
+
+    /**
+     * Put the commands that are in the hash table, into the list.
+     */
+    private void appendPrefCmdsToList(Map cmdHash, List cmdList) {
+	Iterator verb_enum = cmdHash.keySet().iterator();
+
+	while (verb_enum.hasNext()) {
+	    String verb = (String)verb_enum.next();
+	    if (!checkForVerb(cmdList, verb)) {
+		List cmdList2 = (List)cmdHash.get(verb); // get the list
+		String className = (String)cmdList2.get(0);
+		cmdList.add(new CommandInfo(verb, className));
+	    }
+	}
+    }
+
+    /**
+     * Check the cmdList to see if this command exists, return
+     * true if the verb is there.
+     */
+    private boolean checkForVerb(List cmdList, String verb) {
+	Iterator ee = cmdList.iterator();
+	while (ee.hasNext()) {
+	    String enum_verb =
+		(String)((CommandInfo)ee.next()).getCommandName();
+	    if (enum_verb.equals(verb))
+		return true;
+	}
+	return false;
+    }
+
+    /**
+     * Get all the available commands in all mailcap files known to
+     * this instance of MailcapCommandMap for this MIME type.
+     *
+     * @param mimeType	the MIME type
+     * @return the CommandInfo objects representing all the commands.
+     */
+    public synchronized CommandInfo[] getAllCommands(String mimeType) {
+	List cmdList = new ArrayList();
+	if (mimeType != null)
+	    mimeType = mimeType.toLowerCase(Locale.ENGLISH);
+
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    Map cmdMap = DB[i].getMailcapList(mimeType);
+	    if (cmdMap != null)
+		appendCmdsToList(cmdMap, cmdList);
+	}
+
+	// now add the fallback commands
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    Map cmdMap = DB[i].getMailcapFallbackList(mimeType);
+	    if (cmdMap != null)
+		appendCmdsToList(cmdMap, cmdList);
+	}
+
+	CommandInfo[] cmdInfos = new CommandInfo[cmdList.size()];
+	cmdInfos = (CommandInfo[])cmdList.toArray(cmdInfos);
+
+	return cmdInfos;
+    }
+
+    /**
+     * Put the commands that are in the hash table, into the list.
+     */
+    private void appendCmdsToList(Map typeHash, List cmdList) {
+	Iterator verb_enum = typeHash.keySet().iterator();
+
+	while (verb_enum.hasNext()) {
+	    String verb = (String)verb_enum.next();
+	    List cmdList2 = (List)typeHash.get(verb);
+	    Iterator cmd_enum = ((List)cmdList2).iterator();
+
+	    while (cmd_enum.hasNext()) {
+		String cmd = (String)cmd_enum.next();
+		cmdList.add(new CommandInfo(verb, cmd));
+		// cmdList.add(0, new CommandInfo(verb, cmd));
+	    }
+	}
+    }
+
+    /**
+     * Get the command corresponding to <code>cmdName</code> for the MIME type.
+     *
+     * @param mimeType	the MIME type
+     * @param cmdName	the command name
+     * @return the CommandInfo object corresponding to the command.
+     */
+    public synchronized CommandInfo getCommand(String mimeType,
+							String cmdName) {
+	if (mimeType != null)
+	    mimeType = mimeType.toLowerCase(Locale.ENGLISH);
+
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    Map cmdMap = DB[i].getMailcapList(mimeType);
+	    if (cmdMap != null) {
+		// get the cmd list for the cmd
+		List v = (List)cmdMap.get(cmdName);
+		if (v != null) {
+		    String cmdClassName = (String)v.get(0);
+
+		    if (cmdClassName != null)
+			return new CommandInfo(cmdName, cmdClassName);
+		}
+	    }
+	}
+
+	// now try the fallback list
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    Map cmdMap = DB[i].getMailcapFallbackList(mimeType);
+	    if (cmdMap != null) {
+		// get the cmd list for the cmd
+		List v = (List)cmdMap.get(cmdName);
+		if (v != null) {
+		    String cmdClassName = (String)v.get(0);
+
+		    if (cmdClassName != null)
+			return new CommandInfo(cmdName, cmdClassName);
+		}
+	    }
+	}
+	return null;
+    }
+
+    /**
+     * Add entries to the registry.  Programmatically 
+     * added entries are searched before other entries.<p>
+     *
+     * The string that is passed in should be in mailcap
+     * format.
+     *
+     * @param mail_cap a correctly formatted mailcap string
+     */
+    public synchronized void addMailcap(String mail_cap) {
+	// check to see if one exists
+	LogSupport.log("MailcapCommandMap: add to PROG");
+	if (DB[PROG] == null)
+	    DB[PROG] = new MailcapFile();
+
+	DB[PROG].appendToMailcap(mail_cap);
+    }
+
+    /**
+     * Return the DataContentHandler for the specified MIME type.
+     *
+     * @param mimeType	the MIME type
+     * @return		the DataContentHandler
+     */
+    public synchronized DataContentHandler createDataContentHandler(
+							String mimeType) {
+	if (LogSupport.isLoggable())
+	    LogSupport.log(
+		"MailcapCommandMap: createDataContentHandler for " + mimeType);
+	if (mimeType != null)
+	    mimeType = mimeType.toLowerCase(Locale.ENGLISH);
+
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    if (LogSupport.isLoggable())
+		LogSupport.log("  search DB #" + i);
+	    Map cmdMap = DB[i].getMailcapList(mimeType);
+	    if (cmdMap != null) {
+		List v = (List)cmdMap.get("content-handler");
+		if (v != null) {
+		    String name = (String)v.get(0);
+		    DataContentHandler dch = getDataContentHandler(name);
+		    if (dch != null)
+			return dch;
+		}
+	    }
+	}
+
+	// now try the fallback entries
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    if (LogSupport.isLoggable())
+		LogSupport.log("  search fallback DB #" + i);
+	    Map cmdMap = DB[i].getMailcapFallbackList(mimeType);
+	    if (cmdMap != null) {
+		List v = (List)cmdMap.get("content-handler");
+		if (v != null) {
+		    String name = (String)v.get(0);
+		    DataContentHandler dch = getDataContentHandler(name);
+		    if (dch != null)
+			return dch;
+		}
+	    }
+	}
+	return null;
+    }
+
+    private DataContentHandler getDataContentHandler(String name) {
+	if (LogSupport.isLoggable())
+	    LogSupport.log("    got content-handler");
+	if (LogSupport.isLoggable())
+	    LogSupport.log("      class " + name);
+	try {
+	    ClassLoader cld = null;
+	    // First try the "application's" class loader.
+	    cld = SecuritySupport.getContextClassLoader();
+	    if (cld == null)
+		cld = this.getClass().getClassLoader();
+	    Class cl = null;
+	    try {
+		cl = cld.loadClass(name);
+	    } catch (Exception ex) {
+		// if anything goes wrong, do it the old way
+		cl = Class.forName(name);
+	    }
+	    if (cl != null)		// XXX - always true?
+		return (DataContentHandler)cl.newInstance();
+	} catch (IllegalAccessException e) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("Can't load DCH " + name, e);
+	} catch (ClassNotFoundException e) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("Can't load DCH " + name, e);
+	} catch (InstantiationException e) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("Can't load DCH " + name, e);
+	}
+	return null;
+    }
+
+    /**
+     * Get all the MIME types known to this command map.
+     *
+     * @return		array of MIME types as strings
+     * @since	JAF 1.1
+     */
+    public synchronized String[] getMimeTypes() {
+	List mtList = new ArrayList();
+
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    String[] ts = DB[i].getMimeTypes();
+	    if (ts != null) {
+		for (int j = 0; j < ts.length; j++) {
+		    // eliminate duplicates
+		    if (!mtList.contains(ts[j]))
+			mtList.add(ts[j]);
+		}
+	    }
+	}
+
+	String[] mts = new String[mtList.size()];
+	mts = (String[])mtList.toArray(mts);
+
+	return mts;
+    }
+
+    /**
+     * Get the native commands for the given MIME type.
+     * Returns an array of strings where each string is
+     * an entire mailcap file entry.  The application
+     * will need to parse the entry to extract the actual
+     * command as well as any attributes it needs. See
+     * <A HREF="http://www.ietf.org/rfc/rfc1524.txt">RFC 1524</A>
+     * for details of the mailcap entry syntax.  Only mailcap
+     * entries that specify a view command for the specified
+     * MIME type are returned.
+     *
+     * @param	mimeType	the MIME type
+     * @return		array of native command entries
+     * @since	JAF 1.1
+     */
+    public synchronized String[] getNativeCommands(String mimeType) {
+	List cmdList = new ArrayList();
+	if (mimeType != null)
+	    mimeType = mimeType.toLowerCase(Locale.ENGLISH);
+
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    String[] cmds = DB[i].getNativeCommands(mimeType);
+	    if (cmds != null) {
+		for (int j = 0; j < cmds.length; j++) {
+		    // eliminate duplicates
+		    if (!cmdList.contains(cmds[j]))
+			cmdList.add(cmds[j]);
+		}
+	    }
+	}
+
+	String[] cmds = new String[cmdList.size()];
+	cmds = (String[])cmdList.toArray(cmds);
+
+	return cmds;
+    }
+
+    /**
+     * for debugging...
+     *
+    public static void main(String[] argv) throws Exception {
+	MailcapCommandMap map = new MailcapCommandMap();
+	CommandInfo[] cmdInfo;
+
+	cmdInfo = map.getPreferredCommands(argv[0]);
+	System.out.println("Preferred Commands:");
+	for (int i = 0; i < cmdInfo.length; i++)
+	    System.out.println("Command " + cmdInfo[i].getCommandName() + " [" +
+					    cmdInfo[i].getCommandClass() + "]");
+	cmdInfo = map.getAllCommands(argv[0]);
+	System.out.println();
+	System.out.println("All Commands:");
+	for (int i = 0; i < cmdInfo.length; i++)
+	    System.out.println("Command " + cmdInfo[i].getCommandName() + " [" +
+					    cmdInfo[i].getCommandClass() + "]");
+	DataContentHandler dch = map.createDataContentHandler(argv[0]);
+	if (dch != null)
+	    System.out.println("DataContentHandler " +
+						dch.getClass().toString());
+	System.exit(0);
+    }
+    */
+}
diff --git a/android/activation/src/main/java/javax/activation/MimeType.java b/android/activation/src/main/java/javax/activation/MimeType.java
new file mode 100644
index 0000000..49fe6e5
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/MimeType.java
@@ -0,0 +1,329 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.io.*;
+import java.util.Locale;
+
+/**
+ * A Multipurpose Internet Mail Extension (MIME) type, as defined
+ * in RFC 2045 and 2046.
+ */
+public class MimeType implements Externalizable {
+
+    private String    primaryType;
+    private String    subType;
+    private MimeTypeParameterList parameters;
+
+    /**
+     * A string that holds all the special chars.
+     */
+    private static final String TSPECIALS = "()<>@,;:/[]?=\\\"";
+
+    /**
+     * Default constructor.
+     */
+    public MimeType() {
+        primaryType = "application";
+        subType = "*";
+        parameters = new MimeTypeParameterList();
+    }
+
+    /**
+     * Constructor that builds a MimeType from a String.
+     *
+     * @param rawdata	the MIME type string
+     * @exception	MimeTypeParseException	if the MIME type can't be parsed
+     */
+    public MimeType(String rawdata) throws MimeTypeParseException {
+        parse(rawdata);
+    }
+
+    /**
+     * Constructor that builds a MimeType with the given primary and sub type
+     * but has an empty parameter list.
+     *
+     * @param primary	the primary MIME type
+     * @param sub	the MIME sub-type
+     * @exception	MimeTypeParseException	if the primary type or subtype
+     *						is not a valid token
+     */
+    public MimeType(String primary, String sub) throws MimeTypeParseException {
+        //    check to see if primary is valid
+        if (isValidToken(primary)) {
+            primaryType = primary.toLowerCase(Locale.ENGLISH);
+        } else {
+            throw new MimeTypeParseException("Primary type is invalid.");
+        }
+
+        //    check to see if sub is valid
+        if (isValidToken(sub)) {
+            subType = sub.toLowerCase(Locale.ENGLISH);
+        } else {
+            throw new MimeTypeParseException("Sub type is invalid.");
+        }
+
+        parameters = new MimeTypeParameterList();
+    }
+
+    /**
+     * A routine for parsing the MIME type out of a String.
+     */
+    private void parse(String rawdata) throws MimeTypeParseException {
+        int slashIndex = rawdata.indexOf('/');
+        int semIndex = rawdata.indexOf(';');
+        if ((slashIndex < 0) && (semIndex < 0)) {
+            //    neither character is present, so treat it
+            //    as an error
+            throw new MimeTypeParseException("Unable to find a sub type.");
+        } else if ((slashIndex < 0) && (semIndex >= 0)) {
+            //    we have a ';' (and therefore a parameter list),
+            //    but no '/' indicating a sub type is present
+            throw new MimeTypeParseException("Unable to find a sub type.");
+        } else if ((slashIndex >= 0) && (semIndex < 0)) {
+            //    we have a primary and sub type but no parameter list
+            primaryType = rawdata.substring(0, slashIndex).trim().
+						toLowerCase(Locale.ENGLISH);
+            subType = rawdata.substring(slashIndex + 1).trim().
+						toLowerCase(Locale.ENGLISH);
+            parameters = new MimeTypeParameterList();
+        } else if (slashIndex < semIndex) {
+            //    we have all three items in the proper sequence
+            primaryType = rawdata.substring(0, slashIndex).trim().
+						toLowerCase(Locale.ENGLISH);
+            subType = rawdata.substring(slashIndex + 1, semIndex).trim().
+						toLowerCase(Locale.ENGLISH);
+            parameters = new MimeTypeParameterList(rawdata.substring(semIndex));
+        } else {
+            // we have a ';' lexically before a '/' which means we
+	    // have a primary type and a parameter list but no sub type
+            throw new MimeTypeParseException("Unable to find a sub type.");
+        }
+
+        //    now validate the primary and sub types
+
+        //    check to see if primary is valid
+        if (!isValidToken(primaryType))
+            throw new MimeTypeParseException("Primary type is invalid.");
+
+        //    check to see if sub is valid
+        if (!isValidToken(subType))
+            throw new MimeTypeParseException("Sub type is invalid.");
+    }
+
+    /**
+     * Retrieve the primary type of this object.
+     *
+     * @return	the primary MIME type
+     */
+    public String getPrimaryType() {
+        return primaryType;
+    }
+
+    /**
+     * Set the primary type for this object to the given String.
+     *
+     * @param primary	the primary MIME type
+     * @exception	MimeTypeParseException	if the primary type
+     *						is not a valid token
+     */
+    public void setPrimaryType(String primary) throws MimeTypeParseException {
+        //    check to see if primary is valid
+        if (!isValidToken(primaryType))
+            throw new MimeTypeParseException("Primary type is invalid.");
+        primaryType = primary.toLowerCase(Locale.ENGLISH);
+    }
+
+    /**
+     * Retrieve the subtype of this object.
+     *
+     * @return	the MIME subtype
+     */
+    public String getSubType() {
+        return subType;
+    }
+
+    /**
+     * Set the subtype for this object to the given String.
+     *
+     * @param sub	the MIME subtype
+     * @exception	MimeTypeParseException	if the subtype
+     *						is not a valid token
+     */
+    public void setSubType(String sub) throws MimeTypeParseException {
+        //    check to see if sub is valid
+        if (!isValidToken(subType))
+            throw new MimeTypeParseException("Sub type is invalid.");
+        subType = sub.toLowerCase(Locale.ENGLISH);
+    }
+
+    /**
+     * Retrieve this object's parameter list.
+     *
+     * @return	a MimeTypeParameterList object representing the parameters
+     */
+    public MimeTypeParameterList getParameters() {
+        return parameters;
+    }
+
+    /**
+     * Retrieve the value associated with the given name, or null if there
+     * is no current association.
+     *
+     * @param name	the parameter name
+     * @return		the paramter's value
+     */
+    public String getParameter(String name) {
+        return parameters.get(name);
+    }
+
+    /**
+     * Set the value to be associated with the given name, replacing
+     * any previous association.
+     *
+     * @param name	the parameter name
+     * @param value	the paramter's value
+     */
+    public void setParameter(String name, String value) {
+        parameters.set(name, value);
+    }
+
+    /**
+     * Remove any value associated with the given name.
+     *
+     * @param name	the parameter name
+     */
+    public void removeParameter(String name) {
+        parameters.remove(name);
+    }
+
+    /**
+     * Return the String representation of this object.
+     */
+    public String toString() {
+        return getBaseType() + parameters.toString();
+    }
+
+    /**
+     * Return a String representation of this object
+     * without the parameter list.
+     *
+     * @return	the MIME type and sub-type
+     */
+    public String getBaseType() {
+        return primaryType + "/" + subType;
+    }
+
+    /**
+     * Determine if the primary and sub type of this object is
+     * the same as what is in the given type.
+     *
+     * @param type	the MimeType object to compare with
+     * @return		true if they match
+     */
+    public boolean match(MimeType type) {
+        return primaryType.equals(type.getPrimaryType())
+                    && (subType.equals("*")
+                            || type.getSubType().equals("*")
+                            || (subType.equals(type.getSubType())));
+    }
+
+    /**
+     * Determine if the primary and sub type of this object is
+     * the same as the content type described in rawdata.
+     *
+     * @param rawdata	the MIME type string to compare with
+     * @return		true if they match
+     * @exception	MimeTypeParseException	if the MIME type can't be parsed
+     */
+    public boolean match(String rawdata) throws MimeTypeParseException {
+        return match(new MimeType(rawdata));
+    }
+
+    /**
+     * The object implements the writeExternal method to save its contents
+     * by calling the methods of DataOutput for its primitive values or
+     * calling the writeObject method of ObjectOutput for objects, strings
+     * and arrays.
+     *
+     * @param out	the ObjectOutput object to write to
+     * @exception IOException Includes any I/O exceptions that may occur
+     */
+    public void writeExternal(ObjectOutput out) throws IOException {
+        out.writeUTF(toString());
+	out.flush();
+    }
+
+    /**
+     * The object implements the readExternal method to restore its
+     * contents by calling the methods of DataInput for primitive
+     * types and readObject for objects, strings and arrays.  The
+     * readExternal method must read the values in the same sequence
+     * and with the same types as were written by writeExternal.
+     *
+     * @param in	the ObjectInput object to read from
+     * @exception ClassNotFoundException If the class for an object being
+     *              restored cannot be found.
+     */
+    public void readExternal(ObjectInput in)
+				throws IOException, ClassNotFoundException {
+        try {
+            parse(in.readUTF());
+        } catch (MimeTypeParseException e) {
+            throw new IOException(e.toString());
+        }
+    }
+
+    //    below here be scary parsing related things
+
+    /**
+     * Determine whether or not a given character belongs to a legal token.
+     */
+    private static boolean isTokenChar(char c) {
+        return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
+    }
+
+    /**
+     * Determine whether or not a given string is a legal token.
+     */
+    private boolean isValidToken(String s) {
+        int len = s.length();
+        if (len > 0) {
+            for (int i = 0; i < len; ++i) {
+                char c = s.charAt(i);
+                if (!isTokenChar(c)) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * A simple parser test,
+     * for debugging...
+     *
+    public static void main(String[] args)
+				throws MimeTypeParseException, IOException {
+        for (int i = 0; i < args.length; ++i) {
+            System.out.println("Original: " + args[i]);
+
+            MimeType type = new MimeType(args[i]);
+
+            System.out.println("Short:    " + type.getBaseType());
+            System.out.println("Parsed:   " + type.toString());
+            System.out.println();
+        }
+    }
+    */
+}
diff --git a/android/activation/src/main/java/javax/activation/MimeTypeParameterList.java b/android/activation/src/main/java/javax/activation/MimeTypeParameterList.java
new file mode 100644
index 0000000..6298063
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/MimeTypeParameterList.java
@@ -0,0 +1,324 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.util.Hashtable;
+import java.util.Enumeration;
+import java.util.Locale;
+
+/**
+ * A parameter list of a MimeType
+ * as defined in RFC 2045 and 2046. The Primary type of the
+ * object must already be stripped off.
+ *
+ * @see javax.activation.MimeType
+ */
+public class MimeTypeParameterList {
+    private Hashtable parameters;
+
+    /**
+     * A string that holds all the special chars.
+     */
+    private static final String TSPECIALS = "()<>@,;:/[]?=\\\"";
+
+
+    /**
+     * Default constructor.
+     */
+    public MimeTypeParameterList() {
+        parameters = new Hashtable();
+    }
+
+    /**
+     * Constructs a new MimeTypeParameterList with the passed in data.
+     *
+     * @param parameterList an RFC 2045, 2046 compliant parameter list.
+     * @exception	MimeTypeParseException	if the MIME type can't be parsed
+     */
+    public MimeTypeParameterList(String parameterList)
+					throws MimeTypeParseException {
+        parameters = new Hashtable();
+
+        //    now parse rawdata
+        parse(parameterList);
+    }
+
+    /**
+     * A routine for parsing the parameter list out of a String.
+     *
+     * @param parameterList an RFC 2045, 2046 compliant parameter list.
+     * @exception	MimeTypeParseException	if the MIME type can't be parsed
+     */
+    protected void parse(String parameterList) throws MimeTypeParseException {
+	if (parameterList == null)
+	    return;
+
+        int length = parameterList.length();
+        if (length <= 0)
+	    return;
+
+	int i;
+	char c;
+	for (i = skipWhiteSpace(parameterList, 0);
+		i < length && (c = parameterList.charAt(i)) == ';';
+		i = skipWhiteSpace(parameterList, i)) {
+	    int lastIndex;
+	    String name;
+	    String value;
+
+	    //    eat the ';'
+	    i++;
+
+	    //    now parse the parameter name
+
+	    //    skip whitespace
+	    i = skipWhiteSpace(parameterList, i);
+
+	    // tolerate trailing semicolon, even though it violates the spec
+	    if (i >= length)
+		return;
+
+	    //    find the end of the token char run
+	    lastIndex = i;
+	    while ((i < length) && isTokenChar(parameterList.charAt(i)))
+		i++;
+
+	    name = parameterList.substring(lastIndex, i).
+						toLowerCase(Locale.ENGLISH);
+
+	    //    now parse the '=' that separates the name from the value
+	    i = skipWhiteSpace(parameterList, i);
+
+	    if (i >= length || parameterList.charAt(i) != '=')
+		throw new MimeTypeParseException(
+		    "Couldn't find the '=' that separates a " +
+		    "parameter name from its value.");
+
+	    //    eat it and parse the parameter value
+	    i++;
+	    i = skipWhiteSpace(parameterList, i);
+
+	    if (i >= length)
+		throw new MimeTypeParseException(
+			"Couldn't find a value for parameter named " + name);
+
+	    //    now find out whether or not we have a quoted value
+	    c = parameterList.charAt(i);
+	    if (c == '"') {
+		//    yup it's quoted so eat it and capture the quoted string
+		i++;
+		if (i >= length)
+		    throw new MimeTypeParseException(
+			    "Encountered unterminated quoted parameter value.");
+
+		lastIndex = i;
+
+		//    find the next unescaped quote
+		while (i < length) {
+		    c = parameterList.charAt(i);
+		    if (c == '"')
+			break;
+		    if (c == '\\') {
+			//    found an escape sequence
+			//    so skip this and the
+			//    next character
+			i++;
+		    }
+		    i++;
+		}
+		if (c != '"')
+		    throw new MimeTypeParseException(
+			"Encountered unterminated quoted parameter value.");
+
+		value = unquote(parameterList.substring(lastIndex, i));
+		//    eat the quote
+		i++;
+	    } else if (isTokenChar(c)) {
+		//    nope it's an ordinary token so it
+		//    ends with a non-token char
+		lastIndex = i;
+		while (i < length && isTokenChar(parameterList.charAt(i)))
+		    i++;
+		value = parameterList.substring(lastIndex, i);
+	    } else {
+		//    it ain't a value
+		throw new MimeTypeParseException(
+			"Unexpected character encountered at index " + i);
+	    }
+
+	    //    now put the data into the hashtable
+	    parameters.put(name, value);
+	}
+	if (i < length) {
+	    throw new MimeTypeParseException(
+		"More characters encountered in input than expected.");
+	}
+    }
+
+    /**
+     * Return the number of name-value pairs in this list.
+     *
+     * @return	the number of parameters
+     */
+    public int size() {
+        return parameters.size();
+    }
+
+    /**
+     * Determine whether or not this list is empty.
+     *
+     * @return	true if there are no parameters
+     */
+    public boolean isEmpty() {
+        return parameters.isEmpty();
+    }
+
+    /**
+     * Retrieve the value associated with the given name, or null if there
+     * is no current association.
+     *
+     * @param name	the parameter name
+     * @return		the parameter's value
+     */
+    public String get(String name) {
+        return (String)parameters.get(name.trim().toLowerCase(Locale.ENGLISH));
+    }
+
+    /**
+     * Set the value to be associated with the given name, replacing
+     * any previous association.
+     *
+     * @param name	the parameter name
+     * @param value	the parameter's value
+     */
+    public void set(String name, String value) {
+        parameters.put(name.trim().toLowerCase(Locale.ENGLISH), value);
+    }
+
+    /**
+     * Remove any value associated with the given name.
+     *
+     * @param name	the parameter name
+     */
+    public void remove(String name) {
+        parameters.remove(name.trim().toLowerCase(Locale.ENGLISH));
+    }
+
+    /**
+     * Retrieve an enumeration of all the names in this list.
+     *
+     * @return	an enumeration of all parameter names
+     */
+    public Enumeration getNames() {
+        return parameters.keys();
+    }
+
+    /**
+     * Return a string representation of this object.
+     */
+    public String toString() {
+        StringBuffer buffer = new StringBuffer();
+        buffer.ensureCapacity(parameters.size() * 16);
+			//    heuristic: 8 characters per field
+
+        Enumeration keys = parameters.keys();
+        while (keys.hasMoreElements()) {
+            String key = (String)keys.nextElement();
+            buffer.append("; ");
+            buffer.append(key);
+            buffer.append('=');
+	    buffer.append(quote((String)parameters.get(key)));
+        }
+
+        return buffer.toString();
+    }
+
+    //    below here be scary parsing related things
+
+    /**
+     * Determine whether or not a given character belongs to a legal token.
+     */
+    private static boolean isTokenChar(char c) {
+        return ((c > 040) && (c < 0177)) && (TSPECIALS.indexOf(c) < 0);
+    }
+
+    /**
+     * return the index of the first non white space character in
+     * rawdata at or after index i.
+     */
+    private static int skipWhiteSpace(String rawdata, int i) {
+        int length = rawdata.length();
+	while ((i < length) && Character.isWhitespace(rawdata.charAt(i)))
+	    i++;
+        return i;
+    }
+
+    /**
+     * A routine that knows how and when to quote and escape the given value.
+     */
+    private static String quote(String value) {
+        boolean needsQuotes = false;
+
+        //    check to see if we actually have to quote this thing
+        int length = value.length();
+        for (int i = 0; (i < length) && !needsQuotes; i++) {
+            needsQuotes = !isTokenChar(value.charAt(i));
+        }
+
+        if (needsQuotes) {
+            StringBuffer buffer = new StringBuffer();
+            buffer.ensureCapacity((int)(length * 1.5));
+
+            //    add the initial quote
+            buffer.append('"');
+
+            //    add the properly escaped text
+            for (int i = 0; i < length; ++i) {
+                char c = value.charAt(i);
+                if ((c == '\\') || (c == '"'))
+                    buffer.append('\\');
+                buffer.append(c);
+            }
+
+            //    add the closing quote
+            buffer.append('"');
+
+            return buffer.toString();
+        } else {
+            return value;
+        }
+    }
+
+    /**
+     * A routine that knows how to strip the quotes and
+     * escape sequences from the given value.
+     */
+    private static String unquote(String value) {
+        int valueLength = value.length();
+        StringBuffer buffer = new StringBuffer();
+        buffer.ensureCapacity(valueLength);
+
+        boolean escaped = false;
+        for (int i = 0; i < valueLength; ++i) {
+            char currentChar = value.charAt(i);
+            if (!escaped && (currentChar != '\\')) {
+                buffer.append(currentChar);
+            } else if (escaped) {
+                buffer.append(currentChar);
+                escaped = false;
+            } else {
+                escaped = true;
+            }
+        }
+
+        return buffer.toString();
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/MimeTypeParseException.java b/android/activation/src/main/java/javax/activation/MimeTypeParseException.java
new file mode 100644
index 0000000..fd000e2
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/MimeTypeParseException.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+/**
+ * A class to encapsulate MimeType parsing related exceptions.
+ */
+public class MimeTypeParseException extends Exception {
+
+    /**
+     * Constructs a MimeTypeParseException with no specified detail message. 
+     */
+    public MimeTypeParseException() {
+	super();
+    }
+
+    /**
+     * Constructs a MimeTypeParseException with the specified detail message. 
+     *
+     * @param   s   the detail message.
+     */
+    public MimeTypeParseException(String s) {
+	super(s);
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/MimetypesFileTypeMap.java b/android/activation/src/main/java/javax/activation/MimetypesFileTypeMap.java
new file mode 100644
index 0000000..e2dffb7
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/MimetypesFileTypeMap.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import com.sun.activation.registries.MimeTypeFile;
+import com.sun.activation.registries.LogSupport;
+
+/**
+ * This class extends FileTypeMap and provides data typing of files
+ * via their file extension. It uses the <code>.mime.types</code> format. <p>
+ *
+ * <b>MIME types file search order:</b><p>
+ * The MimetypesFileTypeMap looks in various places in the user's
+ * system for MIME types file entries. When requests are made
+ * to search for MIME types in the MimetypesFileTypeMap, it searches  
+ * MIME types files in the following order:
+ * <ol>
+ * <li> Programmatically added entries to the MimetypesFileTypeMap instance.
+ * <li> The file <code>.mime.types</code> in the user's home directory.
+ * <li> The file <code>mime.types</code> in the Java runtime.
+ * <li> The file or resources named <code>META-INF/mime.types</code>.
+ * <li> The file or resource named <code>META-INF/mimetypes.default</code>
+ * (usually found only in the <code>activation.jar</code> file).
+ * </ol>
+ * <p>
+ * (The current implementation looks for the <code>mime.types</code> file
+ * in the Java runtime in the directory <code><i>java.home</i>/conf</code>
+ * if it exists, and otherwise in the directory
+ * <code><i>java.home</i>/lib</code>, where <i>java.home</i> is the value
+ * of the "java.home" System property.  Note that the "conf" directory was
+ * introduced in JDK 9.)
+ * <p>
+ * <b>MIME types file format:</b><p>
+ *
+ * <code>
+ * # comments begin with a '#'<br>
+ * # the format is &lt;mime type&gt; &lt;space separated file extensions&gt;<br>
+ * # for example:<br>
+ * text/plain    txt text TXT<br>
+ * # this would map file.txt, file.text, and file.TXT to<br>
+ * # the mime type "text/plain"<br>
+ * </code>
+ *
+ * @author Bart Calder
+ * @author Bill Shannon
+ */
+public class MimetypesFileTypeMap extends FileTypeMap {
+    /*
+     * We manage a collection of databases, searched in order.
+     */
+    private MimeTypeFile[] DB;
+    private static final int PROG = 0;	// programmatically added entries
+
+    private static final String defaultType = "application/octet-stream";
+
+    private static final String confDir;
+
+    static {
+	String dir = null;
+	try {
+	    dir = (String)AccessController.doPrivileged(
+		new PrivilegedAction() {
+		    public Object run() {
+			String home = System.getProperty("java.home");
+			String newdir = home + File.separator + "conf";
+			File conf = new File(newdir);
+			if (conf.exists())
+			    return newdir + File.separator;
+			else
+			    return home + File.separator + "lib" + File.separator;
+		    }
+		});
+	} catch (Exception ex) {
+	    // ignore any exceptions
+	}
+	confDir = dir;
+    }
+
+    /**
+     * The default constructor.
+     */
+    public MimetypesFileTypeMap() {
+	Vector dbv = new Vector(5);	// usually 5 or less databases
+	MimeTypeFile mf = null;
+	dbv.addElement(null);		// place holder for PROG entry
+
+	LogSupport.log("MimetypesFileTypeMap: load HOME");
+	try {
+	    String user_home = System.getProperty("user.home");
+
+	    if (user_home != null) {
+		String path = user_home + File.separator + ".mime.types";
+		mf = loadFile(path);
+		if (mf != null)
+		    dbv.addElement(mf);
+	    }
+	} catch (SecurityException ex) {}
+
+	LogSupport.log("MimetypesFileTypeMap: load SYS");
+	try {
+	    // check system's home
+	    if (confDir != null) {
+		mf = loadFile(confDir + "mime.types");
+		if (mf != null)
+		    dbv.addElement(mf);
+	    }
+	} catch (SecurityException ex) {}
+
+	LogSupport.log("MimetypesFileTypeMap: load JAR");
+	// load from the app's jar file
+	loadAllResources(dbv, "META-INF/mime.types");
+
+	LogSupport.log("MimetypesFileTypeMap: load DEF");
+	mf = loadResource("/META-INF/mimetypes.default");
+
+	if (mf != null)
+	    dbv.addElement(mf);
+
+	DB = new MimeTypeFile[dbv.size()];
+	dbv.copyInto(DB);
+    }
+
+    /**
+     * Load from the named resource.
+     */
+    private MimeTypeFile loadResource(String name) {
+	InputStream clis = null;
+	try {
+	    clis = SecuritySupport.getResourceAsStream(this.getClass(), name);
+	    if (clis != null) {
+		MimeTypeFile mf = new MimeTypeFile(clis);
+		if (LogSupport.isLoggable())
+		    LogSupport.log("MimetypesFileTypeMap: successfully " +
+			"loaded mime types file: " + name);
+		return mf;
+	    } else {
+		if (LogSupport.isLoggable())
+		    LogSupport.log("MimetypesFileTypeMap: not loading " +
+			"mime types file: " + name);
+	    }
+	} catch (IOException e) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("MimetypesFileTypeMap: can't load " + name, e);
+	} catch (SecurityException sex) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("MimetypesFileTypeMap: can't load " + name, sex);
+	} finally {
+	    try {
+		if (clis != null)
+		    clis.close();
+	    } catch (IOException ex) { }	// ignore it
+	}
+	return null;
+    }
+
+    /**
+     * Load all of the named resource.
+     */
+    private void loadAllResources(Vector v, String name) {
+	boolean anyLoaded = false;
+	try {
+	    URL[] urls;
+	    ClassLoader cld = null;
+	    // First try the "application's" class loader.
+	    cld = SecuritySupport.getContextClassLoader();
+	    if (cld == null)
+		cld = this.getClass().getClassLoader();
+	    if (cld != null)
+		urls = SecuritySupport.getResources(cld, name);
+	    else
+		urls = SecuritySupport.getSystemResources(name);
+	    if (urls != null) {
+		if (LogSupport.isLoggable())
+		    LogSupport.log("MimetypesFileTypeMap: getResources");
+		for (int i = 0; i < urls.length; i++) {
+		    URL url = urls[i];
+		    InputStream clis = null;
+		    if (LogSupport.isLoggable())
+			LogSupport.log("MimetypesFileTypeMap: URL " + url);
+		    try {
+			clis = SecuritySupport.openStream(url);
+			if (clis != null) {
+			    v.addElement(new MimeTypeFile(clis));
+			    anyLoaded = true;
+			    if (LogSupport.isLoggable())
+				LogSupport.log("MimetypesFileTypeMap: " +
+				    "successfully loaded " +
+				    "mime types from URL: " + url);
+			} else {
+			    if (LogSupport.isLoggable())
+				LogSupport.log("MimetypesFileTypeMap: " +
+				    "not loading " +
+				    "mime types from URL: " + url);
+			}
+		    } catch (IOException ioex) {
+			if (LogSupport.isLoggable())
+			    LogSupport.log("MimetypesFileTypeMap: can't load " +
+						url, ioex);
+		    } catch (SecurityException sex) {
+			if (LogSupport.isLoggable())
+			    LogSupport.log("MimetypesFileTypeMap: can't load " +
+						url, sex);
+		    } finally {
+			try {
+			    if (clis != null)
+				clis.close();
+			} catch (IOException cex) { }
+		    }
+		}
+	    }
+	} catch (Exception ex) {
+	    if (LogSupport.isLoggable())
+		LogSupport.log("MimetypesFileTypeMap: can't load " + name, ex);
+	}
+
+	// if failed to load anything, fall back to old technique, just in case
+	if (!anyLoaded) {
+	    LogSupport.log("MimetypesFileTypeMap: !anyLoaded");
+	    MimeTypeFile mf = loadResource("/" + name);
+	    if (mf != null)
+		v.addElement(mf);
+	}
+    }
+
+    /**
+     * Load the named file.
+     */
+    private MimeTypeFile loadFile(String name) {
+	MimeTypeFile mtf = null;
+
+	try {
+	    mtf = new MimeTypeFile(name);
+	} catch (IOException e) {
+	    //	e.printStackTrace();
+	}
+	return mtf;
+    }
+
+    /**
+     * Construct a MimetypesFileTypeMap with programmatic entries
+     * added from the named file.
+     *
+     * @param mimeTypeFileName	the file name
+     * @exception	IOException	for errors reading the file
+     */
+    public MimetypesFileTypeMap(String mimeTypeFileName) throws IOException {
+	this();
+	DB[PROG] = new MimeTypeFile(mimeTypeFileName);
+    }
+
+    /**
+     * Construct a MimetypesFileTypeMap with programmatic entries
+     * added from the InputStream.
+     *
+     * @param is	the input stream to read from
+     */
+    public MimetypesFileTypeMap(InputStream is) {
+	this();
+	try {
+	    DB[PROG] = new MimeTypeFile(is);
+	} catch (IOException ex) {
+	    // XXX - really should throw it
+	}
+    }
+
+    /**
+     * Prepend the MIME type values to the registry.
+     *
+     * @param mime_types A .mime.types formatted string of entries.
+     */
+    public synchronized void addMimeTypes(String mime_types) {
+	// check to see if we have created the registry
+	if (DB[PROG] == null)
+	    DB[PROG] = new MimeTypeFile(); // make one
+
+	DB[PROG].appendToRegistry(mime_types);
+    }
+
+    /**
+     * Return the MIME type of the file object.
+     * The implementation in this class calls
+     * <code>getContentType(f.getName())</code>.
+     *
+     * @param f	the file
+     * @return	the file's MIME type
+     */
+    public String getContentType(File f) {
+	return this.getContentType(f.getName());
+    }
+
+    /**
+     * Return the MIME type based on the specified file name.
+     * The MIME type entries are searched as described above under
+     * <i>MIME types file search order</i>.
+     * If no entry is found, the type "application/octet-stream" is returned.
+     *
+     * @param filename	the file name
+     * @return		the file's MIME type
+     */
+    public synchronized String getContentType(String filename) {
+	int dot_pos = filename.lastIndexOf("."); // period index
+
+	if (dot_pos < 0)
+	    return defaultType;
+
+	String file_ext = filename.substring(dot_pos + 1);
+	if (file_ext.length() == 0)
+	    return defaultType;
+
+	for (int i = 0; i < DB.length; i++) {
+	    if (DB[i] == null)
+		continue;
+	    String result = DB[i].getMIMETypeString(file_ext);
+	    if (result != null)
+		return result;
+	}
+	return defaultType;
+    }
+
+    /**
+     * for debugging...
+     *
+    public static void main(String[] argv) throws Exception {
+	MimetypesFileTypeMap map = new MimetypesFileTypeMap();
+	System.out.println("File " + argv[0] + " has MIME type " +
+						map.getContentType(argv[0]));
+	System.exit(0);
+    }
+    */
+}
diff --git a/android/activation/src/main/java/javax/activation/SecuritySupport.java b/android/activation/src/main/java/javax/activation/SecuritySupport.java
new file mode 100644
index 0000000..c37174b
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/SecuritySupport.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.security.*;
+import java.net.*;
+import java.io.*;
+import java.util.*;
+
+/**
+ * Security related methods that only work on J2SE 1.2 and newer.
+ */
+class SecuritySupport {
+
+    private SecuritySupport() {
+	// private constructor, can't create an instance
+    }
+
+    public static ClassLoader getContextClassLoader() {
+	return (ClassLoader)
+		AccessController.doPrivileged(new PrivilegedAction() {
+	    public Object run() {
+		ClassLoader cl = null;
+		try {
+		    cl = Thread.currentThread().getContextClassLoader();
+		} catch (SecurityException ex) { }
+		return cl;
+	    }
+	});
+    }
+
+    public static InputStream getResourceAsStream(final Class c,
+				final String name) throws IOException {
+	try {
+	    return (InputStream)
+		AccessController.doPrivileged(new PrivilegedExceptionAction() {
+		    public Object run() throws IOException {
+			return c.getResourceAsStream(name);
+		    }
+		});
+	} catch (PrivilegedActionException e) {
+	    throw (IOException)e.getException();
+	}
+    }
+
+    public static URL[] getResources(final ClassLoader cl, final String name) {
+	return (URL[])
+		AccessController.doPrivileged(new PrivilegedAction() {
+	    public Object run() {
+		URL[] ret = null;
+		try {
+		    List v = new ArrayList();
+		    Enumeration e = cl.getResources(name);
+		    while (e != null && e.hasMoreElements()) {
+			URL url = (URL)e.nextElement();
+			if (url != null)
+			    v.add(url);
+		    }
+		    if (v.size() > 0) {
+			ret = new URL[v.size()];
+			ret = (URL[])v.toArray(ret);
+		    }
+		} catch (IOException ioex) {
+		} catch (SecurityException ex) { }
+		return ret;
+	    }
+	});
+    }
+
+    public static URL[] getSystemResources(final String name) {
+	return (URL[])
+		AccessController.doPrivileged(new PrivilegedAction() {
+	    public Object run() {
+		URL[] ret = null;
+		try {
+		    List v = new ArrayList();
+		    Enumeration e = ClassLoader.getSystemResources(name);
+		    while (e != null && e.hasMoreElements()) {
+			URL url = (URL)e.nextElement();
+			if (url != null)
+			    v.add(url);
+		    }
+		    if (v.size() > 0) {
+			ret = new URL[v.size()];
+			ret = (URL[])v.toArray(ret);
+		    }
+		} catch (IOException ioex) {
+		} catch (SecurityException ex) { }
+		return ret;
+	    }
+	});
+    }
+
+    public static InputStream openStream(final URL url) throws IOException {
+	try {
+	    return (InputStream)
+		AccessController.doPrivileged(new PrivilegedExceptionAction() {
+		    public Object run() throws IOException {
+			return url.openStream();
+		    }
+		});
+	} catch (PrivilegedActionException e) {
+	    throw (IOException)e.getException();
+	}
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/URLDataSource.java b/android/activation/src/main/java/javax/activation/URLDataSource.java
new file mode 100644
index 0000000..ad2a13c
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/URLDataSource.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.net.URL;
+import java.net.URLConnection;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+
+/**
+ * The URLDataSource class provides an object that wraps a <code>URL</code>
+ * object in a DataSource interface. URLDataSource simplifies the handling
+ * of data described by URLs within the JavaBeans Activation Framework
+ * because this class can be used to create new DataHandlers. <i>NOTE: The
+ * DataHandler object creates a URLDataSource internally,
+ * when it is constructed with a URL.</i>
+ *
+ * @see javax.activation.DataSource
+ * @see javax.activation.DataHandler
+ */
+public class URLDataSource implements DataSource {
+    private URL url = null;
+    private URLConnection url_conn = null;
+
+    /**
+     * URLDataSource constructor. The URLDataSource class will
+     * not open a connection to the URL until a method requiring it
+     * to do so is called.
+     *
+     * @param url The URL to be encapsulated in this object.
+     */
+    public URLDataSource(URL url) {
+	this.url = url;
+    }
+
+    /**
+     * Returns the value of the URL content-type header field.
+     * It calls the URL's <code>URLConnection.getContentType</code> method
+     * after retrieving a URLConnection object.
+     * <i>Note: this method attempts to call the <code>openConnection</code>
+     * method on the URL. If this method fails, or if a content type is not
+     * returned from the URLConnection, getContentType returns
+     * "application/octet-stream" as the content type.</i>
+     *
+     * @return the content type.
+     */
+    public String getContentType() {
+	String type = null;
+
+	try {
+	    if (url_conn == null)
+		url_conn = url.openConnection();
+	} catch (IOException e) { }
+	
+	if (url_conn != null)
+	    type = url_conn.getContentType();
+
+	if (type == null)
+	    type = "application/octet-stream";
+	
+	return type;
+    }
+
+    /**
+     * Calls the <code>getFile</code> method on the URL used to
+     * instantiate the object.
+     *
+     * @return the result of calling the URL's getFile method.
+     */
+    public String getName() {
+	return url.getFile();
+    }
+
+    /**
+     * The getInputStream method from the URL. Calls the
+     * <code>openStream</code> method on the URL.
+     *
+     * @return the InputStream.
+     */
+    public InputStream getInputStream() throws IOException {
+	return url.openStream();
+    }
+
+    /**
+     * The getOutputStream method from the URL. First an attempt is
+     * made to get the URLConnection object for the URL. If that
+     * succeeds, the getOutputStream method on the URLConnection
+     * is returned.
+     *
+     * @return the OutputStream.
+     */
+    public OutputStream getOutputStream() throws IOException {
+	// get the url connection if it is available
+	url_conn = url.openConnection();
+	
+	if (url_conn != null) {
+	    url_conn.setDoOutput(true);
+	    return url_conn.getOutputStream();
+	} else
+	    return null;
+    }
+
+    /**
+     * Return the URL used to create this DataSource.
+     *
+     * @return The URL.
+     */
+    public URL getURL() {
+	return url;
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/UnsupportedDataTypeException.java b/android/activation/src/main/java/javax/activation/UnsupportedDataTypeException.java
new file mode 100644
index 0000000..b24a788
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/UnsupportedDataTypeException.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package javax.activation;
+
+import java.io.IOException;
+
+/**
+ * Signals that the requested operation does not support the
+ * requested data type.
+ *
+ * @see javax.activation.DataHandler
+ */
+
+public class UnsupportedDataTypeException extends IOException {
+    /**
+     * Constructs an UnsupportedDataTypeException with no detail
+     * message.
+     */
+    public UnsupportedDataTypeException() {
+	super();
+    }
+
+    /**
+     * Constructs an UnsupportedDataTypeException with the specified 
+     * message.
+     * 
+     * @param s The detail message.
+     */
+    public UnsupportedDataTypeException(String s) {
+	super(s);
+    }
+}
diff --git a/android/activation/src/main/java/javax/activation/package.html b/android/activation/src/main/java/javax/activation/package.html
new file mode 100644
index 0000000..37bb34e
--- /dev/null
+++ b/android/activation/src/main/java/javax/activation/package.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<TITLE>javax.activation package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+The JavaBeans(TM) Activation Framework is used by the JavaMail(TM)
+API to manage MIME data.
+</P>
+
+</BODY>
+</HTML>
diff --git a/android/activation/src/main/resources/META-INF/mailcap.default b/android/activation/src/main/resources/META-INF/mailcap.default
new file mode 100644
index 0000000..73ce2fc
--- /dev/null
+++ b/android/activation/src/main/resources/META-INF/mailcap.default
@@ -0,0 +1,9 @@
+#
+# This is a very simple 'mailcap' file
+#
+# No default viewers are included so these entries are commented out.
+#
+#image/gif;;		x-java-view=com.sun.activation.viewers.ImageViewer
+#image/jpeg;;		x-java-view=com.sun.activation.viewers.ImageViewer
+#text/*;;		x-java-view=com.sun.activation.viewers.TextViewer
+#text/*;;		x-java-edit=com.sun.activation.viewers.TextEditor
diff --git a/android/activation/src/main/resources/META-INF/mimetypes.default b/android/activation/src/main/resources/META-INF/mimetypes.default
new file mode 100644
index 0000000..1b4056b
--- /dev/null
+++ b/android/activation/src/main/resources/META-INF/mimetypes.default
@@ -0,0 +1,25 @@
+#
+# A simple, old format, mime.types file
+#
+text/html		html htm HTML HTM
+text/plain		txt text TXT TEXT
+image/gif		gif GIF
+image/ief		ief
+image/jpeg		jpeg jpg jpe JPG
+image/tiff		tiff tif
+image/png		png PNG
+image/x-xwindowdump	xwd
+application/postscript	ai eps ps
+application/rtf		rtf
+application/x-tex	tex
+application/x-texinfo	texinfo texi
+application/x-troff	t tr roff
+audio/basic		au
+audio/midi		midi mid
+audio/x-aifc		aifc
+audio/x-aiff            aif aiff
+audio/x-mpeg		mpeg mpg
+audio/x-wav             wav
+video/mpeg		mpeg mpg mpe
+video/quicktime		qt mov
+video/x-msvideo		avi
diff --git a/android/mail/pom.xml b/android/mail/pom.xml
new file mode 100644
index 0000000..50a9ee1
--- /dev/null
+++ b/android/mail/pom.xml
@@ -0,0 +1,151 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<!--
+    This project builds the JavaMail Android jar file.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>android</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>android-mail</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API for Android</name>
+
+    <properties>
+	<mail.extensionName>
+	    jakarta.mail.android
+	</mail.extensionName>
+	<mail.specificationTitle>
+	    JavaMail(TM) API Design Specification
+	</mail.specificationTitle>
+	<mail.implementationTitle>
+	    javax.mail.android
+	</mail.implementationTitle>
+	<mail.packages.export>
+	    javax.mail.*; version=${mail.spec.version},
+	    com.sun.mail.imap; version=${mail.osgiversion},
+	    com.sun.mail.imap.protocol; version=${mail.osgiversion},
+	    com.sun.mail.iap; version=${mail.osgiversion},
+	    com.sun.mail.pop3; version=${mail.osgiversion},
+	    com.sun.mail.smtp; version=${mail.osgiversion},
+	    com.sun.mail.util; version=${mail.osgiversion},
+	    com.sun.mail.util.logging; version=${mail.osgiversion},
+	    com.sun.mail.handlers; version=${mail.osgiversion}
+	</mail.packages.export>
+    </properties>
+
+    <build>
+	<resources>
+	    <resource>
+		<directory>src/main/resources</directory>
+		<filtering>true</filtering>
+	    </resource>
+	</resources>
+	<plugins>
+	    <plugin>
+		<artifactId>maven-dependency-plugin</artifactId>
+		<executions>
+		    <execution>
+			<!-- download the binaries -->
+			<id>get-binaries</id>
+			<phase>compile</phase>
+			<goals>
+			    <goal>unpack</goal>
+			</goals>
+		    </execution>
+		    <execution>
+			<!-- download the sources -->
+			<id>get-sources</id>
+			<phase>compile</phase>
+			<goals>
+			    <goal>unpack</goal>
+			</goals>
+			<configuration>
+			    <artifactItems>
+				<artifactItem>
+				    <groupId>com.sun.mail</groupId>
+				    <artifactId>jakarta.mail</artifactId>
+				    <version>${mail.version}</version>
+				    <classifier>sources</classifier>
+				    <outputDirectory>
+					${project.build.directory}/sources
+				    </outputDirectory>
+				</artifactItem>
+			    </artifactItems>
+			</configuration>
+		    </execution>
+		</executions>
+		<configuration>
+		    <artifactItems>
+			<artifactItem>
+			    <groupId>com.sun.mail</groupId>
+			    <artifactId>jakarta.mail</artifactId>
+			    <version>${mail.version}</version>
+			</artifactItem>
+		    </artifactItems>
+		    <outputDirectory>
+			${project.build.outputDirectory}
+		    </outputDirectory>
+		    <excludes>
+			com/sun/mail/handlers/handler_base.*,
+			com/sun/mail/handlers/image_*,
+			com/sun/mail/auth/OAuth2Sasl*,
+			com/sun/mail/imap/protocol/IMAPSaslAuthenticator*,
+			com/sun/mail/smtp/SMTPSaslAuthenticator*
+		    </excludes>
+		</configuration>
+	    </plugin>
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<configuration>
+		    <finalName>${project.artifactId}</finalName>
+		    <archive>
+			<manifestFile>
+			  ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+			</manifestFile>
+		    </archive>
+		</configuration>
+	    </plugin>
+	</plugins>
+    </build>
+
+    <dependencies>
+	<dependency>
+	    <groupId>com.sun.mail</groupId>
+	    <artifactId>android-activation</artifactId>
+	    <version>${mail.version}</version>
+	</dependency>
+	<dependency>
+	    <groupId>junit</groupId>
+	    <artifactId>junit</artifactId>
+	    <version>4.12</version>
+	    <scope>test</scope>
+	    <optional>true</optional>
+	</dependency>
+    </dependencies>
+</project>
diff --git a/android/mail/src/main/java/com/sun/mail/handlers/handler_base.java b/android/mail/src/main/java/com/sun/mail/handlers/handler_base.java
new file mode 100644
index 0000000..dae159f
--- /dev/null
+++ b/android/mail/src/main/java/com/sun/mail/handlers/handler_base.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import java.io.IOException;
+import javax.activation.*;
+
+/**
+ * Base class for other DataContentHandlers.
+ */
+public abstract class handler_base implements DataContentHandler {
+
+    /**
+     * Return an array of ActivationDataFlavors that we support.
+     * Usually there will be only one.
+     *
+     * @return	array of ActivationDataFlavors that we support
+     */
+    protected abstract ActivationDataFlavor[] getDataFlavors();
+
+    /**
+     * Given the flavor that matched, return the appropriate type of object.
+     * Usually there's only one flavor so just call getContent.
+     *
+     * @param	aFlavor	the ActivationDataFlavor
+     * @param	ds	DataSource containing the data
+     * @return	the object
+     * @exception	IOException	for errors reading the data
+     */
+    protected Object getData(ActivationDataFlavor aFlavor, DataSource ds)
+				throws IOException {
+	return getContent(ds);
+    }
+
+    /**
+     * Return the DataFlavors for this <code>DataContentHandler</code>.
+     *
+     * @return The DataFlavors
+     */
+    public ActivationDataFlavor[] getTransferDataFlavors() {
+	return getDataFlavors().clone();
+    }
+
+    /**
+     * Return the Transfer Data of type DataFlavor from InputStream.
+     *
+     * @param	df	The DataFlavor
+     * @param	ds	The DataSource corresponding to the data
+     * @return	the object
+     * @exception	IOException	for errors reading the data
+     */
+    public Object getTransferData(ActivationDataFlavor df, DataSource ds) 
+			throws IOException {
+	ActivationDataFlavor[] adf = getDataFlavors();
+	for (int i = 0; i < adf.length; i++) {
+	    // use ActivationDataFlavor.equals, which properly
+	    // ignores Content-Type parameters in comparison
+	    if (adf[i].equals(df))
+		return getData(adf[i], ds);
+	}
+	return null;
+    }
+}
diff --git a/android/pom.xml b/android/pom.xml
new file mode 100644
index 0000000..ed98685
--- /dev/null
+++ b/android/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>android</artifactId>
+    <packaging>pom</packaging>
+    <name>Android support for JavaMail</name>
+
+    <modules>
+	<module>activation</module>
+	<module>mail</module>
+    </modules>
+
+    <properties>
+	<!--
+	    The minimum required Android API version.
+	    This corresponds to Android KitKat.
+	    https://en.wikipedia.org/wiki/Android_version_history
+	    Older versions may also work but this seems old enough.
+	    See the Maven repository for exact versions available:
+	    https://repo1.maven.org/maven2/net/sf/androidscents/signature/
+	    This only checks API signatures; it doesn't imply that it
+	    works correctly in a runtime of that version.
+	-->
+	<android.api.level>19</android.api.level>
+	<android.version>4.4_r1</android.version>
+    </properties>
+
+    <build>
+	<plugins>
+	    <plugin>
+		<groupId>org.codehaus.mojo</groupId>
+		<artifactId>animal-sniffer-maven-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>check-sig</id>
+			<phase>package</phase>
+			<goals>
+			    <goal>check</goal>
+			</goals>
+		    </execution>
+		</executions>
+		<configuration>
+		    <signature>
+			<groupId>net.sf.androidscents.signature</groupId>
+			<artifactId>android-api-level-${android.api.level}</artifactId>
+			<version>${android.version}</version>
+		    </signature>
+		</configuration>
+	    </plugin>
+	</plugins>
+
+	<pluginManagement>
+	    <plugins>
+		<plugin>
+		    <groupId>org.codehaus.mojo</groupId>
+		    <artifactId>animal-sniffer-maven-plugin</artifactId>
+		    <version>1.16</version>
+		</plugin>
+	    </plugins>
+	</pluginManagement>
+    </build>
+
+</project>
diff --git a/ant-common.xml b/ant-common.xml
new file mode 100644
index 0000000..25a01bb
--- /dev/null
+++ b/ant-common.xml
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<!--
+Contains common definition that are platform dependent
+-->
+<target name="tools.init">
+    <condition property="windows">
+        <os family="windows" />
+    </condition>
+    <condition property="unix">
+        <os family="unix" />
+    </condition>
+    <condition property="solaris">
+        <os name="SunOS"/>
+    </condition>
+    <condition property="linux">
+        <os name="Linux"/>
+    </condition>
+    <condition property="mac">
+        <os name="Mac OS X"/>
+    </condition>
+</target>
+<target name="tools.init.windows" if="windows">
+    <property name="JAVAH" value="${java.home}/../bin/javah.exe"/>
+    <property name="MAKE" value="gmake"/>
+</target>
+<target name="tools.init.unix" if="unix" unless="mac">
+    <property name="JAVAH" value="${java.home}/../bin/javah"/>
+    <property name="MAKE" value="gmake"/>
+</target>
+    <target name="tools.init.mac" if="mac">
+    <property name="JAVAH" value="${java.home}/bin/javah"/>
+    <property name="MAKE" value="make"/>
+</target>
+<target name="tools.init.platform" 
+    depends="tools.init, tools.init.windows, tools.init.unix, tools.init.mac">
+</target>
+    
+
+<!--
+    Definitions for Checkstyle
+-->
+<target name="checkstyle"
+        description="Generates a report of coding convention violations.">
+
+    <!-- Get properties from environment -->
+    <property environment="env"/>
+
+    <!-- Set default values if these are not in the environment / cmd line -->
+    <!-- Order of preference is cmd line, environment, default -->
+    <property name="env.JWS_EXTERNAL_COMPONENTS_DIR" value="/net/koori.sfbay/onestop/s1aspe/8.0/external"/>
+    <property name="JWS_EXTERNAL_COMPONENTS_DIR" value="${env.JWS_EXTERNAL_COMPONENTS_DIR}"/>
+    <property name="env.CHECKSTYLE_HOME" value="${JWS_EXTERNAL_COMPONENTS_DIR}/checkstyle"/>
+
+    <!-- CHECKSTYLE_HOME contains Checkstyle jars and config files -->
+    <property name="CHECKSTYLE_HOME" value="${env.CHECKSTYLE_HOME}"/>
+
+    <taskdef resource="checkstyletask.properties" classpath="${CHECKSTYLE_HOME}/checkstyle-all-3.5.jar:${CHECKSTYLE_HOME}/checkstyle-sun-modules.jar"/>
+
+
+    <!-- CHECKSTYLE_DIR, CHECKSTYLE_FILES, CHECKSTYLE_STYLESHEET, 
+         CHECKSTYLE_CONFIG and CHECKSTYLE_OUTPUT variables have defaults below.
+	 They can be overridden by setting corresponding env variables 
+	 in the shell, or from the command line:
+         % ant -DCHECKSTYLE_DIR=somedir -DCHECKSTYLE_FILES="*.java" ....
+         (command-line overrides environment vars).  -->
+    <condition property="CHECKSTYLE_FILES" 
+	       value="${env.CHECKSTYLE_FILES}">
+        <isset property="env.CHECKSTYLE_FILES"/>
+    </condition>
+    <property name="CHECKSTYLE_FILES" value="**/*.java"/>
+
+    <condition property="CHECKSTYLE_DIR" 
+	       value="${env.CHECKSTYLE_DIR}">
+        <isset property="env.CHECKSTYLE_DIR"/>
+    </condition>
+    <property name="CHECKSTYLE_DIR" value="."/>
+
+    <condition property="CHECKSTYLE_STYLESHEET" 
+	       value="${env.CHECKSTYLE_STYLESHEET}">
+        <isset property="env.CHECKSTYLE_STYLESHEET"/>
+    </condition>
+    <property name="CHECKSTYLE_STYLESHEET" 
+              value="${CHECKSTYLE_HOME}/checkstyle-noframes-sorted.xsl"/>
+
+    <condition property="CHECKSTYLE_OUTPUT" 
+	       value="${env.CHECKSTYLE_OUTPUT}">
+        <isset property="env.CHECKSTYLE_OUTPUT"/>
+    </condition>
+    <property name="CHECKSTYLE_OUTPUT" value="checkstyle-report.html"/>
+
+    <condition property="CHECKSTYLE_CONFIG" 
+	       value="${env.CHECKSTYLE_CONFIG}">
+        <isset property="env.CHECKSTYLE_CONFIG"/>
+    </condition>
+    <property name="CHECKSTYLE_CONFIG" value="${CHECKSTYLE_HOME}/as-checks.xml"/>
+
+
+    <echo message="Running Checkstyle on ${CHECKSTYLE_DIR}/${CHECKSTYLE_FILES} using configuration ${CHECKSTYLE_CONFIG} and writing report to ${CHECKSTYLE_OUTPUT}"/>
+
+    <checkstyle config="${CHECKSTYLE_CONFIG}"
+                failOnViolation="false">
+        <formatter type="xml" tofile="checkstyle-report.xml"/>
+        <fileset dir="${CHECKSTYLE_DIR}" includes="${CHECKSTYLE_FILES}"/>
+    </checkstyle>
+
+    <style in="checkstyle-report.xml" out="${CHECKSTYLE_OUTPUT}" style="${CHECKSTYLE_STYLESHEET}"/>
+
+    <delete file="checkstyle-report.xml"/>
+
+</target>
+
+
+<target name="-push-to-maven-init" description="define a task for pushing bits to the maven repository">
+    <echo>${maven.repo.local}/com.sun.wts.tools.mri/jars/maven-repository-importer-${glassfish.maven_repository_importer.version}.jar</echo>
+    <taskdef resource="maven-repository-importer.properties">
+        <classpath>
+            <pathelement path="${maven.repo.local}/com.sun.wts.tools.mri/jars/maven-repository-importer-${glassfish.maven_repository_importer.version}.jar" />
+        </classpath>
+    </taskdef>
+</target>
+
+<!--
+  import files to CVS
+  
+  For example, <cvs-import src="build/doc" dest="jaxb/www/doc" />
+-->
+<macrodef name="cvs-import">
+    <attribute name="src"/>
+    <attribute name="dest"/>
+    <sequential>
+        <tstamp />
+        <echo>importing to CVS...</echo>
+        <cvs dest="@{src}">
+            <commandline>
+                <argument value="-d${glassfish.cvsroot}"/>
+                <argument line="-z9 import -ko"/>
+                <argument value="-W"/>
+                <argument line="*.jar -kb"/>
+                <argument value="-m"/>
+                <argument value="deploying new jars to the java.net maven repository"/>
+                
+                <argument value="@{dest}"/>
+                <argument line="deployment-to-maven-repository t${DSTAMP}${TSTAMP}" />
+            </commandline>
+        </cvs>
+    </sequential>
+</macrodef>
diff --git a/build.properties b/build.properties
new file mode 100644
index 0000000..25e1398
--- /dev/null
+++ b/build.properties
@@ -0,0 +1,108 @@
+#
+# Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+### Component Properties ###
+src.dir=mail/src/main/java
+resources.dir=mail/src/main/resources
+component.classes.dir=mail/target/classes
+### Subcomponent Properties ###
+# dsn
+src.dsn.dir=dsn/src/main/java
+resources.dsn.dir=dsn/src/main/resources
+component.classes.dsn.dir=dsn/target/classes
+# gimap
+src.gimap.dir=gimap/src/main/java
+resources.gimap.dir=gimap/src/main/resources
+component.classes.gimap.dir=gimap/target/classes
+# imap
+resources.imap.dir=imap/src/main/resources
+component.classes.imap.dir=imap/target/classes
+# pop3
+resources.pop3.dir=pop3/src/main/resources
+component.classes.pop3.dir=pop3/target/classes
+# smtp
+resources.smtp.dir=smtp/src/main/resources
+component.classes.smtp.dir=smtp/target/classes
+# mbox
+src.mbox.dir=mbox/src/main/java
+resources.mbox.dir=mbox/src/main/resources
+component.classes.mbox.dir=mbox/target/classes
+# demo
+src.demo.dir=demo/src/main/java
+component.classes.demo.dir=demo/target/classes
+# client
+src.client.dir=client/src/main/java
+component.classes.client.dir=client/target/classes
+# servlet
+src.servlet.dir=servlet/src/main/java
+component.classes.servlet.dir=servlet/target/classes
+# webapp
+src.webapp.dir=webapp/src/main/java
+docroot.webapp.dir=webapp/src/main/webapp
+# taglib
+src.taglib.dir=taglib/src/main/java
+resources.taglib.dir=taglib/src/main/resources
+# logging
+src.logging.dir=logging/src/main/java
+component.classes.logging.dir=logging/target/classes
+# legal
+resources.legal.dir=mail/src/main/resources
+#
+maven.netbeans.exec.build=target/classes
+
+# needed to build everything on JDK 1.5
+activation.jar=activation.jar
+# needed to build servlet and webapp demo programs
+javaee.jar=javaee.jar
+# external URL references for javadocs
+javase.url=http://docs.oracle.com/javase/1.5.0/docs/api
+jaf.url=http://docs.oracle.com/javase/6/docs/api
+
+### Additonal Component Properties for standalone release ###
+release.dir=target/release
+release.specversion=1.5
+release.version=1.5.1-SNAPSHOT
+release.mail.jar=${release.dir}/mail.jar
+release.mailapi.jar=${release.dir}/lib/mailapi.jar
+release.imap.jar=${release.dir}/lib/imap.jar
+release.smtp.jar=${release.dir}/lib/smtp.jar
+release.pop3.jar=${release.dir}/lib/pop3.jar
+release.gimap.jar=${release.dir}/lib/gimap.jar
+release.dsn.jar=${release.dir}/lib/dsn.jar
+release.mbox.jar=${release.dir}/lib/mbox.jar
+release.docs.dir=${release.dir}/docs
+release.javadocs.dir=${release.docs.dir}/javadocs
+
+# Following from bootstrap/project.properties
+
+# To support NetBeans' default compile single file target.
+build.dir=build
+build.classes.dir=${build.dir}/classes
+
+## Maven repository importer
+glassfish.maven_repository_importer.version=1.1
+
+###########################################################
+#  Compilation Flags                                      #
+###########################################################
+javac.debug=on
+javac.optimize=off
+javac.deprecation=off
+javac.source=1.5
+javac.target=1.5
+
+### Use ant.verbose=-verbose for debugging ant targets
+ant.verbose=
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..9f16937
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,588 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<!DOCTYPE project [
+  <!ENTITY commonBuild SYSTEM "ant-common.xml">
+]>
+
+<!--
+
+    NOTE: JavaMail is now built using Maven:
+    https://java.net/projects/javamail/pages/BuildInstructions
+
+    This ant build file is obsolete and may not work.
+
+-->
+
+<project name="JavaMail" default="all" basedir=".">
+
+<!-- ========== Initialize Properties =================================== -->
+
+    <!--
+        component.name: required property.  the value should be the
+                        name of the component directory
+    -->
+    <property name="component.name" value="mail"/>
+
+    <property file="./build.properties"/>
+    
+    &commonBuild;
+
+    <!-- all -->
+    <target name="all" depends="compile, assemble"
+            description="Build entire component">
+    </target>
+
+    <!-- build -->
+    <target name="build" depends="compile, assemble"
+            description="Build entire component">
+    </target>
+
+    <!-- init. Initialization involves creating publishing directories and
+         OS specific targets. -->
+    <target name="init" description="${component.name} initialization">
+        <tstamp>
+            <format property="start.time" pattern="MM/dd/yyyy hh:mm aa"/>
+        </tstamp>
+        <echo message="Building component ${component.name}"/>
+        <mkdir dir="${component.classes.dir}"/>
+    </target>
+
+    <!-- compile -->
+    <target name="compile" depends="init"
+            description="Compile JavaMail sources">
+
+        <copy file="${resources.dir}/javax/mail/Version.java"
+		toFile="${src.dir}/javax/mail/Version.java">
+            <filterset begintoken="$${" endtoken="}">
+                <filter token="mail.version" value="${release.version}"/>
+            </filterset>
+        </copy>
+        <javac srcdir="${src.dir}"
+               destdir="${component.classes.dir}"
+               debug="${javac.debug}"
+               optimize="${javac.optimize}"
+               source="${javac.source}"
+               deprecation="${javac.deprecation}"
+               failonerror="true"
+               target="${javac.target}"
+	       includeantruntime="false">
+            <classpath>
+                <pathelement location="${activation.jar}"/>
+            </classpath>
+            <include name="javax/mail/**"/>
+            <include name="com/sun/mail/**"/>
+        </javac>
+    </target>
+    
+    <!-- prepare manifest files for jars -->
+    <target name="cook-manifest" depends="init"
+            description="Generate MANIFEST.MF files">
+	<!--
+
+	    Could make this simpler with ant-contrib foreach task...
+
+	<foreach list=",imap.,pop3.,smtp.,dsn." target="cook-manifest-file"
+	    param="mf"/>
+	-->
+        <mkdir dir="${component.classes.dir}/manifest"/>
+        <copy file="${resources.dir}/META-INF/MANIFEST.MF"
+		tofile="${component.classes.dir}/manifest/MANIFEST.MF">
+            <filterset begintoken="{" endtoken="}">
+		<filter token="mail.spec.version"
+		    value="${release.specversion}"/>
+		<filter token="mail.version" value="${release.version}"/>
+            </filterset>
+        </copy>
+        <mkdir dir="${component.classes.imap.dir}/manifest"/>
+        <copy file="${resources.imap.dir}/META-INF/MANIFEST.MF"
+		tofile="${component.classes.imap.dir}/manifest/MANIFEST.MF">
+            <filterset begintoken="{" endtoken="}">
+		<filter token="mail.spec.version"
+		    value="${release.specversion}"/>
+		<filter token="mail.version" value="${release.version}"/>
+            </filterset>
+        </copy>
+        <mkdir dir="${component.classes.pop3.dir}/manifest"/>
+        <copy file="${resources.pop3.dir}/META-INF/MANIFEST.MF"
+		tofile="${component.classes.pop3.dir}/manifest/MANIFEST.MF">
+            <filterset begintoken="{" endtoken="}">
+		<filter token="mail.spec.version"
+		    value="${release.specversion}"/>
+		<filter token="mail.version" value="${release.version}"/>
+            </filterset>
+        </copy>
+        <mkdir dir="${component.classes.smtp.dir}/manifest"/>
+        <copy file="${resources.smtp.dir}/META-INF/MANIFEST.MF"
+		tofile="${component.classes.smtp.dir}/manifest/MANIFEST.MF">
+            <filterset begintoken="{" endtoken="}">
+		<filter token="mail.spec.version"
+		    value="${release.specversion}"/>
+		<filter token="mail.version" value="${release.version}"/>
+            </filterset>
+        </copy>
+        <mkdir dir="${component.classes.dsn.dir}/manifest"/>
+        <copy file="${resources.dsn.dir}/META-INF/MANIFEST.MF"
+		tofile="${component.classes.dsn.dir}/manifest/MANIFEST.MF">
+            <filterset begintoken="{" endtoken="}">
+		<filter token="mail.spec.version"
+		    value="${release.specversion}"/>
+		<filter token="mail.version" value="${release.version}"/>
+            </filterset>
+        </copy>
+        <mkdir dir="${component.classes.gimap.dir}/manifest"/>
+        <copy file="${resources.gimap.dir}/META-INF/MANIFEST.MF"
+		tofile="${component.classes.gimap.dir}/manifest/MANIFEST.MF">
+            <filterset begintoken="{" endtoken="}">
+		<filter token="mail.spec.version"
+		    value="${release.specversion}"/>
+		<filter token="mail.version" value="${release.version}"/>
+            </filterset>
+        </copy>
+    </target>
+
+    <!--
+
+	This could be used with foreach, above.
+	XXX - do nested variable references work?
+
+    <target name="cook-manifest-file">
+        <mkdir dir="${component.classes.${mf}dir}/manifest"/>
+        <copy file="${resources.${mf}dir}/META-INF/MANIFEST.MF"
+		tofile="${component.classes.${mf}dir}/manifest/MANIFEST.MF">
+            <filterset begintoken="{" endtoken="}">
+		<filter token="mail.spec.version"
+		    value="${release.specversion}"/>
+		<filter token="mail.version" value="${release.version}"/>
+            </filterset>
+        </copy>
+    </target>
+    -->
+
+    <!-- gimap compile -->
+    <target name="gimapcompile" depends="init"
+            description="Compile com/sun/mail/gimap sources">
+
+        <mkdir dir="${component.classes.gimap.dir}"/>
+        <javac srcdir="${src.gimap.dir}"
+               destdir="${component.classes.gimap.dir}"
+               debug="${javac.debug}"
+               optimize="${javac.optimize}"
+               source="${javac.source}"
+               deprecation="${javac.deprecation}"
+               failonerror="true"
+               target="${javac.target}"
+	       includeantruntime="false">
+            <classpath>
+                <pathelement location="${component.classes.dir}"/>
+                <pathelement location="${activation.jar}"/>
+            </classpath>
+            <include name="com/sun/mail/gimap/**"/>
+        </javac>
+    </target>
+
+    <!-- dsn compile -->
+    <target name="dsncompile" depends="init"
+            description="Compile com/sun/mail/dsn sources">
+
+        <mkdir dir="${component.classes.dsn.dir}"/>
+        <javac srcdir="${src.dsn.dir}"
+               destdir="${component.classes.dsn.dir}"
+               debug="${javac.debug}"
+               optimize="${javac.optimize}"
+               source="${javac.source}"
+               deprecation="${javac.deprecation}"
+               failonerror="true"
+               target="${javac.target}"
+	       includeantruntime="false">
+            <classpath>
+                <pathelement location="${component.classes.dir}"/>
+                <pathelement location="${activation.jar}"/>
+            </classpath>
+            <include name="com/sun/mail/dsn/**"/>
+        </javac>
+    </target>
+
+    <!-- demo compile -->
+    <target name="democompile" depends="init"
+            description="Compile demo sources">
+
+        <mkdir dir="${component.classes.demo.dir}"/>
+        <javac srcdir="${src.demo.dir}"
+               destdir="${component.classes.demo.dir}"
+               debug="${javac.debug}"
+               optimize="${javac.optimize}"
+               source="${javac.source}"
+               deprecation="${javac.deprecation}"
+               failonerror="true"
+               target="${javac.target}"
+	       includeantruntime="false">
+            <classpath>
+                <pathelement location="${component.classes.dir}"/>
+                <pathelement location="${activation.jar}"/>
+            </classpath>
+            <include name="*"/>
+        </javac>
+
+        <mkdir dir="${component.classes.client.dir}"/>
+        <javac srcdir="${src.client.dir}"
+               destdir="${component.classes.client.dir}"
+               debug="${javac.debug}"
+               optimize="${javac.optimize}"
+               source="${javac.source}"
+               deprecation="${javac.deprecation}"
+               failonerror="true"
+               target="${javac.target}"
+	       includeantruntime="false">
+            <classpath>
+                <pathelement location="${component.classes.dir}"/>
+                <pathelement location="${activation.jar}"/>
+            </classpath>
+            <include name="*"/>
+        </javac>
+
+        <mkdir dir="${component.classes.servlet.dir}"/>
+        <javac srcdir="${src.servlet.dir}"
+               destdir="${component.classes.servlet.dir}"
+               debug="${javac.debug}"
+               optimize="${javac.optimize}"
+               source="${javac.source}"
+               deprecation="${javac.deprecation}"
+               failonerror="true"
+               target="${javac.target}"
+	       includeantruntime="false">
+            <classpath>
+                <pathelement location="${component.classes.dir}"/>
+                <pathelement location="${activation.jar}"/>
+                <pathelement location="${javaee.jar}"/>
+            </classpath>
+            <include name="*"/>
+        </javac>
+
+        <mkdir dir="${component.classes.logging.dir}"/>
+        <javac srcdir="${src.logging.dir}"
+               destdir="${component.classes.logging.dir}"
+               debug="${javac.debug}"
+               optimize="${javac.optimize}"
+               source="${javac.source}"
+               deprecation="${javac.deprecation}"
+               failonerror="true"
+               target="${javac.target}"
+	       includeantruntime="false">
+            <classpath>
+                <pathelement location="${component.classes.dir}"/>
+                <pathelement location="${component.classes.demo.dir}"/>
+                <pathelement location="${activation.jar}"/>
+            </classpath>
+            <include name="*"/>
+        </javac>
+
+	<!-- XXX - still need to add rules to build webapp -->
+
+    </target>
+
+    <!-- assemble -->
+    <target name="assemble" depends="jar"/>
+
+    <target name="clean" description="Clean the build">
+        <delete includeEmptyDirs="true" failonerror="false">
+            <fileset dir="${component.classes.dir}"/>
+            <fileset dir="${component.classes.dsn.dir}"/>
+            <fileset dir="${component.classes.gimap.dir}"/>
+            <fileset dir="${component.classes.demo.dir}"/>
+            <fileset dir="${component.classes.client.dir}"/>
+            <fileset dir="${component.classes.servlet.dir}"/>
+            <fileset dir="${component.classes.logging.dir}"/>
+        </delete>
+    </target>
+
+    <target name="clobber" description="Clobber all derived files">
+        <delete includeEmptyDirs="true" failonerror="false">
+            <fileset dir="target"/>
+        </delete>
+    </target>
+
+    <!-- JavaMail bundle build targets -->
+
+    <target name="release" depends="init, jars, democompile, docs">
+	<!-- XXX - need to replace "." with "_" in release.version -->
+        <property name="rel" value="javamail-${release.version}"/>
+        <property name="zipname" value="${rel}.zip"/>
+        <delete file="${basedir}/target/${zipname}"/>
+        <echo message="Creating JavaMail bundle ${basedir}/target/${zipname}"/>
+        <zip destfile="${basedir}/target/${zipname}">
+	    <zipfileset dir="${release.dir}" prefix="${rel}"/>
+            <zipfileset dir="${src.demo.dir}" prefix="${rel}/demo"
+                excludes="**/internal/**"/>
+            <zipfileset dir="${src.client.dir}" prefix="${rel}/demo/client"/>
+            <zipfileset dir="${src.servlet.dir}"
+		prefix="${rel}/demo/servlet"/>
+            <zipfileset dir="webapp" prefix="${rel}/demo/webapp"
+		excludes="src/**,pom.xml"/>
+            <zipfileset dir="${src.webapp.dir}"
+		prefix="${rel}/demo/webapp/src/classes"/>
+            <zipfileset dir="${docroot.webapp.dir}"
+		prefix="${rel}/demo/webapp/src/docroot"/>
+            <zipfileset dir="${src.taglib.dir}"
+		prefix="${rel}/demo/webapp/src/taglib"/>
+            <zipfileset dir="${resources.taglib.dir}"
+		prefix="${rel}/demo/webapp/src/taglib"/>
+            <zipfileset dir="${src.logging.dir}"
+		prefix="${rel}/demo/logging"/>
+        </zip>
+    </target>
+
+    <!-- Build all jars needed for release -->
+    <target name="jars"
+	depends="jar, mailapijar, pop3jar, imapjar, smtpjar, gimapjar, dsnjar"/>
+
+    <!-- Assemble mail.jar -->
+    <target name="jar" depends="init, compile, cook-manifest">
+        <mkdir dir="${release.dir}"/>
+        <jar jarfile="${release.mail.jar}"
+		manifest="${component.classes.dir}/manifest/MANIFEST.MF">
+            <metainf dir="${resources.legal.dir}/META-INF"
+		    includes="LICENSE.txt"/>
+            <metainf dir="${resources.dir}/META-INF"
+                    includes="javamail.charset.map,javamail.default.*,mailcap"/>
+            <fileset dir="${component.classes.dir}">
+                <include name="javax/mail/**/*.class"/>
+                <include name="com/sun/mail/**/*.class"/>
+            </fileset>
+        </jar>
+    </target>
+
+    <!-- Create zip file of source code to publish to maven repo -->
+    <target name="src">
+        <zip file="target/mail.src.zip">
+            <fileset dir="${src.dir}">
+                <include name="javax/mail/**/*.java"/>
+                <include name="com/sun/mail/**/*.java"/>
+            </fileset>
+        </zip>
+    </target>
+
+    <target name="imapjar" depends="init, compile, cook-manifest">
+        <mkdir dir="${release.dir}/lib"/>
+        <jar jarfile="${release.imap.jar}"
+		manifest="${component.classes.imap.dir}/manifest/MANIFEST.MF">
+            <metainf dir="${resources.legal.dir}/META-INF"
+		    includes="LICENSE.txt"/>
+            <metainf dir="${resources.imap.dir}/META-INF"
+		    includes="javamail.providers"/>
+            <fileset dir="${component.classes.dir}">
+                <include name="com/sun/mail/iap/*.class"/>
+                <include name="com/sun/mail/imap/*.class"/>
+                <include name="com/sun/mail/imap/protocol/*.class"/>
+            </fileset>
+        </jar>
+    </target>
+
+    <target name="pop3jar" depends="init, compile, cook-manifest">
+        <mkdir dir="${release.dir}/lib"/>
+        <jar jarfile="${release.pop3.jar}"
+		manifest="${component.classes.pop3.dir}/manifest/MANIFEST.MF">
+            <metainf dir="${resources.legal.dir}/META-INF"
+		    includes="LICENSE.txt"/>
+            <metainf dir="${resources.pop3.dir}/META-INF"
+		    includes="javamail.providers"/>
+            <fileset dir="${component.classes.dir}">
+                <include name="com/sun/mail/pop3/*.class"/>
+            </fileset>
+        </jar>
+     </target>
+
+     <target name="smtpjar" depends="init, compile, cook-manifest">
+        <mkdir dir="${release.dir}/lib"/>
+        <jar jarfile="${release.smtp.jar}"
+		manifest="${component.classes.smtp.dir}/manifest/MANIFEST.MF">
+            <metainf dir="${resources.legal.dir}/META-INF"
+		    includes="LICENSE.txt"/>
+            <metainf dir="${resources.smtp.dir}/META-INF"
+		    includes="javamail.providers,javamail.address.map"/>
+            <fileset dir="${component.classes.dir}">
+                <include name="com/sun/mail/smtp/*.class"/>
+            </fileset>
+        </jar>
+     </target>
+
+     <target name="gimapjar" depends="init, gimapcompile, cook-manifest">
+        <mkdir dir="${release.dir}/lib"/>
+        <jar jarfile="${release.gimap.jar}"
+		manifest="${component.classes.gimap.dir}/manifest/MANIFEST.MF">
+            <metainf dir="${resources.legal.dir}/META-INF"
+		    includes="LICENSE.txt"/>
+            <metainf dir="${resources.gimap.dir}/META-INF"
+		    includes="mailcap"/>
+            <fileset dir="${component.classes.gimap.dir}">
+                <include name="com/sun/mail/gimap/*.class"/>
+            </fileset>
+        </jar>
+     </target>
+
+     <target name="dsnjar" depends="init, dsncompile, cook-manifest">
+        <mkdir dir="${release.dir}/lib"/>
+        <jar jarfile="${release.dsn.jar}"
+		manifest="${component.classes.dsn.dir}/manifest/MANIFEST.MF">
+            <metainf dir="${resources.legal.dir}/META-INF"
+		    includes="LICENSE.txt"/>
+            <metainf dir="${resources.dsn.dir}/META-INF"
+		    includes="mailcap"/>
+            <fileset dir="${component.classes.dsn.dir}">
+                <include name="com/sun/mail/dsn/*.class"/>
+            </fileset>
+        </jar>
+     </target>
+
+     <target name="mailapijar" depends="init, compile, cook-manifest">
+        <mkdir dir="${release.dir}/lib"/>
+        <jar jarfile="${release.mailapi.jar}"
+		manifest="${component.classes.dir}/manifest/MANIFEST.MF">
+            <metainf dir="${resources.legal.dir}/META-INF"
+		    includes="LICENSE.txt"/>
+            <metainf dir="${resources.dir}/META-INF"
+		    includes="javamail.charset.map, mailcap"/>
+            <fileset dir="${component.classes.dir}">
+                <include name="javax/mail/**/*.class"/>
+                <include name="com/sun/mail/util/*.class"/>
+                <include name="com/sun/mail/handlers/*.class"/>
+            </fileset>
+        </jar>
+    </target>
+
+    <!-- javadocs -->
+    <target name="docs" depends="init, jar, jar">
+        <copy todir="${release.dir}">
+            <fileset dir="${basedir}/doc/release" includes="*.txt" />
+        </copy>
+        <mkdir dir="${release.docs.dir}"/>
+        <copy todir="${release.docs.dir}">
+            <fileset dir="${basedir}/doc/spec" includes="*.txt" />
+        </copy>
+	<!--
+	    To allow us to generate javadocs that only include some
+	    classes in certain packages, we need to copy the sources
+	    to another location and run javadoc against that subset
+	    of the sources.
+	-->
+        <copy todir="${release.dir}/javadoc">
+	    <fileset dir="${src.dir}">
+		<include name="**/*.html"/>
+		<include name="javax/mail/**"/>
+	    </fileset>
+	    <fileset dir="${src.dir}"
+		includes="
+		    com/sun/mail/imap/IMAPFolder.java,
+		    com/sun/mail/imap/IMAPMessage.java,
+		    com/sun/mail/imap/IMAPStore.java,
+		    com/sun/mail/imap/IMAPSSLStore.java
+		    com/sun/mail/imap/ACL.java,
+		    com/sun/mail/imap/Rights.java,
+		    com/sun/mail/imap/Quota.java,
+		    com/sun/mail/imap/SortTerm.java,
+		    com/sun/mail/pop3/POP3Store.java,
+		    com/sun/mail/pop3/POP3SSLStore.java,
+		    com/sun/mail/pop3/POP3Folder.java,
+		    com/sun/mail/pop3/POP3Message.java,
+		    com/sun/mail/smtp/SMTPMessage.java,
+		    com/sun/mail/smtp/SMTPAddressFailedException.java,
+		    com/sun/mail/smtp/SMTPAddressSucceededException.java,
+		    com/sun/mail/smtp/SMTPSendFailedException.java,
+		    com/sun/mail/smtp/SMTPSenderFailedException.java,
+		    com/sun/mail/smtp/SMTPTransport.java,
+		    com/sun/mail/smtp/SMTPSSLTransport.java,
+		    com/sun/mail/util/MailConnectException.java,
+		    com/sun/mail/util/MailSSLSocketFactory.java,
+		    com/sun/mail/util/ReadableMime.java,
+		    com/sun/mail/util/logging/MailHandler.java
+		"/>
+	    <fileset dir="${src.dsn.dir}"
+		includes="
+		    com/sun/mail/dsn/package.html,
+		    com/sun/mail/dsn/DeliveryStatus.java,
+		    com/sun/mail/dsn/DispositionNotification.java,
+		    com/sun/mail/dsn/MessageHeaders.java,
+		    com/sun/mail/dsn/MultipartReport.java,
+		    com/sun/mail/dsn/Report.java
+		"/>
+	    <fileset dir="${src.dsn.dir}"
+		includes="
+		    com/sun/mail/gimap/package.html,
+		    com/sun/mail/gimap/GmailStore.java,
+		    com/sun/mail/gimap/GmailSSLStore.java,
+		    com/sun/mail/gimap/GmailFolder.java,
+		    com/sun/mail/gimap/GmailMessage.java,
+		    com/sun/mail/gimap/GmailMsgIdTerm.java,
+		    com/sun/mail/gimap/GmailThrIdTerm.java,
+		    com/sun/mail/gimap/GmailRawSearchTerm.java
+		"/>
+        </copy>
+        <mkdir dir="${release.javadocs.dir}"/>
+        <javadoc packagenames="javax.mail, javax.mail.internet,
+                               javax.mail.search, javax.mail.event,
+                               javax.mail.util,
+                               com.sun.mail.imap,
+                               com.sun.mail.pop3,
+                               com.sun.mail.smtp,
+                               com.sun.mail.gimap,
+                               com.sun.mail.util, com.sun.mail.util.logging,
+                               com.sun.mail.dsn"
+            destdir="${release.javadocs.dir}"
+	    sourcepath="${release.dir}/javadoc"
+            classpath="${activation.jar}:${release.mail.jar}:${release.dsn.jar}:${release.gimap.jar}"
+            author="false"
+            version="false"
+            use="true"
+            overview="${release.dir}/javadoc/overview.html"
+            windowtitle="JavaMail API documentation"
+            doctitle="JavaMail API documentation">
+            <group title="JavaMail API Packages" packages="javax.*"/>
+            <group title="Sun-specific Packages" packages="com.sun.*"/>
+	    <bottom>
+<![CDATA[Copyright &#169; 1996-2013,
+    <a href="http://www.oracle.com">Oracle</a>
+    and/or its affiliates. All Rights Reserved.
+    Use is subject to
+    <a href="{@docRoot}/doc-files/speclicense.html" target="_top">license terms<
+    /a>.
+]]>
+
+	    </bottom>
+	    <!-- following can really slow down javadoc generation
+	    <link href="${javase.url}"/>
+	    <link href="${jaf.url}"/>
+	    -->
+        </javadoc>
+    </target>
+
+    <target name="push-to-maven-prepare" depends="-push-to-maven-init, jar, src"
+        description="creates an image for the 'push-to-maven' goal">
+        <delete dir="target/maven-repo" /><!-- clean it -->
+        <maven-repository-importer
+		destdir="target/maven-repo" version="${release.version}">
+            <artifact jar="target/release/mail.jar" pom="mail.pom"
+		    srczip="target/mail.src.zip" />
+        </maven-repository-importer>
+    </target>
+
+    <target name="push-to-maven" depends="push-to-maven-prepare"
+        description="pushes jars to the java.net maven repository">
+        <cvs-import src="target/maven-repo" dest="glassfish/repo" />
+    </target>
+</project>
diff --git a/client/pom.xml b/client/pom.xml
new file mode 100644
index 0000000..d2c3d78
--- /dev/null
+++ b/client/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>client</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API demo client</name>
+
+    <build>
+        <plugins>
+	    <!--
+		Need to disable the maven-bundle-plugin because the
+		new version doesn't like classes in the default package.
+	    -->
+	    <plugin>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>maven-bundle-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>osgi-manifest</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Because the maven-jar-plugin depends on the manifest file
+		created by the maven-bundle-plugin, we need to disable it too.
+	    -->
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>default-jar</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/client/src/main/java/ComponentFrame.java b/client/src/main/java/ComponentFrame.java
new file mode 100644
index 0000000..c82b56e
--- /dev/null
+++ b/client/src/main/java/ComponentFrame.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.JFrame;
+import javax.swing.WindowConstants;
+
+
+/**
+ * this Frame provides a utility class for displaying a single
+ * Component in a Frame.
+ *
+ * @author	Christopher Cotton
+ */
+
+public class ComponentFrame extends JFrame {
+    
+    /**
+     * creates the frame
+     * @param what	the component to display
+     */
+    public ComponentFrame(Component what) {
+	this(what, "Component Frame");
+    }
+
+    /**
+     * creates the frame with the given name
+     * @param what	the component to display
+     * @param name	the name of the Frame
+     */
+    public ComponentFrame(Component what, String name) {
+	super(name);
+
+	// make sure that we close and dispose ourselves when needed
+	setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+
+	// default size of the frame
+	setSize(700,600);
+
+	// we want to display just the component in the entire frame
+	if (what != null) {
+	    getContentPane().add("Center", what);
+	}
+    }
+}
diff --git a/client/src/main/java/FolderModel.java b/client/src/main/java/FolderModel.java
new file mode 100644
index 0000000..e7a2ec3
--- /dev/null
+++ b/client/src/main/java/FolderModel.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import javax.mail.*;
+import java.util.Date;
+import javax.swing.table.AbstractTableModel; 
+
+/**
+ * Maps the messages in a Folder to the Swing's Table Model
+ *
+ * @author	Christopher Cotton
+ * @author	Bill Shannon
+ */
+
+public class FolderModel extends AbstractTableModel {
+    
+    Folder	folder;
+    Message[]	messages;
+
+    String[]	columnNames = { "Date", "From", "Subject"}; 
+    Class[]	columnTypes = { String.class, String.class, String.class }; 
+
+    public void setFolder(Folder what) throws MessagingException {
+	if (what != null) {
+
+	    // opened if needed
+	    if (!what.isOpen()) {
+		what.open(Folder.READ_WRITE);
+	    }
+    
+	    // get the messages
+	    messages = what.getMessages();
+	    cached = new String[messages.length][];
+	} else {
+	    messages = null;
+	    cached = null;
+	}
+	// close previous folder and switch to new folder
+	if (folder != null)
+	    folder.close(true);
+	folder = what;
+	fireTableDataChanged();
+    }
+    
+    public Message getMessage(int which) {
+	return messages[which];
+    }
+
+    //---------------------
+    // Implementation of the TableModel methods
+    //---------------------
+
+    public String getColumnName(int column) {
+	return columnNames[column];
+    }
+    
+    public Class getColumnClass(int column) {
+	return columnTypes[column];
+    }
+    
+
+    public int getColumnCount() {
+	return columnNames.length; 
+    }
+
+    public int getRowCount() {
+	if (messages == null)
+	    return 0;
+	
+	return messages.length;
+    }
+ 
+    public Object getValueAt(int aRow, int aColumn) {
+	switch(aColumn) {
+	case 0:	// date
+	case 1: // From		String[] what = getCachedData(aRow);
+	case 2: // Subject
+	    String[] what = getCachedData(aRow);
+	    if (what != null) {
+		return what[aColumn];
+	    } else {
+		return "";
+	    }
+	    
+	default:
+	    return "";
+	}
+    }
+
+    protected static String[][]	cached;
+    
+    protected String[] getCachedData(int row) {
+	if (cached[row] == null) {
+	    try{
+		Message m = messages[row];
+	    
+		String[] theData = new String[4];
+	    
+		// Date
+		Date date = m.getSentDate();
+		if (date == null) {
+		    theData[0] = "Unknown";
+		} else {
+		    theData[0] = date.toString();
+		}
+	    
+		// From
+		Address[] adds = m.getFrom();
+		if (adds != null && adds.length != 0) {
+		    theData[1] = adds[0].toString();	    
+		} else {
+		    theData[1] = "";
+		}
+		
+		// Subject
+		String subject = m.getSubject();
+		if (subject != null) {
+		    theData[2] = subject;
+		} else {
+		    theData[2] = "(No Subject)";
+		}
+
+		cached[row] = theData;
+	    }
+	    catch (MessagingException e) {
+		e.printStackTrace();
+	    }
+	}
+	
+	return cached[row];
+    }
+}
diff --git a/client/src/main/java/FolderTreeNode.java b/client/src/main/java/FolderTreeNode.java
new file mode 100644
index 0000000..3e6b925
--- /dev/null
+++ b/client/src/main/java/FolderTreeNode.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.mail.Store;
+import javax.mail.Folder;
+import javax.mail.MessagingException;
+
+/**
+ * Node which represents a Folder in the javax.mail apis. 
+ *
+ * @author Christopher Cotton
+ */
+public class FolderTreeNode extends DefaultMutableTreeNode {
+    
+    protected Folder	folder = null;
+    protected boolean	hasLoaded = false;
+
+    /**
+     * creates a tree node that points to the particular Store.
+     *
+     * @param what	the store for this node
+     */
+    public FolderTreeNode(Folder what) {
+	super(what);
+	folder = what;
+    }
+
+    
+    /**
+     * a Folder is a leaf if it cannot contain sub folders
+     */
+    public boolean isLeaf() {
+	try {
+	    if ((folder.getType() & Folder.HOLDS_FOLDERS) == 0)
+		return true;
+	} catch (MessagingException me) { }
+	
+	// otherwise it does hold folders, and therefore not
+	// a leaf
+	return false;
+    }
+   
+    /**
+     * returns the folder for this node
+     */
+    public Folder getFolder() {
+	return folder;
+    }
+    
+
+
+    /**
+     * return the number of children for this folder node. The first
+     * time this method is called we load up all of the folders
+     * under the store's defaultFolder
+     */
+
+    public int getChildCount() {
+	if (!hasLoaded) {
+	    loadChildren();
+	}
+	return super.getChildCount();
+    }
+    
+    protected void loadChildren() {
+	// if it is a leaf, just say we have loaded them
+	if (isLeaf()) {
+	    hasLoaded = true;
+	    return;
+	}
+
+	try {
+	    // Folder[] sub = folder.listSubscribed();
+	    Folder[] sub = folder.list();
+
+	    // add a FolderTreeNode for each Folder
+	    int num = sub.length;
+	    for(int i = 0; i < num; i++) {
+		FolderTreeNode node = new FolderTreeNode(sub[i]);
+		// we used insert here, since add() would make
+		// another recursive call to getChildCount();
+		insert(node, i);
+	    }
+	    
+	} catch (MessagingException me) {
+	    me.printStackTrace();
+	}
+    }
+
+
+    /**
+     * override toString() since we only want to display a folder's
+     * name, and not the full path of the folder
+     */
+    public String toString() {
+	return folder.getName();
+    }
+    
+}
+
diff --git a/client/src/main/java/FolderViewer.java b/client/src/main/java/FolderViewer.java
new file mode 100644
index 0000000..c762a26
--- /dev/null
+++ b/client/src/main/java/FolderViewer.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.awt.*;
+import javax.mail.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.table.*;
+
+/**
+ * @author	Christopher Cotton
+ * @author	Bill Shannon
+ */
+
+public class FolderViewer extends JPanel {
+
+    FolderModel model = new FolderModel();
+    JScrollPane scrollpane;
+    JTable table;
+
+    public FolderViewer() {
+	this(null);
+    }
+
+    public FolderViewer(Folder what) {
+	super(new GridLayout(1,1));
+
+	table = new JTable(model);
+	table.setShowGrid(false);
+
+	scrollpane = new JScrollPane(table);
+
+	// setup the folder we were given
+	setFolder(what);
+	
+	// find out what is pressed
+	table.getSelectionModel().addListSelectionListener(
+	    new FolderPressed());
+	scrollpane.setPreferredSize(new Dimension(700, 300));
+	add(scrollpane);
+    }
+
+    /**
+     * Change the current Folder for the Viewer
+     *
+     * @param what	the folder to be viewed
+     */
+    public void setFolder(Folder what) {
+	try {
+	    table.getSelectionModel().clearSelection();
+	    if (SimpleClient.mv != null)
+		SimpleClient.mv.setMessage(null);
+	    model.setFolder(what);
+	    scrollpane.invalidate();
+	    scrollpane.validate();
+	} catch (MessagingException me) {
+	    me.printStackTrace();
+	}
+    }
+
+    class FolderPressed implements ListSelectionListener {
+
+	public void valueChanged(ListSelectionEvent e) {
+	    if (model != null && !e.getValueIsAdjusting()) {
+		ListSelectionModel lm = (ListSelectionModel) e.getSource();
+		int which = lm.getMaxSelectionIndex();
+		if (which != -1) {
+		    // get the message and display it
+		    Message msg = model.getMessage(which);
+		    SimpleClient.mv.setMessage(msg);
+		}
+	    }
+	}
+    }
+}
diff --git a/client/src/main/java/MessageViewer.java b/client/src/main/java/MessageViewer.java
new file mode 100644
index 0000000..7b6474f
--- /dev/null
+++ b/client/src/main/java/MessageViewer.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.mail.*;
+import javax.activation.*;
+import java.util.Date;
+import java.io.IOException;
+import javax.swing.JPanel;
+
+/**
+ * @author	Christopher Cotton
+ * @author	Bill Shannon
+ */
+
+public class MessageViewer extends JPanel implements CommandObject {
+    
+    Message	displayed = null;
+    DataHandler	dataHandler = null;
+    String	verb = null;
+    Component	mainbody;
+    TextArea	headers;
+
+    public MessageViewer() {
+	this(null);
+    }
+    
+    public MessageViewer(Message what) {
+	// set our layout
+	super(new GridBagLayout());
+
+	// add the toolbar
+	addToolbar();
+
+	GridBagConstraints gb = new GridBagConstraints();
+	gb.gridwidth = GridBagConstraints.REMAINDER;
+	gb.fill = GridBagConstraints.BOTH;
+	gb.weightx = 1.0;
+	gb.weighty = 0.0;
+
+	// add the headers
+	headers = new TextArea("", 4, 80, TextArea.SCROLLBARS_NONE);
+	headers.setEditable(false);
+	add(headers, gb);
+
+	// now display our message
+	setMessage(what);
+    }
+    
+    /**
+     * sets the current message to be displayed in the viewer
+     */
+    public void setMessage(Message what) {
+	displayed = what;
+
+	if (mainbody != null)
+	    remove(mainbody);
+
+	if (what != null) {
+	    loadHeaders();
+	    mainbody = getBodyComponent();
+	} else {
+	    headers.setText("");
+	    TextArea dummy = new TextArea("", 24, 80, TextArea.SCROLLBARS_NONE);
+	    dummy.setEditable(false);
+	    mainbody = dummy;
+	}
+
+	// add the main body
+	GridBagConstraints gb = new GridBagConstraints();
+	gb.gridwidth = GridBagConstraints.REMAINDER;
+	gb.fill = GridBagConstraints.BOTH;
+	gb.weightx = 1.0;
+	gb.weighty = 1.0;
+	add(mainbody, gb);
+
+	invalidate();
+	validate();
+    }
+
+    protected void addToolbar() {
+	GridBagConstraints gb = new GridBagConstraints();
+	gb.gridheight = 1;
+	gb.gridwidth = 1;
+	gb.fill = GridBagConstraints.NONE;
+	gb.anchor = GridBagConstraints.WEST;
+	gb.weightx = 0.0;
+	gb.weighty = 0.0;
+	gb.insets = new Insets(4,4,4,4);
+
+	// structure button
+	gb.gridwidth = GridBagConstraints.REMAINDER; // only for the last one
+	Button b = new Button("Structure");
+	b.addActionListener( new StructureAction());
+	add(b, gb);
+    }
+
+    protected void loadHeaders() {
+	// setup what we want in our viewer
+	StringBuffer sb = new StringBuffer();
+
+	// date
+	sb.append("Date: ");
+	try {
+	    Date duh = displayed.getSentDate();
+	    if (duh != null) {
+		sb.append(duh.toString());
+	    } else {
+		sb.append("Unknown");
+	    }
+	    
+	    sb.append("\n");
+
+	    // from
+	    sb.append("From: ");
+	    Address[] adds = displayed.getFrom();
+	    if (adds != null && adds.length > 0) {
+		sb.append(adds[0].toString());
+	    }
+	    sb.append("\n");
+
+	    // to
+	    sb.append("To: ");
+	    adds = displayed.getRecipients(Message.RecipientType.TO);
+	    if (adds != null && adds.length > 0) {
+		sb.append(adds[0].toString());
+	    }
+	    sb.append("\n");
+
+	    // subject
+	    sb.append("Subject: ");
+	    sb.append(displayed.getSubject());
+	    
+	    headers.setText(sb.toString());
+	} catch (MessagingException me) {
+	    headers.setText("");
+	}
+    }
+
+    protected Component getBodyComponent() {
+	//------------
+	// now get a content viewer for the main type...
+	//------------
+	try {
+	    DataHandler dh = displayed.getDataHandler();
+	    CommandInfo ci = dh.getCommand("view");
+	    if (ci == null) {
+		throw new MessagingException("view command failed on: " +
+					     displayed.getContentType());
+	    }
+
+	    Object bean = dh.getBean(ci);
+	    if (bean instanceof Component) {
+		return (Component)bean;
+	    } else {
+		throw new MessagingException("bean is not a component " +
+					     bean.getClass().toString());
+	    }
+	} catch (MessagingException me) {
+	    return new Label(me.toString());
+	}
+    }
+    
+    /**
+     * the CommandObject method to accept our DataHandler
+     * @param dh	the datahandler used to get the content
+     */
+    public void setCommandContext(String verb,
+				  DataHandler dh) throws IOException {
+	this.verb = verb;
+	dataHandler = dh;
+
+	Object o = dh.getContent();
+	if (o instanceof Message) {
+	    setMessage((Message)o);
+	}
+	else {
+	    System.out.println( 
+		"MessageViewer - content not a Message object, " + o);
+	    if (o != null){
+		System.out.println(o.getClass().toString());
+	    }
+	}
+    }
+
+
+    class StructureAction implements ActionListener {
+	StringBuffer sb;
+
+	public void actionPerformed(ActionEvent e) {
+	    System.out.println("\n\nMessage Structure");
+	    dumpPart("", displayed);
+	}
+
+	protected void dumpPart(String prefix, Part p) {
+	    try {
+		System.out.println(prefix + "----------------");
+		System.out.println(prefix + 
+				   "Content-Type: " + p.getContentType());
+		System.out.println(prefix + 
+				   "Class: " + p.getClass().toString());
+			    
+		Object o = p.getContent();
+		if (o == null) {
+		    System.out.println(prefix + "Content:  is null");
+		} else {
+		    System.out.println(prefix +
+				       "Content: " + o.getClass().toString());
+		}
+
+		if (o instanceof Multipart) {
+		    String newpref = prefix + "\t";
+		    Multipart mp = (Multipart)o;
+		    int count = mp.getCount();
+		    for (int i = 0; i < count; i++) {
+			dumpPart(newpref, mp.getBodyPart(i));
+		    }
+		}
+	    } catch (MessagingException e) {
+		e.printStackTrace();
+	    } catch (IOException ioex) {
+		System.out.println("Cannot get content" + ioex.getMessage());
+	    }
+	}
+    }
+}
diff --git a/client/src/main/java/MultipartViewer.java b/client/src/main/java/MultipartViewer.java
new file mode 100644
index 0000000..193b8a8
--- /dev/null
+++ b/client/src/main/java/MultipartViewer.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.beans.*;
+import javax.activation.*;
+import javax.mail.*;
+import javax.swing.JPanel;
+
+
+/**
+ * A Viewer Bean for the type multipart/mixed
+ *
+ * @author	Christopher Cotton
+ */
+
+public class MultipartViewer extends JPanel implements CommandObject {
+    
+    protected DataHandler	dh = null;
+    protected String		verb = null;
+    
+    public MultipartViewer() {
+	super(new GridBagLayout());
+    }
+
+    
+    public void setCommandContext(String verb, DataHandler dh) throws IOException {
+	this.verb = verb;
+	this.dh = dh;
+	
+	// get the content, and hope it is a Multipart Object
+	Object content = dh.getContent();
+	if (content instanceof Multipart) {
+	    setupDisplay((Multipart)content);
+	} else {
+	    setupErrorDisplay(content);
+	}
+    }
+
+    protected void setupDisplay(Multipart mp) {
+	// we display the first body part in a main frame on the left, and then
+	// on the right we display the rest of the parts as attachments
+
+	GridBagConstraints gc = new GridBagConstraints();
+	gc.gridheight = GridBagConstraints.REMAINDER;
+	gc.fill = GridBagConstraints.BOTH;
+	gc.weightx = 1.0;
+	gc.weighty = 1.0;
+
+	// get the first part
+	try {
+	    BodyPart bp = mp.getBodyPart(0);
+	    Component comp = getComponent(bp);
+	    add(comp, gc);
+	    
+	} catch (MessagingException me) {
+	    add(new Label(me.toString()), gc);
+	}
+
+	// see if there are more than one parts
+	try {
+	    int count = mp.getCount();
+
+	    // setup how to display them
+	    gc.gridwidth = GridBagConstraints.REMAINDER;
+	    gc.gridheight = 1;
+	    gc.fill = GridBagConstraints.NONE;
+	    gc.anchor = GridBagConstraints.NORTH;
+	    gc.weightx = 0.0;
+	    gc.weighty = 0.0;
+	    gc.insets = new Insets(4,4,4,4);
+
+	    // for each one we create a button with the content type
+	    for(int i = 1; i < count; i++) { // we skip the first one 
+		BodyPart curr = mp.getBodyPart(i);
+		String label = null;
+		if (label == null) label = curr.getFileName();
+		if (label == null) label = curr.getDescription();
+		if (label == null) label = curr.getContentType();
+
+		Button but = new Button(label);
+		but.addActionListener( new AttachmentViewer(curr));
+		add(but, gc);
+	    }
+	    
+	    
+	} catch(MessagingException me2) {
+	    me2.printStackTrace();
+	}
+
+    }
+
+    protected Component getComponent(BodyPart bp) {
+
+	try {
+	    DataHandler dh = bp.getDataHandler();
+	    CommandInfo ci = dh.getCommand("view");
+	    if (ci == null) {
+		throw new MessagingException(
+		    "view command failed on: " +
+		    bp.getContentType());
+	    }
+	    
+	    Object bean = dh.getBean(ci);
+	
+	    if (bean instanceof Component) {
+		return (Component)bean;
+	    } else {
+		if (bean == null)
+		    throw new MessagingException(
+			"bean is null, class " + ci.getCommandClass() +
+			" , command " + ci.getCommandName());
+		else
+		    throw new MessagingException(
+			"bean is not a awt.Component" +
+			bean.getClass().toString());
+	    }
+	}
+	catch (MessagingException me) {
+	    return new Label(me.toString());
+	}
+    }
+    
+
+    
+    protected void setupErrorDisplay(Object content) {
+	String error;
+
+	if (content == null)
+	    error = "Content is null";
+	else
+	    error = "Object not of type Multipart, content class = " +
+	    content.getClass().toString();
+	
+	System.out.println(error);
+	Label lab = new Label(error);
+	add(lab);
+    }
+   
+    class AttachmentViewer implements ActionListener {
+	
+	BodyPart bp = null;
+	
+	public AttachmentViewer(BodyPart part) {
+	    bp = part;
+	}
+	
+	public void actionPerformed(ActionEvent e) {
+	    ComponentFrame f = new ComponentFrame(
+		getComponent(bp), "Attachment");
+	    f.pack();
+	    f.show();
+	}
+    }
+
+}
diff --git a/client/src/main/java/README.txt b/client/src/main/java/README.txt
new file mode 100644
index 0000000..2b00943
--- /dev/null
+++ b/client/src/main/java/README.txt
@@ -0,0 +1,124 @@
+SimpleClient
+------------
+
+Notes:
+======
+
+This should not be taken as a demo of how to use the Swing API, but
+rather a very simple graphical mail client. It shows how viewers can
+be used to display the content from mail messages.  It also (like the
+other demos) shows how to retrieve Folders from a Store, Messages
+from a Folder, and content from Messages.
+
+
+To run the demo:
+================
+
+    1.  If you're using JDK 1.1.x, download the latest version of the JFC
+	(Swing) APIs from http://java.sun.com/products/jfc/download.html.
+	The SimpleClient uses at least version 1.1 of Swing.
+
+	If you're using JDK 1.2 (J2SE 1.2) or newer, Swing is included
+	and no separate download is necessary.
+
+	We *strongly* encourage you to use the latest version of J2SE,
+	which you can download from http://java.sun.com/j2se/.
+
+    2.  Set your CLASSPATH to include the "mail.jar", "activation.jar",
+	and (if you're using JDK 1.1.x and downloaded Swing separately)
+	"swingall.jar", and the current directory.  For example:
+
+	For JDK 1.1 on UNIX:
+
+	export CLASSPATH=/u/me/download/mail.jar:/u/me/download/activation.jar:/u/me/download/swingall.jar:.
+
+	For JDK 1.2 and newer on UNIX:
+
+	export CLASSPATH=/u/me/download/mail.jar:/u/me/download/activation.jar:.
+
+    3.  Go to the demo/client directory
+
+    4.  Compile all the files using your Java compiler.  For example:
+
+	  javac *.java
+
+    5.  Run the demo. For example:
+
+	  java SimpleClient -L imap://username:password@hostname/
+
+	Note that SimpleClient expects to read the "simple.mailcap"
+	file from the current directory.  The simple.mailcap file
+	contains configuration information about viewers needed by
+	the SimpleClient demo program.
+
+
+
+Overview of the Classes
+=======================
+
+Main Classes:
+
+	SimpleClient   =    contains main().
+			     Uses the parameters to the application to
+			     locate the correct Store.  e.g.
+
+				SimpleClient -L imap://cotton:secret@snow-goon/
+
+			     It will create the main frame and
+			     creates a tree.  The tree uses the
+			     StoreTreeNodes and FolderTreeNodes.
+
+	StoreTreeNode   =    subclass of Swing's DefaultMutableTreeNode.
+			     This class shows how to get Folders from
+			     the Store.
+
+	FolderTreeNode  =    subclass of Swing's DefaultMutableTreeNode.
+			     If the folder has messages, it will create
+			     a FolderViewer.  Otherwise it will add the
+			     subfolders to the tree.
+
+	SimpleAuthenticator = subclass of javax.mail.Authenticator. If
+			     the Store is missing the username or the
+			     password, this authenticator will be used.
+			     It displays a dialog requesting the
+			     information from the user.
+				
+
+Viewing Folders:
+
+	FolderViewer    =    Uses a Swing Table to display all of the
+			     Message in a Folder.  The "model" of the
+			     data for this Table is a FolderModel which
+			     knows how to get displayable information
+			     from a Message.
+
+JAF Viewers:
+
+	MessageViewer   =    Uses the content of the DataHandler.  The
+			     content will be a javax.mail.Message
+			     object.  Displays the headers and then
+			     uses the JAF to find another viewer for
+			     the content type of the Message.  (either
+			     multipart/mixed, image/gif, or text/plain)
+
+	MultipartViewer =    Uses the content of the DataHandler.  The
+			     content will be a javax.mail.Multipart
+			     object.  Uses the JAF to find another
+			     viewer for the first BodyPart's content.
+			     Also puts Buttons (as "attachments") for
+			     the rest of the BodyParts.  When the
+			     Button are pressed, it uses the JAF to
+			     find a viewer for the BodyPart's content,
+			     and displays it in a separate frame (using
+			     ComponentFrame).
+
+	TextViewer      =    Uses the content of the DataHandler.  The
+			     content will be either a java.lang.String
+			     object, or a java.io.InputStream object.
+			     Creates a TextArea and sets the text using
+			     the String or InputStream.
+
+Support Classes:
+
+	ComponentFrame  =    support class which takes a java.awt.Component
+			     and displays it in a Frame.
diff --git a/client/src/main/java/SimpleAuthenticator.java b/client/src/main/java/SimpleAuthenticator.java
new file mode 100644
index 0000000..55bacc5
--- /dev/null
+++ b/client/src/main/java/SimpleAuthenticator.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import javax.mail.*;
+import java.net.InetAddress;
+import java.awt.*;
+import javax.swing.*;
+
+/**
+ * Simple Authenticator for requesting password information.
+ *
+ * @author	Christopher Cotton
+ * @author	Bill Shannon
+ */
+
+public class SimpleAuthenticator extends Authenticator {
+
+    Frame frame;
+    String username;
+    String password;
+
+    public SimpleAuthenticator(Frame f) {
+	this.frame = f;
+    }
+
+    protected PasswordAuthentication getPasswordAuthentication() {
+
+	// given a prompt?
+	String prompt = getRequestingPrompt();
+	if (prompt == null)
+	    prompt = "Please login...";
+
+	// protocol
+	String protocol = getRequestingProtocol();
+	if (protocol == null)
+	    protocol = "Unknown protocol";
+
+	// get the host
+	String host = null;
+	InetAddress inet = getRequestingSite();
+	if (inet != null)
+	    host = inet.getHostName();
+	if (host == null)
+	    host = "Unknown host";
+
+	// port
+	String port = "";
+	int portnum = getRequestingPort();
+	if (portnum != -1)
+	    port = ", port " + portnum + " ";
+
+	// Build the info string
+	String info = "Connecting to " + protocol + " mail service on host " +
+								host + port;
+
+	//JPanel d = new JPanel();
+	// XXX - for some reason using a JPanel here causes JOptionPane
+	// to display incorrectly, so we workaround the problem using
+	// an anonymous JComponent.
+	JComponent d = new JComponent() { };
+
+	GridBagLayout gb = new GridBagLayout();
+	GridBagConstraints c = new GridBagConstraints();
+	d.setLayout(gb);
+	c.insets = new Insets(2, 2, 2, 2);
+
+	c.anchor = GridBagConstraints.WEST;
+	c.gridwidth = GridBagConstraints.REMAINDER;
+	c.weightx = 0.0;
+	d.add(constrain(new JLabel(info), gb, c));
+	d.add(constrain(new JLabel(prompt), gb, c));
+
+	c.gridwidth = 1;
+	c.anchor = GridBagConstraints.EAST;
+	c.fill = GridBagConstraints.NONE;
+	c.weightx = 0.0;
+	d.add(constrain(new JLabel("Username:"), gb, c));
+
+	c.anchor = GridBagConstraints.EAST;
+	c.fill = GridBagConstraints.HORIZONTAL;
+	c.gridwidth = GridBagConstraints.REMAINDER;
+	c.weightx = 1.0;
+	String user = getDefaultUserName();
+	JTextField username = new JTextField(user, 20);
+	d.add(constrain(username, gb, c));
+
+	c.gridwidth = 1;
+	c.fill = GridBagConstraints.NONE;
+	c.anchor = GridBagConstraints.EAST;
+	c.weightx = 0.0;
+	d.add(constrain(new JLabel("Password:"), gb, c));
+
+	c.anchor = GridBagConstraints.EAST;
+	c.fill = GridBagConstraints.HORIZONTAL;
+	c.gridwidth = GridBagConstraints.REMAINDER;
+	c.weightx = 1.0;
+	JPasswordField password = new JPasswordField("", 20);
+	d.add(constrain(password, gb, c));
+	// XXX - following doesn't work
+	if (user != null && user.length() > 0)
+	    password.requestFocus();
+	else
+	    username.requestFocus();
+	
+	int result = JOptionPane.showConfirmDialog(frame, d, "Login",
+	    JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);
+	
+	if (result == JOptionPane.OK_OPTION)
+	    return new PasswordAuthentication(username.getText(),
+						password.getText());
+	else
+	    return null;
+    }
+
+    private Component constrain(Component cmp,
+				GridBagLayout gb, GridBagConstraints c) {
+	gb.setConstraints(cmp, c);
+	return (cmp);
+    }
+}
diff --git a/client/src/main/java/SimpleClient.java b/client/src/main/java/SimpleClient.java
new file mode 100644
index 0000000..447be55
--- /dev/null
+++ b/client/src/main/java/SimpleClient.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.table.*;
+import javax.swing.tree.*;
+import javax.swing.event.*;
+
+
+/**
+ * Demo app that shows a very simple Mail Client
+ *
+ * @author Christopher Cotton
+ * @author Bill Shannon
+ */
+
+public class SimpleClient {
+
+    static Vector url = new Vector();
+    static FolderViewer fv;
+    static MessageViewer mv;
+
+    public static void main(String argv[]) {
+	boolean usage = false;
+
+	for (int optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-L")) {
+		url.addElement(argv[++optind]);
+	    } else if (argv[optind].startsWith("-")) {
+		usage = true;
+		break;
+	    } else {
+		usage = true;
+		break;
+	    }
+	}
+
+	if (usage || url.size() == 0) {
+	    System.out.println("Usage: SimpleClient -L url");
+	    System.out.println("   where url is protocol://username:password@hostname/");
+	    System.exit(1);
+	}
+
+	try {
+	    // Set up our Mailcap entries.  This will allow the JAF
+	    // to locate our viewers.
+	    File capfile = new File("simple.mailcap");
+	    if (!capfile.isFile()) {
+		System.out.println(
+		    "Cannot locate the \"simple.mailcap\" file.");
+		System.exit(1);
+	    }
+	    
+	    CommandMap.setDefaultCommandMap( new MailcapCommandMap(
+		new FileInputStream(capfile)));
+		
+	    JFrame frame = new JFrame("Simple JavaMail Client");
+	    frame.addWindowListener(new WindowAdapter() {
+		public void windowClosing(WindowEvent e) {System.exit(0);}});
+	    //frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
+		
+	    // Get a Store object
+	    SimpleAuthenticator auth = new SimpleAuthenticator(frame);
+	    Session session = 
+		Session.getDefaultInstance(System.getProperties(), auth);
+	    //session.setDebug(true);
+
+	    DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
+
+	    // create a node for each store we have
+	    for (Enumeration e = url.elements() ; e.hasMoreElements() ;) {
+		String urlstring = (String) e.nextElement();
+		URLName urln = new URLName(urlstring);
+		Store store = session.getStore(urln);
+		
+		StoreTreeNode storenode = new StoreTreeNode(store);
+		root.add(storenode);
+	    }	    
+
+	    DefaultTreeModel treeModel = new DefaultTreeModel(root);
+	    JTree tree = new JTree(treeModel);
+	    tree.addTreeSelectionListener(new TreePress());
+
+	    /* Put the Tree in a scroller. */
+	    JScrollPane        sp = new JScrollPane();
+	    sp.setPreferredSize(new Dimension(250, 300));
+	    sp.getViewport().add(tree);
+
+	    /* Create a double buffered JPanel */
+	    JPanel sv = new JPanel(new BorderLayout());
+	    sv.add("Center", sp);
+
+	    fv = new FolderViewer(null);
+
+	    JSplitPane jsp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
+				sv, fv);
+	    jsp.setOneTouchExpandable(true);
+	    mv = new MessageViewer();
+	    JSplitPane jsp2 = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
+				jsp, mv);
+	    jsp2.setOneTouchExpandable(true);
+
+	    frame.getContentPane().add(jsp2);
+	    frame.pack();
+	    frame.show();
+
+	} catch (Exception ex) {
+	    System.out.println("SimpletClient caught exception");
+	    ex.printStackTrace();
+	    System.exit(1);
+	}
+    }
+
+}
+
+class TreePress implements TreeSelectionListener {
+    
+    public void valueChanged(TreeSelectionEvent e) {
+	TreePath path = e.getNewLeadSelectionPath();
+	if (path != null) {
+	    Object o = path.getLastPathComponent();
+	    if (o instanceof FolderTreeNode) {
+		FolderTreeNode node = (FolderTreeNode)o;
+		Folder folder = node.getFolder();
+
+		try {
+		    if ((folder.getType() & Folder.HOLDS_MESSAGES) != 0) {
+			SimpleClient.fv.setFolder(folder);
+		    }
+		} catch (MessagingException me) { }
+	    }
+	}
+    }
+}
diff --git a/client/src/main/java/StoreTreeNode.java b/client/src/main/java/StoreTreeNode.java
new file mode 100644
index 0000000..3ca9a5d
--- /dev/null
+++ b/client/src/main/java/StoreTreeNode.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.mail.*;
+
+/**
+ * Node which represents a Store in the javax.mail apis. 
+ *
+ * @author Christopher Cotton
+ */
+public class StoreTreeNode extends DefaultMutableTreeNode {
+    
+    protected Store	store = null;
+    protected Folder	folder = null;
+    protected String	display = null;
+
+    /**
+     * creates a tree node that points to the particular Store.
+     *
+     * @param what	the store for this node
+     */
+    public StoreTreeNode(Store what) {
+	super(what);
+	store = what;
+    }
+
+    
+    /**
+     * a Store is never a leaf node.  It can always contain stuff
+     */
+    public boolean isLeaf() {
+	return false;
+    }
+   
+
+    /**
+     * return the number of children for this store node. The first
+     * time this method is called we load up all of the folders
+     * under the store's defaultFolder
+     */
+
+    public int getChildCount() {
+	if (folder == null) {
+	    loadChildren();
+	}
+	return super.getChildCount();
+    }
+    
+    protected void loadChildren() {
+	try {
+	    // connect to the Store if we need to
+	    if (!store.isConnected()) {
+		store.connect();
+	    }
+
+	    // get the default folder, and list the
+	    // subscribed folders on it
+	    folder = store.getDefaultFolder();
+	    // Folder[] sub = folder.listSubscribed();
+	    Folder[] sub = folder.list();
+
+	    // add a FolderTreeNode for each Folder
+	    int num = sub.length;
+	    for(int i = 0; i < num; i++) {
+		FolderTreeNode node = new FolderTreeNode(sub[i]);
+		// we used insert here, since add() would make
+		// another recursive call to getChildCount();
+		insert(node, i);
+	    }
+	    
+	} catch (MessagingException me) {
+	    me.printStackTrace();
+	}
+    }
+
+    /**
+     * We override toString() so we can display the store URLName
+     * without the password.
+     */
+
+    public String toString() {
+	if (display == null) {
+	    URLName url = store.getURLName();
+	    if (url == null) {
+		display = store.toString();
+	    } else {
+		// don't show the password
+		URLName too = new URLName( url.getProtocol(), url.getHost(), url.getPort(),
+					   url.getFile(), url.getUsername(), null);
+		display = too.toString();
+	    }
+	}
+	
+	return display;
+    }
+    
+    
+}
+
diff --git a/client/src/main/java/TextViewer.java b/client/src/main/java/TextViewer.java
new file mode 100644
index 0000000..1c9e58c
--- /dev/null
+++ b/client/src/main/java/TextViewer.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.awt.*;
+import java.io.*;
+import java.beans.*;
+import javax.activation.*;
+import javax.swing.JPanel;
+import javax.swing.JTextArea;
+import javax.swing.JScrollPane;
+
+
+/**
+ * A very simple TextViewer Bean for the MIMEType "text/plain"
+ *
+ * @author	Christopher Cotton
+ */
+
+public class TextViewer extends JPanel implements CommandObject 
+{
+
+    private JTextArea text_area = null;
+    private DataHandler dh = null;
+    private String	verb = null;
+
+    /**
+     * Constructor
+     */
+    public TextViewer() {
+	super(new GridLayout(1,1));
+
+	// create the text area
+	text_area = new JTextArea();
+	text_area.setEditable(false);
+	text_area.setLineWrap(true);
+
+	// create a scroll pane for the JTextArea
+	JScrollPane sp = new JScrollPane();
+	sp.setPreferredSize(new Dimension(300, 300));
+	sp.getViewport().add(text_area);
+	
+	add(sp);
+    }
+
+
+    public void setCommandContext(String verb, DataHandler dh)
+	throws IOException {
+
+	this.verb = verb;
+	this.dh = dh;
+	
+	this.setInputStream( dh.getInputStream() );
+    }
+
+
+  /**
+   * set the data stream, component to assume it is ready to
+   * be read.
+   */
+  public void setInputStream(InputStream ins) {
+      
+      int bytes_read = 0;
+      // check that we can actually read
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      byte data[] = new byte[1024];
+      
+      try {
+	  while((bytes_read = ins.read(data)) >0)
+		  baos.write(data, 0, bytes_read);
+	  ins.close();
+      } catch(Exception e) {
+	  e.printStackTrace();
+      }
+
+      // convert the buffer into a string
+      // place in the text area
+      text_area.setText(baos.toString());
+
+    }
+}
diff --git a/client/src/main/java/simple.mailcap b/client/src/main/java/simple.mailcap
new file mode 100644
index 0000000..e65e13a
--- /dev/null
+++ b/client/src/main/java/simple.mailcap
@@ -0,0 +1,9 @@
+#
+# Example command map, using the MailcapCommandMap.
+#
+# for our viewers
+#
+message/*;;		x-java-view=MessageViewer
+text/plain;;		x-java-view=TextViewer
+multipart/*;;		x-java-view=MultipartViewer
+
diff --git a/copyright-exclude b/copyright-exclude
new file mode 100644
index 0000000..040308f
--- /dev/null
+++ b/copyright-exclude
@@ -0,0 +1,31 @@
+copyright-exclude
+/MANIFEST.MF
+/META-INF/services/
+/META-INF/javamail
+/META-INF/mailcap
+/README
+doc/release/
+doc/spec/
+/LICENSE.txt
+CONTRIBUTING.md
+LICENSE.md
+.hgtags
+.hgignore
+client/src/main/java/simple.mailcap
+mail/src/oldtest/java/javax/mail/internet/encodedheaders.data
+mail/src/oldtest/java/javax/mail/internet/decodetest
+mail.sig
+webapp/build.bat
+webapp/webapp.README.txt
+logging/src/main/java/
+mail/src/main/java/com/sun/mail/util/logging/
+mail/src/main/resources/META-INF/hk2-locator/default
+mail/src/test/java/com/sun/mail/util/logging/
+mail/src/test/resources/javax/mail/internet/paramdata
+mail/src/test/resources/javax/mail/internet/paramdatanostrict
+mail/src/test/resources/javax/mail/internet/tokenlist
+mail/src/test/resources/javax/mail/internet/addrlist
+mail/src/test/resources/javax/mail/internet/MailDateFormat_old.ser
+mail/src/test/resources/javax/mail/internet/MailDateFormat_new.ser
+mail/src/main/java/doc-files/speclicense.html
+android/activation/src/main/resources/META-INF/mimetypes.default
diff --git a/demo/pom.xml b/demo/pom.xml
new file mode 100644
index 0000000..c2becf0
--- /dev/null
+++ b/demo/pom.xml
@@ -0,0 +1,83 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>demo</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API demos</name>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+		    <excludes>
+			<exclude>internal/**</exclude>
+		    </excludes>
+                </configuration>
+	    </plugin>
+
+	    <!--
+		Need to disable the maven-bundle-plugin because the
+		new version doesn't like classes in the default package.
+	    -->
+	    <plugin>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>maven-bundle-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>osgi-manifest</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Because the maven-jar-plugin depends on the manifest file
+		created by the maven-bundle-plugin, we need to disable it too.
+	    -->
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>default-jar</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/demo/src/main/java/CRLFOutputStream.java b/demo/src/main/java/CRLFOutputStream.java
new file mode 100644
index 0000000..00cb23d
--- /dev/null
+++ b/demo/src/main/java/CRLFOutputStream.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+
+/**
+ * Convert lines into the canonical MIME format, that is,
+ * terminate lines with CRLF. <p>
+ *
+ * This stream can be used with the Part.writeTo and Message.writeTo
+ * methods to generate the canonical MIME format of the data for the
+ * purpose of (e.g.) sending it via SMTP or computing a digital
+ * signature.
+ */
+public class CRLFOutputStream extends FilterOutputStream {
+    protected int lastb = -1;
+    protected static byte[] newline;
+    static {
+	newline = new byte[2];
+	newline[0] = (byte)'\r';
+	newline[1] = (byte)'\n';
+    }
+
+    public CRLFOutputStream(OutputStream os) {
+	super(os);
+    }
+
+    public void write(int b) throws IOException {
+	if (b == '\r') {
+	    out.write(newline);
+	} else if (b == '\n') {
+	    if (lastb != '\r')
+		out.write(newline);
+	} else {
+	    out.write(b);
+	}
+	lastb = b;
+    }
+
+    public void write(byte b[]) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+	int start = off;
+
+	len += off;
+	for (int i = start; i < len ; i++) {
+	    if (b[i] == '\r') {
+		out.write(b, start, i - start);
+		out.write(newline);
+		start = i + 1;
+	    } else if (b[i] == '\n') {
+		if (lastb != '\r') {
+		    out.write(b, start, i - start);
+		    out.write(newline);
+		}
+		start = i + 1;
+	    }
+	    lastb = b[i];
+	}
+	if ((len - start) > 0)
+	    out.write(b, start, len - start);
+    }
+}
diff --git a/demo/src/main/java/NewlineOutputStream.java b/demo/src/main/java/NewlineOutputStream.java
new file mode 100644
index 0000000..c620029
--- /dev/null
+++ b/demo/src/main/java/NewlineOutputStream.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+
+/**
+ * Convert the various newline conventions to the local platform's
+ * newline convention. <p>
+ *
+ * This stream can be used with the Message.writeTo method to
+ * generate a message that uses the local plaform's line terminator
+ * for the purpose of (e.g.) saving the message to a local file.
+ */
+public class NewlineOutputStream extends FilterOutputStream {
+    private int lastb = -1;
+    private static byte[] newline;
+
+    public NewlineOutputStream(OutputStream os) {
+	super(os);
+	if (newline == null) {
+	    String s = System.lineSeparator();
+	    if (s == null || s.length() <= 0)
+		s = "\n";
+	    try {
+		newline = s.getBytes("iso-8859-1");	// really us-ascii
+	    } catch (UnsupportedEncodingException ex) {
+		// should never happen
+		newline = new byte[] { (byte)'\n' };
+	    }
+	}
+    }
+
+    public void write(int b) throws IOException {
+	if (b == '\r') {
+	    out.write(newline);
+	} else if (b == '\n') {
+	    if (lastb != '\r')
+		out.write(newline);
+	} else {
+	    out.write(b);
+	}
+	lastb = b;
+    }
+
+    public void write(byte b[]) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+	for (int i = 0 ; i < len ; i++) {
+	    write(b[off + i]);
+	}
+    }
+}
diff --git a/demo/src/main/java/README.txt b/demo/src/main/java/README.txt
new file mode 100644
index 0000000..781d064
--- /dev/null
+++ b/demo/src/main/java/README.txt
@@ -0,0 +1,384 @@
+	README for the demo programs in this directory
+	==============================================
+
+These demo programs illustrate how to use the JavaMail API to
+perform a number of common email functions.  Note these these
+programs are not intended to be examples of good user interfaces,
+or good command line interfaces.  No one is expected to actually
+*use* these programs for anything real.  Rather, their value is
+in the source code.  Don't look at their command line arguments
+or user interface to figure out what JavaMail can do, look at
+their source code.  We strongly recommend that you read the
+source code and understand what these programs are doing before
+running them.
+
+All of these programs are simple command line tools with a UNIX
+style interface.  On Windows you'll need to run them in an MS-DOS
+window.  We apologize in advance for the inconsistency in how these
+programs accept options.  There are generally two styles.  The very
+simple style (e.g., as used by copier.java) requires a fixed number
+of arguments in a fixed order.  Others (e.g., folderlist.java) take
+UNIX-style options, many of which are optional, and which may appear
+in any order.  The following notes should help you figure it out,
+but if in doubt, read the source code.
+
+
+
+- copier.java
+
+	This program copies the specified messages from one folder to
+	another. Both folders must belong to the same store.
+
+  Usage:
+	java copier <urlname> <src> <dest> <start> <end>
+
+  Arguments (in order):
+
+  <urlname>	: URL of the Store. The URL should include
+		  the password as well (if needed).
+		  Example: "imap://john:password@mailstore.com"
+
+  <src>		: source folder
+  <dest>	: destination folder
+  <start>	: start message number
+  <end>		: end message number
+
+
+- folderlist.java
+
+	This program lists information about the folders in a Store.
+
+   Usage:
+	java folderlist -L <url> -T <protocol> -H <host> -U <user> -P <passwd>
+		   [-R <root>] [-r] [-v] [-D] <pattern>
+
+   Options:
+
+   -L <url>	: URL of the Store. The URL should include
+		  the password as well (if needed).
+		  Example: "imap://john:password@mailstore.com"
+   -T <protocol> : store protocol (Ex: "imap")
+   -H <host>	: hostname of store.
+   -U <user>	: username (if needed)
+   -P <passwd>	: password (if needed)
+   -R <root>	: root of the folder hierarchy. This is optional. If
+		  not present, listing starts from the default folder.
+   -r		: list recursively - folder and all subfolders.
+   -v		: verbose - show more info about each folder.
+   -D		: Turn on session debugging
+   <pattern>	: folders that match this pattern are listed. Use "*"
+		  as wildcard to match everything.
+
+
+- monitor.java
+
+	Illustrates how to monitor a folder for interesting events,
+	like new mail arrival.
+
+   Usage:
+	java monitor <host> <user> <password> <mbox> <freq>
+
+   Arguments (in order):
+
+   <host>	: hostname of store.
+   <user>	: username (if needed)
+   <passwd>	: password (if needed)
+   <mbox>	: folder to monitor
+   <freq>	: frequency of monitoring
+
+
+- mover.java
+
+	Moves messages between folders.  The folders must belong to the
+	same store.
+
+   Usage:
+	java mover -T <protocol> -H <host> -U <user> -P <passwd> [-v]
+		-s <src> -d <dest> [-x] <start> <end>
+
+   Options:
+
+   -T <protocol> : store protocol (Ex: "imap")
+   -H <host>	: hostname of store.
+   -U <user>	: username (if needed)
+   -P <passwd>	: password (if needed)
+   -s <src>	: source folder
+   -d <dest>	: destination folder
+   -v		: Optional verbose option
+   -x		: Optional expunge option, to expunge the deleted
+		  messages from src
+
+   Arguments (in order):
+
+   <start>	: start message number
+   <end>	: end message number
+
+
+- msgmultisendsample.java
+
+	Demonstrates how to construct and send a multipart message.
+
+   Usage:
+	java msgmultisendsample <to> <from> <smtphost> true|false
+
+   Arguments (in order):
+
+   <to>		: Recipient address
+   <from>	: Sender address
+   <smtphost>	: name of SMTP server
+   true|false	: "true" to turn on session debugging, "false" otherwise
+
+
+- msgsend.java
+
+	Send a simple text message. Optionally saves a copy
+	of the outgoing message in a folder (record-folder).
+
+	Most parameters to this program are optional. When
+	the program is run, it interactively asks for
+	the "To" and "Subject" fields if not already available.
+	Then the program expects the body of the message.
+	After you type in the body, hit Ctrl-D on Unix
+	systems or Ctrl-Z on Windows systems to send
+	the message.
+
+   Usage:
+	java msgsend -L <store-url> -T <protocol> -H <host> -U <user>
+		-P <passwd> -s <subject> -o <from> -c <cc> -b <bcc>
+		-f <record> -M <smtphost> [-d] <to>
+
+   Options:
+
+   -L <store-url> : URL of the store for the record-folder
+   -T <protocol> : If <store-url> is not present, this indicates
+		  the store protocol for the record-folder.
+   -H <host>	: If <store-url> is not present, this indicates
+		  the hostname for the record-folder.
+   -U <user>	: If <store-url> is not present, this indicates
+		  the username for the record-folder.
+   -P <passwd>	: If <store-url> is not present, this indicates
+		  the password for the record-folder.
+   -f <record>	: name of record-folder.
+   -M <smtphost> : Host name of SMTP server.  Defaults to "localhost"
+		  which often works on UNIX but rarely on Windows.
+   -s <subject>	: Subject of message to be sent
+   -o <from>	: From address of message to be sent
+   -c <cc>	: Cc address of message to be sent
+   -b <bcc>	: Bcc address of message to be sent
+   -d		: Turn on session debugging.
+   -a <file>	: Include file as an attachment with the message
+
+   Argument:
+
+   <to>		: To address of message to be sent
+
+
+- msgsendsample.java
+
+	Demonstrates how to construct and send a simple text message.
+
+   Usage:
+	java msgsendsample <to> <from> <smtphost> true|false
+
+   Arguments (in order):
+
+   <to>		: Recipient address
+   <from>	: Sender address
+   <smtphost>	: name of SMTP server
+   true|false	: "true" to turn on session debugging, "false" otherwise
+
+
+- msgshow.java
+
+	Displays message(s) from a folder or from stdin.
+
+   Usage:
+	java msgshow -L <url> -T <protocol> -H <host> -p <port>
+		-U <user> -P <password> -f <mailbox>
+		[-D] [-s] [-S] [-a] [-v] [msgnum]
+	java msgshow -m [-D] [-s] [-S] [-v]
+
+   Options:
+
+   -L <url>	: URL of the Store. The URL should include
+		  the password as well (if needed).
+		  Example: "imap://john:password@mailstore.com"
+   -T <protocol> : If <url> is not present, this indicates
+		  the store protocol
+   -H <host>	: If <url> is not present, this indicates
+		  the hostname
+   -p <port>	: If <url> is not present, this indicates
+		  the port number (usually not needed)
+   -U <user>	: If <url> is not present, this indicates
+		  the username
+   -P <passwd>	: If <url> is not present, this indicates
+		  the password
+   -f <mailbox>	: Folder to open
+   -m		: Read message from standard input
+   -D		: Turn on session debugging
+   -s		: Show the structure of the message, but not the contents
+   -S		: Save attachments to appropriately named files
+   -a		: Show ALERTS and NOTIFICATIONS from the Store
+   -v		: Verbose mode - show total messages and number of new messages
+
+   Argument:
+
+   <msgnum>	: the message to be displayed. If this
+  		  parameter is not present, all messages in the
+		  folder are displayed.
+
+
+- namespace.java
+
+	Displays the namespaces supported by a store.
+
+   Usage:
+	java namespace -L <url> -T <protocol> -H <host> -p <port>
+		-U <user> -P <password> [-D]
+
+   Options:
+
+   -L <url>	: URL of the Store. The URL should include
+		  the password as well (if needed).
+		  Example: "imap://john:password@mailstore.com"
+   -T <protocol> : If <url> is not present, this indicates
+		  the store protocol
+   -H <host>	: If <url> is not present, this indicates
+		  the hostname
+   -p <port>	: If <url> is not present, this indicates
+		  the port number (usually not needed)
+   -U <user>	: If <url> is not present, this indicates
+		  the username
+   -P <passwd>	: If <url> is not present, this indicates
+		  the password
+   -D		: Turn on session debugging
+
+
+- populate.java
+
+	Copies an entire folder hierarchy from one message store to
+	another.
+
+   Usage:
+	java populate -s <src-url> -d <dest-url> -D -f
+
+   Options:
+
+   -s <src-url>	: URL of source folder
+   -d <dest-url> : URL of destination folder
+   -D		: Turn on session debugging
+   -f		: force the copy to occur even if the destination
+		  folder already exists
+   -S		: skip special folders named "SCCS", "Drafts", "Trash", and
+		  "Shared Folders"
+   -c		: clear out old folders before copying messages
+   -P		: don't preserve flags when copying messages
+
+
+- registry.java
+
+	Demonstrates how to query the JavaMail "registry" for providers,
+	set default providers, etc.
+
+   Usage:
+	java registry
+
+
+- search.java
+
+	Search the given folder for messages matching the
+	given criteria.  Illustrates the use of the
+	javax.mail.search package.
+
+   Usage:
+	java search -L <url> -T <prot> -H <host> -U <user> -P <passwd>
+		-f <folder> -subject <subject> -from <from>
+		-today -or
+
+   Options:
+
+   -L <url>	: URL of the store
+   -T <protocol> : If <url> is not present, this indicates
+		  the store protocol
+   -H <host>	: If <url> is not present, this indicates
+		  the hostname
+   -U <user>	: If <url> is not present, this indicates
+		  the username
+   -P <passwd>	: If <url> is not present, this indicates
+		  the password
+   -f <folder>	: folder to search
+
+   -or		: If this flag is present, the search will
+		  return messages that match any one of the
+		  below criteria. Else the search will only
+		  return messages that match all the criteria
+
+   -subject <subject>	: search for messages containing this string
+			  as the Subject
+   -from <from>		: search for messages containing this string
+			  as the From address
+   -today		: search for messages received today
+
+
+- sendfile.java
+
+	Send the specified file to the given address.  The file
+	is sent as an attachment.  An SMTP server must be available.
+
+   Usage:
+	java sendfile <to> <from> <smtphost> <file> true|false
+
+   Arguments (in order):
+
+   <to>		: Recipient address
+   <from>	: Sender address
+   <smtphost>	: name of SMTP server
+   <file>	: name of file to be sent
+   true|false	: "true" to turn on session debugging, "false" otherwise
+
+
+- sendhtml.java
+
+	The sendhtml program works like the msgsend program, taking
+	the same options and input, but the text collected from the
+	user is sent as type "text/html" instead of "text/plain".
+
+	This program is a good example of how to send arbitrary
+	string data as any arbitrary MIME type.
+
+
+- smtpsend.java
+
+	Takes the same options as the msgsend program, but illustrates
+	how to handle SMTP-specific error codes.  Also accepts the
+	following options:
+
+   Option:
+
+   -v		: verbose output
+   -A		: use SMTP authentication
+   -S		: use SSL
+
+- transport.java
+
+	Illustrates how to use an explicit Transport object, how to
+	handle transport exceptions, and how to handle transport events.
+
+   Usage:
+	java transport <to> <from> <smtphost> <file> true|false
+
+   Arguments (in order):
+
+   <to>		: Recipient address
+   <from>	: Sender address
+   <smtphost>	: name of SMTP server
+   <file>	: name of file to be sent
+   true|false	: "true" to turn on session debugging, "false" otherwise
+
+
+- uidmsgshow.java
+
+	The uidmsgshow program works like the msgshow program, taking
+	the same options, except instead of using message numbers, it
+	uses message UID's.  This will typically only work with IMAP
+	message stores.
diff --git a/demo/src/main/java/copier.java b/demo/src/main/java/copier.java
new file mode 100644
index 0000000..0bce3f7
--- /dev/null
+++ b/demo/src/main/java/copier.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 1996, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+/**
+ *
+ * @author	Christopher Cotton
+ */
+
+import javax.mail.*;
+
+/**
+ * copier will copy a specified number of messages from one folder
+ * to another folder. it demonstrates how to use the JavaMail APIs
+ * to copy messages.<p>
+ *
+ * Usage for copier: copier <i>store urlname</i> 
+ * <i>src folder</i> <i>dest folder</i> <i>start msg #</i> <i>end msg #</i><p>
+ *
+ */
+
+public class copier {
+
+    public static void main(String argv[]) {
+	boolean debug = false;	// change to get more errors
+	
+	if (argv.length != 5) {
+	    System.out.println( "usage: copier <urlname> <src folder>" +
+				"<dest folder> <start msg #> <end msg #>");
+	    return;
+	}
+
+	try {
+	    URLName url = new URLName(argv[0]);
+	    String src = argv[1];	// source folder
+	    String dest = argv[2];	// dest folder
+	    int start = Integer.parseInt(argv[3]);	// copy from message #
+	    int end = Integer.parseInt(argv[4]);	// to message #
+
+	    // Get the default Session object
+
+	    Session session = Session.getInstance(System.getProperties(), null);
+	    // session.setDebug(debug);
+
+	    // Get a Store object that implements the protocol.
+	    Store store = session.getStore(url);
+	    store.connect();
+	    System.out.println("Connected...");
+
+	    // Open Source Folder
+	    Folder folder = store.getFolder(src);
+	    folder.open(Folder.READ_WRITE);
+	    System.out.println("Opened source...");	  
+
+	    if (folder.getMessageCount() == 0) {
+		  System.out.println("Source folder has no messages ..");
+		  folder.close(false);
+		  store.close();
+	    }
+
+	    // Open destination folder, create if needed 
+	    Folder dfolder = store.getFolder(dest);
+	    if (!dfolder.exists()) // create
+		dfolder.create(Folder.HOLDS_MESSAGES);
+
+	    Message[] msgs = folder.getMessages(start, end);
+	    System.out.println("Got messages...");	  
+
+	    // Copy messages into destination, 
+	    folder.copyMessages(msgs, dfolder);
+	    System.out.println("Copied messages...");	  
+
+	    // Close the folder and store
+	    folder.close(false);
+	    store.close();
+	    System.out.println("Closed folder and store...");
+	    
+	} catch (Exception e) {
+	    e.printStackTrace();
+	}
+
+	System.exit(0);
+    }
+}
diff --git a/demo/src/main/java/folderlist.java b/demo/src/main/java/folderlist.java
new file mode 100644
index 0000000..ae2285f
--- /dev/null
+++ b/demo/src/main/java/folderlist.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 1996, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.Properties;
+import javax.mail.*;
+
+import com.sun.mail.imap.*;
+
+/**
+ * Demo app that exercises the Message interfaces.
+ * List information about folders.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class folderlist {
+    static String protocol = null;
+    static String host = null;
+    static String user = null;
+    static String password = null;
+    static String url = null;
+    static String root = null;
+    static String pattern = "%";
+    static boolean recursive = false;
+    static boolean verbose = false;
+    static boolean debug = false;
+
+    public static void main(String argv[]) throws Exception {
+	int optind;
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-R")) {
+		root = argv[++optind];
+	    } else if (argv[optind].equals("-r")) {
+		recursive = true;
+	    } else if (argv[optind].equals("-v")) {
+		verbose = true;
+	    } else if (argv[optind].equals("-D")) {
+		debug = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: folderlist [-T protocol] [-H host] [-U user] [-P password] [-L url]");
+		System.out.println(
+"\t[-R root] [-r] [-v] [-D] [pattern]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+	if (optind < argv.length)
+	    pattern = argv[optind];
+
+	// Get a Properties object
+	Properties props = System.getProperties();
+
+	// Get a Session object
+	Session session = Session.getInstance(props, null);
+	session.setDebug(debug);
+
+	// Get a Store object
+	Store store = null;
+	Folder rf = null;
+	if (url != null) {
+	    URLName urln = new URLName(url);
+	    store = session.getStore(urln);
+	    store.connect();
+	} else {
+	    if (protocol != null)
+		store = session.getStore(protocol);
+	    else
+		store = session.getStore();
+
+	    // Connect
+	    if (host != null || user != null || password != null)
+		store.connect(host, user, password);
+	    else
+		store.connect();
+	}
+
+	// List namespace
+	if (root != null)
+	    rf = store.getFolder(root);
+	else
+	    rf = store.getDefaultFolder();
+
+	dumpFolder(rf, false, "");
+	if ((rf.getType() & Folder.HOLDS_FOLDERS) != 0) {
+	    Folder[] f = rf.list(pattern);
+	    for (int i = 0; i < f.length; i++)
+		dumpFolder(f[i], recursive, "    ");
+	}
+
+	store.close();
+    }
+
+    static void dumpFolder(Folder folder, boolean recurse, String tab)
+					throws Exception {
+	System.out.println(tab + "Name:      " + folder.getName());
+	System.out.println(tab + "Full Name: " + folder.getFullName());
+	System.out.println(tab + "URL:       " + folder.getURLName());
+
+	if (verbose) {
+	    if (!folder.isSubscribed())
+		System.out.println(tab + "Not Subscribed");
+
+	    if ((folder.getType() & Folder.HOLDS_MESSAGES) != 0) {
+		if (folder.hasNewMessages())
+		    System.out.println(tab + "Has New Messages");
+		System.out.println(tab + "Total Messages:  " +
+						folder.getMessageCount());
+		System.out.println(tab + "New Messages:    " +
+						folder.getNewMessageCount());
+		System.out.println(tab + "Unread Messages: " +
+						folder.getUnreadMessageCount());
+	    }
+	    if ((folder.getType() & Folder.HOLDS_FOLDERS) != 0)
+		System.out.println(tab + "Is Directory");
+
+	    /*
+	     * Demonstrate use of IMAP folder attributes
+	     * returned by the IMAP LIST response.
+	     */
+	    if (folder instanceof IMAPFolder) {
+		IMAPFolder f = (IMAPFolder)folder;
+		String[] attrs = f.getAttributes();
+		if (attrs != null && attrs.length > 0) {
+		    System.out.println(tab + "IMAP Attributes:");
+		    for (int i = 0; i < attrs.length; i++)
+			System.out.println(tab + "    " + attrs[i]);
+		}
+	    }
+	}
+
+	System.out.println();
+
+	if ((folder.getType() & Folder.HOLDS_FOLDERS) != 0) {
+	    if (recurse) {
+		Folder[] f = folder.list();
+		for (int i = 0; i < f.length; i++)
+		    dumpFolder(f[i], recurse, tab + "    ");
+	    }
+	}
+    }
+}
diff --git a/demo/src/main/java/internal/TtyAuthenticator.java b/demo/src/main/java/internal/TtyAuthenticator.java
new file mode 100644
index 0000000..ead5a32
--- /dev/null
+++ b/demo/src/main/java/internal/TtyAuthenticator.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import java.net.*;
+import javax.mail.*;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Authenticator;
+
+/**
+ * A simple Authenticator that prompts for the user name and password on stdin.
+ * Puts up a dialog something like:
+ * <p> <pre>
+ * Connecting to &lt;protocol&gt; mail service on host &lt;addr&gt;, port &lt;port&gt;.
+ * &lt;prompt&gt;
+ *
+ * User Name: [defaultUserName]
+ * Password:
+ * </pre> <p>
+ *
+ * @author Bill Shannon
+ */
+
+public class TtyAuthenticator extends Authenticator {
+
+    /**
+     * @return The PasswordAuthentication collected from the
+     *		user, or null if none is provided.
+     */
+    protected PasswordAuthentication getPasswordAuthentication() {
+	BufferedReader in = new BufferedReader(
+				new InputStreamReader((System.in)));
+	StringBuffer sb = new StringBuffer();
+	sb.append("Connecting to ");
+	sb.append(getRequestingProtocol());
+	sb.append(" mail service on host ");
+	sb.append(getRequestingSite().getHostName());
+	int port = getRequestingPort();
+	if (port > 0) {
+	    sb.append(", port ");
+	    sb.append(port);
+	}
+	sb.append(".");
+	System.out.println(sb.toString());
+	String prompt = getRequestingPrompt();
+	if (prompt != null)
+	    System.out.println(prompt);
+	System.out.println();
+	String userName = get(in, "User Name", getDefaultUserName());
+	String password = getpw("Password");
+	if (userName == null)
+	    return null;
+	else
+	    return new PasswordAuthentication(userName, password);
+    }
+
+    private static final String get(BufferedReader in,
+				String name, String value) {
+	PrintStream p = System.out;
+
+	p.print(name + ": ");
+	if (value != null)
+	    p.print("[" + value + "] ");
+	p.flush();
+
+	try {
+	    String s = in.readLine();
+	    if (s.length() == 0)
+		return value;
+	    else
+		return s;
+	} catch (IOException e) {
+	    return value;
+	}
+    }
+
+    private static final String getpw(String name) {
+	Console cons;
+	char[] passwd;
+	if ((cons = System.console()) != null &&
+	    (passwd = cons.readPassword("[%s] ", name)) != null)
+	    return new String(passwd);
+	return "";
+    }
+
+    // main program, for debugging.
+    // Usage: java TtyAuthenticator host port protocol prompt defaultUser
+    public static void main(String argv[]) throws Exception {
+	Session sess = Session.getInstance(System.getProperties(),
+					new TtyAuthenticator());
+	PasswordAuthentication pw = sess.requestPasswordAuthentication(
+		InetAddress.getByName(argv[0]),
+		Integer.parseInt(argv[1]), argv[2], z(argv[3]), z(argv[4]));
+	System.out.println("User: " + n(pw.getUserName()));
+	System.out.println("Password: " + n(pw.getPassword()));
+    }
+
+    private static final String n(String s) {
+	return s == null ? "<null>" : s;
+    }
+
+    private static final String z(String s) {
+	return s.length() > 0 ? s : null;
+    }
+}
diff --git a/demo/src/main/java/internal/answer.java b/demo/src/main/java/internal/answer.java
new file mode 100644
index 0000000..e7f8963
--- /dev/null
+++ b/demo/src/main/java/internal/answer.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.search.*;
+import javax.mail.internet.*;
+
+/**
+ * Program to manage the javamail@Sun.COM mailing list by keeping track
+ * of which messages have been answered.  Message that have been answered
+ * have the ANSWERED flag set.  Messages that are replies to other messages
+ * have the FLAGGED flag set. <p>
+ *
+ * Note that this program operates on the log file directly and thus all
+ * access to the log file using this program must be through the same
+ * IMAP server so that the flags are handled consistently. <p>
+ *
+ * When run with no message number arguments it will list the unanswered
+ * messages.  The -u flag will cause it to first update the mailbox to
+ * account for any recent replies. <p>
+ *
+ * When run with message number arguments it will mark those messages as
+ * answered.  This is useful for marking spam and other random messages
+ * that will never be replied to. <p>
+ *
+ * The -a flag will cause it to mark all messages as answered.  Useful for
+ * flushing the ever accumulating spam. <p>
+ *
+ * @author Bill Shannon
+ */
+
+public class answer {
+
+    static String protocol = "imap";
+    static String host = "anybodys.sfbay";
+    static String user = "javamail";
+    static String password = "1javamail1";
+    static String mbox =
+	"/net/anybodys.sfbay/export6/javamail/logs/javamail.log";
+    static String url = null;
+    static boolean verbose = false;
+    static boolean update = false;
+    static boolean markAll = false;
+
+    public static void main(String argv[]) {
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-v")) {
+		verbose = true;
+	    } else if (argv[optind].equals("-f")) {
+		mbox = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-u")) {
+		update = true;
+	    } else if (argv[optind].equals("-a")) {
+		markAll = true;
+		update = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: answer [-L url] [-T protocol] [-H host] [-U user] [-P password]\n" +
+"\t[-f mailbox] [-v] [-u] [-a] [msgno ...]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+        try {
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getDefaultInstance(props,
+				    new TtyAuthenticator());
+
+	    // Get a Store object
+	    Store store = null;
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, user, password);
+		else
+		    store.connect();
+	    }
+	    
+
+	    // Open the Folder
+
+	    Folder folder = store.getDefaultFolder();
+	    if (folder == null) {
+	        System.out.println("No default folder");
+	        System.exit(1);
+	    }
+
+	    folder = folder.getFolder(mbox);
+	    if (folder == null) {
+	        System.out.println("Invalid folder");
+	        System.exit(1);
+	    }
+
+	    if (optind < argv.length || update)
+		folder.open(Folder.READ_WRITE);
+	    else
+		folder.open(Folder.READ_ONLY);
+	    int totalMessages = folder.getMessageCount();
+
+	    if (totalMessages == 0) {
+		System.out.println("Empty folder");
+		folder.close(false);
+		store.close();
+		System.exit(1);
+	    }
+
+	    if (verbose) {
+		int newMessages = folder.getNewMessageCount();
+		pv("Total messages = " + totalMessages);
+		pv("New messages = " + newMessages);
+		pv("-------------------------------");
+	    }
+
+	    if (optind >= argv.length) {
+		// get all messages that aren't answered or flagged
+		Flags f = new Flags();
+		f.add(Flags.Flag.ANSWERED);
+		f.add(Flags.Flag.FLAGGED);
+
+		// Use a suitable FetchProfile
+		FetchProfile fp = new FetchProfile();
+		fp.add(FetchProfile.Item.ENVELOPE);
+		fp.add(FetchProfile.Item.FLAGS);
+
+		Message[] msgs = folder.search(new FlagTerm(f, false));
+		folder.fetch(msgs, fp);
+
+		if (update) {
+		    doUpdate(msgs);
+		    // re-fetch messages
+		    msgs = folder.search(new FlagTerm(f, false));
+		    pv("");
+		    pv("");
+		}
+
+		System.out.println("Unanswered messages:");
+		for (int i = 0; i < msgs.length; i++) {
+		    Message msg = msgs[i];
+		    System.out.println("--------------------------");
+		    System.out.println("MESSAGE #" +
+			msg.getMessageNumber() + ":");
+		    Question q = new Question(msg);
+		    System.out.println(q);
+		    if (q.isReply())
+			System.out.println(
+			    "A reply to a message we haven't seen");
+		    if (markAll) {
+			int msgno = msg.getMessageNumber();
+			if (q.isReply()) {
+			    pv("Flagging message #" + msgno);
+			    msg.setFlag(Flags.Flag.FLAGGED, true);
+			} else {
+			    pv("Answering message #" + msgno);
+			    msg.setFlag(Flags.Flag.ANSWERED, true);
+			}
+		    }
+		}
+
+	    } else {
+		for (int i = optind; i < argv.length; i++) {
+		    int msgno = Integer.parseInt(argv[i]);
+		    Message msg = folder.getMessage(msgno);
+		    Question q = new Question(msg);
+		    if (q.isReply()) {
+			pv("Flagging message #" + msgno);
+			msg.setFlag(Flags.Flag.FLAGGED, true);
+		    } else {
+			pv("Answering message #" + msgno);
+			msg.setFlag(Flags.Flag.ANSWERED, true);
+		    }
+		}
+	    }
+
+	    folder.close(false);
+	    store.close();
+	} catch (Exception ex) {
+	    System.out.println("Oops, got exception! " + ex.getMessage());
+	    ex.printStackTrace();
+	    System.exit(1);
+	}
+	System.exit(0);
+    }
+
+    /**
+     * Update the mailbox to account for any replies.
+     */
+    static void doUpdate(Message[] msgs) throws MessagingException {
+	// a hash table of all the unanswered questions
+	Hashtable qt = new Hashtable();
+
+	Address javamailAddress = new InternetAddress("javamail@Sun.COM");
+	SearchTerm javamail = new OrTerm(
+		new RecipientTerm(Message.RecipientType.TO, javamailAddress),
+		new RecipientTerm(Message.RecipientType.CC, javamailAddress));
+
+	for (int i = 0; i < msgs.length; i++) {
+	    Message msg = msgs[i];
+	    pv("--------------------------");
+	    pv("MESSAGE #" + msg.getMessageNumber() + ":");
+	    Question q = new Question(msg);
+	    pv(q.toString());
+	    Question q0;
+	    if ((q0 = (Question)qt.get(q)) != null) {
+		pv("Found in table");
+		if (q.isReply()) {
+		    Message qm = q0.getMessage();
+		    if (!qm.isSet(Flags.Flag.ANSWERED)) {
+			pv("Answered:");
+			pv(q0.toString());
+			qm.setFlag(Flags.Flag.ANSWERED, true);
+		    }
+		    pv("is reply, flagging it");
+		    msg.setFlag(Flags.Flag.FLAGGED, true);
+		} else {
+		    // a second copy of a message that's not a reply?
+		    // shouldn't happen.
+		}
+	    } else {
+		pv("NOT found in table");
+		if (q.isReply()) {
+		    // a reply to a message we haven't seen?
+		    pv("flagging, but no original msg");
+		    msg.setFlag(Flags.Flag.FLAGGED, true);
+		} else {
+		    // an original question, put it in the table
+		    qt.put(q, q);
+		    // if the message looks like spam, flag it
+		    if (!javamail.match(msg)) {
+			pv("SPAM!!!");
+			msg.setFlag(Flags.Flag.FLAGGED, true);
+		    }
+		}
+	    }
+	}
+    }
+
+    /**
+     * Print verbose.
+     */
+    static void pv(String s) {
+	if (verbose)
+	    System.out.println(s);
+    }
+}
+
+/**
+ * This class represents a single "question" sent to javamail@Sun.COM,
+ * or possibly a reply to such a question.
+ */
+class Question {
+    Message	msg;
+    Address	sender;
+    Address[]	recipients;
+    String	subject;
+    Date	date;
+    boolean	reply = false;
+
+    Question(Message msg) throws MessagingException {
+	this.msg = msg;
+	sender = msg.getReplyTo()[0];    // XXX - assume only one
+	subject = msg.getSubject();
+	if (subject == null)
+	    subject = "";
+	else if (subject.regionMatches(true, 0, "Re: ", 0, 4)) {
+	    subject = subject.substring(4);
+	    reply = true;
+	    recipients = msg.getRecipients(Message.RecipientType.TO);
+	}
+	subject = subject.trim();
+	date = msg.getSentDate();
+	if (date == null)
+	    date = msg.getReceivedDate();
+    }
+
+    public boolean isReply() {
+	return reply;
+    }
+
+    public Message getMessage() {
+	return msg;
+    }
+
+    public int hashCode() {
+	return subject.hashCode();
+    }
+
+    public boolean equals(Object obj) {
+	if (!(obj instanceof Question))
+	    return false;
+	Question q = (Question)obj;
+	if (this.reply == q.reply)
+	    return this.sender.equals(q.sender) &&
+		    this.subject.equals(q.subject);
+
+	/*
+	 * A reply is equal to a question if the sender of the question
+	 * is one of the recipients of the reply.
+	 */
+	Question qq, qr;
+	if (this.reply) {
+	    qq = q;
+	    qr = this;
+	} else {
+	    qq = this;
+	    qr = q;
+	}
+	for (int i = 0; i < qr.recipients.length; i++) {
+	    if (qq.sender.equals(qr.recipients[i]))
+		return true;
+	}
+	return false;
+    }
+
+    public String toString() {
+	return "Date: " + date + "\nFrom: " + sender + "\nSubject: " + subject;
+    }
+}
diff --git a/demo/src/main/java/internal/foldersplit.java b/demo/src/main/java/internal/foldersplit.java
new file mode 100644
index 0000000..c9e4445
--- /dev/null
+++ b/demo/src/main/java/internal/foldersplit.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.text.*;
+import javax.mail.*;
+
+/**
+ *
+ * Split mail folders according to date of messages.
+ *
+ * @author Bill Shannon
+ */
+
+public class foldersplit {
+
+    static String protocol;
+    static String host = null;
+    static String user = null;
+    static String password = null;
+    static String dst = null;
+    static String url = null;
+    static int port = -1;
+    static boolean verbose = false;
+    static boolean debug = false;
+    static boolean nop = false;
+    static SimpleDateFormat df = new SimpleDateFormat("yyyy.MM");
+
+    public static void main(String argv[]) {
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-d")) {
+		dst = argv[++optind];
+	    } else if (argv[optind].equals("-v")) {
+		verbose = true;
+	    } else if (argv[optind].equals("-n")) {
+		nop = true;
+	    } else if (argv[optind].equals("-D")) {
+		debug = true;
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-p")) {
+		port = Integer.parseInt(argv[++optind]);
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: foldersplit [-L url] [-T protocol] [-H host] [-p port] [-U user]");
+		System.out.println(
+"\t[-P password] [-v] [-D] folder ...");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+        try {
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getDefaultInstance(props, null);
+	    session.setDebug(debug);
+
+	    // Get a Store object
+	    Store store = null;
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, port, user, password);
+		else
+		    store.connect();
+	    }
+	    
+
+	    // Open the Folder
+
+	    Folder deffolder = store.getDefaultFolder();
+	    if (deffolder == null) {
+	        System.out.println("No default folder");
+	        System.exit(1);
+	    }
+	    Folder dstfolder;
+	    if (dst == null) {
+		dstfolder = deffolder;
+	    } else {
+		dstfolder = deffolder.getFolder(dst);
+		if (dstfolder == null) {
+		    System.out.println("No destination folder");
+		    System.exit(1);
+		}
+	    }
+
+	    Folder outf = null;
+	    for (; optind < argv.length; optind++) {
+		Folder folder = deffolder.getFolder(argv[optind]);
+		if (folder == null) {
+		    System.out.println("Invalid folder: " + argv[optind]);
+		    continue;
+		}
+		System.out.println("Folder: " + folder.getFullName());
+
+		folder.open(Folder.READ_ONLY);
+		int totalMessages = folder.getMessageCount();
+
+		if (totalMessages == 0) {
+		    System.out.println("Empty folder");
+		    folder.close(false);
+		    continue;
+		}
+
+		if (verbose) {
+		    int newMessages = folder.getNewMessageCount();
+		    System.out.println("Total messages = " + totalMessages);
+		    System.out.println("New messages = " + newMessages);
+		    System.out.println("-------------------------------");
+		}
+
+		// Attributes & Flags for all messages ..
+		Message[] msgs = folder.getMessages();
+
+		// Use a suitable FetchProfile
+		FetchProfile fp = new FetchProfile();
+		fp.add(FetchProfile.Item.ENVELOPE);
+		fp.add(FetchProfile.Item.FLAGS);
+		folder.fetch(msgs, fp);
+
+		for (int i = 0; i < msgs.length; i++) {
+		    if (verbose) {
+			System.out.println("--------------------------");
+			System.out.println("MESSAGE #" + (i + 1) + ":");
+		    }
+		    Date d = msgs[i].getReceivedDate();
+		    if (d != null) {
+			String n = df.format(d);
+			if (verbose)
+			    System.out.println(n);
+			if (outf == null || !n.equals(outf.getName())) {
+			    outf = dstfolder.getFolder(n);
+			    if (!outf.exists()) {
+				System.out.println("Creating: " +
+						    outf.getFullName());
+				if (!nop)
+				    outf.create(Folder.HOLDS_MESSAGES);
+			    }
+			}
+		    }
+		    if (!nop)
+			outf.appendMessages(new Message[] { msgs[i] });
+		}
+		folder.close(false);
+	    }
+
+	    store.close();
+	} catch (Exception ex) {
+	    System.out.println("Oops, got exception! " + ex.getMessage());
+	    ex.printStackTrace();
+	}
+	System.exit(1);
+    }
+}
diff --git a/demo/src/main/java/internal/fpopulate.java b/demo/src/main/java/internal/fpopulate.java
new file mode 100644
index 0000000..8333213
--- /dev/null
+++ b/demo/src/main/java/internal/fpopulate.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/*
+ * Copy folder hierarchies between files and a Store. This is a useful 
+ * utility to populate new (and possibly empty) mail stores. Specify
+ * the source as a directory name and the destination folders as a URL.
+ *	
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class fpopulate {
+
+    static boolean force = false;
+    static boolean skipSCCS = false;
+    static boolean clear = false;
+
+    static Session session;
+
+    public static void main(String argv[]) {
+    	String srcdir = null;
+    	String dstURL = null;
+	boolean debug = false;
+
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-s")) {
+		srcdir = argv[++optind];
+	    } else if (argv[optind].equals("-d")) {
+		dstURL = argv[++optind];
+	    } else if (argv[optind].equals("-D")) {
+		debug = true;
+	    } else if (argv[optind].equals("-f")) {
+		force = true;
+	    } else if (argv[optind].equals("-S")) {
+		skipSCCS = true;
+	    } else if (argv[optind].equals("-c")) {
+		clear = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		printUsage();
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+
+	    if (srcdir == null || dstURL == null) {
+		printUsage();
+		System.exit(1);
+	    }
+
+	    session = Session.getDefaultInstance(
+				System.getProperties(), null);
+	    session.setDebug(debug);
+
+	    // Get source folder
+	    File srcFolder = new File(srcdir);
+	    if (!srcFolder.exists()) {
+		System.out.println("source folder does not exist");
+		System.exit(1);
+	    }
+
+	    // Set up destination folder
+	    URLName dstURLName = new URLName(dstURL);
+	    Folder dstFolder;
+	    // Check if the destination URL has a folder specified. If
+	    // not, we use the source folder name
+	    if (dstURLName.getFile() == null) {
+		Store s = session.getStore(dstURLName);
+		s.connect();
+		dstFolder = s.getFolder(srcFolder.getName());
+	    } else
+		dstFolder = session.getFolder(new URLName(dstURL));
+
+	    if (clear && dstFolder.exists()) {
+		if (!dstFolder.delete(true)) {
+		    System.out.println("couldn't delete " +
+						dstFolder.getFullName());
+		    return;
+		}
+	    }
+	    copy(srcFolder, dstFolder);
+
+	    // Close the respective stores.
+	    dstFolder.getStore().close();
+
+	} catch (MessagingException mex) {
+	    System.out.println(mex.getMessage());
+	    mex.printStackTrace();
+	} catch (IOException ioex) {
+	    System.out.println(ioex.getMessage());
+	    ioex.printStackTrace();
+	}
+    }
+
+    private static void copy(File src, Folder dst)
+		throws MessagingException, IOException {
+	System.out.println("Populating " + dst.getFullName());
+
+	if (!dst.exists()) {
+	    // Create it.
+	    int type = holdsMessages(src) ?
+		    Folder.HOLDS_MESSAGES : Folder.HOLDS_FOLDERS;
+	    if (!dst.create(type)) {
+		System.out.println("couldn't create " + dst.getFullName());
+		return;
+	    }
+
+	    // Copy over any messages from src to dst
+	    if (holdsMessages(src))
+		copyMessages(src, dst);
+	} else  {
+	    System.out.println(dst.getFullName() + " already exists");
+	    // Copy over any messges from src to dst
+	    if (force && holdsMessages(src))
+		copyMessages(src, dst);
+	}
+
+	// Copy over subfolders
+	if (holdsFolders(src)) {
+	    String[] sf = src.list();
+	    for (int  i = 0; sf != null && i < sf.length; i++) {
+		// skip SCCS directories?
+		if (skipSCCS && sf[i].equals("SCCS"))
+		    continue;
+		File f = new File(src, sf[i]);
+		if (f.isDirectory())
+		    copy(f, dst.getFolder(sf[i]));
+	    }
+    	}
+    }
+
+    /**
+     * Does this directory hold messages?
+     * Return true if there's at least one message.
+     */
+    private static boolean holdsMessages(File f) {
+	File msg = new File(f, "1");
+	return msg.exists();
+    }
+
+    private static boolean holdsFolders(File f) {
+	return !holdsMessages(f);	// XXX - hack for now
+    }
+
+    /**
+     * Copy message files from the source directory to the
+     * destination folder.  Message files must be named "1",
+     * "2", etc.  The first missing number terminates the
+     * copy.
+     */
+    private static void copyMessages(File src, Folder dst)
+				throws MessagingException, IOException {
+	System.out.println("  Copy from " + src + " to " + dst);
+	int msgnum = 1;
+	Message[] msgs = new Message[1];
+	for (;;) {
+	    File f = new File(src, String.valueOf(msgnum));
+	    if (!f.exists())	// break when we find a message missing
+		break;
+	    FileInputStream fis = new FileInputStream(f);
+	    BufferedInputStream is = new BufferedInputStream(fis);
+	    is.mark(1024);
+	    DataInputStream dis = new DataInputStream(is);
+	    String line = dis.readLine();
+	    /*
+	     * If it's in UNIX mbox format, we skip the first line,
+	     * otherwise we start reading at the beginning.
+	     */
+	    if (!line.startsWith("From "))
+		is.reset();
+	    MimeMessage msg = new MimeMessage(session, is);
+	    fis.close();
+	    msgs[0] = msg;
+	    dst.appendMessages(msgs);
+	    msgnum++;
+	}
+	System.out.println("  Copied " + (msgnum - 1) + " messages");
+    }
+
+    private static void printUsage() {
+	System.out.println("fpopulate [-D] [-f] [-S] [-c] " +
+			   "-s source_dir -d dest_url");
+	System.out.println("URLs are of the form: " +
+		  	   "protocol://username:password@hostname/foldername");
+	System.out.println("The destination URL does not need a foldername," +
+		  	   " in which case, the source foldername is used");
+    }
+}
diff --git a/demo/src/main/java/internal/msgsperweek.java b/demo/src/main/java/internal/msgsperweek.java
new file mode 100644
index 0000000..7105b97
--- /dev/null
+++ b/demo/src/main/java/internal/msgsperweek.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.text.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.search.*;
+import javax.mail.internet.*;
+
+/**
+ * Program that generates stats about new messages received per week 
+ * by the javamail@Sun.COM mailing list. It tracks *new* messages only,
+ * the rule being that the message has a single addressee: 
+ * "javamail@Sun.COM". Also searches each message body for the "pop3" 
+ * string and counts its occurrence.
+ * 
+ * Note that this program operates on the log file directly and thus all
+ * access to the log file using this program must be through the same
+ * IMAP server so that the flags are handled consistently.
+ *
+ * @author Max Spivak
+ */
+
+public class msgsperweek {
+
+    static String protocol = "imap";
+    static String host = "anybodys.sfbay";
+    static String user = "javamail";
+    static String password = "1javamail1";
+    static String mbox =
+	"/net/anybodys.sfbay/export6/javamail/logs/javamail.log";
+    static String url = null;
+    static boolean verbose = false;
+    static boolean doPop3 = false;
+    static Calendar cal = null;
+
+    public static void main(String argv[]) {
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-v")) {
+		verbose = true;
+	    } else if (argv[optind].equals("-f")) {
+		mbox = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-p")) {
+		doPop3 = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: msgperweek [-L url] [-T protocol] [-H host] [-U user] [-P password]\n"+
+"\t[-f mailbox] [-v] [-p]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	System.out.println("msgsperweek: Generating stats, please wait...");
+	
+	cal = Calendar.getInstance();
+	
+        try {
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getDefaultInstance(props,
+				    new TtyAuthenticator());
+
+	    //if (verbose)
+	    //session.setDebug(true);
+
+	    // Get a Store object
+	    Store store = null;
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, user, password);
+		else
+		    store.connect();
+	    }
+	    
+
+	    // Open the Folder
+
+	    Folder folder = store.getDefaultFolder();
+	    if (folder == null) {
+	        System.out.println("No default folder");
+	        System.exit(1);
+	    }
+
+	    folder = folder.getFolder(mbox);
+	    if (folder == null) {
+	        System.out.println("Invalid folder");
+	        System.exit(1);
+	    }
+
+	    folder.open(Folder.READ_ONLY);
+	    int totalMessages = folder.getMessageCount();
+
+	    if (totalMessages == 0) {
+		System.out.println("Empty folder");
+		folder.close(false);
+		store.close();
+		System.exit(1);
+	    }
+
+	    if (verbose) {
+		int newMessages = folder.getNewMessageCount();
+		pv("Total messages = " + totalMessages);
+		pv("New messages = " + newMessages);
+		pv("-------------------------------");
+	    }
+
+	    // Use a suitable FetchProfile
+	    FetchProfile fp = new FetchProfile();
+	    fp.add(FetchProfile.Item.ENVELOPE);
+
+	    SearchTerm term = new RecipientTerm(Message.RecipientType.TO,
+				     new InternetAddress("javamail@sun.com"));
+	    Message[] msgs = folder.search(term);
+	    folder.fetch(msgs, fp);
+
+	    Hashtable wks = new Hashtable();
+	    int totalCount = 0;
+	    BodyTerm pop3Search = new BodyTerm("pop3");
+	    int pop3Requests = 0;
+
+	    // go through each msg and count it if it's an incoming msg
+	    for (int i = 0; i < msgs.length; i++) {
+		// make sure we only have a single addressee:
+		// javamail@sun.com, which means it's a new message
+		// from outside of Sun
+		Address[] recs=msgs[i].getRecipients(Message.RecipientType.TO);
+		if (recs.length > 1)
+		    continue;
+
+		// get msgs date
+		Date d = msgs[i].getSentDate();
+		if (d == null)
+		    d = msgs[i].getReceivedDate();
+		cal.setTime(d);
+
+		// figure out what week it is
+		int week = cal.get(Calendar.WEEK_OF_YEAR);
+		int yr = cal.get(Calendar.YEAR);
+		String wkInYr = week + "  " + yr;
+
+		// increment that week's count
+		String num = (String)wks.get(wkInYr);
+		totalCount++;
+		if (num == null)
+		    wks.put(wkInYr, "1");
+		else {
+		    int count = Integer.parseInt(num);
+		    count++;
+		    Integer str = new Integer(count);
+		    wks.put(wkInYr, str.toString());
+		}
+
+		// check for pop3 requests
+		if (doPop3 && pop3Search.match(msgs[i]))
+		    pop3Requests++;
+	    }
+
+	    // print out the statistics
+	    int startWk = 40;
+	    int startYr = 1997;
+	    Calendar now = Calendar.getInstance();
+	    now.setTime(new Date());
+	    cal.set(startYr, 1, 1);
+	    cal.set(Calendar.WEEK_OF_YEAR, startWk);
+	    cal.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);
+	    DateFormat df = new SimpleDateFormat("E MMM dd yyyy");
+	    int totalWks = 0;
+
+	    System.out.println("\nNumber of new messages a week sent to javamail@Sun.COM by external JavaMail\nusers. This does not include our answers and follow-up mail.");
+	    for (;;) {
+		if (cal.after(now))
+		    break;
+		else {
+		    totalWks++;
+		    String wkInYr = (cal.get(Calendar.WEEK_OF_YEAR)) +
+			"  " + 
+			(cal.get(Calendar.YEAR));
+		    Object o = wks.get(wkInYr);
+		    if (o != null) {
+			String i = (String)o;
+			System.out.println("  week of " + 
+					   df.format(cal.getTime()) + 
+					   ": " + 
+					   i + " messages");
+		    }
+		    		    
+		    // increment to next week
+		    cal.add(Calendar.WEEK_OF_YEAR, 1);
+		}
+	    }
+	    System.out.println("------------------");
+	    System.out.println("Total messages received: " + totalCount);
+	    System.out.println("Average messages/week:    " + 
+			       totalCount/totalWks);
+	    if (doPop3)
+		System.out.println("Total POP3 requests:      " + pop3Requests);
+	    folder.close(false);
+	    store.close();
+	} catch (Exception ex) {
+	    System.out.println("Oops, got exception! " + ex.getMessage());
+	    ex.printStackTrace();
+	}
+	System.exit(1);
+    }
+
+
+    /**
+     * Print verbose.
+     */
+    static void pv(String s) {
+	if (verbose)
+	    System.out.println(s);
+    }
+}
diff --git a/demo/src/main/java/internal/testidle.java b/demo/src/main/java/internal/testidle.java
new file mode 100644
index 0000000..6d65f9b
--- /dev/null
+++ b/demo/src/main/java/internal/testidle.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.util.concurrent.atomic.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+import com.sun.mail.imap.*;
+
+/**
+ * Test program for IDLE support in IMAP provider.
+ *
+ * Run several threads that access the folder's connection
+ * while another thread runs the IDLE command.  A timer
+ * thread checks that each thread is making progress, to
+ * detect deadlock.
+ *
+ * XXX - Should have another thread add messages to the folder
+ *	 to test the new message notification works properly.
+ */
+public class testidle {
+
+    public static Store store;
+    public static Folder folder;
+    public static boolean showStructure = false;
+    public static boolean saveAttachments = false;
+    public static int attnum = 0;
+    public static int totalTime;
+
+    public static final AtomicBoolean stop = new AtomicBoolean(false);
+    public static final AtomicInteger folderProgress = new AtomicInteger();
+    public static final AtomicInteger msgProgress = new AtomicInteger();
+
+    public static void main(String argv[]) {
+	if (argv.length != 5) {
+	    System.out.println(
+		"Usage: testidle <host> <user> <password> <mbox> <time>");
+	    System.exit(1);
+	}
+
+        try {
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+	    // session.setDebug(true);
+
+	    // Get a Store object
+	    store = session.getStore("imap");
+
+	    store.addStoreListener(new StoreListener() {
+		public void notification(StoreEvent e) {
+		    System.out.println("StoreEvent: type " +
+			e.getMessageType() + ", message " +
+			e.getMessage());
+		}
+	    });
+
+	    // Connect
+	    store.connect(argv[0], argv[1], argv[2]);
+
+	    // Open a Folder
+	    folder = store.getFolder(argv[3]);
+	    if (folder == null || !folder.exists()) {
+		System.out.println("Invalid folder");
+		System.exit(1);
+	    }
+
+	    folder.open(Folder.READ_WRITE);
+
+	    // Add messageCountListener to listen for new messages
+	    folder.addMessageCountListener(new MessageCountAdapter() {
+		public void messagesAdded(MessageCountEvent ev) {
+		    Message[] msgs = ev.getMessages();
+		    System.out.println("Got " + msgs.length + " new messages");
+
+		    // Just dump out the new messages
+		    for (int i = 0; i < msgs.length; i++) {
+			try {
+			    System.out.println("-----");
+			    System.out.println("Message " +
+				msgs[i].getMessageNumber() + ":");
+			    msgs[i].writeTo(System.out);
+			} catch (IOException ioex) { 
+			    ioex.printStackTrace();	
+			} catch (MessagingException mex) {
+			    mex.printStackTrace();
+			}
+		    }
+		}
+	    });
+
+	    totalTime = Integer.parseInt(argv[4]);
+
+	    new Thread("timer") {
+		public void run() {
+		    timer();
+		}
+	    }.start();
+
+	    new Thread("message reader") {
+		public void run() {
+		    readMessages();
+		}
+	    }.start();
+
+	    new Thread("folder reader") {
+		public void run() {
+		    readFolder();
+		}
+	    }.start();
+
+	    new Thread("store idle") {
+		public void run() {
+		    storeIdle();
+		}
+	    }.start();
+
+
+	    /*
+	     * make sure two threads running idle works properly
+	    new Thread("idle") {
+		public void run() {
+		    try {
+			doIdle();
+		    } catch (MessagingException mex) { }
+		}
+	    }.start();
+	     */
+
+	    doIdle();
+
+	    System.out.println("connected " + store.isConnected());
+
+	} catch (Exception ex) {
+	    ex.printStackTrace();
+	}
+    }
+
+    /**
+     * Run the idle command until told to stop.
+     */
+    public static void doIdle() throws MessagingException {
+	boolean supportsIdle = false;
+	try {
+	    if (folder instanceof IMAPFolder) {
+		IMAPFolder f = (IMAPFolder)folder;
+		f.idle();
+		supportsIdle = true;
+	    }
+	} catch (FolderClosedException fex) {
+	    throw fex;
+	} catch (MessagingException mex) {
+	    supportsIdle = false;
+	}
+	while (!stop.get()) {
+	    if (supportsIdle && folder instanceof IMAPFolder) {
+		IMAPFolder f = (IMAPFolder)folder;
+		f.idle();
+		/*
+		System.out.println("IDLE done in " +
+					Thread.currentThread().getName());
+		 */
+	    } else {
+		try {
+		    Thread.sleep(1000); // sleep for 1000 milliseconds
+		} catch (InterruptedException ex) { }
+
+		// This is to force the IMAP server to send us
+		// EXISTS notifications. 
+		folder.getMessageCount();
+	    }
+	}
+    }
+
+    /**
+     * Monitor progress of threads and tell them to stop
+     * when their time is up.
+     */
+    public static void timer() {
+	long tend = System.currentTimeMillis() + totalTime;
+	int fpcnt = folderProgress.get();
+	int mpcnt = msgProgress.get();
+	while (System.currentTimeMillis() < tend) {
+	    try {
+		Thread.sleep(1000);
+	    } catch (InterruptedException ex) { }
+	    int nfpcnt = folderProgress.get();
+	    int nmpcnt = msgProgress.get();
+	    if (!(nfpcnt > fpcnt && nmpcnt > mpcnt)) {
+		System.out.println("THREAD STUCK?");
+		System.out.printf("fpcnt %d nfpcnt %d\n", fpcnt, nfpcnt);
+		System.out.printf("mpcnt %d nmpcnt %d\n", mpcnt, nmpcnt);
+	    }
+	    fpcnt = nfpcnt;
+	    mpcnt = nmpcnt;
+	}
+	System.out.println("STOPPING");
+	stop.set(true);
+	try {
+	    folder.getUnreadMessageCount();	// force IDLE to terminate
+	} catch (MessagingException mex) { }
+    }
+
+    /**
+     * Read all the messages in the folder and access all their content.
+     */
+    public static void readMessages() {
+	try {
+	    while (!stop.get()) {
+		int cnt = folder.getMessageCount();
+		for (int i = 1; i <= cnt; i++) {
+		    //System.out.println("dump " + i);
+		    Message msg = folder.getMessage(i);
+		    dumpEnvelope(msg);
+		    msgProgress.incrementAndGet();
+		    try {
+			Thread.sleep(25);
+		    } catch (InterruptedException ex) { }
+		}
+	    }
+	    System.out.println("messages DONE");
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	}
+    }
+
+    /**
+     * Perform folder commands to compete with the message
+     * access commands.
+     */
+    public static void readFolder() {
+	try {
+	    while (!stop.get()) {
+		int cnt = folder.getUnreadMessageCount();
+		store.isConnected();	// poke the store too
+		folderProgress.incrementAndGet();
+		try {
+		    Thread.sleep(100);
+		} catch (InterruptedException ex) { }
+	    }
+	    System.out.println("folder DONE");
+	} catch (MessagingException mex) {
+	    System.out.println(mex);
+	}
+    }
+
+    /**
+     * Run the idle command until told to stop.
+     */
+    public static void storeIdle() {
+	boolean supportsIdle = false;
+	try {
+	    if (store instanceof IMAPStore) {
+		IMAPStore s = (IMAPStore)store;
+		s.idle();
+		supportsIdle = true;
+	    }
+	} catch (MessagingException mex) {
+	    supportsIdle = false;
+	}
+	try {
+	    while (!stop.get()) {
+		if (supportsIdle && store instanceof IMAPStore) {
+		    IMAPStore s = (IMAPStore)store;
+		    s.idle();
+		    /*
+		    */
+		    System.out.println("IDLE done in " +
+					    Thread.currentThread().getName());
+		     /*
+		     */
+		} else {
+		    try {
+			Thread.sleep(1000); // sleep for 1000 milliseconds
+		    } catch (InterruptedException ex) { }
+		}
+	    }
+	} catch (MessagingException mex) {
+	    System.out.println(mex);
+	}
+    }
+
+    /**
+     * Dump contents of message part.
+     * (Copied from msgshow.java)
+     */
+    public static void dumpPart(Part p) throws Exception {
+	if (p instanceof Message)
+	    dumpEnvelope((Message)p);
+
+	/** Dump input stream .. 
+
+	InputStream is = p.getInputStream();
+	// If "is" is not already buffered, wrap a BufferedInputStream
+	// around it.
+	if (!(is instanceof BufferedInputStream))
+	    is = new BufferedInputStream(is);
+	int c;
+	while ((c = is.read()) != -1)
+	    System.out.write(c);
+
+	**/
+
+	String ct = p.getContentType();
+	try {
+	    pr("CONTENT-TYPE: " + (new ContentType(ct)).toString());
+	} catch (ParseException pex) {
+	    pr("BAD CONTENT-TYPE: " + ct);
+	}
+	String filename = p.getFileName();
+	if (filename != null)
+	    pr("FILENAME: " + filename);
+
+	/*
+	 * Using isMimeType to determine the content type avoids
+	 * fetching the actual content data until we need it.
+	 */
+	if (p.isMimeType("text/plain")) {
+	    pr("This is plain text");
+	    pr("---------------------------");
+	    if (!showStructure && !saveAttachments)
+		System.out.println((String)p.getContent());
+	} else if (p.isMimeType("multipart/*")) {
+	    pr("This is a Multipart");
+	    pr("---------------------------");
+	    Multipart mp = (Multipart)p.getContent();
+	    level++;
+	    int count = mp.getCount();
+	    for (int i = 0; i < count; i++)
+		dumpPart(mp.getBodyPart(i));
+	    level--;
+	} else if (p.isMimeType("message/rfc822")) {
+	    pr("This is a Nested Message");
+	    pr("---------------------------");
+	    level++;
+	    dumpPart((Part)p.getContent());
+	    level--;
+	} else {
+	    if (!showStructure && !saveAttachments) {
+		/*
+		 * If we actually want to see the data, and it's not a
+		 * MIME type we know, fetch it and check its Java type.
+		 */
+		Object o = p.getContent();
+		if (o instanceof String) {
+		    pr("This is a string");
+		    pr("---------------------------");
+		    System.out.println((String)o);
+		} else if (o instanceof InputStream) {
+		    pr("This is just an input stream");
+		    pr("---------------------------");
+		    InputStream is = (InputStream)o;
+		    int c;
+		    while ((c = is.read()) != -1)
+			System.out.write(c);
+		} else {
+		    pr("This is an unknown type");
+		    pr("---------------------------");
+		    pr(o.toString());
+		}
+	    } else {
+		// just a separator
+		pr("---------------------------");
+	    }
+	}
+
+	/*
+	 * If we're saving attachments, write out anything that
+	 * looks like an attachment into an appropriately named
+	 * file.  Don't overwrite existing files to prevent
+	 * mistakes.
+	 */
+	if (saveAttachments && level != 0 && !p.isMimeType("multipart/*")) {
+	    String disp = p.getDisposition();
+	    // many mailers don't include a Content-Disposition
+	    if (disp == null || disp.equalsIgnoreCase(Part.ATTACHMENT)) {
+		if (filename == null)
+		    filename = "Attachment" + attnum++;
+		pr("Saving attachment to file " + filename);
+		try {
+		    File f = new File(filename);
+		    if (f.exists())
+			// XXX - could try a series of names
+			throw new IOException("file exists");
+		    ((MimeBodyPart)p).saveFile(f);
+		} catch (IOException ex) {
+		    pr("Failed to save attachment: " + ex);
+		}
+		pr("---------------------------");
+	    }
+	}
+    }
+
+    public static void dumpEnvelope(Message m) throws Exception {
+	pr("This is the message envelope");
+	pr("---------------------------");
+	Address[] a;
+	// FROM 
+	if ((a = m.getFrom()) != null) {
+	    for (int j = 0; j < a.length; j++)
+		pr("FROM: " + a[j].toString());
+	}
+
+	// TO
+	if ((a = m.getRecipients(Message.RecipientType.TO)) != null) {
+	    for (int j = 0; j < a.length; j++) {
+		pr("TO: " + a[j].toString());
+		InternetAddress ia = (InternetAddress)a[j];
+		if (ia.isGroup()) {
+		    InternetAddress[] aa = ia.getGroup(false);
+		    for (int k = 0; k < aa.length; k++)
+			pr("  GROUP: " + aa[k].toString());
+		}
+	    }
+	}
+
+	// SUBJECT
+	pr("SUBJECT: " + m.getSubject());
+
+	// DATE
+	Date d = m.getSentDate();
+	pr("SendDate: " +
+	    (d != null ? d.toString() : "UNKNOWN"));
+
+	// FLAGS
+	Flags flags = m.getFlags();
+	StringBuffer sb = new StringBuffer();
+	Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
+
+	boolean first = true;
+	for (int i = 0; i < sf.length; i++) {
+	    String s;
+	    Flags.Flag f = sf[i];
+	    if (f == Flags.Flag.ANSWERED)
+		s = "\\Answered";
+	    else if (f == Flags.Flag.DELETED)
+		s = "\\Deleted";
+	    else if (f == Flags.Flag.DRAFT)
+		s = "\\Draft";
+	    else if (f == Flags.Flag.FLAGGED)
+		s = "\\Flagged";
+	    else if (f == Flags.Flag.RECENT)
+		s = "\\Recent";
+	    else if (f == Flags.Flag.SEEN)
+		s = "\\Seen";
+	    else
+		continue;	// skip it
+	    if (first)
+		first = false;
+	    else
+		sb.append(' ');
+	    sb.append(s);
+	}
+
+	String[] uf = flags.getUserFlags(); // get the user flag strings
+	for (int i = 0; i < uf.length; i++) {
+	    if (first)
+		first = false;
+	    else
+		sb.append(' ');
+	    sb.append(uf[i]);
+	}
+	pr("FLAGS: " + sb.toString());
+
+	// X-MAILER
+	String[] hdrs = m.getHeader("X-Mailer");
+	if (hdrs != null)
+	    pr("X-Mailer: " + hdrs[0]);
+	else
+	    pr("X-Mailer NOT available");
+    }
+
+    static String indentStr = "                                               ";
+    static int level = 0;
+
+    /**
+     * Print a, possibly indented, string.
+     */
+    public static void pr(String s) {
+	/*
+	if (showStructure)
+	    System.out.print(indentStr.substring(0, level * 2));
+	System.out.println(s);
+	*/
+    }
+}
diff --git a/demo/src/main/java/monitor.java b/demo/src/main/java/monitor.java
new file mode 100644
index 0000000..86fb299
--- /dev/null
+++ b/demo/src/main/java/monitor.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 1996, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.activation.*;
+
+import com.sun.mail.imap.*;
+
+/* Monitors given mailbox for new mail */
+
+public class monitor {
+
+    public static void main(String argv[]) {
+	if (argv.length != 5) {
+	    System.out.println(
+		"Usage: monitor <host> <user> <password> <mbox> <freq>");
+	    System.exit(1);
+	}
+	System.out.println("\nTesting monitor\n");
+
+	try {
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+	    // session.setDebug(true);
+
+	    // Get a Store object
+	    Store store = session.getStore("imap");
+
+	    // Connect
+	    store.connect(argv[0], argv[1], argv[2]);
+
+	    // Open a Folder
+	    Folder folder = store.getFolder(argv[3]);
+	    if (folder == null || !folder.exists()) {
+		System.out.println("Invalid folder");
+		System.exit(1);
+	    }
+
+	    folder.open(Folder.READ_WRITE);
+
+	    // Add messageCountListener to listen for new messages
+	    folder.addMessageCountListener(new MessageCountAdapter() {
+		public void messagesAdded(MessageCountEvent ev) {
+		    Message[] msgs = ev.getMessages();
+		    System.out.println("Got " + msgs.length + " new messages");
+
+		    // Just dump out the new messages
+		    for (int i = 0; i < msgs.length; i++) {
+			try {
+			    System.out.println("-----");
+			    System.out.println("Message " +
+				msgs[i].getMessageNumber() + ":");
+			    msgs[i].writeTo(System.out);
+			} catch (IOException ioex) { 
+			    ioex.printStackTrace();	
+			} catch (MessagingException mex) {
+			    mex.printStackTrace();
+			}
+		    }
+		}
+	    });
+			
+	    // Check mail once in "freq" MILLIseconds
+	    int freq = Integer.parseInt(argv[4]);
+	    boolean supportsIdle = false;
+	    try {
+		if (folder instanceof IMAPFolder) {
+		    IMAPFolder f = (IMAPFolder)folder;
+		    f.idle();
+		    supportsIdle = true;
+		}
+	    } catch (FolderClosedException fex) {
+		throw fex;
+	    } catch (MessagingException mex) {
+		supportsIdle = false;
+	    }
+	    for (;;) {
+		if (supportsIdle && folder instanceof IMAPFolder) {
+		    IMAPFolder f = (IMAPFolder)folder;
+		    f.idle();
+		    System.out.println("IDLE done");
+		} else {
+		    Thread.sleep(freq); // sleep for freq milliseconds
+
+		    // This is to force the IMAP server to send us
+		    // EXISTS notifications. 
+		    folder.getMessageCount();
+		}
+	    }
+
+	} catch (Exception ex) {
+	    ex.printStackTrace();
+	}
+    }
+}
diff --git a/demo/src/main/java/mover.java b/demo/src/main/java/mover.java
new file mode 100644
index 0000000..acf2e0d
--- /dev/null
+++ b/demo/src/main/java/mover.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 1996, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import java.util.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/* MOVE messages between mailboxes */
+
+public class mover {
+
+    static String protocol = "imap";
+    static String host = null;
+    static String user = null;
+    static String password = null;
+    static String src = null;
+    static String dest = null;
+    static boolean expunge = false;
+    static String url = null;
+
+    public static void main(String argv[]) {
+	int start = 1; int end = -1;
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) { 	    // protocol
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) { // host
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) { // user
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) { // password
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-s")) { // Source mbox
+		src = argv[++optind];
+	    } else if (argv[optind].equals("-d")) { // Destination mbox
+		dest = argv[++optind];
+	    } else if (argv[optind].equals("-x")) { // Expunge ?
+		expunge = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: mover [-T protocol] [-H host] [-U user] [-P password] [-L url] [-v]");
+		System.out.println(
+"\t[-s source mbox] [-d destination mbox] [-x] [msgnum1] [msgnum2]");
+		System.out.println(
+"\t The -x option => EXPUNGE deleted messages");
+		System.out.println(
+"\t msgnum1 => start of message-range; msgnum2 => end of message-range");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	if (optind < argv.length)
+	    start = Integer.parseInt(argv[optind++]); // start msg
+
+	if (optind < argv.length)
+	    end = Integer.parseInt(argv[optind++]);   // end msg
+
+	try {
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+
+	    // Get a Store object
+	    Store store = null;
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, user, password);
+		else
+		    store.connect();
+	    }
+	    
+
+	    // Open source Folder
+	    Folder folder = store.getFolder(src);
+	    if (folder == null || !folder.exists()) {
+		System.out.println("Invalid folder: " + src);
+		System.exit(1);
+	    }
+
+	    folder.open(Folder.READ_WRITE);
+
+	    int count = folder.getMessageCount();
+	    if (count == 0) { // No messages in the source folder
+		System.out.println(folder.getName() + " is empty");
+		// Close folder, store and return
+		folder.close(false);
+		store.close();
+		return;
+	    }
+
+	    // Open destination folder, create if reqd
+	    Folder dfolder = store.getFolder(dest);
+	    if (!dfolder.exists())
+		dfolder.create(Folder.HOLDS_MESSAGES);
+
+	    if (end == -1)
+		end = count;
+
+	    // Get the message objects to copy
+	    Message[] msgs = folder.getMessages(start, end);
+	    System.out.println("Moving " + msgs.length + " messages");
+
+	    if (msgs.length != 0) {
+		folder.copyMessages(msgs, dfolder);
+		folder.setFlags(msgs, new Flags(Flags.Flag.DELETED), true);
+
+		// Dump out the Flags of the moved messages, to insure that
+		// all got deleted
+		for (int i = 0; i < msgs.length; i++) {
+		    if (!msgs[i].isSet(Flags.Flag.DELETED))
+			System.out.println("Message # " + msgs[i] + 
+						" not deleted");
+		}
+	    }
+	    
+	    // Close folders and store
+	    folder.close(expunge);
+	    store.close();
+
+	} catch (MessagingException mex) {
+	    Exception ex = mex;
+	    do {
+		System.out.println(ex.getMessage());
+		if (ex instanceof MessagingException)
+		    ex = ((MessagingException)ex).getNextException();
+		else
+		    ex = null;
+	    } while (ex != null);
+	}
+    }
+}
diff --git a/demo/src/main/java/msgmultisendsample.java b/demo/src/main/java/msgmultisendsample.java
new file mode 100644
index 0000000..abde0dc
--- /dev/null
+++ b/demo/src/main/java/msgmultisendsample.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 1996, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+/**
+ * msgmultisendsample creates a simple multipart/mixed message and sends it.
+ * Both body parts are text/plain.
+ * <p>
+ * usage: <code>java msgmultisendsample <i>to from smtp true|false</i></code>
+ * where <i>to</i> and <i>from</i> are the destination and
+ * origin email addresses, respectively, and <i>smtp</i>
+ * is the hostname of the machine that has smtp server
+ * running.  The last parameter either turns on or turns off
+ * debugging during sending.
+ *
+ * @author	Max Spivak
+ */
+public class msgmultisendsample {
+    static String msgText1 = "This is a message body.\nHere's line two.";
+    static String msgText2 = "This is the text in the message attachment.";
+
+    public static void main(String[] args) {
+	if (args.length != 4) {
+	    System.out.println("usage: java msgmultisend <to> <from> <smtp> true|false");
+	    return;
+	}
+
+	String to = args[0];
+	String from = args[1];
+	String host = args[2];
+	boolean debug = Boolean.valueOf(args[3]).booleanValue();
+
+	// create some properties and get the default Session
+	Properties props = new Properties();
+	props.put("mail.smtp.host", host);
+
+	Session session = Session.getInstance(props, null);
+	session.setDebug(debug);
+	
+	try {
+	    // create a message
+	    MimeMessage msg = new MimeMessage(session);
+	    msg.setFrom(new InternetAddress(from));
+	    InternetAddress[] address = {new InternetAddress(to)};
+	    msg.setRecipients(Message.RecipientType.TO, address);
+	    msg.setSubject("JavaMail APIs Multipart Test");
+	    msg.setSentDate(new Date());
+
+	    // create and fill the first message part
+	    MimeBodyPart mbp1 = new MimeBodyPart();
+	    mbp1.setText(msgText1);
+
+	    // create and fill the second message part
+	    MimeBodyPart mbp2 = new MimeBodyPart();
+	    // Use setText(text, charset), to show it off !
+	    mbp2.setText(msgText2, "us-ascii");
+
+	    // create the Multipart and its parts to it
+	    Multipart mp = new MimeMultipart();
+	    mp.addBodyPart(mbp1);
+	    mp.addBodyPart(mbp2);
+
+	    // add the Multipart to the message
+	    msg.setContent(mp);
+	    
+	    // send the message
+	    Transport.send(msg);
+	} catch (MessagingException mex) {
+	    mex.printStackTrace();
+	    Exception ex = null;
+	    if ((ex = mex.getNextException()) != null) {
+		ex.printStackTrace();
+	    }
+	}
+    }
+}
diff --git a/demo/src/main/java/msgsend.java b/demo/src/main/java/msgsend.java
new file mode 100644
index 0000000..7a7def9
--- /dev/null
+++ b/demo/src/main/java/msgsend.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import java.util.Properties;
+import java.util.Date;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * Demo app that shows how to construct and send an RFC822
+ * (singlepart) message.
+ *
+ * XXX - allow more than one recipient on the command line
+ *
+ * @author Max Spivak
+ * @author Bill Shannon
+ */
+
+public class msgsend {
+
+    public static void main(String[] argv) {
+	String  to, subject = null, from = null, 
+		cc = null, bcc = null, url = null;
+	String mailhost = null;
+	String mailer = "msgsend";
+	String file = null;
+	String protocol = null, host = null, user = null, password = null;
+	String record = null;	// name of folder in which to record mail
+	boolean debug = false;
+	BufferedReader in =
+			new BufferedReader(new InputStreamReader(System.in));
+	int optind;
+
+	/*
+	 * Process command line arguments.
+	 */
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-M")) {
+		mailhost = argv[++optind];
+	    } else if (argv[optind].equals("-f")) {
+		record = argv[++optind];
+	    } else if (argv[optind].equals("-a")) {
+		file = argv[++optind];
+	    } else if (argv[optind].equals("-s")) {
+		subject = argv[++optind];
+	    } else if (argv[optind].equals("-o")) { // originator
+		from = argv[++optind];
+	    } else if (argv[optind].equals("-c")) {
+		cc = argv[++optind];
+	    } else if (argv[optind].equals("-b")) {
+		bcc = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-d")) {
+		debug = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: msgsend [[-L store-url] | [-T prot] [-H host] [-U user] [-P passwd]]");
+		System.out.println(
+"\t[-s subject] [-o from-address] [-c cc-addresses] [-b bcc-addresses]");
+		System.out.println(
+"\t[-f record-mailbox] [-M transport-host] [-a attach-file] [-d] [address]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+	    /*
+	     * Prompt for To and Subject, if not specified.
+	     */
+	    if (optind < argv.length) {
+		// XXX - concatenate all remaining arguments
+		to = argv[optind];
+		System.out.println("To: " + to);
+	    } else {
+		System.out.print("To: ");
+		System.out.flush();
+		to = in.readLine();
+	    }
+	    if (subject == null) {
+		System.out.print("Subject: ");
+		System.out.flush();
+		subject = in.readLine();
+	    } else {
+		System.out.println("Subject: " + subject);
+	    }
+
+	    /*
+	     * Initialize the JavaMail Session.
+	     */
+	    Properties props = System.getProperties();
+	    // XXX - could use Session.getTransport() and Transport.connect()
+	    // XXX - assume we're using SMTP
+	    if (mailhost != null)
+		props.put("mail.smtp.host", mailhost);
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    /*
+	     * Construct the message and send it.
+	     */
+	    Message msg = new MimeMessage(session);
+	    if (from != null)
+		msg.setFrom(new InternetAddress(from));
+	    else
+		msg.setFrom();
+
+	    msg.setRecipients(Message.RecipientType.TO,
+					InternetAddress.parse(to, false));
+	    if (cc != null)
+		msg.setRecipients(Message.RecipientType.CC,
+					InternetAddress.parse(cc, false));
+	    if (bcc != null)
+		msg.setRecipients(Message.RecipientType.BCC,
+					InternetAddress.parse(bcc, false));
+
+	    msg.setSubject(subject);
+
+	    String text = collect(in);
+
+	    if (file != null) {
+		// Attach the specified file.
+		// We need a multipart message to hold the attachment.
+		MimeBodyPart mbp1 = new MimeBodyPart();
+		mbp1.setText(text);
+		MimeBodyPart mbp2 = new MimeBodyPart();
+		mbp2.attachFile(file);
+		MimeMultipart mp = new MimeMultipart();
+		mp.addBodyPart(mbp1);
+		mp.addBodyPart(mbp2);
+		msg.setContent(mp);
+	    } else {
+		// If the desired charset is known, you can use
+		// setText(text, charset)
+		msg.setText(text);
+	    }
+
+	    msg.setHeader("X-Mailer", mailer);
+	    msg.setSentDate(new Date());
+
+	    // send the thing off
+	    Transport.send(msg);
+
+	    System.out.println("\nMail was sent successfully.");
+
+	    /*
+	     * Save a copy of the message, if requested.
+	     */
+	    if (record != null) {
+		// Get a Store object
+		Store store = null;
+		if (url != null) {
+		    URLName urln = new URLName(url);
+		    store = session.getStore(urln);
+		    store.connect();
+		} else {
+		    if (protocol != null)		
+			store = session.getStore(protocol);
+		    else
+			store = session.getStore();
+
+		    // Connect
+		    if (host != null || user != null || password != null)
+			store.connect(host, user, password);
+		    else
+			store.connect();
+		}
+
+		// Get record Folder.  Create if it does not exist.
+		Folder folder = store.getFolder(record);
+		if (folder == null) {
+		    System.err.println("Can't get record folder.");
+		    System.exit(1);
+		}
+		if (!folder.exists())
+		    folder.create(Folder.HOLDS_MESSAGES);
+
+		Message[] msgs = new Message[1];
+		msgs[0] = msg;
+		folder.appendMessages(msgs);
+
+		System.out.println("Mail was recorded successfully.");
+	    }
+
+	} catch (Exception e) {
+	    e.printStackTrace();
+	}
+    }
+
+    /**
+     * Read the body of the message until EOF.
+     */
+    public static String collect(BufferedReader in) throws IOException {
+	String line;
+	StringBuffer sb = new StringBuffer();
+	while ((line = in.readLine()) != null) {
+	    sb.append(line);
+	    sb.append("\n");
+	}
+	return sb.toString();
+    }
+}
diff --git a/demo/src/main/java/msgsendsample.java b/demo/src/main/java/msgsendsample.java
new file mode 100644
index 0000000..c810a01
--- /dev/null
+++ b/demo/src/main/java/msgsendsample.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 1996, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+/**
+ * msgsendsample creates a very simple text/plain message and sends it.
+ * <p>
+ * usage: <code>java msgsendsample <i>to from smtphost true|false</i></code>
+ * where <i>to</i> and <i>from</i> are the destination and
+ * origin email addresses, respectively, and <i>smtphost</i>
+ * is the hostname of the machine that has the smtp server
+ * running. The last parameter either turns on or turns off
+ * debugging during sending.
+ *
+ * @author Max Spivak
+ */
+public class msgsendsample {
+    static String msgText = "This is a message body.\nHere's the second line.";
+
+    public static void main(String[] args) {
+	if (args.length != 4) {
+	    usage();
+	    System.exit(1);
+	}
+
+	System.out.println();
+	
+	String to = args[0];
+	String from = args[1];
+	String host = args[2];
+	boolean debug = Boolean.valueOf(args[3]).booleanValue();
+
+	// create some properties and get the default Session
+	Properties props = new Properties();
+	props.put("mail.smtp.host", host);
+	if (debug) props.put("mail.debug", args[3]);
+
+	Session session = Session.getInstance(props, null);
+	session.setDebug(debug);
+	
+	try {
+	    // create a message
+	    MimeMessage msg = new MimeMessage(session);
+	    msg.setFrom(new InternetAddress(from));
+	    InternetAddress[] address = {new InternetAddress(to)};
+	    msg.setRecipients(Message.RecipientType.TO, address);
+	    msg.setSubject("JavaMail APIs Test");
+	    msg.setSentDate(new Date());
+	    // If the desired charset is known, you can use
+	    // setText(text, charset)
+	    msg.setText(msgText);
+	    
+	    Transport.send(msg);
+	} catch (MessagingException mex) {
+	    System.out.println("\n--Exception handling in msgsendsample.java");
+
+	    mex.printStackTrace();
+	    System.out.println();
+	    Exception ex = mex;
+	    do {
+		if (ex instanceof SendFailedException) {
+		    SendFailedException sfex = (SendFailedException)ex;
+		    Address[] invalid = sfex.getInvalidAddresses();
+		    if (invalid != null) {
+			System.out.println("    ** Invalid Addresses");
+			for (int i = 0; i < invalid.length; i++) 
+			    System.out.println("         " + invalid[i]);
+		    }
+		    Address[] validUnsent = sfex.getValidUnsentAddresses();
+		    if (validUnsent != null) {
+			System.out.println("    ** ValidUnsent Addresses");
+			for (int i = 0; i < validUnsent.length; i++) 
+			    System.out.println("         "+validUnsent[i]);
+		    }
+		    Address[] validSent = sfex.getValidSentAddresses();
+		    if (validSent != null) {
+			System.out.println("    ** ValidSent Addresses");
+			for (int i = 0; i < validSent.length; i++) 
+			    System.out.println("         "+validSent[i]);
+		    }
+		}
+		System.out.println();
+		if (ex instanceof MessagingException)
+		    ex = ((MessagingException)ex).getNextException();
+		else
+		    ex = null;
+	    } while (ex != null);
+	}
+    }
+
+    private static void usage() {
+	System.out.println("usage: java msgsendsample <to> <from> <smtp> true|false");
+    }
+}
diff --git a/demo/src/main/java/msgshow.java b/demo/src/main/java/msgshow.java
new file mode 100644
index 0000000..47ed000
--- /dev/null
+++ b/demo/src/main/java/msgshow.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+
+/*
+ * Demo app that exercises the Message interfaces.
+ * Show information about and contents of messages.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class msgshow {
+
+    static String protocol;
+    static String host = null;
+    static String user = null;
+    static String password = null;
+    static String mbox = null;
+    static String url = null;
+    static int port = -1;
+    static boolean verbose = false;
+    static boolean debug = false;
+    static boolean showStructure = false;
+    static boolean showMessage = false;
+    static boolean showAlert = false;
+    static boolean saveAttachments = false;
+    static int attnum = 1;
+
+    public static void main(String argv[]) {
+	int optind;
+	InputStream msgStream = System.in;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-v")) {
+		verbose = true;
+	    } else if (argv[optind].equals("-D")) {
+		debug = true;
+	    } else if (argv[optind].equals("-f")) {
+		mbox = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-p")) {
+		port = Integer.parseInt(argv[++optind]);
+	    } else if (argv[optind].equals("-s")) {
+		showStructure = true;
+	    } else if (argv[optind].equals("-S")) {
+		saveAttachments = true;
+	    } else if (argv[optind].equals("-m")) {
+		showMessage = true;
+	    } else if (argv[optind].equals("-a")) {
+		showAlert = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: msgshow [-L url] [-T protocol] [-H host] [-p port] [-U user]");
+		System.out.println(
+"\t[-P password] [-f mailbox] [msgnum ...] [-v] [-D] [-s] [-S] [-a]");
+		System.out.println(
+"or     msgshow -m [-v] [-D] [-s] [-S] [-f msg-file]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+	    session.setDebug(debug);
+
+	    if (showMessage) {
+		MimeMessage msg;
+		if (mbox != null)
+		    msg = new MimeMessage(session,
+			new BufferedInputStream(new FileInputStream(mbox)));
+		else
+		    msg = new MimeMessage(session, msgStream);
+		dumpPart(msg);
+		System.exit(0);
+	    }
+
+	    // Get a Store object
+	    Store store = null;
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		if (showAlert) {
+		    store.addStoreListener(new StoreListener() {
+			public void notification(StoreEvent e) {
+			    String s;
+			    if (e.getMessageType() == StoreEvent.ALERT)
+				s = "ALERT: ";
+			    else
+				s = "NOTICE: ";
+			    System.out.println(s + e.getMessage());
+			}
+		    });
+		}
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, port, user, password);
+		else
+		    store.connect();
+	    }
+	    
+
+	    // Open the Folder
+
+	    Folder folder = store.getDefaultFolder();
+	    if (folder == null) {
+		System.out.println("No default folder");
+		System.exit(1);
+	    }
+
+	    if (mbox == null)
+		mbox = "INBOX";
+	    folder = folder.getFolder(mbox);
+	    if (folder == null) {
+		System.out.println("Invalid folder");
+		System.exit(1);
+	    }
+
+	    // try to open read/write and if that fails try read-only
+	    try {
+		folder.open(Folder.READ_WRITE);
+	    } catch (MessagingException ex) {
+		folder.open(Folder.READ_ONLY);
+	    }
+	    int totalMessages = folder.getMessageCount();
+
+	    if (totalMessages == 0) {
+		System.out.println("Empty folder");
+		folder.close(false);
+		store.close();
+		System.exit(1);
+	    }
+
+	    if (verbose) {
+		int newMessages = folder.getNewMessageCount();
+		System.out.println("Total messages = " + totalMessages);
+		System.out.println("New messages = " + newMessages);
+		System.out.println("-------------------------------");
+	    }
+
+	    if (optind >= argv.length) {
+		// Attributes & Flags for all messages ..
+		Message[] msgs = folder.getMessages();
+
+		// Use a suitable FetchProfile
+		FetchProfile fp = new FetchProfile();
+		fp.add(FetchProfile.Item.ENVELOPE);
+		fp.add(FetchProfile.Item.FLAGS);
+		fp.add("X-Mailer");
+		folder.fetch(msgs, fp);
+
+		for (int i = 0; i < msgs.length; i++) {
+		    System.out.println("--------------------------");
+		    System.out.println("MESSAGE #" + (i + 1) + ":");
+		    dumpEnvelope(msgs[i]);
+		    // dumpPart(msgs[i]);
+		}
+	    } else {
+		while (optind < argv.length) {
+		    int msgnum = Integer.parseInt(argv[optind++]);
+		    System.out.println("Getting message number: " + msgnum);
+		    Message m = null;
+		    
+		    try {
+			m = folder.getMessage(msgnum);
+			dumpPart(m);
+		    } catch (IndexOutOfBoundsException iex) {
+			System.out.println("Message number out of range");
+		    }
+		}
+	    }
+
+	    folder.close(false);
+	    store.close();
+	} catch (Exception ex) {
+	    System.out.println("Oops, got exception! " + ex.getMessage());
+	    ex.printStackTrace();
+	    System.exit(1);
+	}
+	System.exit(0);
+    }
+
+    public static void dumpPart(Part p) throws Exception {
+	if (p instanceof Message)
+	    dumpEnvelope((Message)p);
+
+	/** Dump input stream .. 
+
+	InputStream is = p.getInputStream();
+	// If "is" is not already buffered, wrap a BufferedInputStream
+	// around it.
+	if (!(is instanceof BufferedInputStream))
+	    is = new BufferedInputStream(is);
+	int c;
+	while ((c = is.read()) != -1)
+	    System.out.write(c);
+
+	**/
+
+	String ct = p.getContentType();
+	try {
+	    pr("CONTENT-TYPE: " + (new ContentType(ct)).toString());
+	} catch (ParseException pex) {
+	    pr("BAD CONTENT-TYPE: " + ct);
+	}
+	String filename = p.getFileName();
+	if (filename != null)
+	    pr("FILENAME: " + filename);
+
+	/*
+	 * Using isMimeType to determine the content type avoids
+	 * fetching the actual content data until we need it.
+	 */
+	if (p.isMimeType("text/plain")) {
+	    pr("This is plain text");
+	    pr("---------------------------");
+	    if (!showStructure && !saveAttachments)
+		System.out.println((String)p.getContent());
+	} else if (p.isMimeType("multipart/*")) {
+	    pr("This is a Multipart");
+	    pr("---------------------------");
+	    Multipart mp = (Multipart)p.getContent();
+	    level++;
+	    int count = mp.getCount();
+	    for (int i = 0; i < count; i++)
+		dumpPart(mp.getBodyPart(i));
+	    level--;
+	} else if (p.isMimeType("message/rfc822")) {
+	    pr("This is a Nested Message");
+	    pr("---------------------------");
+	    level++;
+	    dumpPart((Part)p.getContent());
+	    level--;
+	} else {
+	    if (!showStructure && !saveAttachments) {
+		/*
+		 * If we actually want to see the data, and it's not a
+		 * MIME type we know, fetch it and check its Java type.
+		 */
+		Object o = p.getContent();
+		if (o instanceof String) {
+		    pr("This is a string");
+		    pr("---------------------------");
+		    System.out.println((String)o);
+		} else if (o instanceof InputStream) {
+		    pr("This is just an input stream");
+		    pr("---------------------------");
+		    InputStream is = (InputStream)o;
+		    int c;
+		    while ((c = is.read()) != -1)
+			System.out.write(c);
+		} else {
+		    pr("This is an unknown type");
+		    pr("---------------------------");
+		    pr(o.toString());
+		}
+	    } else {
+		// just a separator
+		pr("---------------------------");
+	    }
+	}
+
+	/*
+	 * If we're saving attachments, write out anything that
+	 * looks like an attachment into an appropriately named
+	 * file.  Don't overwrite existing files to prevent
+	 * mistakes.
+	 */
+	if (saveAttachments && level != 0 && p instanceof MimeBodyPart &&
+		!p.isMimeType("multipart/*")) {
+	    String disp = p.getDisposition();
+	    // many mailers don't include a Content-Disposition
+	    if (disp == null || disp.equalsIgnoreCase(Part.ATTACHMENT)) {
+		if (filename == null)
+		    filename = "Attachment" + attnum++;
+		pr("Saving attachment to file " + filename);
+		try {
+		    File f = new File(filename);
+		    if (f.exists())
+			// XXX - could try a series of names
+			throw new IOException("file exists");
+		    ((MimeBodyPart)p).saveFile(f);
+		} catch (IOException ex) {
+		    pr("Failed to save attachment: " + ex);
+		}
+		pr("---------------------------");
+	    }
+	}
+    }
+
+    public static void dumpEnvelope(Message m) throws Exception {
+	pr("This is the message envelope");
+	pr("---------------------------");
+	Address[] a;
+	// FROM 
+	if ((a = m.getFrom()) != null) {
+	    for (int j = 0; j < a.length; j++)
+		pr("FROM: " + a[j].toString());
+	}
+
+	// REPLY TO
+	if ((a = m.getReplyTo()) != null) {
+	    for (int j = 0; j < a.length; j++)
+		pr("REPLY TO: " + a[j].toString());
+	}
+
+	// TO
+	if ((a = m.getRecipients(Message.RecipientType.TO)) != null) {
+	    for (int j = 0; j < a.length; j++) {
+		pr("TO: " + a[j].toString());
+		InternetAddress ia = (InternetAddress)a[j];
+		if (ia.isGroup()) {
+		    InternetAddress[] aa = ia.getGroup(false);
+		    for (int k = 0; k < aa.length; k++)
+			pr("  GROUP: " + aa[k].toString());
+		}
+	    }
+	}
+
+	// SUBJECT
+	pr("SUBJECT: " + m.getSubject());
+
+	// DATE
+	Date d = m.getSentDate();
+	pr("SendDate: " +
+	    (d != null ? d.toString() : "UNKNOWN"));
+
+	// FLAGS
+	Flags flags = m.getFlags();
+	StringBuffer sb = new StringBuffer();
+	Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
+
+	boolean first = true;
+	for (int i = 0; i < sf.length; i++) {
+	    String s;
+	    Flags.Flag f = sf[i];
+	    if (f == Flags.Flag.ANSWERED)
+		s = "\\Answered";
+	    else if (f == Flags.Flag.DELETED)
+		s = "\\Deleted";
+	    else if (f == Flags.Flag.DRAFT)
+		s = "\\Draft";
+	    else if (f == Flags.Flag.FLAGGED)
+		s = "\\Flagged";
+	    else if (f == Flags.Flag.RECENT)
+		s = "\\Recent";
+	    else if (f == Flags.Flag.SEEN)
+		s = "\\Seen";
+	    else
+		continue;	// skip it
+	    if (first)
+		first = false;
+	    else
+		sb.append(' ');
+	    sb.append(s);
+	}
+
+	String[] uf = flags.getUserFlags(); // get the user flag strings
+	for (int i = 0; i < uf.length; i++) {
+	    if (first)
+		first = false;
+	    else
+		sb.append(' ');
+	    sb.append(uf[i]);
+	}
+	pr("FLAGS: " + sb.toString());
+
+	// X-MAILER
+	String[] hdrs = m.getHeader("X-Mailer");
+	if (hdrs != null)
+	    pr("X-Mailer: " + hdrs[0]);
+	else
+	    pr("X-Mailer NOT available");
+    }
+
+    static String indentStr = "                                               ";
+    static int level = 0;
+
+    /**
+     * Print a, possibly indented, string.
+     */
+    public static void pr(String s) {
+	if (showStructure)
+	    System.out.print(indentStr.substring(0, level * 2));
+	System.out.println(s);
+    }
+}
diff --git a/demo/src/main/java/namespace.java b/demo/src/main/java/namespace.java
new file mode 100644
index 0000000..0e36247
--- /dev/null
+++ b/demo/src/main/java/namespace.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+
+/*
+ * Demo app that exercises the namespace interfaces.
+ * Show the namespaces supported by a store.
+ *
+ * @author Bill Shannon
+ */
+
+public class namespace {
+
+    static String protocol;
+    static String host = null;
+    static String user = null;
+    static String password = null;
+    static String url = null;
+    static int port = -1;
+    static boolean debug = false;
+    static String suser = "other";
+
+    public static void main(String argv[]) {
+	int msgnum = -1;
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-D")) {
+		debug = true;
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-p")) {
+		port = Integer.parseInt(argv[++optind]);
+	    } else if (argv[optind].equals("-u")) {
+		suser = argv[++optind];
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: namespace [-L url] [-T protocol] [-H host] [-p port] [-U user]");
+		System.out.println(
+"\t[-P password] [-u other-user] [-D]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+	    session.setDebug(debug);
+
+	    // Get a Store object
+	    Store store = null;
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, port, user, password);
+		else
+		    store.connect();
+	    }
+
+	    printFolders("Personal", store.getPersonalNamespaces());
+	    printFolders("User \"" + suser + "\"",
+				store.getUserNamespaces(suser));
+	    printFolders("Shared", store.getSharedNamespaces());
+
+	    store.close();
+	} catch (Exception ex) {
+	    System.out.println("Oops, got exception! " + ex.getMessage());
+	    ex.printStackTrace();
+	}
+	System.exit(0);
+    }
+
+    private static void printFolders(String name, Folder[] folders)
+				throws MessagingException {
+	System.out.println(name + " Namespace:");
+	if (folders == null || folders.length == 0) {
+	    System.out.println("  <none>");
+	    return;
+	}
+	for (int i = 0; i < folders.length; i++) {
+	    String fn = folders[i].getFullName();
+	    if (fn.length() == 0)
+		fn = "<default folder>";
+	    try {
+		System.out.println("  " + fn +
+			", delimiter \"" + folders[i].getSeparator() + "\"");
+		Folder[] fl = folders[i].list();
+		if (fl.length > 0) {
+		    System.out.println("  Subfolders:");
+		    for (int j = 0; j < fl.length; j++)
+			System.out.println("    " + fl[j].getFullName());
+		}
+	    } catch (FolderNotFoundException ex) { }
+	}
+    }
+}
diff --git a/demo/src/main/java/populate.java b/demo/src/main/java/populate.java
new file mode 100644
index 0000000..721c16d
--- /dev/null
+++ b/demo/src/main/java/populate.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/*
+ * Copy folder hierarchies between different Stores. This is a useful 
+ * utility to populate new (and possibly empty) mail stores. Specify
+ * both the source and destination folders as URLs.
+ *	
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class populate {
+
+    static boolean force = false;
+    static boolean skipSpecial = false;
+    static boolean clear = false;
+    static boolean dontPreserveFlags = false;
+    static boolean warn = false;
+
+    public static void main(String argv[]) {
+	String srcURL = null;
+	String dstURL = null;
+	boolean debug = false;
+
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-s")) {
+		srcURL = argv[++optind];
+	    } else if (argv[optind].equals("-d")) {
+		dstURL = argv[++optind];
+	    } else if (argv[optind].equals("-D")) {
+		debug = true;
+	    } else if (argv[optind].equals("-f")) {
+		force = true;
+	    } else if (argv[optind].equals("-S")) {
+		skipSpecial = true;
+	    } else if (argv[optind].equals("-c")) {
+		clear = true;
+	    } else if (argv[optind].equals("-P")) {
+		dontPreserveFlags = true;
+	    } else if (argv[optind].equals("-W")) {
+		warn = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		printUsage();
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+
+	    if (srcURL == null || dstURL == null) {
+		printUsage();
+		System.exit(1);
+	    }
+
+	    Session session = Session.getInstance(
+				System.getProperties(), null);
+	    session.setDebug(debug);
+
+	    // Get source folder
+	    URLName srcURLName = new URLName(srcURL);
+	    Folder srcFolder;
+	    // Check if the source URL has a folder specified. If
+	    // not, we use the default folder
+	    if (srcURLName.getFile() == null) {
+		Store s = session.getStore(srcURLName);
+		s.connect();
+		srcFolder = s.getDefaultFolder();
+	    } else {
+		srcFolder = session.getFolder(new URLName(srcURL));
+		if (!srcFolder.exists()) {
+		    System.out.println("source folder does not exist");
+		    srcFolder.getStore().close();
+		    System.exit(1);
+		}
+	    }
+
+	    // Set up destination folder
+	    URLName dstURLName = new URLName(dstURL);
+	    Folder dstFolder;
+	    // Check if the destination URL has a folder specified. If
+	    // not, we use the source folder name
+	    if (dstURLName.getFile() == null) {
+		Store s = session.getStore(dstURLName);
+		s.connect();
+		dstFolder = s.getFolder(srcFolder.getName());
+	    } else
+		dstFolder = session.getFolder(dstURLName);
+
+	    if (clear && dstFolder.exists()) {
+		if (!dstFolder.delete(true)) {
+		    System.out.println("couldn't delete " +
+						dstFolder.getFullName());
+		    return;
+		}
+	    }
+	    copy(srcFolder, dstFolder);
+
+	    // Close the respective stores.
+	    srcFolder.getStore().close();
+	    dstFolder.getStore().close();
+
+	} catch (MessagingException mex) {
+	    System.out.println(mex.getMessage());
+	    mex.printStackTrace();
+	}
+    }
+
+    private static void copy(Folder src, Folder dst)
+		throws MessagingException {
+	System.out.println("Populating " + dst.getFullName());
+
+	Folder ddst = dst;
+	Folder[] srcFolders = null;
+	if ((src.getType() & Folder.HOLDS_FOLDERS) != 0)
+	    srcFolders = src.list();
+	boolean srcHasChildren = srcFolders != null && srcFolders.length > 0;
+
+	if (!dst.exists()) {
+	    // Create it.
+	    boolean dstHoldsOnlyFolders = false;
+	    if (!dst.create(src.getType())) {
+		// might not be able to create a folder that holds both
+		if (!dst.create(srcHasChildren ? Folder.HOLDS_FOLDERS :
+						Folder.HOLDS_MESSAGES)) {
+		    // might only be able to create one type (Gmail)
+		    if (!dst.create(Folder.HOLDS_MESSAGES)) {
+			System.out.println("couldn't create " +
+							    dst.getFullName());
+			return;
+		    }
+		}
+		dstHoldsOnlyFolders = srcHasChildren;
+	    }
+
+	    // Copy over any messges from src to dst
+	    if ((src.getType() & Folder.HOLDS_MESSAGES) != 0) {
+		src.open(Folder.READ_ONLY);
+		if (dstHoldsOnlyFolders) {
+		    if (src.getMessageCount() > 0) {
+			System.out.println("Unable to copy messages from " +
+			    src.getFullName() + " to " + dst.getFullName() +
+			    " because destination holds only folders");
+		    }
+		} else
+		    copyMessages(src, dst);
+		src.close(false);
+	    }
+	} else  {
+	    System.out.println(dst.getFullName() + " already exists");
+	    // Copy over any messges from src to dst
+	    if (force && (src.getType() & Folder.HOLDS_MESSAGES) != 0) {
+		src.open(Folder.READ_ONLY);
+		copyMessages(src, dst);
+		src.close(false);
+	    }
+	}
+
+	// Copy over subfolders
+	if (srcHasChildren) {
+	    for (int i = 0; i < srcFolders.length; i++) {
+		// skip special directories?
+		if (skipSpecial) {
+		    if (srcFolders[i].getName().equals("SCCS") ||
+			srcFolders[i].getName().equals("Drafts") ||
+			srcFolders[i].getName().equals("Trash") ||
+			srcFolders[i].getName().equals("Shared Folders"))
+			continue;
+		}
+		copy(srcFolders[i], dst.getFolder(srcFolders[i].getName()));
+	    }
+	}
+    }
+
+    /**
+     * Copy message from src to dst.  If dontPreserveFlags is set
+     * we first copy the messages to memory, clear all the flags,
+     * and then copy to the destination.
+     */
+    private static void copyMessages(Folder src, Folder dst)
+				throws MessagingException {
+	Message[] msgs = src.getMessages();
+	if (dontPreserveFlags) {
+	    for (int i = 0; i < msgs.length; i++) {
+		MimeMessage m = new MimeMessage((MimeMessage)msgs[i]);
+		m.setFlags(m.getFlags(), false);
+		msgs[i] = m;
+	    }
+	}
+	if (warn) {
+	    // have to copy messages one at a time
+	    for (int i = 0; i < msgs.length; i++) {
+		try {
+		    src.copyMessages(new Message[] { msgs[i] }, dst);
+		} catch (MessagingException mex) {
+		    System.out.println("WARNING: Copy of message " + (i + 1) +
+			" from " + src.getFullName() +
+			" to " + dst.getFullName() +
+			" failed: " + mex.toString());
+		}
+	    }
+	} else
+	    src.copyMessages(msgs, dst);
+    }
+
+    private static void printUsage() {
+	System.out.println("populate [-D] [-f] [-S] [-c] " +
+			   "-s source_url -d dest_url");
+	System.out.println("URLs are of the form: " +
+			   "protocol://username:password@hostname/foldername");
+	System.out.println("The destination URL does not need a foldername," +
+			   " in which case, the source foldername is used");
+    }
+}
diff --git a/demo/src/main/java/registry.java b/demo/src/main/java/registry.java
new file mode 100644
index 0000000..9529490
--- /dev/null
+++ b/demo/src/main/java/registry.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * This class demonstrates how to query the registry for available
+ * Providers, set default providers, etc. See section 5.2 in the
+ * JavaMail Spec for details on how to use the registry.
+ * 
+ * See the comments inline for what's happening.
+ *
+ * @author Max Spivak
+ */
+
+public class registry {
+    // let's remember a few providers
+    static Provider _aProvider, _bProvider, _sunSMTP, _sunIMAP; 
+
+    public static void main(String[] args) {
+	Properties props = new Properties();
+
+	// set smtp and imap to be our default 
+	// transport and store protocols, respectively
+	props.put("mail.transport.protocol", "smtp");
+	props.put("mail.store.protocol", "imap");
+
+	// 
+	props.put("mail.smtp.class", "com.sun.mail.smtp.SMTPTransport");
+	props.put("mail.imap.class", "com.sun.mail.imap.IMAPStore");
+	
+	Session session = Session.getInstance(props, null);
+	//session.setDebug(true);
+
+	// Retrieve all configured providers from the Session
+	System.out.println("\n------ getProviders()----------");
+	Provider[] providers = session.getProviders();
+	for (int i = 0; i < providers.length; i++) {
+	    System.out.println("** " + providers[i]);
+
+	    // let's remember some providers so that we can use them later
+	    // (I'm explicitly showing multiple ways of testing Providers)
+	    // BTW, no Provider "ACME Corp" will be found in the default
+	    // setup b/c its not in any javamail.providers resource files
+	    String s = null;
+	    if (((s = providers[i].getVendor()) != null) &&
+		s.startsWith("ACME Corp")) {
+		_aProvider = providers[i];
+	    }
+
+	    // this Provider won't be found by default either
+	    if (providers[i].getClassName().endsWith("application.smtp"))
+		_bProvider = providers[i];
+
+	    // this Provider will be found since com.sun.mail.imap.IMAPStore
+	    // is configured in javamail.default.providers
+	    if (providers[i].getClassName().equals("com.sun.mail.imap.IMAPStore")){
+		_sunIMAP = providers[i];
+	    }
+
+	    // this Provider will be found as well since there is an
+	    // Oracle SMTP transport configured by 
+	    // default in javamail.default.providers
+	    if (((s = providers[i].getVendor()) != null) &&
+		s.startsWith("Oracle") && 
+		providers[i].getType() == Provider.Type.TRANSPORT &&
+		providers[i].getProtocol().equalsIgnoreCase("smtp")) {
+		_sunSMTP = providers[i];
+	    }
+	}
+	
+	System.out.println("\n------ initial protocol defaults -------");
+	try {
+	    System.out.println("imap: " + session.getProvider("imap"));
+	    System.out.println("smtp: " + session.getProvider("smtp"));
+	    // the NNTP provider will fail since we don't have one configured
+	    System.out.println("nntp: " + session.getProvider("nntp"));
+	} catch (NoSuchProviderException mex) { 
+	    System.out.println(">> This exception is OK since there is no NNTP Provider configured by default");
+	    mex.printStackTrace();
+	}
+
+	System.out.println("\n------ set new protocol defaults ------");
+	// set some new defaults
+	try {
+	    // since _aProvider isn't configured, this will fail
+	    session.setProvider(_aProvider);  // will fail
+	} catch (NoSuchProviderException mex) { 
+	    System.out.println(">> Exception expected: _aProvider is null");
+	    mex.printStackTrace(); 
+	}
+	try {
+	    // _sunIMAP provider should've configured correctly; should work
+	    session.setProvider(_sunIMAP);
+	} catch (NoSuchProviderException mex) { mex.printStackTrace(); }
+	try {
+	    System.out.println("imap: " + session.getProvider("imap"));
+	    System.out.println("smtp: " + session.getProvider("smtp"));
+	} catch (NoSuchProviderException mex) { mex.printStackTrace(); }
+
+
+	System.out.println("\n\n----- get some stores ---------");
+	// multiple ways to retrieve stores. these will print out the
+	// string "imap:" since its the URLName for the store
+	try {
+	    System.out.println("getStore(): " + session.getStore());
+	    System.out.println("getStore(Provider): " + 
+			       session.getStore(_sunIMAP));
+	} catch (NoSuchProviderException mex) {
+	    mex.printStackTrace();
+	}
+
+	try {
+	    System.out.println("getStore(imap): " + session.getStore("imap"));
+	    // pop3 will fail since it doesn't exist
+	    System.out.println("getStore(pop3): " + session.getStore("pop3"));
+	} catch (NoSuchProviderException mex) { 
+	    System.out.println(">> Exception expected: no pop3 provider");
+	    mex.printStackTrace(); 
+	}
+
+
+	System.out.println("\n\n----- now for transports/addresses ---------");
+	// retrieve transports; these will print out "smtp:" (like stores did)
+	try {
+	    System.out.println("getTransport(): " + session.getTransport());
+	    System.out.println("getTransport(Provider): " + 
+			       session.getTransport(_sunSMTP));
+	    System.out.println("getTransport(smtp): " + 
+			       session.getTransport("smtp"));
+	    System.out.println("getTransport(Address): " + 
+			       session.getTransport(new InternetAddress("mspivak@apilon")));
+	    // News will fail since there's no news provider configured
+	    System.out.println("getTransport(News): " + 
+			       session.getTransport(new NewsAddress("rec.humor")));
+	} catch (MessagingException mex) { 
+	    System.out.println(">> Exception expected: no news provider configured");
+	    mex.printStackTrace(); 
+	}
+    }
+}
diff --git a/demo/src/main/java/search.java b/demo/src/main/java/search.java
new file mode 100644
index 0000000..f9bbe42
--- /dev/null
+++ b/demo/src/main/java/search.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.search.*;
+import javax.activation.*;
+
+/*
+ * Search the given folder for messages matching the given
+ * criteria.
+ *
+ * @author John Mani
+ */
+
+public class search {
+
+    static String protocol = "imap";
+    static String host = null;
+    static String user = null;
+    static String password = null;
+    static String mbox = "INBOX";
+    static String url = null;
+    static boolean debug = false;
+
+    public static void main(String argv[]) {
+	int optind;
+
+	String subject = null;
+	String from = null;
+	boolean or = false;
+	boolean today = false;
+	int size = -1;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-or")) {
+		or = true;
+	    } else if (argv[optind].equals("-D")) {
+		debug = true;
+	    } else if (argv[optind].equals("-f")) {
+		mbox = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-subject")) {
+		subject = argv[++optind];
+	    } else if (argv[optind].equals("-from")) {
+		from = argv[++optind];
+	    } else if (argv[optind].equals("-today")) {
+		today = true;
+	    } else if (argv[optind].equals("-size")) {
+		size = Integer.parseInt(argv[++optind]);
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+		 "Usage: search [-D] [-L url] [-T protocol] [-H host] " + 
+		 "[-U user] [-P password] [-f mailbox] " + 
+		 "[-subject subject] [-from from] [-or] [-today]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+
+	    if ((subject == null) && (from == null) && !today && size < 0) {
+		System.out.println(
+			    "Specify either -subject, -from, -today, or -size");
+		System.exit(1);
+	    }
+
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+	    session.setDebug(debug);
+
+	    // Get a Store object
+	    Store store = null;
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, user, password);
+		else
+		    store.connect();
+	    }
+	    
+
+	    // Open the Folder
+
+	    Folder folder = store.getDefaultFolder();
+	    if (folder == null) {
+		System.out.println("Cant find default namespace");
+		System.exit(1);
+	    }
+
+	    folder = folder.getFolder(mbox);
+	    if (folder == null) {
+		System.out.println("Invalid folder");
+		System.exit(1);
+	    }
+
+	    folder.open(Folder.READ_ONLY);
+	    SearchTerm term = null;
+
+	    if (subject != null)
+		term = new SubjectTerm(subject);
+	    if (from != null) {
+		FromStringTerm fromTerm = new FromStringTerm(from);
+		if (term != null) {
+		    if (or)
+			term = new OrTerm(term, fromTerm);
+		    else
+			term = new AndTerm(term, fromTerm);
+		}
+		else
+		    term = fromTerm;
+	    }
+	    if (today) {
+		Calendar c = Calendar.getInstance();
+		c.set(Calendar.HOUR, 0);
+		c.set(Calendar.MINUTE, 0);
+		c.set(Calendar.SECOND, 0);
+		c.set(Calendar.MILLISECOND, 0);
+		c.set(Calendar.AM_PM, Calendar.AM);
+		ReceivedDateTerm startDateTerm = 
+		 new ReceivedDateTerm(ComparisonTerm.GE, c.getTime());
+		c.add(Calendar.DATE, 1);	// next day
+		ReceivedDateTerm endDateTerm = 
+		 new ReceivedDateTerm(ComparisonTerm.LT, c.getTime());
+		SearchTerm dateTerm = new AndTerm(startDateTerm, endDateTerm);
+		if (term != null) {
+		    if (or)
+			term = new OrTerm(term, dateTerm);
+		    else
+			term = new AndTerm(term, dateTerm);
+		}
+		else
+		    term = dateTerm;
+	    }
+
+	    if (size >= 0) {
+		SizeTerm sizeTerm = new SizeTerm(ComparisonTerm.GT, size);
+		if (term != null) {
+		    if (or)
+			term = new OrTerm(term, sizeTerm);
+		    else
+			term = new AndTerm(term, sizeTerm);
+		}
+		else
+		    term = sizeTerm;
+	    }
+
+	    Message[] msgs = folder.search(term);
+	    System.out.println("FOUND " + msgs.length + " MESSAGES");
+	    if (msgs.length == 0) // no match
+		System.exit(1);
+
+	    // Use a suitable FetchProfile
+	    FetchProfile fp = new FetchProfile();
+	    fp.add(FetchProfile.Item.ENVELOPE);
+	    folder.fetch(msgs, fp);
+
+	    for (int i = 0; i < msgs.length; i++) {
+		System.out.println("--------------------------");
+		System.out.println("MESSAGE #" + (i + 1) + ":");
+		dumpPart(msgs[i]);
+	    }
+
+	    folder.close(false);
+	    store.close();
+	} catch (Exception ex) {
+	    System.out.println("Oops, got exception! " + ex.getMessage());
+	    ex.printStackTrace();
+	}
+
+	System.exit(1);
+    }
+
+    public static void dumpPart(Part p) throws Exception {
+	if (p instanceof Message) {
+	    Message m = (Message)p;
+	    Address[] a;
+	    // FROM 
+	    if ((a = m.getFrom()) != null) {
+		for (int j = 0; j < a.length; j++)
+		    System.out.println("FROM: " + a[j].toString());
+	    }
+
+	    // TO
+	    if ((a = m.getRecipients(Message.RecipientType.TO)) != null) {
+		for (int j = 0; j < a.length; j++)
+		    System.out.println("TO: " + a[j].toString());
+	    }
+
+	    // SUBJECT
+	    System.out.println("SUBJECT: " + m.getSubject());
+
+	    // DATE
+	    Date d = m.getSentDate();
+	    System.out.println("SendDate: " +
+		(d != null ? d.toLocaleString() : "UNKNOWN"));
+
+	    // FLAGS:
+	    Flags flags = m.getFlags();
+	    StringBuffer sb = new StringBuffer();
+	    Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
+
+	    boolean first = true;
+	    for (int i = 0; i < sf.length; i++) {
+		String s;
+		Flags.Flag f = sf[i];
+		if (f == Flags.Flag.ANSWERED)
+		    s = "\\Answered";
+		else if (f == Flags.Flag.DELETED)
+		    s = "\\Deleted";
+		else if (f == Flags.Flag.DRAFT)
+		    s = "\\Draft";
+		else if (f == Flags.Flag.FLAGGED)
+		    s = "\\Flagged";
+		else if (f == Flags.Flag.RECENT)
+		    s = "\\Recent";
+		else if (f == Flags.Flag.SEEN)
+		    s = "\\Seen";
+		else
+		    continue;	// skip it
+		if (first)
+		    first = false;
+		else
+		    sb.append(' ');
+		sb.append(s);
+	    }
+
+	    String[] uf = flags.getUserFlags(); // get the user flag strings
+	    for (int i = 0; i < uf.length; i++) {
+		if (first)
+		    first = false;
+		else
+		    sb.append(' ');
+		sb.append(uf[i]);
+	    }
+	    System.out.println("FLAGS = " + sb.toString());
+	}
+
+	System.out.println("CONTENT-TYPE: " + p.getContentType());
+
+	/* Dump input stream
+	InputStream is = ((MimeMessage)m).getInputStream();
+	int c;
+	while ((c = is.read()) != -1)
+	    System.out.write(c);
+	*/
+
+	Object o = p.getContent();
+	if (o instanceof String) {
+	    System.out.println("This is a String");
+	    System.out.println((String)o);
+	} else if (o instanceof Multipart) {
+	    System.out.println("This is a Multipart");
+	    Multipart mp = (Multipart)o;
+	    int count = mp.getCount();
+	    for (int i = 0; i < count; i++)
+		dumpPart(mp.getBodyPart(i));
+	} else if (o instanceof InputStream) {
+	    System.out.println("This is just an input stream");
+	    InputStream is = (InputStream)o;
+	    int c;
+	    while ((c = is.read()) != -1)
+		System.out.write(c);
+	}
+    }
+}
diff --git a/demo/src/main/java/sendfile.java b/demo/src/main/java/sendfile.java
new file mode 100644
index 0000000..0f44544
--- /dev/null
+++ b/demo/src/main/java/sendfile.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 1996, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+/**
+ * sendfile will create a multipart message with the second
+ * block of the message being the given file.<p>
+ *
+ * This demonstrates how to use the FileDataSource to send
+ * a file via mail.<p>
+ *
+ * usage: <code>java sendfile <i>to from smtp file true|false</i></code>
+ * where <i>to</i> and <i>from</i> are the destination and
+ * origin email addresses, respectively, and <i>smtp</i>
+ * is the hostname of the machine that has smtp server
+ * running.  <i>file</i> is the file to send. The next parameter
+ * either turns on or turns off debugging during sending.
+ *
+ * @author	Christopher Cotton
+ */
+public class sendfile {
+
+    public static void main(String[] args) {
+	if (args.length != 5) {
+	    System.out.println("usage: java sendfile <to> <from> <smtp> <file> true|false");
+	    System.exit(1);
+	}
+
+	String to = args[0];
+	String from = args[1];
+	String host = args[2];
+	String filename = args[3];
+	boolean debug = Boolean.valueOf(args[4]).booleanValue();
+	String msgText1 = "Sending a file.\n";
+	String subject = "Sending a file";
+	
+	// create some properties and get the default Session
+	Properties props = System.getProperties();
+	props.put("mail.smtp.host", host);
+	
+	Session session = Session.getInstance(props, null);
+	session.setDebug(debug);
+	
+	try {
+	    // create a message
+	    MimeMessage msg = new MimeMessage(session);
+	    msg.setFrom(new InternetAddress(from));
+	    InternetAddress[] address = {new InternetAddress(to)};
+	    msg.setRecipients(Message.RecipientType.TO, address);
+	    msg.setSubject(subject);
+
+	    // create and fill the first message part
+	    MimeBodyPart mbp1 = new MimeBodyPart();
+	    mbp1.setText(msgText1);
+
+	    // create the second message part
+	    MimeBodyPart mbp2 = new MimeBodyPart();
+
+	    // attach the file to the message
+	    mbp2.attachFile(filename);
+
+	    /*
+	     * Use the following approach instead of the above line if
+	     * you want to control the MIME type of the attached file.
+	     * Normally you should never need to do this.
+	     *
+	    FileDataSource fds = new FileDataSource(filename) {
+		public String getContentType() {
+		    return "application/octet-stream";
+		}
+	    };
+	    mbp2.setDataHandler(new DataHandler(fds));
+	    mbp2.setFileName(fds.getName());
+	     */
+
+	    // create the Multipart and add its parts to it
+	    Multipart mp = new MimeMultipart();
+	    mp.addBodyPart(mbp1);
+	    mp.addBodyPart(mbp2);
+
+	    // add the Multipart to the message
+	    msg.setContent(mp);
+
+	    // set the Date: header
+	    msg.setSentDate(new Date());
+
+	    /*
+	     * If you want to control the Content-Transfer-Encoding
+	     * of the attached file, do the following.  Normally you
+	     * should never need to do this.
+	     *
+	    msg.saveChanges();
+	    mbp2.setHeader("Content-Transfer-Encoding", "base64");
+	     */
+
+	    // send the message
+	    Transport.send(msg);
+	    
+	} catch (MessagingException mex) {
+	    mex.printStackTrace();
+	    Exception ex = null;
+	    if ((ex = mex.getNextException()) != null) {
+		ex.printStackTrace();
+	    }
+	} catch (IOException ioex) {
+	    ioex.printStackTrace();
+	}
+    }
+}
diff --git a/demo/src/main/java/sendhtml.java b/demo/src/main/java/sendhtml.java
new file mode 100644
index 0000000..5c6410c
--- /dev/null
+++ b/demo/src/main/java/sendhtml.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import java.util.Properties;
+import java.util.Date;
+
+import javax.mail.*;
+import javax.activation.*;
+import javax.mail.internet.*;
+import javax.mail.util.*;
+
+/**
+ * Demo app that shows how to construct and send a single part html
+ * message.  Note that the same basic technique can be used to send
+ * data of any type.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ * @author Max Spivak
+ */
+
+public class sendhtml {
+
+    public static void main(String[] argv) {
+	new sendhtml(argv);
+    }
+
+    public sendhtml(String[] argv) {
+
+	String  to, subject = null, from = null, 
+		cc = null, bcc = null, url = null;
+	String mailhost = null;
+	String mailer = "sendhtml";
+	String protocol = null, host = null, user = null, password = null;
+	String record = null;	// name of folder in which to record mail
+	boolean debug = false;
+	BufferedReader in =
+			new BufferedReader(new InputStreamReader(System.in));
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-M")) {
+		mailhost = argv[++optind];
+	    } else if (argv[optind].equals("-f")) {
+		record = argv[++optind];
+	    } else if (argv[optind].equals("-s")) {
+		subject = argv[++optind];
+	    } else if (argv[optind].equals("-o")) { // originator
+		from = argv[++optind];
+	    } else if (argv[optind].equals("-c")) {
+		cc = argv[++optind];
+	    } else if (argv[optind].equals("-b")) {
+		bcc = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-d")) {
+		debug = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: sendhtml [[-L store-url] | [-T prot] [-H host] [-U user] [-P passwd]]");
+		System.out.println(
+"\t[-s subject] [-o from-address] [-c cc-addresses] [-b bcc-addresses]");
+		System.out.println(
+"\t[-f record-mailbox] [-M transport-host] [-d] [address]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+	    if (optind < argv.length) {
+		// XXX - concatenate all remaining arguments
+		to = argv[optind];
+		System.out.println("To: " + to);
+	    } else {
+		System.out.print("To: ");
+		System.out.flush();
+		to = in.readLine();
+	    }
+	    if (subject == null) {
+		System.out.print("Subject: ");
+		System.out.flush();
+		subject = in.readLine();
+	    } else {
+		System.out.println("Subject: " + subject);
+	    }
+
+	    Properties props = System.getProperties();
+	    // XXX - could use Session.getTransport() and Transport.connect()
+	    // XXX - assume we're using SMTP
+	    if (mailhost != null)
+		props.put("mail.smtp.host", mailhost);
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    // construct the message
+	    Message msg = new MimeMessage(session);
+	    if (from != null)
+		msg.setFrom(new InternetAddress(from));
+	    else
+		msg.setFrom();
+
+	    msg.setRecipients(Message.RecipientType.TO,
+					InternetAddress.parse(to, false));
+	    if (cc != null)
+		msg.setRecipients(Message.RecipientType.CC,
+					InternetAddress.parse(cc, false));
+	    if (bcc != null)
+		msg.setRecipients(Message.RecipientType.BCC,
+					InternetAddress.parse(bcc, false));
+
+	    msg.setSubject(subject);
+
+	    collect(in, msg);
+
+	    msg.setHeader("X-Mailer", mailer);
+	    msg.setSentDate(new Date());
+
+	    // send the thing off
+	    Transport.send(msg);
+
+	    System.out.println("\nMail was sent successfully.");
+
+	    // Keep a copy, if requested.
+
+	    if (record != null) {
+		// Get a Store object
+		Store store = null;
+		if (url != null) {
+		    URLName urln = new URLName(url);
+		    store = session.getStore(urln);
+		    store.connect();
+		} else {
+		    if (protocol != null)		
+			store = session.getStore(protocol);
+		    else
+			store = session.getStore();
+
+		    // Connect
+		    if (host != null || user != null || password != null)
+			store.connect(host, user, password);
+		    else
+			store.connect();
+		}
+
+		// Get record Folder.  Create if it does not exist.
+		Folder folder = store.getFolder(record);
+		if (folder == null) {
+		    System.err.println("Can't get record folder.");
+		    System.exit(1);
+		}
+		if (!folder.exists())
+		    folder.create(Folder.HOLDS_MESSAGES);
+
+		Message[] msgs = new Message[1];
+		msgs[0] = msg;
+		folder.appendMessages(msgs);
+
+		System.out.println("Mail was recorded successfully.");
+	    }
+
+	} catch (Exception e) {
+	    e.printStackTrace();
+	}
+    }
+
+    public void collect(BufferedReader in, Message msg)
+					throws MessagingException, IOException {
+	String line;
+	String subject = msg.getSubject();
+	StringBuffer sb = new StringBuffer();
+	sb.append("<HTML>\n");
+	sb.append("<HEAD>\n");
+	sb.append("<TITLE>\n");
+	sb.append(subject + "\n");
+	sb.append("</TITLE>\n");
+	sb.append("</HEAD>\n");
+
+	sb.append("<BODY>\n");
+	sb.append("<H1>" + subject + "</H1>" + "\n");
+
+	while ((line = in.readLine()) != null) {
+	    sb.append(line);
+	    sb.append("\n");
+	}
+
+	sb.append("</BODY>\n");
+	sb.append("</HTML>\n");
+
+	msg.setDataHandler(new DataHandler(
+		new ByteArrayDataSource(sb.toString(), "text/html")));
+    }
+}
diff --git a/demo/src/main/java/smtpsend.java b/demo/src/main/java/smtpsend.java
new file mode 100644
index 0000000..2e26eb5
--- /dev/null
+++ b/demo/src/main/java/smtpsend.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import java.net.InetAddress;
+import java.util.Properties;
+import java.util.Date;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.smtp.*;
+
+/**
+ * Demo app that shows how to construct and send an RFC822
+ * (singlepart) message.
+ *
+ * XXX - allow more than one recipient on the command line
+ *
+ * This is just a variant of msgsend.java that demonstrates use of
+ * some SMTP-specific features.
+ *
+ * @author Max Spivak
+ * @author Bill Shannon
+ */
+
+public class smtpsend {
+
+    /**
+     * Example of how to extend the SMTPTransport class.
+     * This example illustrates how to issue the XACT
+     * command before the SMTPTransport issues the DATA
+     * command.
+     *
+    public static class SMTPExtension extends SMTPTransport {
+	public SMTPExtension(Session session, URLName url) {
+	    super(session, url);
+	    // to check that we're being used
+	    System.out.println("SMTPExtension: constructed");
+	}
+
+	protected synchronized OutputStream data() throws MessagingException {
+	    if (supportsExtension("XACCOUNTING"))
+		issueCommand("XACT", 250);
+	    return super.data();
+	}
+    }
+     */
+
+    public static void main(String[] argv) {
+	String  to, subject = null, from = null, 
+		cc = null, bcc = null, url = null;
+	String mailhost = null;
+	String mailer = "smtpsend";
+	String file = null;
+	String protocol = null, host = null, user = null, password = null;
+	String record = null;	// name of folder in which to record mail
+	boolean debug = false;
+	boolean verbose = false;
+	boolean auth = false;
+	String prot = "smtp";
+	BufferedReader in =
+			new BufferedReader(new InputStreamReader(System.in));
+	int optind;
+
+	/*
+	 * Process command line arguments.
+	 */
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-M")) {
+		mailhost = argv[++optind];
+	    } else if (argv[optind].equals("-f")) {
+		record = argv[++optind];
+	    } else if (argv[optind].equals("-a")) {
+		file = argv[++optind];
+	    } else if (argv[optind].equals("-s")) {
+		subject = argv[++optind];
+	    } else if (argv[optind].equals("-o")) { // originator
+		from = argv[++optind];
+	    } else if (argv[optind].equals("-c")) {
+		cc = argv[++optind];
+	    } else if (argv[optind].equals("-b")) {
+		bcc = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-d")) {
+		debug = true;
+	    } else if (argv[optind].equals("-v")) {
+		verbose = true;
+	    } else if (argv[optind].equals("-A")) {
+		auth = true;
+	    } else if (argv[optind].equals("-S")) {
+		prot = "smtps";
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: smtpsend [[-L store-url] | [-T prot] [-H host] [-U user] [-P passwd]]");
+		System.out.println(
+"\t[-s subject] [-o from-address] [-c cc-addresses] [-b bcc-addresses]");
+		System.out.println(
+"\t[-f record-mailbox] [-M transport-host] [-d] [-a attach-file]");
+		System.out.println(
+"\t[-v] [-A] [-S] [address]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+	    /*
+	     * Prompt for To and Subject, if not specified.
+	     */
+	    if (optind < argv.length) {
+		// XXX - concatenate all remaining arguments
+		to = argv[optind];
+		System.out.println("To: " + to);
+	    } else {
+		System.out.print("To: ");
+		System.out.flush();
+		to = in.readLine();
+	    }
+	    if (subject == null) {
+		System.out.print("Subject: ");
+		System.out.flush();
+		subject = in.readLine();
+	    } else {
+		System.out.println("Subject: " + subject);
+	    }
+
+	    /*
+	     * Initialize the JavaMail Session.
+	     */
+	    Properties props = System.getProperties();
+	    if (mailhost != null)
+		props.put("mail." + prot + ".host", mailhost);
+	    if (auth)
+		props.put("mail." + prot + ".auth", "true");
+
+	    /*
+	     * Create a Provider representing our extended SMTP transport
+	     * and set the property to use our provider.
+	     *
+	    Provider p = new Provider(Provider.Type.TRANSPORT, prot,
+		"smtpsend$SMTPExtension", "JavaMail demo", "no version");
+	    props.put("mail." + prot + ".class", "smtpsend$SMTPExtension");
+	     */
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    /*
+	     * Register our extended SMTP transport.
+	     *
+	    session.addProvider(p);
+	     */
+
+	    /*
+	     * Construct the message and send it.
+	     */
+	    Message msg = new MimeMessage(session);
+	    if (from != null)
+		msg.setFrom(new InternetAddress(from));
+	    else
+		msg.setFrom();
+
+	    msg.setRecipients(Message.RecipientType.TO,
+					InternetAddress.parse(to, false));
+	    if (cc != null)
+		msg.setRecipients(Message.RecipientType.CC,
+					InternetAddress.parse(cc, false));
+	    if (bcc != null)
+		msg.setRecipients(Message.RecipientType.BCC,
+					InternetAddress.parse(bcc, false));
+
+	    msg.setSubject(subject);
+
+	    String text = collect(in);
+
+	    if (file != null) {
+		// Attach the specified file.
+		// We need a multipart message to hold the attachment.
+		MimeBodyPart mbp1 = new MimeBodyPart();
+		mbp1.setText(text);
+		MimeBodyPart mbp2 = new MimeBodyPart();
+		mbp2.attachFile(file);
+		MimeMultipart mp = new MimeMultipart();
+		mp.addBodyPart(mbp1);
+		mp.addBodyPart(mbp2);
+		msg.setContent(mp);
+	    } else {
+		// If the desired charset is known, you can use
+		// setText(text, charset)
+		msg.setText(text);
+	    }
+
+	    msg.setHeader("X-Mailer", mailer);
+	    msg.setSentDate(new Date());
+
+	    // send the thing off
+	    /*
+	     * The simple way to send a message is this:
+	     *
+	    Transport.send(msg);
+	     *
+	     * But we're going to use some SMTP-specific features for
+	     * demonstration purposes so we need to manage the Transport
+	     * object explicitly.
+	     */
+	    SMTPTransport t =
+		(SMTPTransport)session.getTransport(prot);
+	    try {
+		if (auth)
+		    t.connect(mailhost, user, password);
+		else
+		    t.connect();
+		t.sendMessage(msg, msg.getAllRecipients());
+	    } finally {
+		if (verbose)
+		    System.out.println("Response: " +
+						t.getLastServerResponse());
+		t.close();
+	    }
+
+	    System.out.println("\nMail was sent successfully.");
+
+	    /*
+	     * Save a copy of the message, if requested.
+	     */
+	    if (record != null) {
+		// Get a Store object
+		Store store = null;
+		if (url != null) {
+		    URLName urln = new URLName(url);
+		    store = session.getStore(urln);
+		    store.connect();
+		} else {
+		    if (protocol != null)		
+			store = session.getStore(protocol);
+		    else
+			store = session.getStore();
+
+		    // Connect
+		    if (host != null || user != null || password != null)
+			store.connect(host, user, password);
+		    else
+			store.connect();
+		}
+
+		// Get record Folder.  Create if it does not exist.
+		Folder folder = store.getFolder(record);
+		if (folder == null) {
+		    System.err.println("Can't get record folder.");
+		    System.exit(1);
+		}
+		if (!folder.exists())
+		    folder.create(Folder.HOLDS_MESSAGES);
+
+		Message[] msgs = new Message[1];
+		msgs[0] = msg;
+		folder.appendMessages(msgs);
+
+		System.out.println("Mail was recorded successfully.");
+	    }
+
+	} catch (Exception e) {
+	    /*
+	     * Handle SMTP-specific exceptions.
+	     */
+	    if (e instanceof SendFailedException) {
+		MessagingException sfe = (MessagingException)e;
+		if (sfe instanceof SMTPSendFailedException) {
+		    SMTPSendFailedException ssfe =
+				    (SMTPSendFailedException)sfe;
+		    System.out.println("SMTP SEND FAILED:");
+		    if (verbose)
+			System.out.println(ssfe.toString());
+		    System.out.println("  Command: " + ssfe.getCommand());
+		    System.out.println("  RetCode: " + ssfe.getReturnCode());
+		    System.out.println("  Response: " + ssfe.getMessage());
+		} else {
+		    if (verbose)
+			System.out.println("Send failed: " + sfe.toString());
+		}
+		Exception ne;
+		while ((ne = sfe.getNextException()) != null &&
+			ne instanceof MessagingException) {
+		    sfe = (MessagingException)ne;
+		    if (sfe instanceof SMTPAddressFailedException) {
+			SMTPAddressFailedException ssfe =
+					(SMTPAddressFailedException)sfe;
+			System.out.println("ADDRESS FAILED:");
+			if (verbose)
+			    System.out.println(ssfe.toString());
+			System.out.println("  Address: " + ssfe.getAddress());
+			System.out.println("  Command: " + ssfe.getCommand());
+			System.out.println("  RetCode: " + ssfe.getReturnCode());
+			System.out.println("  Response: " + ssfe.getMessage());
+		    } else if (sfe instanceof SMTPAddressSucceededException) {
+			System.out.println("ADDRESS SUCCEEDED:");
+			SMTPAddressSucceededException ssfe =
+					(SMTPAddressSucceededException)sfe;
+			if (verbose)
+			    System.out.println(ssfe.toString());
+			System.out.println("  Address: " + ssfe.getAddress());
+			System.out.println("  Command: " + ssfe.getCommand());
+			System.out.println("  RetCode: " + ssfe.getReturnCode());
+			System.out.println("  Response: " + ssfe.getMessage());
+		    }
+		}
+	    } else {
+		System.out.println("Got Exception: " + e);
+		if (verbose)
+		    e.printStackTrace();
+	    }
+	}
+    }
+
+    /**
+     * Read the body of the message until EOF.
+     */
+    public static String collect(BufferedReader in) throws IOException {
+	String line;
+	StringBuffer sb = new StringBuffer();
+	while ((line = in.readLine()) != null) {
+	    sb.append(line);
+	    sb.append("\n");
+	}
+	return sb.toString();
+    }
+}
diff --git a/demo/src/main/java/transport.java b/demo/src/main/java/transport.java
new file mode 100644
index 0000000..f3cfbd7
--- /dev/null
+++ b/demo/src/main/java/transport.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.event.*;
+import javax.activation.*;
+
+/**
+ * transport is a simple program that creates a message, explicitly
+ * retrieves a Transport from the session based on the type of the
+ * address (it's InternetAddress, so SMTP will be used) and sends 
+ * the message.
+ *
+ * usage: <code>java transport <i>"toaddr1[, toaddr2]*"  from smtphost 
+ * true|false</i></code><br>
+ * where <i>to</i> and <i>from</i> are the destination and
+ * origin email addresses, respectively, and <i>smtphost</i>
+ * is the hostname of the machine that has the smtp server
+ * running. The <i>to</i> addresses can be either a single email
+ * address or a comma-separated list of email addresses in
+ * quotes, i.e. "joe@machine, jane, max@server.com"
+ * The last parameter either turns on or turns off
+ * debugging during sending.
+ *
+ * @author Max Spivak
+ */
+public class transport implements ConnectionListener, TransportListener {
+    static String msgText = "This is a message body.\nHere's the second line.";
+    static String msgText2 = "\nThis was sent by transport.java demo program.";
+
+    public static void main(String[] args) {
+	Properties props = System.getProperties();
+	// parse the arguments
+	InternetAddress[] addrs = null;
+	InternetAddress from;
+	boolean debug = false;
+	if (args.length != 4) {
+	    usage();
+	    return;
+	} else {
+	    props.put("mail.smtp.host", args[2]);
+	    if (args[3].equals("true")) {
+		debug = true;
+	    } else if (args[3].equals("false")) {
+		debug = false;
+	    } else {
+		usage();
+		return;
+	    }
+
+	    // parse the destination addresses
+	    try {
+		addrs = InternetAddress.parse(args[0], false);
+		from = new InternetAddress(args[1]);
+	    } catch (AddressException aex) {
+		System.out.println("Invalid Address");
+		aex.printStackTrace();
+		return;
+	    }
+	}
+	// create some properties and get a Session
+	Session session = Session.getInstance(props, null);
+	session.setDebug(debug);
+
+	transport t = new transport();
+	t.go(session, addrs, from);
+    }
+
+    public transport() {}
+
+    public void go(Session session, InternetAddress[] toAddr,
+				InternetAddress from) {
+	Transport trans = null;
+
+	try {
+	    // create a message
+	    Message msg = new MimeMessage(session);
+	    msg.setFrom(from);
+	    msg.setRecipients(Message.RecipientType.TO, toAddr);
+	    msg.setSubject("JavaMail APIs transport.java Test");
+	    msg.setSentDate(new Date());  // Date: header
+	    msg.setContent(msgText+msgText2, "text/plain");
+	    msg.saveChanges();
+
+	    // get the smtp transport for the address
+	    trans = session.getTransport(toAddr[0]);
+
+	    // register ourselves as listener for ConnectionEvents 
+	    // and TransportEvents
+	    trans.addConnectionListener(this);
+	    trans.addTransportListener(this);
+
+	    // connect the transport
+	    trans.connect();
+
+	    // send the message
+	    trans.sendMessage(msg, toAddr);
+
+	    // give the EventQueue enough time to fire its events
+	    try {Thread.sleep(5);}catch(InterruptedException e) {}
+
+	} catch (MessagingException mex) {
+	    // give the EventQueue enough time to fire its events
+	    try {Thread.sleep(5);}catch(InterruptedException e) {}
+
+	    System.out.println("Sending failed with exception:");
+	    mex.printStackTrace();
+	    System.out.println();
+	    Exception ex = mex;
+	    do {
+		if (ex instanceof SendFailedException) {
+		    SendFailedException sfex = (SendFailedException)ex;
+		    Address[] invalid = sfex.getInvalidAddresses();
+		    if (invalid != null) {
+			System.out.println("    ** Invalid Addresses");
+			for (int i = 0; i < invalid.length; i++) 
+			    System.out.println("         " + invalid[i]);
+		    }
+		    Address[] validUnsent = sfex.getValidUnsentAddresses();
+		    if (validUnsent != null) {
+			System.out.println("    ** ValidUnsent Addresses");
+			for (int i = 0; i < validUnsent.length; i++) 
+			    System.out.println("         "+validUnsent[i]);
+		    }
+		    Address[] validSent = sfex.getValidSentAddresses();
+		    if (validSent != null) {
+			System.out.println("    ** ValidSent Addresses");
+			for (int i = 0; i < validSent.length; i++) 
+			    System.out.println("         "+validSent[i]);
+		    }
+		}
+		System.out.println();
+		if (ex instanceof MessagingException)
+		    ex = ((MessagingException)ex).getNextException();
+		else
+		    ex = null;
+	    } while (ex != null);
+	} finally {
+	    try {
+		// close the transport
+		if (trans != null)
+		    trans.close();
+	    } catch (MessagingException mex) { /* ignore */ }
+	}
+    }
+
+    // implement ConnectionListener interface
+    public void opened(ConnectionEvent e) {
+	System.out.println(">>> ConnectionListener.opened()");
+    }
+    public void disconnected(ConnectionEvent e) {}
+    public void closed(ConnectionEvent e) {
+	System.out.println(">>> ConnectionListener.closed()");
+    }
+
+    // implement TransportListener interface
+    public void messageDelivered(TransportEvent e) {
+	System.out.println(">>> TransportListener.messageDelivered().");
+	System.out.println(" Valid Addresses:");
+	Address[] valid = e.getValidSentAddresses();
+	if (valid != null) {
+	    for (int i = 0; i < valid.length; i++) 
+		System.out.println("    " + valid[i]);
+	}
+    }
+    public void messageNotDelivered(TransportEvent e) {
+	System.out.println(">>> TransportListener.messageNotDelivered().");
+	System.out.println(" Invalid Addresses:");
+	Address[] invalid = e.getInvalidAddresses();
+	if (invalid != null) {
+	    for (int i = 0; i < invalid.length; i++) 
+		System.out.println("    " + invalid[i]);
+	}
+    }
+    public void messagePartiallyDelivered(TransportEvent e) {
+	System.out.println(">>> TransportListener.messagePartiallyDelivered().");
+	System.out.println(" Valid Addresses:");
+	Address[] valid = e.getValidSentAddresses();
+	if (valid != null) {
+	    for (int i = 0; i < valid.length; i++) 
+		System.out.println("    " + valid[i]);
+	}
+	System.out.println(" Valid Unsent Addresses:");
+	Address[] unsent = e.getValidUnsentAddresses();
+	if (unsent != null) {
+	    for (int i = 0; i < unsent.length; i++) 
+		System.out.println("    " + unsent[i]);
+	}
+	System.out.println(" Invalid Addresses:");
+	Address[] invalid = e.getInvalidAddresses();
+	if (invalid != null) {
+	    for (int i = 0; i < invalid.length; i++) 
+		System.out.println("    " + invalid[i]);
+	}
+    }
+
+    private static void usage() {
+	System.out.println(
+    "usage: java transport \"<to1>[, <to2>]*\" <from> <smtp> true|false");
+	System.out.println(
+    "example: java transport \"joe@machine, jane\" senderaddr smtphost false");
+    }
+}
diff --git a/demo/src/main/java/uidmsgshow.java b/demo/src/main/java/uidmsgshow.java
new file mode 100644
index 0000000..71e4d14
--- /dev/null
+++ b/demo/src/main/java/uidmsgshow.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+/*
+ * Demo app that exercises the Message interfaces.
+ * Access message given its UID.
+ * Show information about and contents of messages.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class uidmsgshow {
+
+    static String protocol;
+    static String host = null;
+    static String user = null;
+    static String password = null;
+    static String mbox = "INBOX";
+    static String url = null;
+    static boolean verbose = false;
+
+    public static void main(String argv[]) {
+	long uid = -1;
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-v")) {
+		verbose = true;
+	    } else if (argv[optind].equals("-f")) {
+		mbox = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println("Usage: uidmsgshow [-L url] [-T protocol] [-H host] [-U user] [-P password] [-f mailbox] [uid] [-v]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+	    if (optind < argv.length)
+		 uid = Long.parseLong(argv[optind]);
+
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    Session session = Session.getInstance(props, null);
+	    // session.setDebug(true);
+
+	    // Get a Store object
+	    Store store = null;
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, user, password);
+		else
+		    store.connect();
+	    }
+	    
+
+	    // Open the Folder
+
+	    Folder folder = store.getDefaultFolder();
+	    if (folder == null) {
+		System.out.println("No default folder");
+		System.exit(1);
+	    }
+
+	    folder = folder.getFolder(mbox);
+	    if (!folder.exists()) {
+		System.out.println(mbox + "  does not exist");
+		System.exit(1);
+	    }
+
+	    if (!(folder instanceof UIDFolder)) {
+		System.out.println(
+		    "This Provider or this folder does not support UIDs");
+		System.exit(1);
+	    }
+
+	    UIDFolder ufolder = (UIDFolder)folder;
+
+	    folder.open(Folder.READ_WRITE);
+	    int totalMessages = folder.getMessageCount();
+
+	    if (totalMessages == 0) {
+		System.out.println("Empty folder");
+		folder.close(false);
+		store.close();
+		System.exit(1);
+	    }
+
+	    if (verbose) {
+		int newMessages = folder.getNewMessageCount();
+		System.out.println("Total messages = " + totalMessages);
+		System.out.println("New messages = " + newMessages);
+		System.out.println("-------------------------------");
+	    }
+
+	    if (uid == -1) {
+		// Attributes & Flags for ALL messages ..
+		Message[] msgs = 
+			ufolder.getMessagesByUID(1, UIDFolder.LASTUID);
+
+		// Use a suitable FetchProfile
+		FetchProfile fp = new FetchProfile();
+		fp.add(FetchProfile.Item.ENVELOPE);
+		fp.add(FetchProfile.Item.FLAGS);
+		fp.add("X-Mailer");
+		folder.fetch(msgs, fp);
+
+		for (int i = 0; i < msgs.length; i++) {
+		    System.out.println("--------------------------");
+		    System.out.println("MESSAGE UID #" + 
+					ufolder.getUID(msgs[i]) + ":");
+		    dumpEnvelope(msgs[i]);
+		    // dumpPart(msgs[i]);
+		}
+	    } else {
+		System.out.println("Getting message UID: " + uid);
+		Message m = ufolder.getMessageByUID(uid);
+		if (m != null)
+		    dumpPart(m);
+		else
+		    System.out.println(
+			"This Message does not exist on this folder");
+	    }
+
+	    folder.close(false);
+	    store.close();
+	} catch (Exception ex) {
+	    System.out.println("Oops, got exception! " + ex.getMessage());
+	    ex.printStackTrace();
+	}
+	System.exit(1);
+    }
+
+    public static void dumpPart(Part p) throws Exception {
+	if (p instanceof Message)
+	    dumpEnvelope((Message)p);
+
+	/* Dump input stream
+	InputStream is = new BufferedInputStream(p.getInputStream());
+	int c;
+	while ((c = is.read()) != -1)
+	    System.out.write(c);
+	*/
+
+	System.out.println("CONTENT-TYPE: " + p.getContentType());
+
+	Object o = p.getContent();
+	if (o instanceof String) {
+	    System.out.println("This is a String");
+	    System.out.println("---------------------------");
+	    System.out.println((String)o);
+	} else if (o instanceof Multipart) {
+	    System.out.println("This is a Multipart");
+	    System.out.println("---------------------------");
+	    Multipart mp = (Multipart)o;
+	    int count = mp.getCount();
+	    for (int i = 0; i < count; i++)
+		dumpPart(mp.getBodyPart(i));
+	} else if (o instanceof Message) {
+	    System.out.println("This is a Nested Message");
+	    System.out.println("---------------------------");
+	    dumpPart((Part)o);
+	} else if (o instanceof InputStream) {
+	    System.out.println("This is just an input stream");
+	    System.out.println("---------------------------");
+	    InputStream is = (InputStream)o;
+	    int c;
+	    while ((c = is.read()) != -1)
+		System.out.write(c);
+	}
+    }
+
+    public static void dumpEnvelope(Message m) throws Exception {
+	System.out.println("This is the message envelope");
+	System.out.println("---------------------------");
+	Address[] a;
+	// FROM 
+	if ((a = m.getFrom()) != null) {
+	    for (int j = 0; j < a.length; j++)
+		System.out.println("FROM: " + a[j].toString());
+	}
+
+	// TO
+	if ((a = m.getRecipients(Message.RecipientType.TO)) != null) {
+	    for (int j = 0; j < a.length; j++)
+		System.out.println("TO: " + a[j].toString());
+	}
+
+	// SUBJECT
+	System.out.println("SUBJECT: " + m.getSubject());
+
+	// DATE
+	Date d = m.getSentDate();
+	System.out.println("SendDate: " +
+	    (d != null ? d.toString() : "UNKNOWN"));
+
+	// SIZE
+	System.out.println("Size: " + m.getSize());
+
+	// FLAGS:
+	Flags flags = m.getFlags();
+	StringBuffer sb = new StringBuffer();
+	Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
+
+	boolean first = true;
+	for (int i = 0; i < sf.length; i++) {
+	    String s;
+	    Flags.Flag f = sf[i];
+	    if (f == Flags.Flag.ANSWERED)
+		s = "\\Answered";
+	    else if (f == Flags.Flag.DELETED)
+		s = "\\Deleted";
+	    else if (f == Flags.Flag.DRAFT)
+		s = "\\Draft";
+	    else if (f == Flags.Flag.FLAGGED)
+		s = "\\Flagged";
+	    else if (f == Flags.Flag.RECENT)
+		s = "\\Recent";
+	    else if (f == Flags.Flag.SEEN)
+		s = "\\Seen";
+	    else
+		continue;	// skip it
+	    if (first)
+		first = false;
+	    else
+		sb.append(' ');
+	    sb.append(s);
+	}
+
+	String[] uf = flags.getUserFlags(); // get the user flag strings
+	for (int i = 0; i < uf.length; i++) {
+	    if (first)
+		first = false;
+	    else
+		sb.append(' ');
+	    sb.append(uf[i]);
+	}
+	System.out.println("FLAGS = " + sb.toString());
+
+	// X-MAILER
+	String[] hdrs = m.getHeader("X-Mailer");
+	if (hdrs != null)
+	    System.out.println("X-Mailer: " + hdrs[0]);
+	else
+	    System.out.println("X-Mailer NOT available");
+    }
+}
diff --git a/doc/release/ApacheJServ.html b/doc/release/ApacheJServ.html
new file mode 100644
index 0000000..47c85d2
--- /dev/null
+++ b/doc/release/ApacheJServ.html
@@ -0,0 +1,98 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="JavaSoftware">
+   <meta name="GENERATOR" content="Mozilla/4.61 [en] (WinNT; U) [Netscape]">
+   <title>ApacheJServ</title>
+</head>
+<body>
+If you don't already have Apache web server and Apache JServ
+module installed and running,
+<br>you'll need to download them from the&nbsp; <a href="http://www.apache.org">Apache
+Software Foundation</a>&nbsp; and install them. We
+<br>were able to find binary versions of Apache web server for both the
+Solaris and Windows NT
+<br>operating systems, and Apache JServ 1.1.2 for Windows NT. However,
+for Solaris,
+<br>Apache JServ needed to be built from the source.
+<p>(on Solaris)
+<blockquote>Apache Web Server 1.3.9 (<a href="http://httpd.apache.org/dist/binaries/">binary</a>)
+<br>Apache JServ 1.1.2 (built from <a href="http://java.apache.org/">source</a>)</blockquote>
+(on NT)
+<blockquote>Apache Web Server&nbsp; 1.3.14 (<a href="http://httpd.apache.org/dist/binaries/">binary</a>)
+<br>Apache JServ 1.1.2 (<a href="http://java.apache.org/">binary</a>)</blockquote>
+To run the JavaMailServlet, you must add the JavaMail and JavaBeans Activation
+Framework
+<br>jar files to the web server's system classpath. Apache JServ also requires
+that the
+<br>Java Servlet Development Kit&nbsp; 2.0 (<a href="http://java.sun.com/products/servlet/archive.html">JSDK2.0</a>)
+is installed and also added to the web server's
+<br>system classpath. JSDK2.0 is a reference implementation of the Servlet
+API Specification Version 2.0.
+<p>When running Apache JServ in automatic mode, add wrapper.classpath properties
+for each
+<br>of these jar files to the servlet engine's propeties file. The default
+engine properties file is
+<br>jserv.properties and is usually found in Apache JServ's 'conf' directory.
+Wrapper properties
+<br>are used to automatically start the servlet engine.
+<p>(on Solaris)
+<blockquote>wrapper.classpath=/files/java/lib/JSDK2.0/lib/jsdk.jar
+<br>wrapper.classpath=/files/java/lib/javamail-1.2/mail.jar
+<br>wrapper.classpath=/files/java/lib/jaf-1.0.1/activation.jar</blockquote>
+(on NT)
+<blockquote>wrapper.classpath=d:\java\lib\jsdk2.0\lib\jsdk.jar
+<br>wrapper.classpath=d:\java\lib\javamail-1.2\mail.jar
+<br>wrapper.classpath=d:\java\lib\jaf-1.0.1\activation.jar</blockquote>
+When running Apache JServ in manual mode, add these jar files to the CLASSPATH
+<br>environment variable (this is documented in the JavaMail README file
+and additional
+<br>Windows NT information is provided <a href="classpath-NT.html">here</a>).
+<p>Once this is done, restart the web server.
+<br>&nbsp;
+<p>Additional Notes
+<blockquote>One thing to watch out for is how the web server is referenced
+when using the
+<br>JavaMailServlet on this web server.&nbsp; It is important to use the
+configured server
+<br>name (i.e. the name assigned with the ServerName directive) in the
+URL. If the
+<br>configured server name includes a domain, it must be referenced including
+the
+<br>domain, even if the JavaMail.html page can be accessed without it.
+<p>For example, Apache is configured with the ServerName set to 'shadygrove'.
+If the JavaMail demo is referenced by:
+<p>&nbsp;&nbsp;&nbsp; http://shadygrove.east/example/JavaMail.html
+<p>and the initail reference to the JavaMailServlet being:
+<p>&nbsp;&nbsp;&nbsp; http://shadygrove.east/example/JavaMailServlet
+<p>the user is successfully logged in. However, when an attempt is made
+to access the INBOX, the user is instructed to login because there is no
+current session. When this occurs, note that the URL referencing the JavaMailServlet
+no longer contains the domain originally used but the configured ServerName.
+<p>&nbsp;&nbsp;&nbsp; http://shadygrove/example/JavaMailServlet
+<br>&nbsp;
+<p>Also note that Apache JServ does not use a SecurityManager or enforce
+any Java
+<br>security policy.</blockquote>
+
+</body>
+</html>
diff --git a/doc/release/CHANGES.txt b/doc/release/CHANGES.txt
new file mode 100644
index 0000000..165141f
--- /dev/null
+++ b/doc/release/CHANGES.txt
@@ -0,0 +1,804 @@
+You can find more information about each bug number by visiting the
+Issue Tracker and looking up each bug you're interested in.
+
+Bug IDs that start with "E" can be found in the GitHub Issue Tracker
+for the Eclipse EE4J JavaMail project (after removing the "E"):
+
+	https://github.com/eclipse-ee4j/javamail/issues/<bug-number>
+
+Bug IDs that start with "GH" can be found in the GitHub Issue Tracker
+(after removing the "GH"):
+
+	https://github.com/javaee/javamail/issues/<bug-number>
+
+Bug IDs that start with "G" can be found in the GlassFish Issue Tracker
+(after removing the "G"):
+
+	https://github.com/javaee/glassfish/issues/<bug-number>
+
+Seven digit bug numbers are from the old Sun bug database, which is no
+longer available.
+
+
+		  CHANGES IN THE 1.6.3 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.6.3 release.
+
+E  348	Change Maven coordinates to match EE4J and Jakarta EE conventions
+
+
+		  CHANGES IN THE 1.6.2 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.6.2 release.
+
+GH  306	Infinite loop parsing invalid ID response
+GH  307	StringIndexOutOfBoundsException when *.proxy.host contains a colon
+GH  309	Multipart Content-Transfer-Encoding trailing space
+GH  310	Android app fails to build with JavaMail 1.6.1
+GH  314	InternetAddress fails to detect illegal square brackets in local part
+GH  315	empty Content-Transfer-Encoding header causes IOException
+GH  316	starttls.enable documentation should reference starttls.required prop
+GH  317	use System.lineSeparator() instead of System.getProperty(...)
+GH  321	URLName.getURL() returns incorrect url.
+GH  322	Dots in local part of emails not handled properly
+GH  323	Support loading protocol providers using ServiceLoader
+GH  326	Apply relaxed Content-Disposition parsing to Content-Disposition params
+GH  330	Attachment filename is ignored
+GH  332	http proxy support should support authenticating to the proxy server
+GH  333	search with Unicode char throws BadCommandException with UTF8=ALLOW
+GH  334	gimap set labels error with some non english characters
+
+
+		  CHANGES IN THE 1.6.1 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.6.1 release.
+
+GH  262	Some IMAP servers send EXPUNGE responses for unknown messages
+GH  278	BODYSTRUCTURE Parser fails on specific IMAP Server response 
+GH  283	clean up connections when closing IMAPStore
+GH  287	Allow relaxed Content-Disposition parsing
+GH  289	use a different IMAP tag prefix for each connection
+GH  291	define JDK 9 module name for JavaMail
+GH  296	HTTP proxy support needs to use HTTP/1.1
+
+
+		  CHANGES IN THE 1.6.0 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.6.0 release.
+
+GH   75	MimeMultipart should throw ParseException for parsing errors
+GH   77	MimeMessage.updateHeaders should set the Date header if not already set
+GH   93	Support addressing i18n via RFC 6530/6531/6532
+GH  104	The UIDFolder interface should have a getter for UIDNEXT
+GH  135	MailHandler should choose a better default subject formatter.
+GH  159	Store, Transport, and Folder should implement AutoCloseable
+GH  174	MailDateFormat changes for version 1.6
+GH  183	Fix javac warnings
+GH  209	fails to parse some fetch response that has space before final ')'
+GH  214	IMAP doesn't handle illegal CAPABILITY response after LOGIN/AUTHENTICATE
+GH  226	MailSessionDefinition should use the Repeatable annotation for Java EE 8
+GH  227	IdleManager fails on Android
+GH  228	Test fails: javax.mail.internet.GetLocalAddressTest
+GH  229	Tests fail: com.sun.mail.util.WriteTimeoutSocketTest
+GH  230	MboxFolder.expunge can corrupt mailbox file
+GH  231	CompactFormatter should handle overridden Throwable.toString methods
+GH  232	Update public API to use generics
+GH  233	Malformed IMAP FETCH response throws the wrong exception
+GH  234	RFC822.SIZE > 2GB isn't handled
+GH  237	Protocol#command method call readResponse after IOException is thrown
+GH  238	Possible NPE in Status.<init> line 96
+GH  239	MailHandler should support 'login' verify type.
+GH  240	MailHandler support for non-multipart messages
+GH  241	use of YoungerTerm/OlderTerm on server without WITHIN support fails
+GH  244	The UIDFolder interface should have a MAXUID constant
+GH  245	java.io.IOException: No content when reading msg with empty attachment
+GH  247	look for resource files in <java.home>/conf on JDK 1.9
+GH  248	MimeUtility should treat GB2312 as one of the supersets GBK or GB18030
+GH  249	Flags convenience methods
+GH  250	SMTP support for the CHUNKING extension of RFC 3030
+GH  251	MimeUtility.unfold squashes multiple spaces
+GH  252	JavaMail PLAIN authentication should implement RFC 4616
+GH  253	Support connecting through web proxy servers
+GH  256	support UIDPLUS UIDNOTSTICKY response code
+GH  257	SASL authentication should always allow UTF-8 username and password
+GH  258	android-activation MANIFEST has empty Bundle-SymbolicName
+
+
+		  CHANGES IN THE 1.5.6 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.5.6 release.
+
+GH  199	Support LogRecord.setMillis being deprecated in JDK 9
+GH  200	Logging should support LogRecord.getInstant
+GH  202	Create common super class for logging tests
+GH  205	NPE by APOP detection when no greeting banner
+GH  206	Make IMAPProtocol.handleLoginResult protected
+GH  207	InternetAddress.parse fails for valid domain literal address
+GH  210	unsolicited FETCH response *must* invalidate X-GM-LABELS in cache
+GH  211	MimeBodyPart.isMimeType returns false if type header can't be parsed
+GH  213	NPE in Tomcat ClassLoader causes Session.getInstance to fail
+GH  215	Deadlock in IMAPFolder.doProtocolCommand()
+GH  216	InternetAddress.getLocalAddress should use
+	InetAddress.getCanonicalHostName
+GH  217	Store finalizers should not talk to server
+GH  219	MailHandler verify should load additional content handlers
+GH  220	NullPointerException if SASL is enabled on Android
+GH  221	write timeouts don't work with SSL on Android
+GH  222	JavaMail allows injection of unwanted headers
+GH  223	Message.setRecipient(type, null) should remove recipients
+
+
+		  CHANGES IN THE 1.5.5 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.5.5 release.
+
+GH  168	add support for setting GMail labels on messages
+GH  169	Add spam filter for use with MailHandler.
+GH  170	Address MailDateFormat issues
+GH  172	Typo in "mail.stmp.sendpartial"
+GH  173	mail.mime.encodefilename property should override RFC 2231 encoding
+GH  176	IMAP should support a mail.imap.auth.mechanisms property like SMTP
+GH  177	setting mail.<protocol>.auth.mechanisms should override
+	mail.<protocol>.auth.<mechanism>.disable
+GH  178	add support for OAuth 2.0 without SASL
+GH  179	capability() command doesn't properly transform errors
+GH  180	MailHandler needs better support for stateful filters.
+GH  181	add support for IMAP login referrals (RFC 2221)
+GH  182	whitespace line at beginning confuses InternetHeaders
+GH  184	Eliminate legacy classes
+GH  185	IndexOutOfBoundsException reading IMAP literal when connection fails
+GH  186	IdleManager dies with CancelledKeyException
+GH  187	IdleManager can deadlock when not busy
+GH  188	IMAP Folder methods throw runtime exceptions when connection drops
+GH  189	InternetAddress doesn't detect some illegal newlines
+GH  190	Status class doesn't decode mailbox name
+GH  191	add support for IMAP COMPRESS extension (RFC 4978)
+GH  194	Empty Gmail X-GM-LABELS list is misparsed
+GH  195	IMAPMessage.getReceivedDate should check if receivedDate is present
+	before loading envelope
+GH  196	CollectorFormatter descending order data race
+GH  198	off-by-1 error in Response.readStringList causes early termination of
+	parsing FETCH response
+GH  201	INTERNALDATE FetchProfile Item
+GH  203	Exchange returns NIL instead of "" for empty parameter, causing NPE
+
+
+		  CHANGES IN THE 1.5.4 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.5.4 release.
+
+GH  149	Include elapsed time, thread id, and sequence for logging formatters.
+GH  153	MailHandlerTest does not check field is static or final
+GH  157	IdleManager can deadlock with frequent notifications
+GH  158	IdleManager can deadlock when connection fails
+GH  160	IMAP provider should support the MOVE extension (RFC 6851)
+GH  162	MODSEQ should be stored in IMAPMessage if CONDSTORE is enabled
+GH  163	Space character lost from end of quoted-printable body parts
+GH  164	GmailMessage extensions are not cached after implicit FETCH
+GH  165	IMAP message sets should be sorted in cases where order doesn't matter
+GH  166	ID command shouldn't escape NIL value
+GH  167	Make IMAPProtocol class extendable
+
+
+		  CHANGES IN THE 1.5.3 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.5.3 release.
+
+GH  122	Make constructor of POP3Folder protected to allow subclassing
+GH  123	calling IdleManager.watch twice on same folder fails
+GH  124	NPE in IMAPFolder.copyUIDMessages when COPYUID not returned
+GH  127	Message-Id leaks current user/hostname of the Java process (security)
+GH  128	IMAP idle breaks interrupt flag
+GH  129	Date search terms result in wrong greater-than SEARCH commands for IMAP
+GH  131	address similar to (x)<y>(z) will throw StringIndexOutOfBoundsException
+GH  132	Update logging demos to use the new 1.5.2 features
+GH  133	Use classloader ergonomics in the MailHandler
+GH  137	ArrayIndexOutOfBoundsException in IMAPFolder.copyUIDMessages
+GH  138	attachment filenames aren't being encoded by default
+GH  139	Include javadoc example formats for logging.
+GH  141	SharedFileInputStream has problems with 2GB+ files
+GH  143	MimeBodyPart with copied DataHandler doesn't always set encoding
+GH  144	skip unusable Store and Transport classes
+GH  145	long parameter values should be split using RFC 2231
+GH  146	javax.mail.Authenticator thread safety
+GH  148	Modify MailHandler to support Google App Engine.
+GH  150	EXPUNGE response during UID FETCH breaks UID->seqnum mapping
+GH  151	ArrayIndexOutOfBoundsException caused by out-of-range IMAP responses
+GH  154	write timeouts don't work with a custom SSL socket factory
+GH  155	SMTP SASL DIGEST-MD5 fails on postfix since the last reply sent is "*"
+
+
+		  CHANGES IN THE 1.5.2 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.5.2 release.
+
+GH   57	allow IMAP search to throw SearchException when search is too complex
+GH   88	NullPointerException at IMAPFolder#getMessagesByUID if msg not found
+GH   89	add option to use canonical host name for SASL
+GH   90	IMAP astring parsing incorrect
+GH   91	SMTP SASL support doesn't handle authentication failure properly
+GH   92	SASL authentication failures should not try other methods
+GH   94	add OAuth2 support to JavaMail
+GH   95	IMAP failures during close can leave connection unusable
+GH   96	need way to monitor IMAP responses
+GH   97	MimeUtility.encodeText() does not work with Unicode surrogate pairs
+GH  105	IMAP alerts and notifications are not sent during authentication
+GH  106	add ability to fetch and cache entire IMAP message
+GH  107	add ability to return server-specific STATUS responses
+GH  108	add ability to set "peek" flag for all IMAP messages
+GH  109	add ability to specify scope of event queue
+GH  110	add ability to specify an Executor to process events
+GH  113	handle multiple IMAP BODY elements in a single FETCH response
+GH  115	add more efficient way to monitor multiple folders for new messages
+GH  116	Include a subject formatter for the logging package
+GH  118	Broken equals in URLName, NewsAddress
+GH  119	NullPointerException in ContentType.match
+GH  120	hashCode of two equals instances does not match for ModifiedSinceTerm,
+	YoungerTerm, OlderTerm
+GH  121	NullPointerException in InternetAddress
+
+
+		  CHANGES IN THE 1.5.1 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.5.1 release.
+
+GH   66	Wrong "source" version in build.properties
+GH   67	IMAP provider should support the QRESYNC extension (RFC 5162)
+GH   68	IMAP provider should support the WITHIN search extension (RFC 5032)
+GH   69	JavaMail does not handle write timeouts
+GH   70	method fill() isn't synchronized correctly in SharedFileInputStream
+GH   71	NullPointerException in MimeUtility#quote()
+GH   72	support RFC 2359 COPYUID response code
+GH   73	Empty FROM Field causes Exception
+GH   74	Filename isn't parsed correctly if charset is not set
+GH   76	copying a DataHandler from a parsed message to a new message fails
+GH   78	MimeMessage does not unfold address headers before parsing them
+GH   80	When using XGWTRUSTEDAPP mechanism, LOGIN should not be issued if no
+	authzid is specified
+GH   82	support empty IMAP ENVELOPE address list instead of NIL
+GH   83	JavaMail should support the IMAP ID extension
+GH   84	Typo: "mechansims"
+GH   86	Exchange returns out of range message numbers for SEARCH
+GH   87	require extra permission to create default Session with SecurityManager
+
+
+		  CHANGES IN THE 1.5.0 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.5.0 release.
+
+GH   37	add FetchProfile.Item.SIZE
+GH   38	fix protected fields in final classes in javax.mail.search
+GH   39	add MimeMultipart(String subtype, BodyPart... bps) constructor
+GH   40	exceptions should support exception chaining
+GH   41	ParameterList needs to support use by IMAP
+GH   42	ContentType.toString & ContentDisposition.toString shouldn't return null
+GH   44	add Transport.send(msg, username, password) method
+GH   45	add MimeMessage.setFrom(String) method
+GH   46	add Message.getSession() method
+GH   47	MimeBodyPart.attachFile should set the disposition to ATTACHMENT
+GH   48	add MimeMessage.reply(replyToAll, setAnswered) method
+GH   49	add "next" methods to HeaderTokenizer to help parsing bad headers
+GH   51	add @MailSessionDefinition and @MailSessionDefinitions for Java EE 7
+GH   52	make cachedContent field protected in MimeMessage and MimeBodyPart
+GH   53	make MimeMultipart fields protected to allow subclassing
+GH   55	need simple way to override MIME type and encoding of attachment
+GH   56	enable RFC 2231 support by default
+GH   62	Exception when parsing bad address with unclosed quote in mail header
+GH   64	when failing to connect to a server, provide more detail in exception
+
+
+		  CHANGES IN THE 1.4.7 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.4.7 release.
+
+GH   60	NullPointerException when accessing the content of a message attachment
+GH   61	IMAPProtocol.sasllogin uses old constructor for IMAPSaslAuthenticator
+
+
+		  CHANGES IN THE 1.4.6 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.4.6 release.
+
+GH   28	IMAP Store socket leak on connect with ProtocolException
+GH   29	STARTTLS when already using SSL may cause connection to fail
+GH   30	Yahoo: Error reading emails containing "undisclosed recipients"
+GH   32	Infinite loop if Quota information is not correctly reported by server
+GH   54	Issue in decoding filename with charset iso-2022-jp from Mime header
+	based on rfc 2231
+<no id>	add mail.imap.ignorebodystructuresize to work around server bugs
+<no id>	add "gimap" EXPERIMENTAL Gmail IMAP provider
+<no id>	add isSSL() method to all protocol providers
+<no id>	add support for debug output using java.util.logging
+<no id>	avoid NullPointerException when encountering a bad Content-Type
+
+
+		  CHANGES IN THE 1.4.5 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.4.5 release.
+
+7021190	MimeMessage.setRecipients(type, String) does not accept null address
+GH   15	ArrayIndexOutOfBoundsException for some IMAP protocol errors
+GH   17	MultipartReport.setReport and setDeliveryStatus are broken
+GH   18	Wrong representation of CR/LF are appended to the attachment
+GH   19	SSL Re-Negotiation Problem with checkserveridentity=true
+GH   22	Thread safety in javax.mail.PasswordAuthentication
+GH   25	Add SOCKS5 support
+GH   27	wrong message accessed when another client expunges and adds messages
+<no id>	properly handle timeouts during SSL negotiation
+<no id>	free MIME headers in IMAPMessage.invalidateHeaders
+<no id>	fix exception in POP3Message when reading file cached content twice
+<no id>	suppress auth info in debug output, unless mail.debug.auth=true
+<no id>	better handle timeouts from POP3 server, throwing FolderClosedException
+<no id>	add com.sun.mail.util.ReadableMime to allow reading raw MIME data
+<no id>	work around Gmail IMAP bug with nested messages
+<no id>	close the SMTP connection if there's an I/O error
+
+
+		  CHANGES IN THE 1.4.4 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.4.4 release.
+
+5057742	javax.mail.internet.MimeMessage.addFrom() violates RFC2822
+6778568	lower memory usage of POP3 parsing by buffering to disk
+6905730	MimeMessage.parse() is very slow on malformed message content
+6910675	IMAP provider can lose track of message sequence numbers
+6928566	Header violates RFC 2822 for multiple calls of addRecipient(s)
+6995537	work around this JDK bug that causes iso-2022-jp decoding problems
+G 11069	update the mail.jar manifest to include DynamicImport-Package
+GH   10	make sure socket is closed if SMTP connect fails
+GH   12	Multiparts do not parse correctly in presence of legacy Mac line endings
+GH   14	InputStreams are not closed in MimeMultipart
+<no id>	add mail.mime.windowsfilenames System property to handle IE6 breakage
+<no id>	properly disable TOP if POP3 CAPA response doesn't include it
+<no id>	add mail.pop3.disablecapa property to disable use of the CAPA command
+<no id>	fix support for Properties objects with default Properties objects
+<no id>	integrate NTLM support, no longer needs jcifs.jar
+<no id>	add mail.pop3.cachewriteto property, default false
+<no id>	add mail.pop3.filecache.enable property for caching messages in tmp file
+<no id>	add mail.mime.ignorewhitespacelines property, default false
+<no id>	add support for IMAP UNSELECT command
+<no id>	add mail.mime.contentypehandler System property, to clean Content-Type
+<no id>	add mail.mime.allowencodedmessages System property
+<no id>	add support for SASL authentication to SMTP provider
+<no id>	add SMTPSenderFailedException to indicate problems with sender address
+<no id>	ignore encoding for composite content when writing message
+<no id>	cache POP3 content using java.lang.ref.SoftReference,
+	set mail.pop3.keepmessagecontent to true to disable
+<no id>	add POP3 PIPELINING support
+<no id>	reduce POP3 memory usage, especially if server supports pipelining
+<no id>	add support for IMAP SORT extension (RFC 5256) to IMAPFolder
+<no id>	add demo classes to support non-MIME messages from Outlook
+<no id>	fix deadlock when using IMAP fetch and IDLE
+<no id>	add support for mail.smtp.auth.<mechanism>.disable properties
+<no id>	fix deadlock when accessing IMAP messages while doing a fetch
+
+
+		  CHANGES IN THE 1.4.3 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.4.3 release.
+
+6829124	IMAPFolder deadlock using IMAP IDLE
+6850882	IMAPMessage returns wrong getMessageNumber() from messageRemovedEvent
+6857090	JavaMail is not sending HELO / EHLO according to specs
+6872072	QPEncoderStream write method eats up trailing white space of a string
+6875367	LineOutputStream wraps IOException instead of throwing it directly
+6890265	SMTPTransport does not close socket if STARTTLS is req'd but not sup'd
+G  9941	SMTPTransport violates RFC 2821 in HELO command
+GH    2	InternetAddress verifies domain per RFC1034 instead of RFC822 in strict
+GH    4	added NTLM authentication support for SMTP and IMAP, see NTLMNOTES.txt
+<no id>	add starttls support to POP3
+<no id>	add mail.transport.protocol.<address-type> property
+<no id>	fail POP3Folder.open if STAT command fails
+<no id>	fix POP3Folder.isOpen if POP3 server fails and is then reconnected
+<no id>	better handle modifying messages created from input streams
+<no id>	include server error message in exception when SMTP authentication fails
+<no id>	com.sun.mail.util.logging.MailHandler contributed by Jason Mehrens
+<no id>	add mail.smtp.noop.strict property, default true
+<no id>	add mail.<protocol>.ssl.trust property to list hosts to be trusted
+<no id>	work around buggy IMAP servers that don't quote mbox name in STATUS resp
+
+
+		  CHANGES IN THE 1.4.2 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.4.2 release.
+
+6621377	unexpected result when uuencode data has any line starting with
+	"END" characters
+6629213	base64 encoder sometimes omits CRLF
+6670275	headers may not end up on top where they belong
+6671855	list on IMAP folder that can contain both messages and folders
+	might fail if folder is open
+6672359	SMTPTransport notifying both partially delivered and
+	not delivered methods
+6676257	cannot specify two custom ssl socket factories for starttls usage
+6679333	missing quotes around zero length parameter values
+6720896	add mail.mime.uudecode.ignoreerrors system property
+6720896	add mail.mime.uudecode.ignoremissingbeginend system property
+6730637	deadlocks in IMAP provider when connections fail
+6738454	deadlock when connection is broken
+6738468	javadocs use fully qualified names
+6797756	StringIndexOutOfBoundsError in InternetAddress.parseHeader()
+6799810	getReplyTo() returns zero length array when ReplyTo hdr has no value
+G  3929	Inconsistent synchronization in com.sun.mail.iap.Protocol
+G  4997	BASE64DecoderStream.skip (etc) skips the wrong number of octets
+G  5189	Can't specify SSLSocketFactory for STARTTLS in Javamail 1.4
+G  5861	add mail.<protocol>.starttls.required prop to require use of STARTTLS
+<no id>	ignore socket timeout while waiting in IMAP IDLE
+<no id>	fix bug in MailDateFormat parsing in non-lenient mode
+<no id>	add mail.mime.multipart.allowempty system property to handle (illegal)
+	empty multiparts (see javax.mail.internet.MimeMultipart)
+<no id>	add mail.mime.multipart.ignoreexistingboundaryparameter system property
+	to allow parsing multiparts with incorrect boundary parameters
+<no id>	handle address of the form "Undisclosed-Recipients:;"
+<no id>	add com.sun.mail.util.DecodingException to distinguish decoding errors
+<no id>	add mail.mime.ignoreunknownencoding system property (see MimeUtility)
+<no id>	ignore errors from SMTP RSET command
+<no id>	InternetAddress - detect more errors when strict, accept more when not
+<no id>	add mail.<protocol>.socketFactory and .ssl.socketFactory properties
+<no id>	add mail.<protocol>.ssl.enable property
+<no id>	add mail.<protocol>.ssl.checkserveridentity prop for RFC 2595 checks
+<no id>	add com.sun.mail.util.MailSSLSocketFactory class
+<no id>	fix possible NPE in MimeMessage if flags is not set in copy constructor
+<no id>	SMTP I/O failure incorrectly reports valid sent addresses
+<no id>	avoid creating IMAPMessage objects until they're actually needed
+<no id>	IMAPStore.isConnected might return true even though not connected
+<no id>	add support for Message Delivery Notifications (RFC 3798) to dsn.jar
+<no id>	if mail.mime.parameters.strict=false, param vals can start with specials
+
+
+		  CHANGES IN THE 1.4.1 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.4.1 release.
+
+4107594	IMAP implementation should use the IDLE extension if available
+4119871	MimeMessage.reply() should set the "References" header
+6228377	IMAPFolder's setFlags method handles user flags incorrectly
+6423701	Problem with using OrTerm when the protocol is IMAP
+6431207	SMTP is adding extra CRLF to message content
+6447295	IMAPMessage fails to return Content-Language from bodystructure
+6447799	encoded text not decoded even when mail.mime.decodetext.strict is false
+6447801	MimeBodyPart.writeTo reencodes data unnecessarily
+6456422	NullPointerException in smtptransport when sending MimeMessages
+	with no encoding
+6456444	MimeMessages created from stream are not correctly handled
+	with allow8bitmime
+6478460	java.lang.ArrayIndexOutOfBoundsException: 0 >= 0 in MultipartReport
+6506794	ProtocolException not correctly treated in IMAPStore
+6517273	encoded parameters not decoded when using IMAP
+6538483	JavaMail fails in Turkish locale
+6569311	Deadlock in IMAP attachment handling
+6604571	Folder.hasNewMessages hangs with some IMAP servers when folder is closed
+<no id>	fix performance bug in base64 encoder; now even faster!
+<no id>	throw MessageRemovedException from getContent for IMAP messages
+<no id>	MimeUtility.decodeText should not discard trailing whitespace
+<no id>	SSLSocketFactory should be used for imap and smtp STARTTLS commands
+<no id>	added mail.<prot>.ssl.protocols and mail.<prot>.ssl.ciphersuites props
+<no id>	fix bug in mapping IMAP UIDs to msgs when some msgs have been expunged
+<no id>	MimeMultipart failed to parse stream before adding/removing body parts
+<no id>	if IMAP folder is open, assume it exists, don't ask again
+<no id>	avoid unnecessary copies of the data in ByteArrayDataSource
+<no id> add mail.mime.applefilenames to work around filename encoding bug
+<no id> support decoding multi-segment parameter names per RFC 2231
+<no id> make sure Message-ID is really unique (GlassFish Issue 3064)
+<no id> do SMTP authentication if connect is called with username and password
+	even if mail.smtp.auth is not set
+
+
+		  CHANGES IN THE 1.4 RELEASE
+		  --------------------------
+The following bugs have been fixed in the 1.4 release.
+
+4107342	parameterList class should support non US-ASCII parameters
+4252273	support the IMAP UIDPLUS extension
+4377727	allow applications to dynamically register address type mappings
+4403733	MimeMessage read from a byte stream loses modifications
+4623517	add ByteArrayDataSource class
+4820923	JavaMail loads SocketFactories with wrong classloader
+4971381	add mail.mime.multipart.ignoremissingendboundary System property
+6300765	add MimePart.setText(text, charset, subtype) method
+6300768	add mail.mime.encodefilename and decodefilename properties
+6300771	add Service.connect(user, password)
+6300811	add MimeMultipart.isComplete() method
+6300814	add mail.mime.multipart.ignoremissingboundaryparameter property
+6300828	add MimeMultipart getPreamble and setPreamble methods
+6300831	add MimeMessage.updateMessageID() protected method
+6300833	add MimeMessage.createMimeMessage() protected method
+6300834	make the "part" field of MimePartDataSource protected
+6301381	folder.getSeparator should not require the folder to exist
+6301386	add PreencodedMimeBodyPart class
+6301390	add MimeBodyPart attachFile and saveFile methods
+6302118	add MimeUtility fold and unfold methods
+6302832	allow more control over headers in InternetHeaders object
+6302835	allow applications to dynamically register new protocol providers
+6304051	standard interface for Stores that support quotas
+6304189	add SharedByteArrayInputStream class
+6304193	add SharedFileInputStream class
+6332559	REGRESSION: Bug in JavaMail (1.3.3 !) base64 decoder
+6378822	Transport.isConnected() conflicts with Sendmail NOOP check
+6401071	Deadlock in IMAP attachment handling
+<no id>	handle very large IMAP responses more efficiently
+<no id>	changed default for mail.smtp.quitwait to true
+<no id>	mailcap multipart entry is a JAF 1.1 fallback entry
+<no id>	improve MIME multipart parsing performance by 30% - 40%
+<no id>	add com.sun.mail.dsn package for parsing multipart/report DSN messages
+
+
+		  CHANGES IN THE 1.3.3 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.3.3 release.
+
+4239782	add IMAPFolder.getUIDNext
+4288393	add IMAPMessage.setPeek to allow reading message without setting SEEN
+6214426	POP3Folder.isOpen may return false even though folder is open
+6214448	IMAPStore.isConnected may return true even though server is down
+6236588	Duplicate Message IDs are generated when mutiple threads create messages
+6287582	ArrayIndexOutOfBoundsException when "Sender" field exists with no value
+6288399	IMAP Problem parsing bad envelope address format
+<no id>	improve base64 encoding performance 5X (thanks to John Freeman)
+<no id>	improve base64 decoding performance 3X
+<no id> ignore invalid encodings for composite MIME parts
+<no id> add mail.mime.multipart.ignoremissingboundaryparameter
+<no id>	if IMAP store times out, force folders closed without waiting
+<no id>	don't check if an IMAP folder exists before subscribing/unsubscribing
+<no id> add IMAPMessage.getSender(), getInReplyTo() and getContentLanguage()
+<no id> add IMAPFolder.getAttributes to retrieve LIST response attributes
+<no id> add IMAPStore.hasCapability to check for IMAP server CAPABILITY strings
+<no id> add IMAPMessage.invalidateHeaders for memory management
+<no id> when opening IMAP folder, don't do LIST before SELECT
+<no id> add mail.pop3.disabletop property to disable use of the TOP command
+<no id> add mail.pop3.forgettopheaders property to forget headers from TOP cmd
+<no id> add POP3Folder.getSizes() method to return sizes of all messages
+<no id> add POP3Folder.listCommand() method to return raw results of LIST cmd
+<no id> add SMTPTransport.connect(Socket) to enable ATRN support in server
+
+
+		  CHANGES IN THE 1.3.2 RELEASE
+		  ----------------------------
+The following bugs have been fixed in the 1.3.2 release.
+
+4358984	POP3 provider should support APOP, courtesy of "chamness"
+4711696	Mapping of nested Exceptions of a SendFailedException
+4863399	JavaMail should support specifying the SMTP bind address
+4900116	NotifyResponseHandler in Protocol.java throws an ArrayIndexOutOfBoundExc
+4924077	folder.getDeletedMessageCount() reports number of undeleted messages
+4934814	SASL authentication doesn't default to server specified realm
+4945852	Folder exists() function does not handle well folder names that
+	contains * or %
+4945868	Potential infinite loop in com.sun.mail.imap.protocol.BODY
+4945901	Folder.copyMessages() throws wrong exception in case of deleted messages
+4971383	[RFE] javamail should allow easy access to last smtp response
+4971391	BASE64DecoderStream handling error in encoded streams is too strict
+4996040	SharedInputStream stream closing policy is inconsistent
+4996543	IndexOutOfBoundsException when using SharedInputStream
+4996863	in the com.sun.mail.iap.Response bitfield constant "BAD" is set wrongly
+6041271	APPEND does not consider DST when computing timezone offset
+6067307	Mime-Version capitalization should match MIME spec
+6172894	MIME messages with missing end boundary are not reported as an error
+	(added mail.mime.multipart.ignoremissingendboundary System property)
+<no id>	accommodate some RFC3501 IMAP protocol changes
+<no id>	support RFC822 group lists when parsing IMAP address lists
+<no id>	don't read past end of IMAP part, for buggy servers that don't handle it
+<no id>	fix IMAP NAMESPACE support
+<no id>	allow different SMTPTransport objects to have different localhost names
+<no id>	make sure server is really alive in POP3Folder.isOpen()
+<no id>	support RFC2554 AUTH= submitter via mail.smtp.submitter and
+	SMTPMessage.setSubmitter
+<no id>	added SMTPSendFailedException, SMTPAddressFailedException, and
+	SMTPAddressSucceededException
+<no id>	mail.smtp.reportsuccess causes an exception to be thrown even on
+	successful sends, allowing access to the return codes for each address
+<no id>	fix IMAP isSubscribed in case where LSUB returns a \Noselect folder
+<no id>	parse invalid messages with non-ASCII characters in boundary string
+<no id>	add IMAP AUTH=PLAIN support, courtesy of Sandy McArthur
+<no id>	add SSL support to all protocols, see SSLNOTES.txt for details
+<no id>	add STARTTLS support to IMAP and SMTP protocols, see SSLNOTES.txt
+<no id>	handle IMAP email addresses composed of empty strings
+<no id>	add SASL support to IMAP provider
+<no id>	rename mail.stmp.saslrealm to mail.smtp.sasl.realm
+
+
+		  CHANGES IN THE 1.3.1 FCS RELEASE
+		  --------------------------------
+The following bugs have been fixed in the 1.3.1 release.
+
+4416417	IMAP alerts and notifications are not sent in all cases - more fixes
+4702410	header formatting incorrect for long multibyte
+4707106	AuthenticationFailedException not thrown in some cases with POP3
+4708655	IMAPNestedMessage.getContent without partialfetch
+4709848	message_rfc822 DataContentHandler can cause NPE
+4711606	uudecoder fails when reading more than a byte at a time
+4726447	InternetHeaders.getHeader() doc. doesn't document null pointer return
+4726629	Java Mail very slow with large attachment
+4741812	IMAPFolder can deadlock
+4750514	REGRESSION: MimeBodyPart.getContent fails on image/gif if no X11 present
+4750519	using SSL, SocketFetcher.getSocket0() throws incorrect Exception
+4762643	JavaMail does not support search in all message's parts.
+4780255	Message subject not encoded according to 'mail.mime.charset' property
+4787814	accessibility 508 non-compliance:  api/javax/mail/Session.html
+4790700	JavaMail Store.connect() throws wrong exception when already connected
+4820025	IMAPStore.getPersonalNamespaces throws a ProtocolException
+4874787	InternetAddress.toUnicodeString throws NPE, personal not initialized
+4882554	Line breaks in subject text break message format
+<no id>	don't close connection if open fails, put it back in the pool
+<no id>	don't always fetch entire envelope in IMAPMessage.getSize
+<no id>	fixed demo webapp to work with servlet 2.3 and newer
+<no id>	add DIGEST-MD5 support to SMTP provider, courtesy of Dean Gibson
+<no id>	added mail.smtp.quitwait property to wait for response to QUIT
+<no id>	added mail.imap.auth.login.disable prop to disable AUTHENTICATE LOGIN
+
+
+		  CHANGES IN THE 1.3 FCS RELEASE
+		  ------------------------------
+The following bugs have been fixed in the 1.3 release.
+
+4112002	IMAP provider hangs if APPEND is prohibited
+4201203	I18N: Incorrectly encoded MIME header can't be decoded
+	(set the *System* property "mail.mime.decodetext.strict" to "false")
+4413498	InternetHeaders should add Received headers in front
+4416417	IMAP alerts and notifications are not sent in all cases
+4483125	Multi-line mail header processing is slow
+4483158	null pointer exception for MessageContext.getMessage()
+4483206	Please add a public POP3 TOP method in the next release of the POP3 api
+4484098	IMAP PREAUTH does not work
+4516973	doPrivileged blocks needed for javamail
+4517683	new Flags("FOO").contains("FOO") fails
+4517686	want JavaMail-specific debug output stream
+4638743	JavaMail does not properly parse dates containing folding white space
+4638741	JavaMail does not handle in-spec Internet group addresses properly
+4650940	InternetAddress parsing should be more tolerant of bad addresses
+4650949	wrong encoding chosen for non-text data in rare cases
+4650952	should be able to extract group address members
+4672308	InternetAddress.toString () throws a NullPointerException after creation
+4679516	"NO" Response from IMAP server causes NPE from getSubject()
+4684040	Calling Folder.fetch twice may cause to header duplication
+<no id>	make uudecoder more tolerant of incorrect input
+<no id>	improve performance of SMTP for small messages
+<no id>	handle connection failure during open of POP3 folder
+<no id>	ensure ASCII, not EBCDIC output for POP3 protocol on IBM mainframes
+<no id>	add POP3Message.invalidate method to invalidate cached message data
+<no id>	fix thread safety bug in date formatting when appending to IMAP folders
+<no id>	fix parsing bug in QUOTA support
+<no id>	add mail.imap.allowreadonlyselect property to support shared mailboxes
+<no id>	use thread's context class loader for loading classes
+<no id>	add IMAPFolder.FetchProfileItem.HEADER and SIZE
+<no id>	don't try to logout store connection twice
+<no id>	IMAPFolder.close(false) read-only folder doesn't need to EXAMINE first
+<no id>	add support for group addresses to SMTP transport
+<no id>	use builtin defaults to allow JavaMail to work in netscape 4
+<no id>	tolerate trailing semicolon in Content-Type header (requires JAF 1.0.2)
+<no id>	add x-uue as another synonym for uuencode Content-Transfer-Encoding
+<no id>	set default charset for text parts
+<no id>	properly escape CRLF in MimeUtility.quote
+<no id>	fix NPE in MessagingException.getMessage
+
+
+		  CHANGES IN THE 1.2 FCS RELEASE
+		  ------------------------------
+The following bugs have been fixed in the 1.2 release.
+
+4107752	need MimeMessage(MimeMessage msg) constructor to allow copying message
+4112065	Need API to list and set/remove ACLs on folders (IMAP-specific)
+4119681	MimeMessage should allow creation of light-weight messages
+4124022 Two connections required to IMAP server to open a folder
+4124840	A mechanism to get the raw (unencoded) data from a MimePart is needed
+4126013	javax.mail.search terms should be serializable
+4129743	MimeMessage.parse() and MimeMessage.modified should be protected
+4132029	SMTP Submit is limited to 7bit; does not use ESMTP/8BITMIME
+4140579 MimeUtility.encode() does not allow for filename when using UUEncode
+4163360 Need a suitable MessagingException subclass to indicate read-only folder
+4181144	InternetAddress should be Cloneable
+4230553	AuthenticationFailedException should include error message from server
+4259211 exception constructors inconsistent
+4266390 MailDateFormat class should be part of the public API
+4281729 AddressStringTerm.match bug
+4319895	POP3 provider doesn't enforce read-only mode
+4319957	Ambiguous documentation in Javamail 1.1.3 early access edition
+4328824 string based methods to add recipients
+4328826 getDefaultInstance method with no Authenticator
+4330580	MimeMultipart.getBodyPart(String CID) throws exception
+4333694	NullPointerException in version 1.1.1 of the POP3 Provider
+4336435	quoted right angle bracket not handled in InternetAddress
+4339203	writeTo should automatically call saveChanges
+4340648	MimeUtility.getEncoding(DataHandler) method should be public
+4364827	Support IMAP NAMESPACE extension
+4366373	ContentDisposition class should be public
+4371862	improve performance of MimeMessage
+4372700 ParameterList.toString method should allow for returning folded results
+<no id>	most control characters must be encoded, not sent as "7bit"
+<no id>	appending very large message to IMAP folder uses too much memory
+<no id>	changed multipart boundary generation to not include email address
+<no id>	support IMAP LITERAL+ extension (RFC 2088)
+<no id>	allow SMTP multiline reponses with no text (e.g., "250-")
+<no id>	fix many potential locking bugs in IMAP provider
+<no id>	add mail.smtp.sendpartial property to send msg with some bad addresses
+<no id>	add mail.pop3.rsetbeforequit property (see NOTEST.txt)
+<no id>	throw IllegalStateException instead of MessagingException when folder
+	is not open (or closed, as appropriate)
+<no id>	added support for IMAP QUOTA extension
+<no id>	added support for IMAP PREAUTH greeting response
+<no id>	added DataContentHandler for text/xml data
+<no id>	added SMTPMessage class to specify SMTP options on a per-message basis
+<no id>	added javadocs for Sun protocol providers
+<no id>	mail.pop3.message.class property allows POP3Message class to be replaced
+<no id>	mail.{smtp,imap,pop3}.connectiontimeout property for connection timeouts
+
+
+
+		  CHANGES IN THE 1.1.3 FCS RELEASE
+		  --------------------------------
+The following bugs have been fixed in the 1.1.3 release.
+
+4248755	Problem loading a custom provider
+4249046	don't put space after SMTP FROM: and TO:
+4249058	IMAP appendMessages() should include the message Flags as well.
+4263185	JavaMail and JAF can't find properties when used as std ext
+4271714	DEBUG message always printed when providers loaded from <java.home>/lib
+4276080	getEncoding method doesn't parse MIME header
+4279603	RFC822 and MIME specials does not include period "."
+4292793	using Message.reply(true) twice on the same IMAP message causes NPE
+4293605	javax.mail.MimeMultipart boundary string contains invalid characters
+4296711	JavaMail IMAP provider doesn't set SEEN on messages with 0 length body
+4305687	JavaMail speaking SMTP fails to quote dots in some cases
+<no id>	add support for SMTP Authentication, see NOTES.txt
+<no id>	add support for SMTP Delivery Status Notification, see NOTES.txt
+<no id>	SMTP return address is now set in mail.smtp.from
+<no id>	fix bug in InternetAddress when parsing ``<x@foo.com> (Mr. X)''
+<no id>	improve javadocs in many places based on questions to javamail@sun.com
+<no id>	avoid JDK 1.2 bug 4208960 in SimpleTimeZone.getOffset
+<no id>	canonicalize the URLName before fetching saved PasswordAuthentication
+<no id>	convert SimpleClient to swing 1.1 package names (javax.swing.*)
+<no id>	folder.getURLName() should return native separator, not /, per RFC 2192
+<no id>	use JDK 1.2 ClassLoader.getResources() method (if available) to find all
+	META-INF/javamail.providers and META-INF/javamail.address.map files in
+	the CLASSPATH, to better support protocol provider jar files
+<no id>	encode/decode username and password fields of URLName to allow (e.g.)
+	usernames with "@"
+<no id>	added DataContentHandler for text/html, to simplify creation of HTML
+	messages and body parts
+<no id>	remove escapes from personal name when parsing in InternetAddress
+<no id>	cache results of IMAP STATUS command for 1 second, to improve
+	performance of back-to-back calls to getMessageCount,
+	getNewMessageCount, getUnreadMessageCount
+<no id>	fix InternetHeaders Enumeration to work even if hasMoreElements isn't
+	called
+<no id>	support mail.smtp.timeout property
+
+
+
+		  CHANGES IN THE 1.1.2 FCS RELEASE
+		  --------------------------------
+The following bugs have been fixed in the 1.1.2 release.
+
+<no id> Fixed bug where IMAP server connection hangs around even though
+	the connect() method failed.
+4199595	force quoted-printable encoding of long text lines
+<no id>	fix bug in SMTP output that sometimes duplicated "."
+<no id>	close SMTP transport on I/O error
+4230541	don't send SMTP NOOP unnecessarily
+4216666 IMAP provider INTERNALDATE formatter error, causing 
+	problems during appendMessages()
+4227888 IMAP provider does not honor the UID item in its FetchProfile
+
+
+		  CHANGES IN THE 1.1.1 FCS RELEASE
+		  --------------------------------
+The following bugs have been fixed in the 1.1.1 release.
+
+4181143 personal can't be null in constructor
+4134273 more careful & picky address parsing in InternetAddress parser
+4183700 SMTPTransport fails to close socket under certain situations.
+<no id> IMAP provider retains appended message's internal date during
+	Folder.appendMessages(Message[] m);
+<no id> More efficient server-side search for MessageIDTerm in the
+	IMAP provider
+<no id> Fix RFC2047 decoding bug in InternetAddress.getPersonal()
+<no id> Be more tolerant of illegally formatted dates in date parsing.
+<no id> ignore empty lines in loadMappings
+<no id> forgot to use javaCharset() in MimeUtility.decodeWord()
+<no id> Allow addresses without hostnames in InternetAddress parser
+<no id> unrecognized charsets can cause IllegalArgument runtime
+	exception when invoking getContent().
+<no id> Authentication failure when connecting to Sun IMAP server.
+<no id>	Reset SMTP connection after invalid address to allow future
+	sends to succeed
+<no id>	Any response to an SMTP NOOP command means we're still connected
diff --git a/doc/release/COMPAT.txt b/doc/release/COMPAT.txt
new file mode 100644
index 0000000..05a7ec2
--- /dev/null
+++ b/doc/release/COMPAT.txt
@@ -0,0 +1,377 @@
+			COMPATIBILITY NOTES
+			===================
+
+		    JavaMail(TM) API ${mail.version} release
+		    ------------------------------
+
+The JavaMail 1.6 specification is fully compatible with the JavaMail
+1.5 specification, with the exceptions listed below.
+
+In addition, changes in the implementation may impact
+applications that depend on behavior beyond what is defined by the
+JavaMail specification, or that use features specific to the reference
+implementation.  This note summarizes potential compatibility issues
+with this release of the JavaMail API.
+
+
+-- JavaMail 1.6.3 --
+
+- Maven coordinates for JavaMail changed.
+
+	JavaMail has moved to the Eclipse Foundation as part of the EE4J
+	project, and will be included in the Jakarta EE specification.
+	The Maven coordinates for JavaMail are now:
+
+	- com.sun.mail:jakarta.mail	- complete JavaMail jar file
+	- jakarta.mail:jakarta.mail-api	- API jar file for compiling only
+
+	When updating dependencies to use JavaMail 1.6.3 or newer, the
+	artifactId will need to be changed as above.  The APIs are
+	otherwise compatible.
+
+- Java module system name changed.
+
+	Experimental support for the Java platform module system was
+	added in the previous release using the name "java.mail" for the
+	API module (mailapi.jar).  In this release the name of that
+	module is changed to "jakarta.mail".
+
+
+-- JavaMail 1.6.2 --
+
+- Protocol providers now loaded using the java.util.ServiceLoader mechanism.
+
+	All the protocol providers included with JavaMail, as well as
+	third party providers, can use the Service Loader mechanism to
+	configure the provider, instead of using the
+	META-INF/javamail.default.providers or META-INF/javamail.providers
+	file.  These JavaMail provider configuration files are only read
+	if the ServiceLoader fails to find a provider that supports the
+	requested protocol.  This may have the effect of changing the order
+	in which providers are loaded, which should only be an issue if
+	multiple providers support the same protocol name.
+
+
+-- JavaMail 1.6.1 --
+
+- com.sun.mail.util.logging no longer included in mailapi.jar.
+
+	If you're using mailapi.jar and depend on the com.sun.mail.util.logging
+	classes, you'll need to include the logging-mailhandler.jar file in
+	your application class path.
+
+
+
+-- JavaMail 1.6 --
+
+- Public API updated to use generics
+
+	Methods in the following classes have been updated to use generics.
+	This change should be binary compatible but may require source
+	code changes in applications.
+
+		javax.mail.Multipart
+		javax.mail.Part
+		javax.mail.Service
+		javax.mail.internet.InternetHeaders
+		javax.mail.internet.MimeBodyPart
+		javax.mail.internet.MimeMessage
+		javax.mail.internet.MimePart
+		javax.mail.internet.ParameterList
+
+- JDK 1.7 or newer required
+
+	The JavaMail reference implementation now requires JDK 1.7
+	or newer.  It is expected that the large majority of users
+	are already using JDK 1.7 or newer.
+
+- MailDateFormat changes
+
+	The following changes to MailDateFormat may impact applications:
+
+	* parse(String) now throws a ParseException on invalid input instead
+	  of returning null, to conform to the DateFormat contract
+	* the following methods now throw UnsupportedOperationException
+	  to prevent tampering with MailDateFormat's internals:
+
+		get2DigitYearStart
+		applyLocalizedPattern
+		applyPattern
+		set2DigitYearStart
+		setDateFormatSymbols
+
+- method added to UIDFolder interface
+
+	The UIDFolder interface has a new getUIDNext() method.  Any
+	classes implementing the UIDFolder interface will need to add
+	this method.  The IMAPFolder class implements the UIDFolder
+	interface and has provided this method for some time.
+
+- YoungerTerm, OlderTerm, and ModifiedSinceTerm fall back to local searching
+
+	In general, SearchTerms that are not understood by the IMAP
+	provider or not supported by the IMAP server fall back to doing
+	local searching.  The IMAP-specific YoungerTerm, OlderTerm, and
+	ModifiedSinceTerm were instead throwing an exception if the server
+	didn't support the extension that the search term depended on.
+	This has been fixed to fall back to doing the search locally.
+	This behavior can be changed by setting the property
+	"mail.imap.throwsearchexception" to "true" to cause an exception
+	to be thrown if the server can't perform the search.
+
+
+
+-- JavaMail 1.5.6 --
+
+- finalizers close sockets abruptly
+
+	It's important for finalizers to close an open socket
+	connection to prevent file descriptor leaks.  Previously the
+	finalizers for the IMAP and POP3 providers would try to close
+	the connection cleanly, which could result in a timeout waiting
+	for the server.  They now close the connection without
+	performing any socket I/O, which may result in an unclean
+	shutdown when seen from the server.  Applications should always
+	close Stores and Folders when done with them to avoid the need
+	for the finalizer to do this cleanup.  The Session property
+	"mail.<protocol>.finalizecleanclose" can be set to "true" to
+	force the connection to be closed cleanly in the finalizer.
+
+- InternetAddress.getLocalAddress uses canonical host name
+
+	The InternetAddress.getLocalAddress method now uses the
+	java.net.InetAddress.getCanonicalHostName method if neither the
+	"mail.from" nor "mail.host" properties have been set.  The System
+	property "mail.mime.address.usecanonicalhostname" can be set to
+	"false" to revert to the previous behavior.
+
+- SMTPTransport.sasllogin no longer public
+
+	The SMTPTransport.sasllogin method should never have been
+	declared public.  It's used internally when SASL authentication
+	is requested; applications should not use the method directly.
+
+
+
+-- JavaMail 1.5.4 --
+
+- Idlemanager.watch no longer throws IOException
+
+	The IdleManager.watch method was declared to throw IOException,
+	but never actually threw it.  The declaraction has been changed,
+	which will cause a source incompatibility for code expecting to
+	catch IOException when calling the watch method.
+
+
+
+-- JavaMail 1.5.3 --
+
+- Date search terms result in wrong greater-than SEARCH commands for IMAP
+
+	The IMAP SentDateTerm and ReceivedDateTerm greater-than comparison
+	was actually doing a greater-than-or-equal-to comparison.  This
+	has been fixed in the 1.5.3 release, but programs that accidentally
+	relied on the old behavior may get different results.
+
+- Authenticator is now synchronized
+
+	The call to the Authenticator's getPasswordAuthentication method
+	is now synchronized so that the state stored in the Authenticator
+	is safe if multiple threads try to use the Authenticator
+	simultaneously.  If the application's getPasswordAuthentication
+	method blocks, it will prevent other threads in the same
+	Session from using the Authenticator.
+
+
+
+-- JavaMail 1.5 --
+
+- RFC 2231 parameter encoding/decoding enabled by default
+
+	The System properties "mail.mime.decodeparameters" and
+	"mail.mime.encodeparameters" now default to true instead of false.
+	Now that most mailers support RFC 2231, this is expected to
+	increase interoperability, although in rare cases, and especially
+	when dealing with older mailers, this may cause problems.
+	Parameters may appear encoded, and with a different name,
+	than what the receiver is expecting.
+
+- ContentType.toString and ContentDisposition.toString never return null
+
+	These methods were previously documented to return null in
+	error cases when the fields of the class were not valid.
+	These methods now return an empty string in these cases, to
+	be consistent with the general contract of Object.toString.
+
+- additional classes, methods, and fields
+
+	JavaMail 1.5 adds classes to existing packages, methods to
+	existing classes, and fields to existing classes.  All of
+	these changes have the potential to break source compatibility
+	for applications using the JavaMail API.
+
+- JDK 1.5 or newer required
+
+	The JavaMail reference implementation now requires JDK 1.5
+	or newer.  It is expected that the large majority of users
+	are already using JDK 1.6 or newer.
+
+- protected fields in final classes in javax.mail.search made private
+
+	Some of the final classes in the javax.mail.search package
+	contained protected fields.  Since these classes were final
+	and couldn't be subclassed, the "protected" access qualifier
+	had no effect.  These fields are now private.  It's hard to
+	imagine how this change could impact any applications other
+	than perhaps those using reflection to access these classes.
+
+- MailHandler default attachment filters
+
+	The default used for attachment filters has changed from allow
+	all log records (null) to instead use body filter assigned to
+	getFilter().  This is a safer choice as it maintains any
+	existing filter rules when attachments are added.
+
+- MailHandler default 'TO' address recipient
+
+	If the 'TO' address key is not specified then the local address
+	is used.  The previous behavior was to omit the 'TO' address
+	header.  This can break configurations that are only sending to
+	a set of 'CC' or 'BCC' addresses.  To revert this behavior
+	simply specify a 'TO' address key with an empty address value.
+
+- MailHandler intern of error manager, filters, and formatters.
+
+	When MailHandler is created, the error manager, filters, and
+	formatters are checked for equality.  When equal objects are
+	found they are replaced with a single representation.  This
+	allows objects of the same type to share state for improving
+	performance, adding functionality, etc.  To revert to the
+	previous behavior the error manager, filters, and formatters
+	must retain or be wrapped with objects that retain the identity
+	equals and hash code to prevent interning.
+
+
+
+-- JavaMail 1.4.4 --
+
+- authorization ID may be null
+
+	The IMAP and SMTP providers support a
+	"mail.<protocol>.sasl.authorizationid" property that allows you
+	to specify an authorization ID separately from the authentication
+	ID that's specified as the user name in properties or in the connect
+	method.  The PLAIN authentication method, and some SASL authentication
+	methods support use of the separate authorization ID.  In previous
+	releases, if the authorization ID was not specified, it defaulted
+	to the authentication ID (user name).  This can cause problems if
+	the server doesn't allow an authorization ID even though the SASL
+	method allows specifying one.  In this release, if no authorization
+	ID is specified, null is passed to the SASL method.  If this causes
+	problems for a SASL method implementation or a server, the
+	"mail.<protocol>.sasl.authorizationid" property should be set to
+	the user name used for authentication.
+
+
+
+-- JavaMail 1.4.3 --
+
+- SMTPTransport.isConnected behavior changed
+
+	The SMTPTransport.isConnected method uses the SMTP NOOP command
+	to determine if the server is still alive.  Because many older
+	servers were broken in various ways, any response (other than
+	the 421 "connection timed out" response) was considered a
+	successful response and the server was considered to be still
+	alive.  Unfortunately, Microsoft Exchange has a bug that causes
+	it to return a response code of 451 when it times out a connection
+	instead of the expected 421 response code.  SMTPTransport.isConnected
+	now considers only a 250 response code to indicate success, per
+	the SMTP spec.  The old behavior can be restored by setting the
+	new mail.smtp.noop.strict property to false.
+
+
+
+-- JavaMail 1.4.2 --
+
+- mail.smtp.quitwait default changed
+
+	In previous releases, JavaMail would drop the SMTP connection
+	to the server immediately after sending the QUIT command.
+	This violates the SMTP spec.  The property "mail.stmp.quitwait"
+	controls this behavior.  In this release the default behavior
+	(if the property isn't specified) has changed so that JavaMail
+	will wait for the response from the server before dropping the
+	connection.  In some cases, with some servers, this additional
+	wait time may be noticeable.
+
+
+- MessagingException.getMessage output changed
+
+	The MessagingException class, which is the base class for all
+	JavaMail exceptions, has been retrofitted to support the
+	exception chaining feature added to the java.lang.Throwable
+	class in J2SE 1.4.  The visible impact of this change is that
+	the String returned by the getMessage method will only return
+	the immediate message for the top level exception, instead of
+	including messages for all nested exceptions.
+
+
+- connection timeouts no longer use a thread
+
+	To support connection timeouts in older versions of the JDK,
+	it was necessary for JavaMail to create a thread to make the
+	connection, so that it could interrupt and abandon that
+	thread if the connection timeout expired.  J2SE 1.4 added
+	the ability to specify the connection timeout directly, so
+	JavaMail no longer uses an additional thread for this purpose.
+
+
+- ByteArrayDataSource now part of javax.mail.util
+
+	The ByteArrayDataSource class, which was previously included
+	in source form in the demo directory, is now a standard part
+	of the JavaMail API in the new javax.mail.util package.
+	Applications that are modified to make use of classes in the
+	new package, and that also included a copy of the demo version
+	of ByteArrayDataSource, should be careful to avoid potential
+	name conflicts between these two classes.
+
+
+- mail.SSLSocketFactory.class property no longer supported
+
+	The JavaMail implementation previously used this undocumented
+	property to locate the SSLSocketFactory from which it would
+	create SSLSockets when making an SSL or TLS connection.  This
+	property is no longer used.  The standard
+	javax.net.ssl.SSLSocketFactory is used instead, which supports
+	a standard way of overriding the choice of default SSLSocketFactory.
+	See the SSLSocketFactory javadocs for details.  Most applications
+	should never need to override the default SSLSocketFactory.
+
+
+- Quota class moved from com.sun.mail.imap to javax.mail
+
+	The new Quota APIs in JavaMail are taken directly from the old
+	IMAP-specific classes in the com.sun.mail.imap package.  If you've
+	been using these classes, you'll need to update your application
+	to use the new classes in the javax.mail package.
+
+
+- getProtocol method removed from com.sun.mail.imap.IMAPFolder
+
+	The getProtocol method returns an instance of IMAPProtocol.
+	This was originally intended to allow applications to
+	experiment with extending the IMAP protocol support to use IMAP
+	commands not directly implemented by the IMAP protocol
+	provider.  Unfortunately, to safely use the IMAPProtocol
+	object, you need to obey the locking requirements of the
+	IMAPFolder object, and there's no way to do that from outside
+	the IMAPFolder object.  The doCommand method was added to
+	IMAPFolder to resolve this problem.  Now, with the introduction
+	of IDLE support to the IMAP protocol provider, it's critical to
+	obey the locking requirements.  To prevent mistakes, the old,
+	unsafe, getProtocol method has been removed.  Applications
+	should use the doCommand method for simple IMAP extensions.
+	Use of more complex IMAP extensions may require modification
+	of the IMAP protocol provider.
diff --git a/doc/release/IssueMap.txt b/doc/release/IssueMap.txt
new file mode 100644
index 0000000..960627d
--- /dev/null
+++ b/doc/release/IssueMap.txt
@@ -0,0 +1,261 @@
+#
+# This file is the mapping from old java.net issue numbers to
+# new github issue numbers.  Commit messages from before the migration
+# to github will reference these older issue numbers.
+#
+189 1
+202 2
+965 3
+1207 4
+1343 5
+1345 6
+2728 7
+2986 8
+3155 9
+3442 10
+3444 11
+3539 12
+3565 13
+3566 14
+3815 15
+3983 16
+4002 17
+4065 18
+4296 19
+4355 20
+4448 21
+4511 22
+4579 23
+4581 24
+4583 25
+4743 26
+4753 27
+4906 28
+5086 29
+5090 30
+5218 31
+5233 32
+5257 33
+5285 34
+5300 35
+5681 36
+5682 37
+5683 38
+5684 39
+5685 40
+5686 41
+5687 42
+5688 43
+5689 44
+5690 45
+5691 46
+5692 47
+5693 48
+5694 49
+5713 50
+5743 51
+5769 52
+5770 53
+5816 54
+5818 55
+5819 56
+5820 57
+5821 58
+5822 59
+5829 60
+5830 61
+5847 62
+5853 63
+5861 64
+5896 65
+5901 66
+5924 67
+5925 68
+5933 69
+5934 70
+5978 71
+5987 72
+5989 73
+6004 74
+6069 75
+6072 76
+6074 77
+6102 78
+6104 79
+6108 80
+6121 81
+6125 82
+6137 83
+6141 84
+6143 85
+6160 86
+6161 87
+6181 88
+6201 89
+6203 90
+6207 91
+6208 92
+6216 93
+6238 94
+6260 95
+6261 96
+6274 97
+6275 98
+6276 99
+6277 100
+6278 101
+6279 102
+6280 103
+6281 104
+6283 105
+6324 106
+6325 107
+6326 108
+6327 109
+6328 110
+6329 111
+6330 112
+6336 113
+6348 114
+6352 115
+6353 116
+6361 117
+6365 118
+6366 119
+6367 120
+6368 121
+6379 122
+6407 123
+6430 124
+6452 125
+6467 126
+6496 127
+6498 128
+6526 129
+6528 130
+6535 131
+6551 132
+6552 133
+6565 134
+6568 135
+6572 136
+6585 137
+6638 138
+6644 139
+6650 140
+6657 141
+6662 142
+6667 143
+6668 144
+6687 145
+6703 146
+6712 147
+6718 148
+6719 149
+6755 150
+6762 151
+6765 152
+6767 153
+6772 154
+6778 155
+6787 156
+6804 157
+6817 158
+6823 159
+6824 160
+6827 161
+6840 162
+6844 163
+6850 164
+6852 165
+6860 166
+6863 167
+6886 168
+6905 169
+6907 170
+6913 171
+6938 172
+6943 173
+6949 174
+6954 175
+6964 176
+6965 177
+6966 178
+6973 179
+6989 180
+6997 181
+7009 182
+7010 183
+7011 184
+7014 185
+7019 186
+7026 187
+7027 188
+7028 189
+7030 190
+7035 191
+7042 192
+7051 193
+7052 194
+7075 195
+7083 196
+7088 197
+7090 198
+7091 199
+7092 200
+7094 201
+7097 202
+7104 203
+7118 204
+7140 205
+7150 206
+7151 207
+7156 208
+7182 209
+7238 210
+7332 211
+7355 212
+7356 213
+7371 214
+7378 215
+7471 216
+7472 217
+7483 218
+7506 219
+7512 220
+7513 221
+7529 222
+7536 223
+8387 224
+8389 225
+8398 226
+8399 227
+8403 228
+8404 229
+8405 230
+8408 231
+8415 232
+8420 233
+8422 234
+8428 235
+8435 236
+8486 237
+8487 238
+8492 239
+8540 240
+8550 241
+8558 242
+8562 243
+8568 244
+8583 245
+8621 246
+8748 247
+8810 248
+8822 249
+8833 250
+8846 251
+9169 252
+9418 253
+10909 254
+11057 255
+19563 256
diff --git a/doc/release/JavaWebServer.html b/doc/release/JavaWebServer.html
new file mode 100644
index 0000000..9c34bb0
--- /dev/null
+++ b/doc/release/JavaWebServer.html
@@ -0,0 +1,58 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="JavaSoftware">
+   <meta name="GENERATOR" content="Mozilla/4.61 [en] (WinNT; U) [Netscape]">
+   <title>JavaWebServer</title>
+</head>
+<body>
+If you don't already have Java Web Server installed and running,
+you'll need to download it
+<br>from the&nbsp; <a href="http://www.sun.com/software/jwebserver/index.html">Java
+Web Server</a>&nbsp; product page and install it. We tested the JavaMailServlet
+with
+<br>these configurations.
+<p>(on Solaris)
+<blockquote>JavaWebServer 2.0 (binary)</blockquote>
+(on NT)
+<blockquote>JavaWebServer 2.0 (binary)</blockquote>
+To run the JavaMailServlet, you must add the JavaMail and JavaBeans Activation
+Framework
+<br>jar files to the CLASSPATH environment variable (this is documented
+in the JavaMail README file
+<br>and additional Windows NT information is provided <a href="classpath-NT.html">here</a>).
+<p>Once this is done, restart the web server.
+<br>&nbsp;
+<p>Additional Notes
+<blockquote>If you run JavaWebServer 2.0 with the Java Runtime Environment
+(JRE) supplied with
+<br>JavaWebServer, no additional security policy is necessary. The security
+policy file
+<br>(i.e. jserv.policy) supplied with the JRE contains the necessary permissions
+for local servlets.</blockquote>
+
+<blockquote>If you run JavaWebServer 2.0 with another JRE (with an installed
+JDK for example), you
+<br>must add the necessay security policy for running under a SecurityManager.</blockquote>
+
+</body>
+</html>
diff --git a/doc/release/NOTES.txt b/doc/release/NOTES.txt
new file mode 100644
index 0000000..637a2dd
--- /dev/null
+++ b/doc/release/NOTES.txt
@@ -0,0 +1,206 @@
+			     NOTES
+			     =====
+
+		    JavaMail(TM) API ${mail.version} release
+		    ------------------------------
+
+Welcome to the ${mail.version} release of the JavaMail API implementation. 
+
+Please refer to CHANGES.txt for a list of the changes since the 
+previous release.
+
+Please see the FAQ at https://eclipse-ee4j.github.io/javamail/FAQ
+
+Protocol Providers
+------------------
+
+The JavaMail API jar file "jakarta.mail.jar" includes the full JavaMail
+API implementation and the Sun protocol providers - IMAP, SMTP, and
+POP3.  The simplest way to use the JavaMail API is to just use the
+jakarta.mail.jar file and ignore the other jar files in this package.
+
+In some cases it may be desirable to minimize the size of the JavaMail
+API code used by an application (e.g., in a microservice).
+In this case you might want to include the "mailapi.jar" file, which
+includes *no* protocol providers, along with just the jar file for the
+protocol provider you need.  For example, a microservice that only needs
+to send mail could use the "mailapi.jar" file and the "smtp.jar" file.
+
+An important note when using the separate protocol provider jar files:
+
+-  You can't mix and match the Sun protocol providers between different
+   releases of the JavaMail API.  The Sun protocol providers depend on
+   implementation-specific utility APIs within the mailapi.jar file.
+   (Third party protocol providers that don't depend on these APIs
+   should work fine.)
+
+
+NOTE: The Sun protocol provider documentation is included in the javadocs
+      for the JavaMail API.  This documentation describes how to
+      use features of the Sun protocol providers to directly access
+      some features of the SMTP, IMAP, and POP3 protocols that are
+      not otherwise supported by the standard JavaMail API.
+
+
+Gmail IMAP Provider
+-------------------
+
+This release includes an EXPERIMENTAL Gmail IMAP provider.
+Normal use of Gmail is handled by the standard "imap" protocol
+provider, but the new "gimap" protocol provider supports additional
+Gmail-specific non-standard features.  See the javadocs for the
+com.sun.mail.gimap package for details.  Note that the gimap.jar file
+needs to be added to your CLASSPATH to use this new provider.
+
+
+SASL Support
+------------
+
+On systems that support the Java SASL API (javax.security.sasl, JSR-28),
+such as J2SE 5.0 and later, the IMAP provider can use the SASL API to
+find an appropriate authentication mechanism.  The SASL API also allows
+you to plug in support for custom authentication mechanisms.  See The
+Java SASL API Programming and Deployment Guide at
+https://docs.oracle.com/javase/8/docs/technotes/guides/security/sasl/sasl-refguide.html
+for details on developing custom SASL mechanisms.  See the javadocs for
+the com.sun.mail.imap package for the properties required to enable and
+configure SASL support.
+
+
+DSN Support
+-----------
+
+This release of JavaMail includes EXPERIMENTAL support for creating
+and parsing Delivery Status Notifications, as defined by RFC 3462
+and RFC 3464.  To make use of this support you need to include dsn.jar
+in your CLASSPATH along with jakarta.mail.jar.  See the javadocs for the
+com.sun.mail.dsn package for more details.
+
+The DSN package also provides support for creating and parsing Message
+Disposition Notifications, as defined by RFC 3798.
+
+The APIs unique to this package should be considered EXPERIMENTAL.
+They may be changed in the future in ways that are incompatible with
+applications using the current APIs.
+
+
+NTLM Support
+------------
+
+This release of JavaMail includes EXPERIMENTAL support for the
+Microsoft NTLM authentication mechanism used by Exchange.  See the
+file NTLMNOTES.txt for details.
+
+
+OSGi Support
+------------
+
+The JavaMail jar files are now OSGi bundles.  Please let us know
+of any problems using JavaMail with OSGi.
+
+
+How to submit bug reports
+-------------------------
+
+If you've found a bug, or if you just need help figuring out how to use
+the JavaMail API, please try to include the following information in
+your message to us:
+
+    - a program or code snippet that shows the problem
+    - the platform you are using
+    - the mail server (vendor name, version number) you are using
+    - your environment variable settings
+    - a stack trace, if appropriate
+    - a protocol trace, after turning on session debugging, if appropriate
+
+Most of the problems reported to us fail to include enough of the above
+information to allow us to diagnose your problem.  It will save you and
+us time if you include this information in your first message to us.
+
+By far the most common problems we see are:
+
+Your problem:	Something doesn't work right when talking to my mail server.
+Our response:	Turn on session debugging and send us the protocol trace.
+		See the demo program documentation for how to turn on
+		session debugging for the demo programs.  In your own
+		program, call "session.setDebug(true);".
+
+Your problem:	javax.mail or javax.activation classes not found when compiling.
+Our response:	You didn't set CLASSPATH correctly to find jakarta.mail.jar and
+		jakarta.activation.jar.  See README.txt.
+
+Your problem:	NoSuchProviderException - No such provider for rfc822.
+Our response:	You unjar'ed jakarta.mail.jar.  Don't.
+
+Your problem:	How do I create a message with an attachment?
+Our response:	Create a message with a MimeMultipart content.  See the
+		sendfile.html and msgmultisendsample.java demo programs.
+
+Please check the FAQ at https://eclipse-ee4j.github.io/javamail/FAQ
+before submitting bug reports.
+
+Send your bug reports to:
+
+	javamail_ww@oracle.com
+
+
+
+
+Servers tested with:
+--------------------
+
+  The IMAP implementation works with IMAP4 and IMAP4rev1 servers.
+  The current release has been tested with:
+	Oracle Beehive
+	Oracle Communications Messaging Server 8.0
+	UW IMAP4 server version 2003.339
+	Cyrus IMAP4 server version 1.6.19
+	Microsoft Exchange 2010
+	Microsoft Exchange 2013
+	Microsoft Exchange 2016
+
+  Previous releases have been tested with:
+	Sun Java System Messaging Server version 5.2
+	Sun Java System Messaging Server version 6.3
+	Sun Java System Messaging Server version 7.0
+	Sun Internet Mail Server version 2.0, 3.2, and 4.0
+	Netscape Messaging Server version 3.01 and 4.1
+	Microsoft MCIS Mail Server
+	Lotus Notes
+	Software.com IMAP server
+	Qualcomm Worldmail
+
+  The current release of the SMTP implementation has been tested with:
+	Sendmail version 8.13.8
+	Oracle Beehive
+	Oracle Communications Messaging Server 8.0
+	Microsoft Exchange 2010
+	Microsoft Exchange 2013
+	Microsoft Exchange 2016
+
+  Previous releases have been tested with:
+	Sendmail version 8.6 and 8.9.1
+	Sun Java System Messaging Server version 5.2
+	Sun Java System Messaging Server version 6.3
+	Sun Java System Messaging Server version 7.0
+	Sun Internet Mail Server version 3.2 and 4.0
+	Netscape Messaging Server version 3.01 and 4.1
+	Microsoft Exchange
+	Microsoft MCIS Mail Server
+	Qualcomm Worldmail
+
+How to give feedback
+--------------------
+
+Please send your feedback to this email-address:
+
+	javamail_ww@oracle.com
+
+Check out our website at https://eclipse-ee4j.github.io/javamail/.
+
+You can also find help on StackOverflow:
+
+	https://stackoverflow.com/questions/tagged/javamail
+
+
+------------------------------------------------------------------
diff --git a/doc/release/NTLMNOTES.txt b/doc/release/NTLMNOTES.txt
new file mode 100644
index 0000000..87fce72
--- /dev/null
+++ b/doc/release/NTLMNOTES.txt
@@ -0,0 +1,28 @@
+	Notes for use of NTLM authentication support with JavaMail
+	----------------------------------------------------------
+
+Thanks to the efforts of Luis Serralheiro, JavaMail now suports the use
+of Microsoft's proprietary NTLM authentication mechanism.  This support
+within JavaMail is now derived from the NTLM support in the JDK and
+included directly in JavaMail, with no external dependencies.
+
+This release of JavaMail was tested with Microsoft Exchange 5.5 and 2007.
+
+The SMTP and IMAP providers support the use of NTLM authentication.
+The following properties can be used to configure the NTLM support:
+
+mail.<protocol>.auth.ntlm.domain
+	The NTLM authentication domain.
+
+mail.<protocol>.auth.ntlm.flags
+	NTLM protocol-specific flags.  (not currently used)
+	See http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags for details.
+
+
+NOTE:	This capability is very new and has NOT been thoroughly tested.
+	Please send any feedback or bug reports to us at javamail_ww@oracle.com.
+
+WARNING: This support, and the APIs and properties used to control it,
+should be considered EXPERIMENTAL.  They may be changed in the future
+in ways that are incompatible with applications using the current APIs
+and properties.
diff --git a/doc/release/README.txt b/doc/release/README.txt
new file mode 100644
index 0000000..7f20697
--- /dev/null
+++ b/doc/release/README.txt
@@ -0,0 +1,175 @@
+			README
+			======
+
+	    JavaMail(TM) API ${mail.version} release
+	    ------------------------------
+
+Welcome to the JavaMail API ${mail.version} release!  This release includes
+versions of the JavaMail API implementation, IMAP, SMTP, and POP3
+service providers, some examples, and documentation for the JavaMail
+API.
+
+Please see the FAQ at https://eclipse-ee4j.github.io/javamail/FAQ
+
+JDK Version notes
+-----------------
+
+The JavaMail API supports JDK 1.7 or higher.  Note that we have
+currently tested this implementation with JDK 1.7 and 1.8.
+
+While JavaMail will work with JAF 1.0.2, we recommend the use of JAF 1.1
+or newer.  JAF 1.2.1 is currently the newest version.  Note that JAF 1.1
+is included in JDK 1.6 and JAF 1.1.1 is included in JDK 1.6.0_10 and
+later.  JAF 1.2.0 is included but hidden in JDK 9 and has been removed
+from JDK 11.  JAF 1.2.1 is available separately (see below).
+
+
+Protocols supported
+-------------------
+
+This release supports the following Internet standard mail protocols:
+
+    IMAP - a message Store protocol, for reading messages from a server
+    POP3 - a message Store protocol, for reading messages from a server
+    SMTP - a message Transport protocol, for sending messages to a server
+
+The following table lists the names of the supported protocols (as used
+in the JavaMail API) and their capabilities:
+
+	Protocol	Store or	Uses	Supports
+	Name		Transport?	SSL?	STARTTLS?
+	-------------------------------------------------
+	imap		Store		No	Yes
+	imaps		Store		Yes	N/A
+	gimap		Store		Yes	N/A
+	pop3		Store		No	Yes
+	pop3s		Store		Yes	N/A
+	smtp		Transport	No	Yes
+	smtps		Transport	Yes	N/A
+
+See our web page at https://eclipse-ee4j.github.io/javamail/
+for the latest information on third party protocol providers.
+
+
+Download
+--------
+
+See the JavaMail project page to download this release.
+
+	https://eclipse-ee4j.github.io/javamail/
+
+
+Requirements
+------------
+
+Note that the JavaMail API requires the JavaBeans(TM) Activation
+Framework package to be installed as well if you're using or JDK 11 or later.
+
+Download the latest version of the JavaBeans Activation Framework from
+
+	https://github.com/eclipse-ee4j/jaf/releases
+
+and install it in a suitable location.
+
+
+Installation
+------------
+
+  UNIX/Linux
+  ----------
+
+  1. Download the jakarta.mail.jar file from the JavaMail project website.
+     https://github.com/eclipse-ee4j/javamail/releases
+
+  2. Set your CLASSPATH to include the "jakarta.mail.jar" file obtained from
+     the download, as well as the current directory.
+
+     Assuming you have downloaded jakarta.mail.jar to the /u/me/download/
+     directory, the following would work:
+
+      export CLASSPATH=$CLASSPATH:/u/me/download/jakarta.mail.jar:.
+
+    (Don't forget the trailing "." for the current directory.)
+    Also, if you're using JDK 1.5, include the "activation.jar" file that you
+    obtained from downloading the JavaBeans Activation Framework.  For example:
+
+      export CLASSPATH=$CLASSPATH:/u/me/download/activation/activation.jar
+
+  3. Download the javamail-samples.zip file from the project website.
+     https://github.com/eclipse-ee4j/javamail/releases
+
+  4. Compile any sample program using your Java compiler. For example:
+
+      javac msgshow.java
+
+  5. Run the sample program.  The '-' option lists the required and optional
+     command-line options to successfully run any sample.  For example:
+
+      java msgshow -
+
+    lists the available options.  And
+
+      java msgshow -T imap -H <mailserver> -U <username> -P <passwd> -f INBOX 5
+
+    uses the IMAP protocol to display message number 5 from your INBOX.
+
+  (Additional instructions on how to run the simple mail reader sample
+  and servlet sample are provided in client/README.txt and servlet/README.txt,
+  respectively.)
+
+
+  Windows
+  -------
+
+  1. Download the jakarta.mail.jar file from the JavaMail project website.
+     https://github.com/eclipse-ee4j/javamail/releases
+
+  2. Set your CLASSPATH to include the "jakarta.mail.jar" file obtained from
+     the download, as well as the current directory.
+
+     Assuming you have downloaded jakarta.mail.jar to the /u/me/download/
+     directory, the following would work:
+
+      set CLASSPATH=%CLASSPATH%;c:\download\jakarta.mail.jar;.
+
+    (Don't forget the trailing "." for the current directory.)
+    Also, if you're using JDK 1.5, include the "activation.jar" file that you
+    obtained from downloading the JavaBeans Activation Framework.  For example:
+
+      set CLASSPATH=%CLASSPATH%;c:\download\activation\activation.jar
+
+  3. Download the javamail-samples.zip file from the project website.
+     https://github.com/eclipse-ee4j/javamail/releases
+
+  4. Compile any sample program using your Java compiler. For example:
+
+      javac msgshow.java
+
+  5. Run the sample program.  The '-' option lists the required and optional
+     command-line options to successfully run any sample.  For example:
+
+      java msgshow -
+
+    lists the available options.  And
+
+      java msgshow -T imap -H <mailserver> -U <username> -P <passwd> -f INBOX 5
+
+    uses the IMAP protocol to display message number 5 from your INBOX.
+
+  (Additional instructions on how to run the simple mail reader sample
+  and servlet sample are provided in client/README.txt and servlet/README.txt,
+  respectively.)
+
+
+Problems?
+---------
+
+The JavaMail FAQ at https://eclipse-ee4j.github.io/javamail/FAQ
+includes information on protocols supported, installation problems,
+debugging tips, etc.
+
+See the NOTES.txt file for information on how to report bugs.
+
+Enjoy!
+
+The JavaMail API Team
diff --git a/doc/release/SSLNOTES.txt b/doc/release/SSLNOTES.txt
new file mode 100644
index 0000000..93e2eac
--- /dev/null
+++ b/doc/release/SSLNOTES.txt
@@ -0,0 +1,311 @@
+		Notes for use of SSL with JavaMail
+		----------------------------------
+
+JavaMail now supports accessing mail servers over connections secured
+using SSL or TLS.  To simplify such access, there are two alternative
+approaches to enable use of SSL.
+
+First, and perhaps the simplest, is to set a property to enable use
+of SSL.  For example, to enable use of SSL for SMTP connections, set
+the property "mail.smtp.ssl.enable" to "true".
+
+Alternatively, you can configure JavaMail to use one of the SSL-enabled
+protocol names.  In addition to the non-SSL JavaMail protocols "imap",
+"pop3", and "smtp", the protocols "imaps", "pop3s", and "smtps" can
+be used to connect to the corresponding services using an SSL
+connection.
+
+In addition, the "imap" and "smtp" protocols support use of the
+STARTTLS command (see RFC 2487 and RFC 3501) to switch the connection
+to be secured by TLS.
+
+Use of the STARTTLS command is preferred in cases where the server
+supports both SSL and non-SSL connections.
+
+This SSL/TLS support in JavaMail works only when JavaMail is used on
+a version of J2SE that includes SSL support.  We have tested this
+support on J2SE 1.4 and newer, which include SSL support.  The
+SSL support is provided by the JSSE package, which is also available
+for earlier versions of J2SE.  We have not tested such configurations.
+
+-- STARTTLS support
+
+The STARTTLS support is available in the standard "imap" and "smtp"
+protocols, but must be enabled by setting the appropriate property,
+mail.imap.starttls.enable or mail.smtp.starttls.enable, to "true".
+When set, if the server supports the STARTTLS command, it will be
+used after making the connection and before sending any login
+information.
+
+
+-- Secure protocols
+
+When using the new protocol names, configuration properties must also use
+these protocol names.  For instance, set the property "mail.smtps.host"
+to specify the host name of the machine to connect to when using the
+"smtps" protocol for SMTP over SSL.  Similarly, to set the IMAP protocol
+timeout when using the "imaps" protocol for IMAP over SSL, set the property
+"mail.imaps.timeout".  See the package documentation for the different
+protocol packages for the list of available properties, which are
+always set using property names of the form mail.<protocol>.<property>.
+
+The Transport.send method will use the default transport protocol,
+which remains "smtp".  To enable SMTP connections over SSL, set the
+"mail.smtp.ssl.enable" property to "true".  This is usually the easiest
+approach.
+
+Alternatively, to change the default transport protocol 
+returned by the Session.getTransport() method to SMTP over SSL, set
+the property "mail.transport.protocol" to "smtps".  To change the
+transport used for internet addresses (as returned by the
+Session.getTransport(Address) method, and used by the Transport.send
+method), use
+
+	session.setProtocolForAddress("rfc822", "smtps");
+
+
+-- Trusted Certificates
+
+To establish an SSL/TLS connection, the JavaMail client must be able
+to verify that the security certificate presented by the server
+it is connecting to is "trusted" by the client.  Trusted certificates
+are maintained in a Java keystore file on the client.  The J2SE
+SDK "keytool" command is used to maintain the keystore file.
+
+There are two common approaches for verifying server certificates.
+The first approach is probably most common for servers accessible to
+partners outside a company.  The second approach is probably most
+common for servers used within a company.
+
+1. Server certificates may be signed be a well known public
+   Certificate Authority.  The default Java keystore file contains
+   the public keys of well known Certificate Authorities and can
+   verify the server's certificate by following the chain of
+   certificates signing the server's certificate back to one of
+   these well known CA certificates.
+
+   In this case the client doesn't need to manage certificates
+   explicitly but can just use the default keystore file.
+
+2. Server certificates may be "self-signed".  In this case there is
+   no chain of signatures to use in verifying the server's certificate.
+   Instead, the client will need the server's certificate in the
+   client's keystore file.  The server's certificate is imported into
+   the keystore file once, using the keytool command, and after that
+   is used to verify connections to the server.  A single keystore file
+   may contain certificates of many servers.
+
+   In this case the client will need to set the appropriate System
+   properties to point to the client's keystore file containing the
+   trusted certificate.  These properties can be set when invoking
+   the "java" command, or can be set programmatically.  For example,
+
+	java -Djavax.net.ssl.trustStore=$HOME/.keystore ...
+
+   See the JSSE Reference Guide for details:
+   http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#CustomizingStores
+
+
+-- Server Identity Check
+
+RFC 2595 specifies addition checks that must be performed on the
+server's certificate to ensure that the server you connected to is
+the server you intended to connect to.  This reduces the risk of
+"man in the middle" attacks.  For compatibility with earlier releases
+of JavaMail, these additional checks are disabled by default.  We
+strongly recommend that you enable these checks when using SSL.  To
+enable these checks, set the "mail.<protocol>.ssl.checkserveridentity"
+property to "true".
+
+
+-- Socket Factories
+
+In earlier releases it was necessary to explicitly set a socket
+factory property to enable use of SSL.  In almost all cases, this
+is no longer necessary.  SSL support is built in.  However, there
+is one case where a special socket factory may be needed.
+
+JavaMail now includes a special SSL socket factory that can simplify
+dealing with servers with self-signed certificates.  While the
+recommended approach is to include the certificate in your keystore
+as described above, the following approach may be simpler in some cases.
+
+The class com.sun.mail.util.MailSSLSocketFactory can be used as a
+simple socket factory that allows trusting all hosts or a specific set
+of hosts.  For example:
+
+	MailSSLSocketFactory sf = new MailSSLSocketFactory();
+	sf.setTrustAllHosts(true);
+	// or
+	// sf.setTrustedHosts(new String[] { "my-server" });
+	props.put("mail.smtp.ssl.enable", "true");
+	// also use following for additional safety
+	//props.put("mail.smtp.ssl.checkserveridentity", "true");
+	props.put("mail.smtp.ssl.socketFactory", sf);
+
+Use of MailSSLSocketFactory avoids the need to add the certificate to
+your keystore as described above, or configure your own TrustManager
+as described below.
+
+
+-- Debugging
+
+Debugging problems with certificates and keystores can be difficult.
+The JSSE Reference Guide contains information on debugging utilities
+that can help.  See:
+http://download.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html#Debug
+
+There are some debugging options in the JDK that can help, depending
+on the sorts of problems you're having.  Setting the following system
+properties will produce additional debugging output:
+
+	java.security.debug=certpath
+	javax.net.debug=trustmanager 
+
+Set these on the command line when you run your program using, for example:
+
+	java -Djava.security.debug=certpath -Djavax.net.debug=trustmanager ...
+
+
+-- keytool Usage
+
+Given a certificate for the server as used in case #2 above, you can
+import this certificate into your Java keystore file using a command
+such as:
+
+	keytool -import -alias imap-server -file imap.cer
+
+The keytool command can also be used to generate a self-signed certificate
+that can be used by your mail server, if you're setting up your own server.
+Other utilities, such as those included with the OpenSSL package, can also
+be used to generate such certificates, and they can be imported into the
+Java keystore using keytool.
+
+For more information on using the keytool command, see the keytool
+reference pages at:
+http://download.oracle.com/javase/6/docs/technotes/guides/security/index.html
+
+
+-- Configuring Your Own Trust Manager
+
+When using SSL/TLS, it's important to ensure that the server you connect
+to is actually the server you expected to connect to, to prevent "man in
+the middle" attacks on your communication.  The recommended technique is
+to configure the Java keystore using one of the methods described above.
+If, for some reason, that approach is not workable, it's also possible
+to configure the SSL/TLS implementation to use your own TrustManager
+class to evaluate whether to trust the server you've connected to.
+
+The following "dummy" classes illustrate the framework necessary to create
+your own TrustManager implementation.
+
+First, a replacement for the standard SSLSocketFactory is needed, to allow
+you to specify which TrustManager to use:
+
+==> DummySSLSocketFactory.java <==
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.*;
+
+
+/**
+ * DummySSLSocketFactory
+ */
+public class DummySSLSocketFactory extends SSLSocketFactory {
+    private SSLSocketFactory factory;
+
+    public DummySSLSocketFactory() {
+	try {
+	    SSLContext sslcontext = SSLContext.getInstance("TLS");
+	    sslcontext.init(null,
+				 new TrustManager[] { new DummyTrustManager()},
+				 null);
+	    factory = (SSLSocketFactory)sslcontext.getSocketFactory();
+	} catch(Exception ex) {
+	    // ignore
+	}
+    }
+
+    public static SocketFactory getDefault() {
+	return new DummySSLSocketFactory();
+    }
+
+    public Socket createSocket() throws IOException {
+	return factory.createSocket();
+    }
+
+    public Socket createSocket(Socket socket, String s, int i, boolean flag)
+				throws IOException {
+	return factory.createSocket(socket, s, i, flag);
+    }
+
+    public Socket createSocket(InetAddress inaddr, int i,
+				InetAddress inaddr1, int j) throws IOException {
+	return factory.createSocket(inaddr, i, inaddr1, j);
+    }
+
+    public Socket createSocket(InetAddress inaddr, int i)
+				throws IOException {
+	return factory.createSocket(inaddr, i);
+    }
+
+    public Socket createSocket(String s, int i, InetAddress inaddr, int j)
+				throws IOException {
+	return factory.createSocket(s, i, inaddr, j);
+    }
+
+    public Socket createSocket(String s, int i) throws IOException {
+	return factory.createSocket(s, i);
+    }
+
+    public String[] getDefaultCipherSuites() {
+	return factory.getDefaultCipherSuites();
+    }
+
+    public String[] getSupportedCipherSuites() {
+	return factory.getSupportedCipherSuites();
+    }
+}
+
+
+Next you need the actual implementation of the TrustManager.  This dummy
+trust manager trusts anything.  THIS IS NOT SECURE!!!
+
+==> DummyTrustManager.java <==
+
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.X509Certificate;
+
+
+/**
+ * DummyTrustManager - NOT SECURE
+ */
+public class DummyTrustManager implements X509TrustManager {
+
+    public void checkClientTrusted(X509Certificate[] cert, String authType) {
+	// everything is trusted
+    }
+
+    public void checkServerTrusted(X509Certificate[] cert, String authType) {
+	// everything is trusted
+    }
+
+    public X509Certificate[] getAcceptedIssuers() {
+	return new X509Certificate[0];
+    }
+}
+
+Finally, you need to configure JavaMail to use your SSLSocketFactory.
+Set the appropriate protocol-specific property, e.g.,
+
+    props.setProperty("mail.imap.ssl.enable", "true");
+    props.setProperty("mail.imap.ssl.socketFactory.class",
+					"DummySSLSocketFactory");
+    props.setProperty("mail.imap.ssl.socketFactory.fallback", "false");
+    Session session = Session.getInstance(props, null);
+
+Similar properties would need to be set to use other protocols.
diff --git a/doc/release/Tomcat.html b/doc/release/Tomcat.html
new file mode 100644
index 0000000..8077875
--- /dev/null
+++ b/doc/release/Tomcat.html
@@ -0,0 +1,43 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="JavaSoftware">
+   <title>Tomcat</title>
+</head>
+<body>
+Tomcat is a complete web server written in Java that provides
+support for the Servlet and JSP specifications.
+Tomcat is developed as a part of the Apache
+<a href="http://jakarta.apache.org">Jakarta Project</a>.
+Both binaries and source are available from the Jakarta web site.
+<p>
+To run the JavaMailServlet, you must add the JavaMail and JavaBeans
+Activation Framework jar files to the <code>lib</code> directory
+under the directory in which you installed Tomcat.  This will
+cause Tomcat to include these jar files in its classpath automatically.
+(Some packages of Tomcat version 4 include JavaMail support.)
+Depending on the version of Tomcat used, the <code>mail.jar</code>
+and <code>activation.jar</code> files should be copied to the
+<code>lib</code> directory or to an appropriate subdirectory of the
+<code>lib</code> directory.
+</body>
+</html>
diff --git a/doc/release/classpath-NT.html b/doc/release/classpath-NT.html
new file mode 100644
index 0000000..c0057ae
--- /dev/null
+++ b/doc/release/classpath-NT.html
@@ -0,0 +1,64 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="JavaSoftware">
+   <meta name="GENERATOR" content="Mozilla/4.61 [en] (WinNT; U) [Netscape]">
+   <title>NT Classpath</title>
+</head>
+<body>
+Environment variables are administered on Windows NT from the
+<b>System
+Properties</b> Control Panel settings (Start->Settings->Control Panel;
+it's the <b>System</b> icon in the Control Panel). The <b>Environment</b>
+tab contains all of the System and User Variables.
+<p>System Variables exist for all users while User Variables only exist
+for the current user. Only users with Administrator privileges are able
+to define System Variables. For this example, environment variables will
+be defined as User Variables.
+<p>If this is the first time the CLASSPATH environment variable is being
+defined, select the first User Variables entry (its name/value pair will
+show up below the User Variables list). Enter 'CLASSPATH' in the 'Variable:'
+text edit field.
+<p>If the CLASSPATH environment variable has already been previously defined,
+simply select it from the User Variables list.
+<p>There are a couple of ways of adding something to an environment variable.
+It can be added directly, or through the use of a separately defined environment
+variable.
+<p>When the JavaMail and Java Activation Framework (JAF) jar files are
+added directly to the CLASSPATH, the Value field should contain (for example)
+".;D:\Java\javamail-1.2\mail.jar;D:\Java\jaf-1.0.1\activation.jar".
+<p><img SRC="images/direct-classpath.jpg" height=466 width=410>
+<br>&nbsp;
+<p>When the JavaMail and Java Activation Framework (JAF) jar files are
+added to the CLASSPATH using separate environment variables, the Value
+field should contain (for example) ".;%JAVAMAIL_JAR%;%JAF_JAR%"
+<br>&nbsp;
+<p><img SRC="images/indirect-classpath.jpg" height=466 width=410>
+<br>&nbsp;
+<p>Once you have finished updating the Value field, you must press the
+Set button. Then press OK. It is not necessary to reboot or log out, however
+these new settings will only be present in new Command Prompt windows.
+Any existing windows must be closed and reopened for the new settings to
+take effect. Any running Java applications which inherit the system CLASSPATH
+also need to be restarted.
+</body>
+</html>
diff --git a/doc/release/iPlanet.html b/doc/release/iPlanet.html
new file mode 100644
index 0000000..c420863
--- /dev/null
+++ b/doc/release/iPlanet.html
@@ -0,0 +1,50 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+   <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+   <meta name="Author" content="JavaSoftware">
+   <meta name="GENERATOR" content="Mozilla/4.61 [en] (WinNT; U) [Netscape]">
+   <title>iPlantet</title>
+</head>
+<body>
+If you don't already have iPlanet Web Server installed and running,
+you'll need to download it
+<br>from the <a href="http://www.iplanet.com/products/infrastructure/web_servers/iws/index.html">iPlanet
+Web Server</a>&nbsp; product page and install it. We tested the JavaMailServlet
+with
+<br>these configurations.
+<p>(on Solaris)
+<blockquote>&nbsp;iPlanet Web Server 4.1 (binary)</blockquote>
+(on NT)
+<blockquote>&nbsp;iPlanet Web Server 4.1 (binary)</blockquote>
+To run the JavaMailServlet, you must add the JavaMail and JavaBeans Activation
+Framework
+<br>jar files to the CLASSPATH environment variable (this is documented
+in the JavaMail README file
+<br>and additional Windows NT information is provided <a href="classpath-NT.html">here</a>).
+<p>Once this is done, restart the web server.
+<br>&nbsp;
+<p>Additional Notes
+<blockquote>iPlanet Web Server 4.1 does not use a SecurityManager or enforce
+any Java security policy.</blockquote>
+
+</body>
+</html>
diff --git a/doc/release/images/direct-classpath.jpg b/doc/release/images/direct-classpath.jpg
new file mode 100644
index 0000000..f78df04
--- /dev/null
+++ b/doc/release/images/direct-classpath.jpg
Binary files differ
diff --git a/doc/release/images/indirect-classpath.jpg b/doc/release/images/indirect-classpath.jpg
new file mode 100644
index 0000000..88813ac
--- /dev/null
+++ b/doc/release/images/indirect-classpath.jpg
Binary files differ
diff --git a/doc/spec/JavaMail-1.1-changes.txt b/doc/spec/JavaMail-1.1-changes.txt
new file mode 100644
index 0000000..028dbec
--- /dev/null
+++ b/doc/spec/JavaMail-1.1-changes.txt
@@ -0,0 +1,656 @@
+		
+		JavaMail 1.1
+		============
+
+Following is a description of the new APIs that are being
+introduced in JavaMail 1.1
+
+Please send comments and feedback to javamail@sun.com
+
+(1) MessageContext
+==================
+
+In some cases it is desirable for the object representing the content
+of a BodyPart to know something about the "context" in which it is
+operating, e.g., what other data is contained in the same Multipart
+object, who sent the message containing the data, etc.  This allows
+for more interesting content types that know more about the message
+containing them and the mail system in general.
+
+Some uses of multipart/related might require these capabilities.
+For instance, the handler for a text/html body part contained in a
+multipart/related object might need to know about the containing
+object in order to find the related image data needed to display the
+HTML document.
+
+There is one particular case in the current implementation where
+this need arises.  (This is a bug in the current release.)  When
+constructing a MimeMessage object for a nested message contained
+in another message, the DataContentHandler needs the Session
+object in order to construct the new MimeMessage object.
+
+To solve these problems we introduce a new class, a new interface,
+and a number of new methods.
+
+The MessageContext class provides the basic information about the
+"context" in which a content object is operating:
+
+/**
+ * The context in which a piece of Message content is contained.  A
+ * <code>MessageContext</code> object is returned by the
+ * <code>getMessageContext</code> method of the
+ * <code>MessageAware</code> interface.  <code>MessageAware</code> is
+ * typically implemented by <code>DataSources</code> to allow a
+ * <code>DataContentHandler</code> to pass on information about the
+ * context in which a data content object is operating.
+ *
+ * @see javax.mail.MessageAware
+ * @see javax.activation.DataSource
+ * @see javax.activation.DataContentHandler
+ */
+public class MessageContext {
+
+    /**
+     * Create a MessageContext object describing the context of the 
+     * given Part.
+     */
+    public MessageContext(Part part)
+
+    /**
+     * Return the Part that contains the content.
+     *
+     * @return	the containing Part, or null if not known
+     */
+    public Part getPart()
+
+    /**
+     * Return the Message that contains the content.
+     * Follows the parent chain up through containing Multipart
+     * objects until it comes to a Message object, or null.
+     *
+     * @return	the containing Message, or null if not known
+     */
+    public Message getMessage()
+
+    /**
+     * Return the Session we're operating in.
+     *
+     * @return	the Session, or null if not known
+     */
+    public Session getSession()
+}
+
+
+A MessageContext object can be obtained by a DataContentHandler from a
+DataSource that also implements the MessageAware interface:
+
+/**
+ * An interface optionally implemented by <code>DataSources</code> to
+ * supply information to a <code>DataContentHandler</code> about the
+ * message context in which the data content object is operating.
+ *
+ * @see javax.mail.MessageContext
+ * @see javax.activation.DataSource
+ * @see javax.activation.DataContentHandler
+ */
+public interface MessageAware {
+    /**
+     * Return the message context.
+     */
+    public MessageContext getMessageContext();
+}
+
+
+This new interface and class provides the basic information needed by
+the DataContentHandler to use in constructing more "interesting" objects
+representing a particular type of data.
+
+To allow navigation up the chain of components contained in a Message,
+we add the following methods.
+
+First, on BodyPart we add:
+
+    /**
+     * The <code>Multipart</code> object containing this 
+     * <code>BodyPart</code>, if known.
+     */
+    protected Multipart parent;
+
+    /**
+     * Return the containing <code>Multipart</code> object,
+     * or <code>null</code> if not known.
+     */
+    public Multipart getParent()
+
+
+On Multipart we add:
+
+    /**
+     * The <code>Part</code> containing this <code>Multipart</code>,
+     * if known.
+     */
+    protected Part parent;
+
+    /**
+     * Return the <code>Part</code> that contains this
+     * (cod<code>Multipart</c object, or <code>null</code> if not known.
+     */
+    public Part getParent()
+
+    /**
+     * Set the parent of this <code>Multipart</code> to be the specified
+     * <code>Part</code>.  Normally called by the <code>Message</code>
+     * or <code>BodyPart</code> <code>setContent(Multipart)</code>
+     * method. <p>
+     *
+     * <code>parent</code> may be <code>null</code> if the
+     * <code>Multipart</code> is being removed from its containing
+     * <code>Part</code>.
+     */
+    public void setParent(Part parent)
+
+
+To enable this new functionality we change the implementations of
+MimeBodyPart, MimeMessage, and MimeMultipart to maintain the "parent"
+links where possible.  We also change MimePartDataSource to implement
+the MessageAware interface.
+
+===================================================================
+
+(2) MessageID
+=============
+The MimeMessage class requires a getMessageID() method.
+
+The client can now fetch it as a header, but since this is a "standard"
+RFC822 header, we want to provide an easier access method. 
+
+An added benefit is that protocols like IMAP, which provide this info
+as part of the ENVELOPE structure, can implement this method much more
+efficiently.
+
+ /**
+  * Returns the value of the "Message-ID" header field. Returns
+  * null if this field is unavailable or its value is absent. <p>
+  *
+  * The default implementation provided here uses the
+  * <code>getHeader()</code> method to return the value of the
+  * "Message-ID" field.
+  *
+  * @return 	Message-ID
+  * @exception	MessagingException, if the retrieval of this field
+  *		causes any exception.
+  * @see	javax.mail.search.MessageIDTerm
+  */
+  public String getMessageID() throws MessagingException;
+
+===================================================================
+
+(3) UIDFolder
+=============
+
+The following methods in the UIDFolder interface have certain problems.
+
+We are proposing changes that will fix these problems. These are
+binary incompatible changes, but we think these changes are necessary for
+this interface to work in a useful manner. We are also not aware of
+anyone that has shipped a Store Provider that implements this interface,
+so we are hoping that the effect of this change is minimal (non-existent).
+
+(1) Message getMessageByUID(long uid)
+
+Description:
+	Get the Message corresponding to the given UID. If no such
+message exists, the java.util.NoSuchElementException is thrown.
+
+Problem:
+	A disconnected client can have a stash of UIDs from a
+previous session. Some of these UIDs may have been expunged from the
+server, but the disconnected client does not know this. It is quite
+reasonable (and expected) for a disconnected client to use this
+method when reconnecting - to check whether a UID is valid or not at
+the server.
+	Since failure is an expected result here, using a
+RunTimeException to indicate it, seems wrong and counter-intuitive for
+the client.
+
+Solution:
+	Our solution is to allow this method to return null to indicate
+that the requested UID is not valid anymore. Thus, this method will no
+longer throw the java.util.NoSuchElementException.
+
+(2)  Message[] getMessagesByUID(long start, long end)
+
+Description:
+	Get the Messages corresponding to the given UID range. If any
+UID is invalid, the java.util.NoSuchElementException is thrown.
+
+Problem:
+	Similar, but worse than (1). We think that disconnected clients
+(or clients that prefer to use UIDs) may issue 
+	getMessagesByUID(1, LASTUID) 
+to get all the Messages at the server, especially when the client does
+not know the exact UIDs for this folder. In this case, we certainly
+do not want this method to fail if some id in the given range is 
+not a valid UID; rather we want it to return all available Messages from
+the server.
+
+Solution:
+	Our solution is to remove the NoSuchElementException exception
+and allow this method to return Message objects in the given range.
+
+(3) Message[] getMessagesByUID(long[] uids)
+
+Description:
+	Get the Messages corresponding to the given UID range. If any
+UID is invalid, the java.util.NoSuchElementException is thrown.
+
+Problem:
+	Identical to (1).
+	A disconnected client can have a stash of UIDs from a
+previous session. Some of these UIDs may have been expunged from the
+server, but the disconnected client does not know this. It is quite
+reasonable (and expected) for a disconnected client to use this
+method when reconnecting - to check whether a set of UIDs are valid
+or not at the server.
+	Since failure can be an expected result, using a
+RunTimeException to indicate it, seems wrong and counter-intuitive for
+the client.
+
+Solution:
+	Our solution is to allow this method to return null entries
+to indicate that a requested UID is not valid anymore. Thus, the
+message array returned by this method can have null entries for the
+invalid UIDs; and this method will no longer throw the 
+java.util.NoSuchElementException.
+	Note that the size of the returned Message array is the same
+as the size of the request array of UIDs, and that each entry in the
+Message array corresponds to the same index entry in the UID array.
+===================================================================
+
+(4) InternetAddress
+===================
+
+The InternetAddress class needs the following protected field to
+properly support encoding of the "personal name".
+
+	protected String encodedPersonal; // the encoded personal name
+
+No other API changes are associated with this. 
+
+This is a binary compatible change to JavaMail 1.0. However, there is
+a potential problem for any existing InternetAddress subclasses, which
+can cause them to break. This typically will affect only providers,
+not clients.
+
+Details:
+-------
+
+The InternetAddress implementation will use this field to store the
+encoded personal name. 
+
+	The getPersonal() method will return the protected
+'personal' field; if this field is null, it will check the 'encodedPersonal'
+field and decode its value and return it.
+	The toString() method will use the protected 'encodedPersonal'
+field to create the RFC2047 compliant address string. If this field is
+null, it will check the 'personal' field, encode that if necessary and
+use it.
+
+This implies that, if an InternetAddress subclass changes either 
+the 'personal' or 'encodedPersonal' fields,  it should set the other
+to null to force its recomputation. Unfortunately, this also implies
+that existing subclasses of InternetAddress that directly set the
+'personal' field can break in certain situations.
+	We feel that the risk of this happening is minimal, since we
+don't expect that our users have subclassed InternetAddress. Also, this
+is necessary to properly support encoded personal names, so we feel that
+this has to be done.
+================================================================
+
+(5) MimeCharset
+================
+
+A utility method to convert java charsets into MIME charsets is
+needed.
+
+The JDK supports a variety of charsets. The JDK has names for these
+charsets, unfortunately they dont always match to their MIME or IANA
+equivalents. 
+
+It is necessary in some cases, (especially for providers) to map the
+JDK charset names into their MIME or IANA equivalents. This method does
+that.
+
+The API:
+-------
+
+This is a new static method to the javax.mail.internet.MimeUtility class
+
+	/**
+	 * Convert a java charset into its MIME charset name. <p>
+	 *
+	 * Note that a future version of JDK (post 1.2) might provide
+	 * this functionality, in which case, we might deprecate this
+	 * method then.
+	 *
+	 * @param   charset    the JDK charset
+	 * @return	the MIME/IANA equivalent. If a mapping
+	 *		is not possible, the passed in charset itself
+	 *		is returned.
+	 */			
+	public static String mimeCharset(String charset);
+
+====================================================================
+
+(6) getDefaultJavaCharset()
+============================
+
+This method returns the default charset for the platform's locale, as
+a Java charset. This is a new static method to the MimeUtility class.
+
+    /**
+     * Get the default charset for this locale. <p>
+     *
+     * @return  the default charset of the platform's locale,
+     *          as a Java charset. (NOT a MIME charset)
+     */
+    public static String getDefaultJavaCharset()
+
+====================================================================
+
+(7) Method to print out the nested Exceptions
+=============================================
+
+The MessagingException class currently allows nested exceptions. It
+would be nice to have one single method to dump out all the 
+nested exceptions' messages into System.out
+
+Proposal
+--------
+
+Override the getMessage() method from the superclass (Throwable), to
+append the messages from all nested Exceptions. This is similar to
+java.rmi.RemoteException.
+
+==================================================================
+
+(8) New SearchTerms
+====================
+
+The current address related search terms - AddressTerm, FromTerm and
+RecipientTerm are limited in that they operate on Address objects, not
+Strings. These terms use the equals() method to compare the addresses -
+which is not useful for the common case of substring comparisons.
+
+Hence we introduce three new SearchTerms:
+
+public AddressStringTerm extends StringTerm {
+    /**
+     * Constructor.
+     * @param pattern	the address pattern to be compared
+     */
+    protected AddressStringTerm(String pattern);
+
+    /**
+     * Check whether the address pattern specified in the 
+     * constructor is a substring of the string representation of 
+     * the given Address object.
+     *
+     * @param	address	the address to match against
+     */
+    protected boolean match(Address address);
+}
+
+public FromStringTerm extends AddressStringTerm {
+    /**
+     * Constructor.
+     * @param	address	the address to be compared.
+     */
+    public FromStringTerm(String string);
+
+    /**
+     * Check whether the address specified in the constructor is
+     * a substring of the "From" attribute of this message.
+     *
+     * @param	msg the address comparison is applied to this Message
+     */
+     public boolean match(Message msg);
+}
+
+public RecipientStringTerm extends AddressStringTerm {
+
+    /**
+     * Constructor.
+     *
+     * @param type      the recipient type
+     * @param address	the address to be compared.
+     */
+    public RecipientStringTerm(Message.RecipientType type, String address);
+
+    /**
+     * Return the type of recipient to match with.
+     */
+    public Message.RecipientType getRecipientType();
+
+    /**
+     * Check whether the address specified in the constructor is
+     * a substring of the given Recipient attribute of this message.
+     *
+     * @param	msg the address comparison is applied to this Message
+     */
+     public boolean match(Message msg);
+}
+
+==================================================================
+
+(9) InternetAddress.toString(Address[] addresses, int used)
+===========================================================
+
+As per RFC2047 (MIME), the length of a header field that contains
+encoded-words is limited to 76 characters.
+
+There are two methods in the InternetAddress class that generate
+RFC822 style address strings:
+
+ - The toString() method on InternetAddress, which generates
+ the string for one InternetAddress object
+ - The toString() static method, which generates a comma separated
+ string for the given array of InternetAddress objects.
+
+Both these methods currently do not honor the 76 character limit.
+Actually, the former does to an extent, since the encodedWord
+generator (i.e the MimeUtility.encodeWord() method) does break
+encoded words into multiples, if they stretch beyond 76 characters.
+
+Solution
+--------
+
+For the 1.1 release, we are planning to fix the the toString()
+static method as follows:
+
+Add a new static method 
+	static String toString(Address[] address, int used)
+
+This method takes an array of Address objects and an integer representing
+the number of "used" character positions for the first line of this
+field. The typical use of this method is when setting RFC822 headers,
+like the "From" header. 'used' can be set to sizeof("From: ") in
+this case.
+
+When generating the string, this method starts a new line if the
+addition of the next address.toString() causes the current line's 
+line-length to go over 76.
+
+Note that this algorithm does not work right if the length of a single
+InternetAddress is itself more than 76 characters. Also, it does not
+optimally break an address field, so that the maximum characters are
+accommodated in a single line.
+
+So, essentially, this is an initial attempt to solve this problem. We
+will add more APIs in the next version to further refine this.
+==================================================================
+
+(10) Folder.getMode()
+=====================
+
+It is currently not possible to tell whether a folder is open READ_ONLY
+or READ_WRITE without attempting to write it and catching the exception.
+
+We propose to add a protected field to Folder to store the open mode and
+a new method to Folder to return the open mode.  Because existing 
+subclasses will not use this new field, we can't guarantee that the 
+method will always return the correct value (although all Folder subclasses
+in the JavaMail package will be updated to return the correct value).
+
+    /**
+     * The open mode (<code>Folder.READ_ONLY</code>,
+     * <code>Folder.READ_WRITE</code>, or -1 if not known).
+     */
+    protected int mode = -1;
+
+    /**
+     * Return the open mode of this folder.  Returns
+     * <code>Folder.READ_ONLY</code>, <code>Folder.READ_WRITE</code>,
+     * or -1 if the open mode is not known (usually only because an older
+     * <code>Folder</code> provider has not been updated to use this new
+     * method).
+     *
+     * @exception	IllegalStateException if this folder is not opened
+     * @return	        the open mode of this folder
+     */
+    public int getMode() {
+	if (!isOpen())
+	    throw new IllegalStateException("Folder not open");
+	return mode;
+    }
+
+====================================================================
+
+(11) Folder.getURLName()
+========================
+
+The URLName support in the JavaMail 1.0 API's is incomplete and
+inadequately specified.  While you can get a Folder from a Session
+given a URLName for the folder, you can't find out the URLName
+for a given Folder object.  We propose adding the following method
+to Folder:
+
+    /**
+     * Return a URLName representing this folder.  The returned URLName
+     * does <em>not</em> include the password used to access the store.
+     *
+     * @return	the URLName representing this folder
+     * @see	URLName
+     */
+    public URLName getURLName() throws MessagingException
+
+Previously it was not specified whether the URLName returned from
+the Store.getURLName method would include the password field or
+not, but sometimes it did.  We propose to tighen the specification
+and fix the implementation so that the password field is not returned:
+
+    /**
+     * Return a URLName representing this store.  The returned URLName
+     * does <em>not</em> include the password field.  <p>
+     *
+     * Subclasses should only override this method if their
+     * URLName does not follow the standard format.
+     *
+     * @return	the URLName representing this store
+     * @see	URLName
+     */
+    public URLName getURLName()
+
+Similarly for Transport.
+
+Previously it was unspecified how the Store and Transport connect
+methods interacted with the url field.  In some cases it would be
+updated and in other cases it would not.  We propose to tighen the
+specification as follows:
+
+    /**
+     * Connect to the specified address. This method provides a simple
+     * authentication scheme that requires a username and password. <p>
+     *
+     * If the connection is successful, an "open" ConnectionEvent is
+     * delivered to any ConnectionListeners on this Store. <p>
+     *
+     * It is an error to connect to an already connected Store. <p>
+     *
+     * The implementation in the Store class will collect defaults
+     * for the host, user, and password from the session, prompting the
+     * user if necessary, and will then call the protocolConnect method,
+     * which the subclass must override.  The subclass should also
+     * implement the <code>getURLName</code> method, or use the 
+     * implementation in this class. <p>
+     *
+     * On a successful connection, the <code>setURLName</code> method is
+     * called with a URLName that includes the information used to make
+     * the connection, including the password. <p>
+     *
+     * If the password passed in is null and this is the first successful
+     * connection to this store, the user name and the password
+     * collected from the user will be saved as defaults for subsequent
+     * connection attempts to this same store.  If the password passed
+     * in is not null, it is not saved, on the assumption that the 
+     * application is managing passwords explicitly.
+     *
+     * @param host 	the host to connect to
+     * @param user	the user name
+     * @param password	this user's password
+     * @exception AuthenticationFailedException	for authentication failures
+     * @exception MessagingException		for other failures
+     * @exception IllegalStateException	if the store is already connected
+     * @see javax.mail.event.ConnectionEvent
+     */
+    public void connect(String host, String user, String password)
+			throws MessagingException
+
+And add this method to Store and Transport:
+
+    /**
+     * Set the URLName representing this store.
+     * Normally used to update the <code>url</code> field
+     * after a store has successfully connected. <p>
+     *
+     * Subclasses should only override this method if their
+     * URLName does not follow the standard format. <p>
+     *
+     * The implementation in the Store class simply sets the
+     * <code>url</code> field.
+     *
+     * @see URLName
+     */
+    protected void setURLName(URLName url)
+
+And finally, to simplify the implementation of Store and Transport,
+and make the common design patterns between them more clear, we're
+considering introducing a new Service class as a superclass of
+Store and Transport, and moving all common methods (the various
+connnect methods, URLName methods, and some listener methods) to
+the superclass.  Note that this is a binary compatible change.
+
+=====================================================================
+
+12) New Service class
+======================
+
+To emphasize the commonality in behavior between the Store and
+Transport classes, and to simplify maintenance of these classes, we
+propose moving many of the common methods to a new superclass called
+javax.mail.Service.  Store and Transport would then extend Service.
+These existing methods currently have identical implementations in the
+Store and Transport classes so moving them to a common superclass will
+not change the behavior of either Store or Transport.
+
+The Service class will contain all the methods and fields having to do
+with connecting, connection listeners, and naming via URLNames.  The
+Store class retains the methods for getting Folders and managing Store
+and Folder listeners.  The Transport class retains the methods for
+sending messages and managing Transport listeners.
+
+Note that this is a binary compatible change both for existing users of
+the Store and Transport classes, as well as for existing subclasses of
+these classes.
+
+======================================================================
diff --git a/doc/spec/JavaMail-1.2-changes.txt b/doc/spec/JavaMail-1.2-changes.txt
new file mode 100644
index 0000000..8c64f08
--- /dev/null
+++ b/doc/spec/JavaMail-1.2-changes.txt
@@ -0,0 +1,1022 @@
+
+		JavaMail 1.2
+		============
+
+Following is a description of the new APIs that are being
+introduced in JavaMail 1.2.  The numbers in parentheses
+are bug numbers; you can find more information about the
+bug reports at:
+
+    http://developer.java.sun.com/developer/bugParade/index.html
+
+Please send comments and feedback to javamail@sun.com.
+
+
+===================================================================
+
+1. New protected field and methods (4129743)
+--------------------------------------------
+
+To simplify creating subclasses of MimeMessage, the following
+field and method that were previously private are now protected:
+
+    /**
+     * A flag indicating whether the message has been modified.
+     * If the message has not been modified, any data in the
+     * <code>content</code> 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 = false;
+
+    /**
+     * Parse the InputStream setting the <code>headers</code> and
+     * <code>content</code> fields appropriately.  Also resets the
+     * <code>modified</code> 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
+     * @exception       MessagingException
+     */
+    protected void parse(InputStream is) throws MessagingException
+
+
+The javadocs for the following exsting methods have been updated to
+describe the use of the modified flag:
+
+    /**
+     * Default constructor. An empty message object is created.
+     * The <code>headers</code> field is set to an empty InternetHeaders
+     * object. The <code>flags</code> field is set to an empty Flags
+     * object. The <code>modified</code> flag is set to true.
+     */
+    public MimeMessage(Session session)
+
+    /**
+     * Output the message as an RFC 822 format stream, without
+     * specified headers.  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 javax.mail.MessagingException
+     * @exception IOException   if an error occurs writing to the stream
+     *                          or if an error is generated by the
+     *                          javax.activation layer.
+     * @see javax.activation.DataHandler#writeTo
+     */
+    public void writeTo(OutputStream os, String[] ignoreList)
+                                throws IOException, MessagingException
+
+    /**
+     * 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 saveChanges should not be called on such messages. <p>
+     *
+     * This method sets the <code>modified</code> flag to true and then
+     * calls the <code>updateHeaders<code> method.
+     *
+     * @exception       IllegalWriteException if the underlying
+     *                  implementation does not support modification
+     * @exception       IllegalStateException if this message is
+     *                  obtained from a READ_ONLY folder.
+     * @exception       MessagingException
+     */
+    public void saveChanges() throws MessagingException
+
+The following method is added to MimeMessage:
+
+    /**
+     * 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
+     * @exception       MessagingException
+     */
+    protected InternetHeaders createInternetHeaders(InputStream is)
+                                throws MessagingException
+
+Likewise, to simplify creating subclasses of MimeMultipart, the parse
+method is made protected:
+
+    /**
+     * Parse the InputStream from our DataSource, constructing the
+     * appropriate MimeBodyParts.  The <code>parsed</code> flag is
+     * set to true, and if true on entry nothing is done.  This
+     * method is called by all other methods that need data for
+     * the body parts, to make sure the data has been parsed.
+     */
+    protected synchronized void parse() throws MessagingException
+
+and the following protected methods are added:
+
+    /**
+     * 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
+     * @exception       MessagingException
+     */
+    protected InternetHeaders createInternetHeaders(InputStream is)
+                                throws MessagingException
+
+    /**
+     * Create and return a MimeBodyPart object to represent a
+     * body part parsed from the InputStream.  Subclasses can override
+     * this method to return a subclass of MimeBodyPart, if
+     * necessary.  This implementation simply constructs and returns
+     * a MimeBodyPart object.
+     *
+     * @param   headers         the headers for the body part
+     * @param   content         the content ofthe body part
+     * @exception               MessagingException
+     */
+    protected MimeBodyPart createMimeBodyPart(InternetHeaders headers,
+                                byte[] content) throws MessagingException
+
+    /**
+     * Create and return a MimeBodyPart object to represent a
+     * body part parsed from the InputStream.  Subclasses can override
+     * this method to return a subclass of MimeBodyPart, if
+     * necessary.  This implementation simply constructs and returns
+     * a MimeBodyPart object.
+     *
+     * @param	is		InputStream containing the body part
+     * @exception  		MessagingException
+     */
+    protected MimeBodyPart createMimeBodyPart(InputStream is)
+				throws MessagingException
+
+===================================================================
+
+2. MimeMessage and MimeBodyPart getRawInputStream method (4124840)
+------------------------------------------------------------------
+
+In some cases it is desirable to get the data for a body part
+before JavaMail attempts to decode it.  This is particularly important
+if the Content-Transfer-Encoding header is incorrect.  (For example,
+some mail software is known to use "7-bit" instead of the MIME
+defined "7bit".)  Access to this data is currently provided
+through the protected getContentStream method.  Since simply
+making this method public has the potential to cause a source
+incompatibility for any subclasses that declare this method as
+protected, we instead add a new public method that calls this
+protected method:
+
+    /**
+     * 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 #getInputStream
+     * @see #getContentStream
+     */
+    public InputStream getRawInputStream() throws MessagingException
+
+
+===================================================================
+
+3. ReadOnlyFolderException (4163360)
+------------------------------------
+
+A new MessagingException subclass to indicate a read-only folder
+is needed.
+
+Currently, if a client attempts to open a folder in read-write mode
+and the folder is read-only, a MessagingException is thrown. This
+exception type does not indicate that the anomaly was caused due to
+the lack of write-permissions.
+
+/**
+ * This exception is thrown when an attempt is made to open a folder
+ * with read-write access when the folder is marked read-only. <p>
+ */
+
+public class ReadOnlyFolderException extends MessagingException {
+
+    /**
+     * Constructor.
+     * @param folder    the Folder
+     */
+    public ReadOnlyFolderException(Folder folder)
+
+    /**
+     * Constructor.
+     * @param folder    the Folder
+     * @param message   the detailed error message
+     */
+    public ReadOnlyFolderException(Folder folder, String message)
+
+    /**
+     * Returns the Folder object.
+     */
+    public Folder getFolder()
+} 
+
+===================================================================
+
+4. InternetAddress implements Cloneable (4181144)
+-------------------------------------------------
+
+To simplify copying of InternetAddress objects, the InternetAddress
+class will implement the Cloneable interface and will provide a
+public clone method.
+
+public class InternetAddress extends Address implements Cloneable {
+
+    /**
+     * Return a copy of this InternetAddress object.
+     */
+    public Object clone()
+}
+
+===================================================================
+
+5. MimeMessage copy constructor (4107752)
+-----------------------------------------
+
+When forwarding or saving a message retrieved from a Store, it is
+sometimes desirable to be able to modify the message first.  Since
+most Stores do not allow their Message objects to be modified, the
+message must first be copied.  To simplify copying a MimeMessage,
+we introduce a copy constructor that allows a new MimeMessage to
+be created and initialized with a copy of another MimeMessage.
+
+    /**
+     * Constructs a new MimeMessage with content initialized form the
+     * <code>source</code> MimeMessage.  The new message is independent
+     * of the original. <p>
+     * 
+     * @param   source  the message to copy content from
+     * @exception       MessagingException
+     */
+    public MimeMessage(MimeMessage source) throws MessagingException
+
+===================================================================
+
+6. AuthenticationFailedException includes server message (4230553)
+------------------------------------------------------------------
+
+When authentication with a server fails, the server often supplies
+some information in its protocol message that indicates the reason
+for the failure.  To allow a service provider to return this information
+to the user, we now allow the Service.protocolConnect() method to
+throw an AuthenticationFailedException in this case.  The exception
+may contain a string message that includes the additional information
+from the server.
+
+    /**
+     * The service implementation should override this method to
+     * perform the actual protocol-specific connection attempt.
+     * The default implementation of the <code>connect</code> method
+     * calls this method as needed. <p>
+     *
+     * The <code>protocolConnect</code> method should return
+     * <code>false</code> if a user name or password is required
+     * for authentication but the corresponding parameter is null;
+     * the <code>connect</code> method will prompt the user when
+     * needed to supply missing information.  This method may
+     * also return <code>false</code> if authentication fails for
+     * the supplied user name or password.  Alternatively, this method
+     * may throw an AuthenticationFailedException when authentication
+     * fails.  This exception may include a String message with more
+     * detail about the failure. <p>
+     *
+     * The <code>protocolConnect</code> method should throw an
+     * exception to report failures not related to authentication,
+     * such as an invalid host name or port number, loss of a
+     * connection during the authentication process, unavailability
+     * of the server, etc.
+     *
+     * @param   host            the name of the host to connect to
+     * @param   port            the port to use (-1 means use default port)
+     * @param   user            the name of the user to login as
+     * @param   password        the user's password
+     * @return  true if connection successful, false if authentication failed
+     * @exception AuthenticationFailedException for authentication failures
+     * @exception MessagingException    for non-authentication failures
+     */
+    protected boolean protocolConnect(String host, int port, String user,
+                                String password) throws MessagingException
+
+===================================================================
+
+7. Support ESMTP 8BITMIME extension (4132029)
+---------------------------------------------
+
+JavaMail chooses the encoding for body parts when the Message.saveChanges()
+method is called.  At the time this method is called, JavaMail has no
+information about the Transport that might be used to send the message.
+Thus, communicating the Transport's capabilities to the part of JavaMail
+that chooses body part encodings is problematic.
+
+To provide some minimal support for the 8BITMIME extension, the SMTP
+protocol provider is extended in the following way.  Note that this
+capability is part of Sun's SMTP protocol provider, and is *not* a
+part of the JavaMail API spec.  We include it here for convenience only.
+
+If the property "mail.smtp.allow8bitmime" is set to "true", and the
+SMTP server supports the 8BITMIME extension, the SMTP Transport will
+traverse the message and adjust the Content-Transfer-Encoding of text
+body parts from "quoted-printable" or "base64" to "8bit" as appropriate.
+
+Note that if the same message is subsequently sent over a Transport
+or to a server that does not support 8bit MIME, the message will *not*
+be converted back to a non-8bit encoding.  Normally this will not be a
+problem.  Note also that a message using "8bit" encoding can be safely
+appended to an IMAP folder.
+
+===================================================================
+
+8. Make MailDateFormat class public (4266390)
+---------------------------------------------
+
+It would be useful to have the MailDateFormat class available as part
+of the javax.mail.internet package to allow parsing and formatting
+dates in other MIME headers.
+
+/**
+ * Formats and parses date specification based on the
+ * draft-ietf-drums-msg-fmt-08 dated January 26, 2000. This is a followup
+ * spec to RFC822.<p>
+ *
+ * This class does not take pattern strings. It always formats the
+ * date based on the specification below.<p>
+ *
+ * 3.3 Date and Time Specification<p>
+ *
+ * Date and time occur in several header fields of a message. This section
+ * specifies the syntax for a full date and time specification. Though folding
+ * whitespace is permitted throughout the date-time specification, it is
+ * recommended that only a single space be used where FWS is required and no
+ * space be used where FWS is optional in the date-time specification; some
+ * older implementations may not interpret other occurrences of folding
+ * whitespace correctly.<p>
+ *
+ * date-time = [ day-of-week "," ] date FWS time [CFWS]<p>
+ *
+ * day-of-week = ([FWS] day-name) / obs-day-of-week<p>
+ *
+ * day-name = "Mon" / "Tue" / "Wed" / "Thu" / "Fri" / "Sat" / "Sun"<p>
+ *
+ * date = day month year<p>
+ *
+ * year = 4*DIGIT / obs-year<p>
+ *
+ * month = (FWS month-name FWS) / obs-month<p>
+ *
+ *<pre>month-name = "Jan" / "Feb" / "Mar" / "Apr" /
+ *             "May" / "Jun" / "Jul" / "Aug" /
+ *             "Sep" / "Oct" / "Nov" / "Dec"
+ * </pre><p>
+ * day = ([FWS] 1*2DIGIT) / obs-day<p>
+ *
+ * time = time-of-day FWS zone<p>
+ *
+ * time-of-day = hour ":" minute [ ":" second ]<p>
+ *
+ * hour = 2DIGIT / obs-hour<p>
+ *
+ * minute = 2DIGIT / obs-minute<p>
+ *
+ * second = 2DIGIT / obs-second<p>
+ *
+ * zone = (( "+" / "-" ) 4DIGIT) / obs-zone<p>
+ *
+ *
+ * The day is the numeric day of the month. The year is any numeric year in
+ * the common era.<p>
+ *
+ * The time-of-day specifies the number of hours, minutes, and optionally
+ * seconds since midnight of the date indicated.<p>
+ *
+ * The date and time-of-day SHOULD express local time.<p>
+ *
+ * The zone specifies the offset from Coordinated Universal Time (UTC,
+ * formerly referred to as "Greenwich Mean Time") that the date and
+ * time-of-day represent. The "+" or "-" indicates whether the time-of-day is
+ * ahead of or behind Universal Time. The first two digits indicate the number
+ * of hours difference from Universal Time, and the last two digits indicate
+ * the number of minutes difference from Universal Time. (Hence, +hhmm means
+ * +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm) minutes). The form
+ * "+0000" SHOULD be used to indicate a time zone at Universal Time. Though
+ * "-0000" also indicates Universal Time, it is used to indicate that the time
+ * was generated on a system that may be in a local time zone other than
+ * Universal Time.<p>
+ *
+ * A date-time specification MUST be semantically valid. That is, the
+ * day-of-the week (if included) MUST be the day implied by the date, the
+ * numeric day-of-month MUST be between 1 and the number of days allowed for
+ * the specified month (in the specified year), the time-of-day MUST be in the
+ * range 00:00:00 through 23:59:60 (the number of seconds allowing for a leap
+ * second; see [STD-12]), and the zone MUST be within the range -9959 through
+ * +9959.<p>
+ *
+ */
+public class MailDateFormat extends SimpleDateFormat {
+ 
+    public MailDateFormat()
+    
+    /**
+     * Formats the given date in the format specified by
+     * draft-ietf-drums-msg-fmt-08 in the current TimeZone
+     *
+     * @param	date		the Date object
+     * @param	dateStrBuf	the formatted string
+     * @param	fieldPosition	the current field position
+     * @returns	StringBuffer	the formatted String
+     */
+    public StringBuffer format(Date date, StringBuffer dateStrBuf,
+                               FieldPosition fieldPosition)
+
+    /**
+     * Parses the given date in the format specified by
+     * draft-ietf-drums-msg-fmt-08 in the current TimeZone
+     *
+     * @param	text	the formatted date to be parsed
+     * @param	pos	the current parse position
+     * @returns	Date	the parsed date in a Date object
+     */
+    public Date parse(String text, ParsePosition pos)
+}
+
+===================================================================
+
+9. String-based MimeMessage setRecipients and addRecipients methods (4328824)
+-----------------------------------------------------------------------------
+The following convenience methods will be added to MimeMessage. They take a
+String for setting/adding a recipient (instead of javax.mail.Address objects).
+
+    /**  
+     * Set the specified recipient type to the given addresses.
+     * If the address parameter is <code>null</code>, 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.
+     * @exception       MessagingException
+     * @see             #getRecipients
+     */  
+    public void setRecipients(Message.RecipientType type, String addresses)
+                                throws MessagingException
+
+    /**
+     * 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.
+     * @exception       MessagingException
+     */  
+    public void addRecipients(Message.RecipientType type, String addresses)
+                                throws MessagingException
+
+===================================================================
+
+10. Add Session.getDefaultInstance(Properties props) and
+        Session.getInstance(Properties props) methods (4328826)
+---------------------------------------------------------------
+
+These are convenience methods for retrieving the default Session or a new
+Session object which does not require an Authenticator parameter
+(it is assumed to be null).
+ 
+    /**
+     * Get the default Session object. If a default has not yet been
+     * setup, a new Session object is created and installed as the
+     * default.<p>
+     *
+     * Note that a default session created with no Authenticator is
+     * available to all code executing in the same Java virtual
+     * machine, and the session can contain security sensitive
+     * information such as user names and passwords.
+     *
+     * @param   props   Properties object. Used only if a new Session
+     *                  object is created.<br>
+     *                  It is expected that the client supplies values
+     *                  for the properties listed in Appendix A of the
+     *                  JavaMail spec (particularly  mail.store.protocol,
+     *                  mail.transport.protocol, mail.host, mail.user,
+     *                  and mail.from) as the defaults are unlikely to
+     *                  work in all cases.
+     * @return          the default Session object
+     */
+    public static Session getDefaultInstance(Properties props)
+
+    /**  
+     * Get a new Session object.
+     *
+     * @param   props   Properties object that hold relevant properties.<br>
+     *                  It is expected that the client supplies values
+     *                  for the properties listed in Appendix A of the
+     *                  JavaMail spec (particularly  mail.store.protocol,
+     *                  mail.transport.protocol, mail.host, mail.user,
+     *                  and mail.from) as the defaults are unlikely to
+     *                  work in all cases.
+     * @return          a new Session object
+     * @see     javax.mail.Authenticator
+     */  
+    public static Session getInstance(Properties props)
+
+===================================================================
+
+11. Allow for providing a filename when using MimeUtility.encode() (4140579)
+----------------------------------------------------------------------------
+
+The UUEncode encoder requires the filename to be inserted into the encoded
+stream. The public access point to the encoder is thru the MimeUtility.encode()
+method, which does not have any parameter that can provide the filename.
+Hence the uuencoded stream always has "encode.buf" as filename. This new
+method allows the setting of the filename.
+
+    /**
+     * Wrap an encoder around the given output stream.
+     * All the encodings defined in RFC 2045 are supported here.
+     * They include "base64", "quoted-printable", "7bit", "8bit" and
+     * "binary". In addition, "uuencode" is also supported.
+     * The <code>filename</code> parameter is used with the "uuencode"
+     * encoding and is included in the encoded output.
+     *
+     * @param   os              output stream
+     * @param   encoding        the encoding of the stream.
+     * @param   filename	name for the file being encoded
+     * @exception       MessagingException
+     * @return                  output stream that applies the
+     *                          specified encoding.
+     */
+    public static OutputStream encode(OutputStream os, String encoding,
+                                      String filename)
+                throws MessagingException
+  
+===================================================================
+
+12. New exception constructors (4259211)
+----------------------------------------
+
+The FolderNotFoundException constructors are not consistant with other
+exceptions defined in the API. New constructors are needed to eliminate
+these inconsistencies.
+ 
+    /**
+     * Constructs a MessagingException with the specified folder
+     * @param folder    the Folder
+     */
+    public FolderNotFoundException(Folder folder)
+ 
+    /**
+     * Constructs a MessagingException with the specified detail message
+     * and the specified folder.
+     * @param folder    the Folder
+     * @param s         the detail message
+     */
+    public FolderNotFoundException(Folder folder, String s)
+
+===================================================================
+
+13. InternetAddress.toUnicodeString() method  (4281729)
+-------------------------------------------------------
+
+Problem: AddressStringTerm.match does not return the correct results
+in some situations.
+
+AddressStringTerm wants to do the match against the formatted address
+string in Unicode, not the ASCII version that might include charset
+encoding information.  To do this it attempts to format the address
+itself, but it's not smart enough to know all the rules about
+formatting an address (e.g., when to quote the personal name) so it
+does this formatting differently than InternetAddress.toString does.
+When the address contains only ASCII characters, the formatting should
+be identical.
+
+    /**
+     * Returns a properly formatted address (RFC 822 syntax) of
+     * Unicode characters.
+     *   
+     * @return          Unicode address string
+     */  
+    public String toUnicodeString()
+    
+===================================================================
+
+14. Call saveChanges automatically on newly constructed message (4339203)
+-------------------------------------------------------------------------
+
+One of the most common errors when constructing new messages is forgetting
+to call the saveChanges() method before writing out the message or calling
+the Transport.sendMessage() method.  To solve this problem we add a flag
+to MimeMessage and change the writeTo() method accordingly:
+
+    /**
+     * Does the <code>saveChanges</code> 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 = false;
+
+    /**
+     * 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 saveChanges 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
+     * @exception       IllegalStateException if this message is
+     *                  obtained from a READ_ONLY folder.
+     * @exception       MessagingException
+     */
+    public void saveChanges() throws MessagingException
+
+    /**
+     * Output the message as an RFC 822 format stream, without
+     * specified headers.  If the <code>saved</code> 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 javax.mail.MessagingException
+     * @exception IOException   if an error occurs writing to the stream
+     *                          or if an error is generated by the
+     *                          javax.activation layer.
+     * @see javax.activation.DataHandler#writeTo
+     */
+    public void writeTo(OutputStream os, String[] ignoreList)
+                                throws IOException, MessagingException
+
+===================================================================
+
+15. New MimeUtility.getEncoding(DataHandler) method (4340648)
+-------------------------------------------------------------
+
+To improve the performance of JavaMail, we previously added a (package
+private) getEncoding() method to MimeUtility.  This method is now public:
+
+    /**
+     * Same as <code>getEncoding(DataSource)</code> except that instead
+     * of reading the data from an <code>InputStream</code> it uses the
+     * <code>writeTo</code> method to examine the data.  This is more
+     * efficient in the common case of a <code>DataHandler</code>
+     * created with an object and a MIME type (for example, a
+     * "text/plain" String) because all the I/O is done in this
+     * thread.  In the case requiring an <code>InputStream</code> the
+     * <code>DataHandler</code> uses a thread, a pair of pipe streams,
+     * and the <code>writeTo</code> method to produce the data. <p>
+     */
+    public static String getEncoding(DataHandler dh)
+
+===================================================================
+
+16. New TransportEvent.getMessage()  method (4331674)
+-----------------------------------------------------
+
+The TransportEvent class saves the message that caused the error,
+but provides no getMessage method for the listener to retrieve the
+Message object. The following method will be added:
+
+    /**
+     * Get the Message object associated with this Transport Event.
+     *   
+     * @return          the Message object 
+     */
+    public Message getMessage()
+
+
+===================================================================
+
+17. javax.mail.search terms should be serializable (4126013)
+------------------------------------------------------------
+
+The javax.mail.search package allows you to programmatically construct
+a search term.  It would be convenient if those terms could be saved
+in persistent storage and restored in a later session.  Using
+serialization to store these expressions is the simplest approach.
+
+Many of the search terms reference other objects that must also be
+serializable.  The most problematic such objects are of the class
+Message.RecipientType.  This class uses the java "type-safe enum"
+idiom, which involves a number of static final instances of the class.
+Applications are allowed to test for equivalence with these "constants"
+by using the "==" equality operator.  Thus, it's critical that only a
+single instance of each constant exist in the Java virtual machine.
+To ensure that this constraint is met when deserializing an object
+of this class, we must take advantage of the JDK 1.2 readReplace()
+method.  Since this method is not available on JDK 1.1, objects of
+this class, and thus search terms that reference them, can not be
+correctly deserialized on JDK 1.1.  This is a limitation of this
+new capability.
+
+To provide this support, the following classes and all their subclasses
+now implement the Serializable interface:
+
+    javax.mail.search.SearchTerm
+    javax.mail.Address
+    javax.mail.Flags
+    javax.mail.Message.RecipientType
+
+In addition, to allow comparison between search terms, the equals
+and hashCode methods on SearchTerm (and all subclasses) now implement
+"value" equivalence rather than identity equivalence.
+
+
+===================================================================
+
+18. Support IMAP NAMESPACE extension (4364827)
+------------------------------------------------------------
+
+We propose the following new APIs to be added to javax.mail.Store to
+provide namespace information.  If the IMAP server supports the
+NAMESPACE extension, it will be used to return this information.
+
+    /**
+     * Return a set of folders representing the <i>personal</i> namespaces
+     * for the current user.  A personal namespace is a set of names that
+     * is considered within the personal scope of the authenticated user.
+     * Typically, only the authenticated user has access to mail folders
+     * in their personal namespace.  If an INBOX exists for a user, it
+     * must appear within the user's personal namespace.  In the
+     * typical case, there should be only one personal namespace for each
+     * user in each Store. <p>
+     *
+     * This implementation returns an array with a single entry containing
+     * the return value of the <code>getDefaultFolder</code> method.
+     * Subclasses should override this method to return appropriate information.
+     *
+     * @exception 	IllegalStateException if this Store is not connected.
+     * @return		array of Folder objects
+     */
+    public Folder[] getPersonalNamespaces() throws MessagingException
+
+    /**
+     * Return a set of folders representing the namespaces for
+     * <code>user</code>.  The namespaces returned represent the
+     * personal namespaces for the user.  To access mail folders in the
+     * other user's namespace, the currently authenticated user must be
+     * explicitly granted access rights.  For example, it is common for
+     * a manager to grant to their secretary access rights to their
+     * mail folders. <p>
+     *
+     * This implementation returns an empty array.  Subclasses should
+     * override this method to return appropriate information.
+     *
+     * @exception 	IllegalStateException if this Store is not connected.
+     * @return		array of Folder objects
+     */
+    public Folder[] getUserNamespaces(String user) throws MessagingException
+
+    /**
+     * Return a set of folders representing the <i>shared</i> namespaces.
+     * A shared namespace is a namespace that consists of mail folders
+     * that are intended to be shared amongst users and do not exist
+     * within a user's personal namespace. <p>
+     *
+     * This implementation returns an empty array.  Subclasses should
+     * override this method to return appropriate information.
+     *
+     * @exception 	IllegalStateException if this Store is not connected.
+     * @return		array of Folder objects
+     */
+    public Folder[] getSharedNamespaces() throws MessagingException
+
+
+===================================================================
+
+19. Make ContentDisposition class public (4366373)
+--------------------------------------------------
+
+The javax.mail.internet.ContentDisposition class is package private
+and should be made public.  The API is:
+
+/**
+ * This class represents a MIME ContentDisposition value. It provides
+ * methods to parse a ContentDisposition string into individual components
+ * and to generate a MIME style ContentDisposition string.
+ */
+
+public class ContentDisposition
+
+    /**
+     * No-arg Constructor.
+     */
+    public ContentDisposition()
+
+    /**
+     * Constructor.
+     *
+     * @param	disposition	disposition
+     * @param	list	ParameterList
+     */
+    public ContentDisposition(String disposition, ParameterList list)
+
+    /**
+     * Constructor that takes a ContentDisposition string. The String
+     * is parsed into its constituents: dispostion and parameters. 
+     * A ParseException is thrown if the parse fails. 
+     *
+     * @param	s	the ContentDisposition string.
+     * @exception	ParseException if the parse fails.
+     */
+    public ContentDisposition(String s) throws ParseException
+
+    /**
+     * Return the disposition value.
+     * @return the disposition
+     */
+    public String getDisposition()
+
+    /**
+     * Return the specified parameter value. Returns <code>null</code>
+     * if this parameter is absent.
+     * @return	parameter value
+     */
+    public String getParameter(String name)
+
+    /**
+     * Return a ParameterList object that holds all the available 
+     * parameters. Returns null if no parameters are available.
+     *
+     * @return	ParameterList
+     */
+    public ParameterList getParameterList()
+
+    /**
+     * Set the primary type. Overrides existing primary type.
+     * @param	primaryType	primary type
+     */
+    public void setDisposition(String disposition)
+
+    /**
+     * Set the specified parameter. If this parameter already exists,
+     * it is replaced by this new value.
+     *
+     * @param	name	parameter name
+     * @param	value	parameter value
+     */
+    public void setParameter(String name, String value)
+
+    /**
+     * Set a new ParameterList.
+     * @param	list	ParameterList
+     */
+    public void setParameterList(ParameterList list)
+
+    /**
+     * Retrieve a RFC2045 style string representation of
+     * this ContentDisposition. Returns <code>null</code> if
+     * the conversion failed.
+     *
+     * @return	RFC2045 style string
+     */
+    public String toString()
+}
+
+===================================================================
+
+20. Improve performance of MimeMessage (4371862)
+--------------------------------------------------
+
+To allow us to improve the performance of the MimeMessage and MimeMultipart
+classes when parsing data from an InputStream, we introduce a new
+interface that allows the data in the InputStream to be shared instead
+of copied, and we use this new interface in key parts of the implementation.
+
+The following field is added to MimeMessage:
+
+    /**
+     * If the data for this message was supplied by an
+     * InputStream that implements the SharedInputStream interface,
+     * <code>contentStream</code> is another such stream representing
+     * the content of this message.  In this case, <code>content</code>
+     * will be null.
+     */
+    protected InputStream contentStream;
+
+The following field is added to MimeBodyPart:
+
+    /**
+     * If the data for this body part was supplied by an
+     * InputStream that implements the SharedInputStream interface,
+     * <code>contentStream</code> is another such stream representing
+     * the content of this body part.  In this case, <code>content</code>
+     * will be null.
+     */
+    protected InputStream contentStream;
+
+The following interface is added:
+
+package javax.mail.internet;
+
+/**
+ * An InputStream that is backed by data that can be shared by multiple
+ * readers may implement this interface.  This allows users of such an
+ * InputStream to determine the current positionin the InputStream, and
+ * to create new InputStreams representing a subset of the data in the
+ * original InputStream.  The new InputStream will access the same
+ * underlying data as the original, without copying the data.
+ */
+
+public interface SharedInputStream {
+    /**
+     * Return the current position in the InputStream, as an
+     * offset from the beginning of the InputStream.
+     *
+     * @return	the current position
+     */
+    public long getPosition();
+
+    /**
+     * Return a new InputStream representing a subset of the data
+     * from this InputStream, starting at <code>start</code> (inclusive)
+     * up to <code>end</code> (exclusive).  <code>start</code> must be
+     * non-negative.  If <code>end</code> is -1, the new stream ends
+     * at the same place as this stream.  The returned InputStream
+     * will also implement the SharedInputStream interface.
+     *
+     * @param	start	the starting position
+     * @param	end	the ending position + 1
+     * @return		the new stream
+     */
+    public InputStream newStream(long start, long end);
+}
+
+===================================================================
+
+21. New ParameterList.toString(int used) method.
+--------------------------------------------------
+
+The ParameterList.toString() method returns its results "unfolded". It
+would be useful to have the results "folded" in certain situations. A
+new method will be added to the ParamterList class which will return
+"folded" results. Folding is defined by RFC 822 as the process of splitting
+a header field into multiple lines. "The general rule is that wherever there
+may be linear-white-space (NOT simply LWSP-chars), a CRLF immediately
+followed by AT LEAST one LWSP-char may instead be inserted." Unfolding is 
+the process of returning to a single line representation. "Unfolding is 
+accomplished  by regarding CRLF immediately followed by a LWSP-char as
+equivalent to the LWSP-char."
+
+    /**
+     * Convert this ParameterList into a MIME String. If this is
+     * an empty list, an empty string is returned.
+     *   
+     * The 'used' parameter specifies the number of character positions
+     * already taken up in the field into which the resulting address
+     * sequence string is to be inserted. It's used to determine where
+     * to "fold" the resulting parameter list.
+     *
+     * @param used      number of character positions already used, in
+     *                  the field into which the parameter list is to
+     *                  be inserted.
+     * @return          String
+     */  
+    public String toString(int used)
diff --git a/doc/spec/JavaMail-1.3-changes.txt b/doc/spec/JavaMail-1.3-changes.txt
new file mode 100644
index 0000000..78cb952
--- /dev/null
+++ b/doc/spec/JavaMail-1.3-changes.txt
@@ -0,0 +1,303 @@
+
+		JavaMail 1.3
+		============
+
+		(Updated April 1, 2002)
+
+Following is a description of the new APIs that are being
+introduced in JavaMail 1.3.  The numbers in parentheses
+are bug numbers; you can find more information about the
+bug reports at:
+
+    http://developer.java.sun.com/developer/bugParade/index.html
+
+Please send comments and feedback to javamail@sun.com.
+
+Many of these changes expand JavaMail's conformance with Internet
+standards, or make JavaMail more tolerant of messages that don't
+quite conform to the standards.  "Be liberal in what you receive
+and conservative in what you send."
+
+
+===================================================================
+
+1. Add setSender and getSender methods to MimeMessage (4405115)
+---------------------------------------------------------------
+
+These convenience methods support setting and reading the RFC 822
+Sender header.
+
+    /** 
+     * 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 requisite header field.
+     *
+     * @return		Address object
+     * @exception	MessagingException
+     * @see		#headers
+     * @since		JavaMail 1.3
+     */
+    public Address getSender() throws MessagingException
+
+    /**
+     * 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 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.
+     * @exception	MessagingException
+     * @since		JavaMail 1.3
+     */
+    public void setSender(Address address) throws MessagingException
+
+===================================================================
+
+2. Add setContentID method to MimeBodyPart (4377720)
+----------------------------------------------------
+
+This convenience method supports setting the Content-ID header.
+
+    /**
+     * Set the "Content-ID" header field of this body part.
+     * If the <code>cid</code> parameter is null, any existing 
+     * "Content-ID" is removed.
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this body part is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException
+     * @since		JavaMail 1.3
+     */
+    public void setContentID(String cid) throws MessagingException
+
+===================================================================
+
+3. Add mail.mime.charset property (4377731)
+-------------------------------------------
+
+The "mail.mime.charset" System property (NOTE: *not* Session property)
+names the default charset to be used by JavaMail.  If not set, the
+standard J2SE "file.encoding" System property is used.  This allows
+applications to specify a default character set for sending messages
+that's different than the character set used for files stored on the
+system.  This is common on Japanese systems.
+
+===================================================================
+
+4. Add getDeletedMesageCount method to Folder (4388730)
+-------------------------------------------------------
+
+This convenience method would return a count of the number of deleted
+messages in a folder.
+
+    /**
+     * Get the number of deleted messages in this Folder. <p>
+     *
+     * This method can be invoked on a closed folder. However, note
+     * that for some folder implementations, getting the deleted message
+     * count can be an expensive operation involving actually opening 
+     * the folder. In such cases, a provider can choose not to support 
+     * this functionality in the closed state, in which case this method
+     * must return -1. <p>
+     *
+     * Clients invoking this method on a closed folder must be aware
+     * that this is a potentially expensive operation. Clients must
+     * also be prepared to handle a return value of -1 in this case. <p>
+     *
+     * This implementation returns -1 if this folder is closed. Else
+     * this implementation gets each Message in the folder using
+     * <code>getMessage(int)</code> and checks whether its
+     * <code>DELETED</code> flag is set. The total number of messages
+     * that have this flag set is returned.
+     *
+     * @return 		number of deleted messages. -1 may be returned
+     *			by certain implementations if this method is
+     *			invoked on a closed folder.
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception       MessagingException
+     * @since		JavaMail 1.3
+     */
+    public int getDeletedMessageCount() throws MessagingException
+
+===================================================================
+
+5. Support parsing "illegal" Internet addresses (4650940)
+---------------------------------------------------------
+
+The parse method on the InternetAddress class takes a flag that tells
+whether or not to strictly enforce the RFC822 syntax rules.  Currently,
+when the flag is false most rules are still checked while a few are not.
+To better support the range of "invalid" addresses seen in real messages,
+and in combination with the following two changes, the parseHeader
+method would enforce fewer syntax rules when the strict flag is false
+and would enforce more rules when the strict flag is true.  If the
+strict flag is false and the parse is successful in separating out an
+email address or addresses, the syntax of the addresses themselves
+would not be checked.  (Introducing a new method preserves
+compatibility with users of the existing parse method.)
+
+    /**
+     * Parse the given sequence of addresses into InternetAddress
+     * objects.  If <code>strict</code> is false, the full syntax rules for
+     * individual addresses are not enforced.  If <code>strict</code> is
+     * true, many (but not all) of the RFC822 syntax rules are enforced.
+     *
+     * Non-strict parsing is typically used when parsing a list of
+     * mail addresses entered by a human.  Strict parsing is typically
+     * used when parsing address headers in mail messages.
+     *
+     * @param	addresslist	comma separated address strings
+     * @param	strict		enforce RFC822 syntax
+     * @return			array of InternetAddress objects
+     * @exception	AddressException if the parse failed
+     * @since		JavaMail 1.3
+     */
+    public static InternetAddress[] parseHeader(String s, boolean strict)
+					    throws AddressException
+
+
+To allow applications to check the syntax of addresses that might've
+been parsed with the strict flag set to false, we add a validate
+method.
+
+    /**
+     * Validate that this address conforms to the syntax rules
+     * of RFC 822.  The current implementation checks many, not
+     * all, syntax rules.  Note that, even though the syntax of
+     * the address may be correct, there's no guarantee that a
+     * mailbox of that name exists.
+     *
+     * @exception	AddressException if the address
+     *			isn't valid.
+     * @since		JavaMail 1.3
+     */
+    public void validate() throws AddressException
+
+
+To control the strict flag when constructing a single InternetAddress
+object we add a new constructor.
+
+    /**
+     * Parse the given string and create an InternetAddress.
+     * If <code>strict</code> is false, the detailed syntax of the
+     * address isn't checked.
+     *
+     * @param	address		the address in RFC822 format
+     * @param	strict		enforce RFC822 syntax
+     * @exception		AddressException if the parse failed
+     * @since			JavaMail 1.3
+     */
+    public InternetAddress(String address, boolean strict)
+						throws AddressException
+
+===================================================================
+
+6. Add mail.mime.address.strict property (4650940)
+--------------------------------------------------
+
+The MimeMessage class will use the new parseHeader method introduced
+above to parse headers in messages.  The "mail.mime.address.strict"
+Session property will control the strict flag passed to the parseHeader
+method.  The default is true.
+
+===================================================================
+
+7. Add mail.mime.decodetext.strict property (4201203)
+-----------------------------------------------------
+
+RFC 2047 requires that encoded text start at the beginning of a
+whitespace separated word.  Some mailers, especially Japanese mailers,
+improperly encode text and included encoded text in the middle of words.
+The "mail.mime.decodetext.strict" System property (NOTE: *not* Session
+property) controls whether JavaMail will attempt to decode such
+incorrectly encoded text.  The default is true.
+
+===================================================================
+
+8. Add mail.mime.encodeeol.strict property (4650949)
+----------------------------------------------------
+
+When choosing an encoding for the data of a message, JavaMail assumes
+that any of CR, LF, or CRLF are valid line terminators in message
+parts that contain only printable ASCII characters, even if the part is
+not a MIME text type.  It's common, especially on UNIX systems, for
+data of MIME type application/octet-stream (for example) to really be
+textual data that should be transmitted with the encoding rules for
+MIME text.  In rare cases, such pure ASCII text may in fact be binary
+data in which the CR and LF characters must be preserved exactly.  The
+"mail.mime.encodeeol.strict" System property (NOTE: *not* Session
+property) controls whether JavaMail will consider a lone CR or LF in a
+body part that's not a MIME text type to indicate that the body part
+needs to be encoded.
+
+===================================================================
+
+9. Add isGroup and getGroup methods to InternetAddress (4650952)
+----------------------------------------------------------------
+
+To better support RFC822 group addresses, the following methods
+would be added.
+
+    /**
+     * Indicates whether this address is an RFC 822 group address.
+     * Note that a group address is different than the mailing
+     * list addresses supported by most mail servers.  Group addresses
+     * are rarely used; see RFC 822 for details.
+     *
+     * @return		true if this address represents a group
+     * @since		JavaMail 1.3
+     */
+    public boolean isGroup()
+
+    /**
+     * Return the members of a group address.  A group may have zero,
+     * one, or more members.  If this address is not a group, null
+     * is returned.  The <code>strict</code> parameter controls whether
+     * the group list is parsed using strict RFC 822 rules or not.
+     * The parsing is done using the <code>parseHeader</code> method.
+     *
+     * @return		array of InternetAddress objects, or null
+     * @exception	AddressException if the group list can't be parsed
+     * @since		JavaMail 1.3
+     */
+    public InternetAddress[] getGroup(boolean strict) throws AddressException
+
+
+===================================================================
+
+10. Support per-session debug output stream (4517686)
+-----------------------------------------------------
+
+To allow the debugging output for a session to be redirected, we add
+the following methods to Session.
+
+    /**
+     * Set the stream to be used for debugging output for this session.
+     * If <code>out</code> is null, <code>System.out</code> will be used.
+     * Note that debugging output that occurs before any session is created,
+     * as a result of setting the <code>mail.debug</code> system property,
+     * will always be sent to <code>System.out</code>.
+     *
+     * @param	out	the PrintStream to use for debugging output
+     * @since		JavaMail 1.3
+     */
+    public void setDebugOut(PrintStream out)
+
+    /**
+     * Returns the stream to be used for debugging output.  If no stream
+     * has been set, <code>System.out</code> is returned.
+     *
+     * @return		the PrintStream to use for debugging output
+     * @since		JavaMail 1.3
+     */
+    public PrintStream getDebugOut()
diff --git a/doc/spec/JavaMail-1.4-changes.txt b/doc/spec/JavaMail-1.4-changes.txt
new file mode 100644
index 0000000..d030996
--- /dev/null
+++ b/doc/spec/JavaMail-1.4-changes.txt
@@ -0,0 +1,1061 @@
+
+		JavaMail 1.4
+		============
+
+		(Updated August 15, 2005)
+
+Following is a description of the new APIs that are being
+introduced in JavaMail 1.4.  The numbers in parentheses
+are bug numbers; you can find more information about the
+bug reports at:
+
+    http://bugs.sun.com/bugdatabase/index.jsp
+
+Please send comments and feedback to javamail@sun.com.
+
+Many of these changes expand JavaMail's conformance with Internet
+standards, or make JavaMail more tolerant of messages that don't
+quite conform to the standards.  "Be liberal in what you receive
+and conservative in what you send."
+
+JavaMail 1.4 will also require at least J2SE 1.4.  This allows
+JavaMail to take advantage of features of more modern J2SE releases.
+
+
+===================================================================
+
+1. Add MimePart.setText(text, charset, subtype) method (6300765)
+----------------------------------------------------------------
+
+The setText method is a convenience method used to set the content
+for a text/plain part.  With the increased use of HTML and XML in
+mail messages, it would be useful to have a convenience method to
+set content of those types as well.  To support this usage we add
+a new method to the MimePart interface:
+
+    /**
+     * Convenience method that sets the given String as this part's
+     * content, with a primary MIME type of "text" and the specified
+     * MIME subtype.  The given Unicode string will be charset-encoded
+     * using the specified charset. The charset is also used to set
+     * the "charset" parameter.
+     *
+     * @param	text	the text content to set
+     * @param	charset	the charset to use for the text
+     * @param	subtype	the MIME subtype to use (e.g., "html")
+     * @exception	MessagingException	if an error occurs
+     * @since	JavaMail 1.4
+     */
+    public void setText(String text, String charset, String subtype)
+                        throws MessagingException;
+
+The MimeMessage and MimeBodyPart classes, which implement the
+MimePart interface, will be updated to provide implementations
+of the new method.
+
+
+===================================================================
+
+2. Add mail.mime.encodefilename and decodefilename properties (6300768)
+-----------------------------------------------------------------------
+
+According to the MIME spec (RFC 2047), filenames included in the
+filename parameter of the Content-Disposition header may not
+include MIME "encoded-words", and thus may contain only US-ASCII
+characters.  However, many mailers violate this spec requirement
+and use standard MIME encoding techniques to store non-ASCII
+filenames in this filename parameter.
+
+If the mail.mime.encodefilename System property is set to "true".
+the MimeMessage and MimeBodyPart setFileName methods will use the
+MimeUtility.encodeText method to encode the filename.
+
+If the mail.mime.decodefilename System property is set to "true".
+the MimeMessage and MimeBodyPart getFileName methods will use the
+MimeUtility.decodeText method to decode the filename.
+
+Both of these properties default to "false".
+
+The following text is added to the MimeMessage and MimeBodyPart
+setFileName methods:
+
+     * If the <code>mail.mime.encodefilename</code> System property
+     * is set to true, the {@link MimeUtility#encodeText
+     * MimeUtility.encodeText method will be used to encode the
+     * filename.  While such encoding is not supported by the MIME
+     * spec, many mailers use this technique to support non-ASCII
+     * characters in filenames.  The default value of this property
+     * is false.
+
+The following text is added to the MimeMessage and MimeBodyPart
+getFileName methods:
+
+     * If the <code>mail.mime.encodefilename</code> System property
+     * is set to true, the {@link MimeUtility#decodeText
+     * MimeUtility.decodeText method will be used to decode the
+     * filename.  While such encoding is not supported by the MIME
+     * spec, many mailers use this technique to support non-ASCII
+     * characters in filenames.  The default value of this property
+     * is false.
+
+
+===================================================================
+
+3. Add Service.connect(user, password) (6300771)
+------------------------------------------------
+
+This convenience method uses the host already known to the Service
+(Transport or Store).  Equivalent to connect(null, user, password).
+
+    /**
+     * Connect to the current host using the specified username
+     * and password.  This method is equivalent to calling the
+     * <code>connect(host, user, password)</code> method with null
+     * for the host name.
+     *
+     * @param user      the user name
+     * @param password  this user's password
+     * @exception AuthenticationFailedException for authentication failures
+     * @exception MessagingException            for other failures
+     * @exception IllegalStateException if the service is already connected
+     * @see javax.mail.event.ConnectionEvent
+     * @see javax.mail.Session#setPasswordAuthentication
+     * @see #connect(java.lang.String, java.lang.String, java.lang.String)
+     * @since		JavaMail 1.4
+     */
+    public void connect(String user, String password) throws MessagingException
+
+
+===================================================================
+
+4. Add mail.mime.multipart.ignoremissingendboundary System property (4971381)
+-----------------------------------------------------------------------------
+
+The current implementation of the MimeMultipart class will
+ignore a missing end boundary line; if EOF is reached when
+parsing the content before seeing an end boundary line, the
+last part of the multipart is terminated and no error is
+returned.
+
+Some users have requested a way to force the multipart
+parsing to more strictly enforce the MIME specification.
+To support this we we introduce a new System property:
+
+    mail.mime.multipart.ignoremissingendboundary
+
+If this property is set to "false" MimeMultipart will throw a
+MessagingException when parsing a multipart that does not
+include the proper end boundary line.
+
+This property is already supported as part of the JavaMail
+implementation.  This change makes the property a part of the
+standard API.
+
+ * The <code>mail.mime.multipart.ignoremissingendboundary</code>
+ * System property may be set to <code>false</code> to cause a
+ * <code>MessagingException</code> to be thrown if the multipart
+ * data does not end with the required end boundary line.  If this
+ * property is set to <code>true</code> or not set, missing end
+ * boundaries are not considered an error and the final body part
+ * ends at the end of the data. <p>
+
+
+===================================================================
+
+5. Add MimeMultipart.isComplete() method (6300811)
+--------------------------------------------------
+
+As described above, parsing of a MIME multipart may terminate
+without an error, even though no final boundary line was seen.
+This method will return true if the final boundary line was
+seen.  This will allow applications to successfully parse
+mal-formed messages, while also being able to tell that they
+were mal-formed.
+
+    /**
+     * Return true if the final boundary line for this
+     * multipart was seen.  When parsing multipart content,
+     * this class will (by default) terminate parsing with
+     * no error if the end of input is reached before seeing
+     * the final multipart boundary line.  In such a case,
+     * this method will return false.  (If the System property
+     * "mail.mime.multipart.ignoremissingendboundary" is set to
+     * false, parsing such a message will instead throw a
+     * MessagingException.)
+     *
+     * @return	true if the final boundary line was seen
+     * @since		JavaMail 1.4
+     */
+    public boolean isComplete() throws MessagingException
+
+
+===================================================================
+
+6. Add mail.mime.multipart.ignoremissingboundaryparameter property (6300814)
+----------------------------------------------------------------------------
+
+The following property is already supported as part of the JavaMail
+implementation.  This change makes the property a part of the
+standard API.
+
+ * The <code>mail.mime.multipart.ignoremissingboundaryparameter</code>
+ * System property may be set to <code>false</code> to cause a
+ * <code>MessagingException</code> to be thrown if the Content-Type
+ * of the MimeMultipart does not include a <code>boundary</code> parameter.
+ * If this property is set to <code>true</code> or not set, the multipart
+ * parsing code will look for a line that looks like a bounary line and
+ * use that as the boundary separating the parts.
+
+
+===================================================================
+
+7. Add MimeMultipart getPreamble and setPreamble methods (6300828)
+------------------------------------------------------------------
+
+In a MIME multipart message, it's possible to include text between
+the headers and the first boundary line.  This text is called the
+preamble.  It may include instructions for users of non-MIME
+compliant software.  The getPreamble method allows access to this
+text when available.  (Note that IMAP servers provide no convenient
+access to this text.)  The setPreamble method allows an application
+to set the preamble for a message being constructed.
+
+    /**
+     * Get the preamble text, if any, that appears before the
+     * first body part of this multipart.  Some protocols,
+     * such as IMAP, will not allow access to the preamble text.
+     *
+     * @return		the preamble text, or null if no preamble
+     * @since		JavaMail 1.4
+     */
+    public String getPreamble() throws MessagingException
+
+    /**
+     * Set the preamble text to be included before the first
+     * body part.  Applications should generally not include
+     * any preamble text.  In some cases it may be helpful to
+     * include preamble text with instructions for users of
+     * pre-MIME software.
+     *
+     * @param	preamble	the preamble text
+     * @since		JavaMail 1.4
+     */
+    public void setPreamble(String preamble) throws MessagingException
+
+
+===================================================================
+
+8. Add MimeMessage.updateMessageID() protected method (6300831)
+---------------------------------------------------------------
+
+Some applications want more control over the data that's used
+to create the Message-ID for a message.  This method allows
+an application to provide a simple subclass of MimeMessage
+that overrides the Message-ID algorithm.
+
+    /**
+     * Update the Message-ID header.  This method is called
+     * by the <code>updateHeaders</code> and allows a subclass
+     * to override only the algorithm for choosing a Message-ID.
+     *
+     * @since		JavaMail 1.4
+     */
+    protected void updateMessageID() throws MessagingException
+
+
+===================================================================
+
+9. Add MimeMessage.createMimeMessage() protected method (6300833)
+-----------------------------------------------------------------
+
+The MimeMessage.reply method creates and returns a new MimeMessage.
+Subclasses of MimeMessage may need the reply method to create a new
+message of the appropriate subclass.  This method allows subclasses
+to control the class created in this case.
+
+    /**
+     * Create and return a MimeMessage object.  The reply method
+     * uses this method to create the MimeMessage object that it
+     * will return.  Subclasses can override this method to return
+     * a subclass of MimeMessage.  This implementation simply constructs
+     * and returns a MimeMessage object using the supplied Session.
+     *
+     * @param	session	the Session to use for the new message
+     * @return		the new MimeMessage object
+     * @since		JavaMail 1.4
+     */
+    protected MimeMessage createMimeMessage(Session session)
+				throws MessagingException
+
+
+===================================================================
+
+10. Make the "part" field of MimePartDataSource protected (6300834)
+-------------------------------------------------------------------
+
+Subclasses of MimePartDataSource may need access to the "part"
+field in order to implement the getInputStream method.  The
+part field is currently private, this change will make it protected.
+
+	/**
+	 * The MimePart that provides the data for this DataSource.
+	 *
+	 * @since	JavaMail 1.4
+	 */
+	protected MimePart part;
+
+
+===================================================================
+
+11. Folder.getSeparator should not require the folder to exist (6301381)
+------------------------------------------------------------------------
+
+IMAP folders are able to determine the separator character without
+knowing whether the folder exists.  Checking whether the folder
+exists in order to throw FolderNotFoundException introduces additional
+overhead.  Because other methods often need to know the separator
+character, this overhead can be noticable.  The specification of this
+method is changed as follows:
+
+    /**
+     * Return the delimiter character that separates this Folder's pathname
+     * from the names of immediate subfolders. This method can be invoked 
+     * on a closed Folder.
+     *
+     * @exception 	FolderNotFoundException if the implementation
+     *			requires the folder to exist, but it does not
+     * @return          Hierarchy separator character
+     */
+    public abstract char getSeparator() throws MessagingException;
+
+
+===================================================================
+
+12. Add PreencodedMimeBodyPart class (6301386)
+----------------------------------------------
+
+In some cases an application will have data that has already
+been encoded using (for example) base64 encoding.  There should
+be an easy way to attach such data to a message without the need
+to decode it and reencode it.  This class provides such support.
+
+/**
+ * A MimeBodyPart that handles data that has already been encoded.
+ * This class is useful when constructing a message and attaching
+ * data that has already been encoded (for example, using base64
+ * encoding).  The data may have been encoded by the application,
+ * or may have been stored in a file or database in encoded form.
+ * The encoding is supplied when this object is created.  The data
+ * is attached to this object in the usual fashion, by using the
+ * <code>setText</code>, <code>setContent</code>, or
+ * <code>setDataHandler</code> methods.
+ *
+ * @since	JavaMail 1.4
+ */
+
+public class PreencodedMimeBodyPart extends MimeBodyPart {
+    /**
+     * Create a PreencodedMimeBodyPart that assumes the data is
+     * encoded using the specified encoding.  The encoding must
+     * be a MIME supported Content-Transfer-Encoding.
+     */
+    public PreencodedMimeBodyPart(String encoding)
+}
+
+
+===================================================================
+
+13. Add MimeBodyPart attachFile and saveFile methods (6301390)
+--------------------------------------------------------------
+
+It's very common for applications to create messages with files
+as attachments, and to receive attachments and save them in files.
+To simplify this usable, we add several convenience methods to the
+MimeBodyPart class:
+
+    /**
+     * Use the specified file to provide the data for this part.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The encoding will be chosen appropriately for the
+     * file data.
+     *
+     * @param		file		the File object to attach
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void attachFile(File file) throws IOException, MessagingException
+
+    /**
+     * Use the specified file to provide the data for this part.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The encoding will be chosen appropriately for the
+     * file data.
+     *
+     * @param		file		the name of the file to attach
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void attachFile(String file) throws IOException, MessagingException
+
+    /**
+     * Save the contents of this part in the specified file.  The content
+     * is decoded and saved, without any of the MIME headers.
+     *
+     * @param		file		the File object to write to
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void saveFile(File file) throws IOException, MessagingException
+
+    /**
+     * Save the contents of this part in the specified file.  The content
+     * is decoded and saved, without any of the MIME headers.
+     *
+     * @param		file		the name of the file to write to
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void saveFile(String file) throws IOException, MessagingException
+
+
+===================================================================
+
+14. Add MimeUtility fold and unfold methods (6302118)
+--------------------------------------------------------------
+
+When dealing with long header lines, it's often necessary to fold
+the lines to avoid exceeding line length limitations.  When retrieving
+the data from such headers, the folding needs to be undone.  The JavaMail
+implementation includes private fold and unfold methods for this purpose.
+These methods should be made public.
+
+    /**
+     * Fold a string at linear whitespace so that each line is no longer
+     * than 76 characters, if possible.  If there are more than 76
+     * non-whitespace characters consecutively, the string is folded at
+     * the first whitespace after that sequence.  The parameter
+     * <code>used</code> indicates how many characters have been used in
+     * the current line; it is usually the length of the header name. <p>
+     *
+     * Note that line breaks in the string aren't escaped; they probably
+     * should be.
+     *
+     * @param	used	characters used in line so far
+     * @param	s	the string to fold
+     * @return		the folded string
+     */
+    public static String fold(int used, String s)
+
+    /**
+     * Unfold a folded header.  Any line breaks that aren't escaped and
+     * are followed by whitespace are removed.
+     *
+     * @param	s	the string to unfold
+     * @return		the unfolded string
+     */
+    public static String unfold(String s)
+
+
+===================================================================
+
+15. Allow more control over headers in InternetHeaders object (6302832)
+-----------------------------------------------------------------------
+
+Some applications, such as mail server applications, need more control
+over the order of headers in the InternetHeaders class.  To support
+such usage, we allow such applications to subclass InternetHeaders
+and access the List of headers directly.  InternetHeaders exposes a
+protected field:
+
+    protected List headers;
+
+The elements of the list are objects of a new protected final class
+InternetHeaders.InternetHeader that extends the javax.mail.Header class.
+To allow the InternetHeader class to make use of the Header class, we
+make the following fields of Header protected:
+
+    /**
+     * The name of the header.
+     *
+     * @since	JavaMail 1.4
+     */
+    protected String name;
+
+    /**
+     * The value of the header.
+     *
+     * @since	JavaMail 1.4
+     */
+    protected String value;
+
+
+===================================================================
+
+16. Allow applications to dynamically register new protocol providers (6302835)
+-----------------------------------------------------------------------
+
+Some applications would like to register new protocol providers at runtime
+rather than depending on the JavaMail configuration files and resources.
+To support such usage we make the constructor for the Provider class
+public:
+
+    /**
+     * Create a new provider of the specified type for the specified
+     * protocol.  The specified class implements the provider.
+     *
+     * @param type      Type.STORE or Type.TRANSPORT
+     * @param protocol  valid protocol for the type
+     * @param classname class name that implements this protocol
+     * @param vendor    optional string identifying the vendor (may be null)
+     * @param version   optional implementation version string (may be null)
+     * @since JavaMail 1.4
+     */
+    public Provider(Type type, String protocol, String classname, 
+	     String vendor, String version)
+
+We also add a new method to Session to allow registering such Providers:
+
+    /**
+     * Add a provider to the session.
+     *
+     * @param	provider	the provider to add
+     * @since	JavaMail 1.4
+     */
+    public void addProvider(Provider provider)
+
+
+===================================================================
+
+17. Allow applications to dynamically register address type mappings (4377727)
+------------------------------------------------------------------------------
+
+Along with the above item, some applications will want to dynamically
+control the mapping from address type to protocol.  This could also
+be used to change the default internet protocol from "smtp" to "smtps".
+We add the following method to Session:
+
+    /**
+     * Set the default transport protocol to use for addresses of
+     * the specified type.  Normally the default is set by the
+     * <code>javamail.default.address.map</code> or
+     * <code>javamail.address.map</code> files or resources.
+     *
+     * @param	addresstype	type of address
+     * @param	protocol	name of protocol
+     * @see #getTransport(Address)
+     * @since	JavaMail 1.4
+     */
+    public void setProtocolForAddress(String addresstype, String protocol)
+
+
+===================================================================
+
+18. ParameterList class should support non US-ASCII parameters (4107342)
+------------------------------------------------------------------------
+
+RFC 2231 describes a method for encoding non-ASCII parameters in MIME
+headers.  We introduce the following System properties to control
+encoding and decoding such parameters.
+
+If the mail.mime.encodeparameters System property is set to "true".
+non-ASCII parameters will be encoded per RFC 2231.
+
+If the mail.mime.decodeparameters System property is set to "true".
+parameters encoded per RFC 2231 will be decoded.
+
+Both of these properties default to "false".
+
+Note that RFC 2231 also describes a technique for splitting long
+parameter values across multiple parameters.  We do not plan to
+support such parameter continuations.
+
+To allow specifying the charset to use for a parameter, we add
+the following method to ParameterList:
+
+    /**
+     * Set a parameter. If this parameter already exists, it is
+     * replaced by this new value.  If the
+     * <code>mail.mime.encodeparameters</code> System property
+     * is true, and the parameter value is non-ASCII, it will be
+     * encoded with the specified charset.
+     *
+     * @param	name 	name of the parameter.
+     * @param	value	value of the parameter.
+     * @param	charset	charset of the parameter value.
+     * @since	JavaMail 1.4
+     */
+    public void set(String name, String value, String charset)
+
+
+===================================================================
+
+19. Standard interface for Stores that support quotas (6304051)
+---------------------------------------------------------------
+
+Some IMAP stores support quotas.  To allow applications to make
+use of quota support without depending on IMAP-specific APIs,
+we provide a QuotaAwareStore interface that Stores, such as the
+IMAP Store, can implement.  We also provide a Quota class to
+represent a set of quotas for a quota root.
+
+package javax.mail;
+
+/**
+ * An interrface implemented by Stores that support quotas.
+ * The {@link #getQuota getQuota} and {@link #setQuota setQuota} methods
+ * support the quota model defined by the IMAP QUOTA extension.
+ * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
+ * for more information. <p>
+ *
+ * @since JavaMail 1.4
+ */
+public interface QuotaAwareStore {
+    /**
+     * Get the quotas for the named quota root.
+     * Quotas are controlled on the basis of a quota root, not
+     * (necessarily) a folder.  The relationship between folders
+     * and quota roots depends on the server.  Some servers
+     * might implement a single quota root for all folders owned by
+     * a user.  Other servers might implement a separate quota root
+     * for each folder.  A single folder can even have multiple
+     * quota roots, perhaps controlling quotas for different
+     * resources.
+     *
+     * @param	root	the name of the quota root
+     * @return		array of Quota objects
+     * @exception MessagingException	if the server doesn't support the
+     *					QUOTA extension
+     */
+    Quota[] getQuota(String root) throws MessagingException;
+
+    /**
+     * Set the quotas for the quota root specified in the quota argument.
+     * Typically this will be one of the quota roots obtained from the
+     * <code>getQuota</code> method, but it need not be.
+     *
+     * @param	quota	the quota to set
+     * @exception MessagingException	if the server doesn't support the
+     *					QUOTA extension
+     */
+    void setQuota(Quota quota) throws MessagingException;
+}
+
+package javax.mail;
+
+/**
+ * This class represents a set of quotas for a given quota root.
+ * Each quota root has a set of resources, represented by the
+ * <code>Quota.Resource</code> class.  Each resource has a name
+ * (for example, "STORAGE"), a current usage, and a usage limit.
+ * See RFC 2087.
+ *
+ * @since JavaMail 1.4
+ */
+
+public class Quota {
+
+    /**
+     * An individual resource in a quota root.
+     *
+     * @since JavaMail 1.4
+     */
+    public static class Resource {
+	/** The name of the resource. */
+	public String name;
+	/** The current usage of the resource. */
+	public long usage;
+	/** The usage limit for the resource. */
+	public long limit;
+
+	/**
+	 * Construct a Resource object with the given name,
+	 * usage, and limit.
+	 *
+	 * @param	name	the resource name
+	 * @param	usage	the current usage of the resource
+	 * @param	limit	the usage limit for the resource
+	 */
+	public Resource(String name, long usage, long limit)
+    }
+
+    /**
+     * The name of the quota root.
+     */
+    public String quotaRoot;
+
+    /**
+     * The set of resources associated with this quota root.
+     */
+    public Quota.Resource[] resources;
+
+    /**
+     * Create a Quota object for the named quotaroot with no associated
+     * resources.
+     *
+     * @param	quotaRoot	the name of the quota root
+     */
+    public Quota(String quotaRoot)
+
+    /**
+     * Set a resource limit for this quota root.
+     *
+     * @param	name	the name of the resource
+     * @param	limit	the resource limit
+     */
+    public void setResourceLimit(String name, long limit)
+}
+
+===================================================================
+
+20. Add ByteArrayDataSource class (4623517)
+-------------------------------------------
+
+The ByteArrayDataSource has been included in the JavaMail demo
+source code for quite some time.  Quite a few applications need
+a class of this sort.  It's time to add it as a standard API.
+To avoid conflicting with applications that have used the demo
+version, we  put this version in a new javax.mail.util package.
+
+package javax.mail.util;
+
+/**
+ * A DataSource backed by a byte array.  The byte array may be
+ * passed in directly, or may be initialized from an InputStream
+ * or a String.
+ *
+ * @since JavaMail 1.4
+ */
+public class ByteArrayDataSource implements DataSource {
+    /**
+     * Create a ByteArrayDataSource with data from the
+     * specified byte array and with the specified MIME type.
+     */
+    public ByteArrayDataSource(byte[] data, String type)
+
+    /**
+     * Create a ByteArrayDataSource with data from the
+     * specified InputStream and with the specified MIME type.
+     * The InputStream is read completely and the data is
+     * stored in a byte array.
+     */
+    public ByteArrayDataSource(InputStream is, String type) throws IOException
+
+    /**
+     * Create a ByteArrayDataSource with data from the
+     * specified String and with the specified MIME type.
+     * The MIME type should include a <code>charset</code>
+     * parameter specifying the charset to be used for the
+     * string.  If the parameter is not included, the
+     * default charset is used.
+     */
+    public ByteArrayDataSource(String data, String type) throws IOException
+
+    /**
+     * Return an InputStream for the data.
+     * Note that a new stream is returned each time
+     * this method is called.
+     */
+    public InputStream getInputStream() throws IOException
+
+    /**
+     * Return an OutputStream for the data.
+     * Writing the data is not supported; an <code>IOException</code>
+     * is always thrown.
+     */
+    public OutputStream getOutputStream() throws IOException
+
+    /**
+     * Get the MIME content type of the data.
+     */
+    public String getContentType()
+
+    /**
+     * Get the name of the data.
+     * By default, an empty string ("") is returned.
+     */
+    public String getName()
+
+    /**
+     * Set the name of the data.
+     */
+    public void setName(String name)
+}
+
+
+===================================================================
+
+21. Add SharedByteArrayInputStream class (6304189)
+--------------------------------------------------
+
+The SharedInputStream interface allows the JavaMail implementation to
+efficiently process data when parsing messages, without needing to
+make many copies of the data.  This class is an implementation of the
+SharedInputStream interface that uses a byte array as the backing store.
+
+package javax.mail.util;
+
+/**
+ * A ByteArrayInputStream that implements the SharedInputStream interface,
+ * allowing the underlying byte array to be shared between multiple readers.
+ *
+ * @since JavaMail 1.4
+ */
+public class SharedByteArrayInputStream extends ByteArrayInputStream
+				implements SharedInputStream {
+    /**
+     * Position within shared buffer that this stream starts at.
+     */
+    protected int start;
+
+    /**
+     * Create a SharedByteArrayInputStream representing the entire
+     * byte array.
+     */
+    public SharedByteArrayInputStream(byte[] buf)
+
+    /**
+     * Create a SharedByteArrayInputStream representing the part
+     * of the byte array from <code>offset</code> for <code>length</code>
+     * bytes.
+     */
+    public SharedByteArrayInputStream(byte[] buf, int offset, int length)
+
+    /**
+     * Return the current position in the InputStream, as an
+     * offset from the beginning of the InputStream.
+     *
+     * @return  the current position
+     */
+    public long getPosition()
+
+    /**
+     * Return a new InputStream representing a subset of the data
+     * from this InputStream, starting at <code>start</code> (inclusive)
+     * up to <code>end</code> (exclusive).  <code>start</code> must be
+     * non-negative.  If <code>end</code> is -1, the new stream ends
+     * at the same place as this stream.  The returned InputStream
+     * will also implement the SharedInputStream interface.
+     *
+     * @param	start	the starting position
+     * @param	end	the ending position + 1
+     * @return		the new stream
+     */
+    public InputStream newStream(long start, long end)
+}
+
+
+===================================================================
+
+22. Add SharedFileInputStream class (6304193)
+---------------------------------------------
+
+Finally, SharedFileInputStream is an implementation of the
+SharedInputStream interface that uses a file as the backing store.
+
+package javax.mail.util;
+
+/**
+ * A <code>SharedFileInputStream</code> is a
+ * <code>BufferedInputStream</code> that buffers
+ * data from the file and supports the <code>mark</code>
+ * and <code>reset</code> methods.  It also supports the
+ * <code>newStream</code> method that allows you to create
+ * other streams that represent subsets of the file.
+ * A <code>RandomAccessFile</code> object is used to
+ * access the file data.
+ *
+ * @since   JavaMail 1.4
+ */
+public class SharedFileInputStream extends BufferedInputStream
+				implements SharedInputStream {
+
+    /**
+     * The file containing the data.
+     * Shared by all related SharedFileInputStream instances.
+     */
+    protected RandomAccessFile in;
+
+    /**
+     * The normal size of the read buffer.
+     */
+    protected int bufsize;
+
+    /**
+     * The file offset that corresponds to the first byte in
+     * the read buffer.
+     */
+    protected long bufpos;
+
+    /**
+     * The file offset of the start of data in this subset of the file.
+     */
+    protected long start = 0;
+
+    /**
+     * The amount of data in this subset of the file.
+     */
+    protected long datalen;
+
+    /**
+     * Creates a <code>SharedFileInputStream</code>
+     * for the file.
+     *
+     * @param   file   the file
+     */
+    public SharedFileInputStream(File file) throws IOException
+
+    /**
+     * Creates a <code>SharedFileInputStream</code>
+     * for the named file.
+     *
+     * @param   file   the file
+     */
+    public SharedFileInputStream(String file) throws IOException
+
+    /**
+     * Creates a <code>SharedFileInputStream</code>
+     * with the specified buffer size.
+     *
+     * @param   file	the file
+     * @param   size   the buffer size.
+     * @exception IllegalArgumentException if size <= 0.
+     */
+    public SharedFileInputStream(File file, int size) throws IOException
+
+    /**
+     * Creates a <code>SharedFileInputStream</code>
+     * with the specified buffer size.
+     *
+     * @param   file	the file
+     * @param   size   the buffer size.
+     * @exception IllegalArgumentException if size <= 0.
+     */
+    public SharedFileInputStream(String file, int size) throws IOException
+
+    /**
+     * See the general contract of the <code>read</code>
+     * method of <code>InputStream</code>.
+     *
+     * @return     the next byte of data, or <code>-1</code> if the end of the
+     *             stream is reached.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public int read() throws IOException
+
+    /**
+     * Reads bytes from this stream into the specified byte array,
+     * starting at the given offset.
+     *
+     * <p> This method implements the general contract of the corresponding
+     * <code>{@link java.io.InputStream#read(byte[], int, int) read}</code>
+     * method of the <code>{@link java.io.InputStream}</code> class.
+     *
+     * @param      b     destination buffer.
+     * @param      off   offset at which to start storing bytes.
+     * @param      len   maximum number of bytes to read.
+     * @return     the number of bytes read, or <code>-1</code> if the end of
+     *             the stream has been reached.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public int read(byte b[], int off, int len) throws IOException
+
+    /**
+     * See the general contract of the <code>skip</code>
+     * method of <code>InputStream</code>.
+     *
+     * @param      n   the number of bytes to be skipped.
+     * @return     the actual number of bytes skipped.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public long skip(long n) throws IOException
+
+    /**
+     * Returns the number of bytes that can be read from this input 
+     * stream without blocking. 
+     *
+     * @return     the number of bytes that can be read from this input
+     *             stream without blocking.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public int available() throws IOException
+
+    /** 
+     * See the general contract of the <code>mark</code>
+     * method of <code>InputStream</code>.
+     *
+     * @param   readlimit   the maximum limit of bytes that can be read before
+     *                      the mark position becomes invalid.
+     * @see     #reset()
+     */
+    public void mark(int readlimit)
+
+    /**
+     * See the general contract of the <code>reset</code>
+     * method of <code>InputStream</code>.
+     * <p>
+     * If <code>markpos</code> is <code>-1</code>
+     * (no mark has been set or the mark has been
+     * invalidated), an <code>IOException</code>
+     * is thrown. Otherwise, <code>pos</code> is
+     * set equal to <code>markpos</code>.
+     *
+     * @exception  IOException  if this stream has not been marked or
+     *               if the mark has been invalidated.
+     * @see        #mark(int)
+     */
+    public void reset() throws IOException
+
+    /**
+     * Tests if this input stream supports the <code>mark</code> 
+     * and <code>reset</code> methods. The <code>markSupported</code> 
+     * method of <code>SharedFileInputStream</code> returns 
+     * <code>true</code>. 
+     *
+     * @return  a <code>boolean</code> indicating if this stream type supports
+     *          the <code>mark</code> and <code>reset</code> methods.
+     * @see     java.io.InputStream#mark(int)
+     * @see     java.io.InputStream#reset()
+     */
+    public boolean markSupported()
+
+    /**
+     * Closes this input stream and releases any system resources 
+     * associated with the stream. 
+     *
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public void close() throws IOException
+
+    /**
+     * Return the current position in the InputStream, as an
+     * offset from the beginning of the InputStream.
+     *
+     * @return  the current position
+     */
+    public long getPosition()
+
+    /**
+     * Return a new InputStream representing a subset of the data
+     * from this InputStream, starting at <code>start</code> (inclusive)
+     * up to <code>end</code> (exclusive).  <code>start</code> must be
+     * non-negative.  If <code>end</code> is -1, the new stream ends
+     * at the same place as this stream.  The returned InputStream
+     * will also implement the SharedInputStream interface.
+     *
+     * @param	start	the starting position
+     * @param	end	the ending position + 1
+     * @return		the new stream
+     */
+    public InputStream newStream(long start, long end)
+
+    /**
+     * Force this stream to close.
+     */
+    protected void finalize() throws Throwable
+}
diff --git a/doc/spec/JavaMail-1.5-changes.txt b/doc/spec/JavaMail-1.5-changes.txt
new file mode 100644
index 0000000..655400d
--- /dev/null
+++ b/doc/spec/JavaMail-1.5-changes.txt
@@ -0,0 +1,823 @@
+
+		JavaMail 1.5
+		============
+
+		(Updated February 7, 2013)
+
+Following is a description of the changes to the JavaMail
+API introduced in JavaMail 1.5.  The numbers in parentheses
+are bug numbers; you can find more information about the
+bug reports at:
+
+    https://github.com/javaee/javamail/issues/<bug number>
+
+Please send comments and feedback to javamail_ww@oracle.com.
+
+JavaMail 1.5 will also require at least J2SE 5.0.  This allows
+JavaMail to take advantage of features of more modern Java SE releases.
+
+
+===================================================================
+
+1. Add FetchProfile.Item.SIZE (37)
+----------------------------------
+
+The FetchProfile.Item.SIZE item allows prefetching the size of
+a message.  Previously this was an IMAP-specific fetch item.
+
+	/**
+	 * SIZE is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the sizes of the messages in the specified 
+	 * range should be prefetched. <p>
+	 *
+	 * @since	JavaMail 1.5
+	 */
+	public static final Item SIZE;
+
+
+===================================================================
+
+2. Fix protected fields in final classes in javax.mail.search (38)
+------------------------------------------------------------------
+
+Several final classes in the javax.mail.search package contain
+protected fields.  Since the classes are final, they can't be
+subclassed, and the protected fields can not be accessed.  This
+change cleans up these fields by making them private.  The following
+fields are changed:
+
+javax.mail.search.AndTerm:
+    private SearchTerm[] terms;
+
+javax.mail.search.FlagTerm:
+    private boolean set;
+    private Flags flags;
+
+javax.mail.search.HeaderTerm:
+    private String headerName;
+
+javax.mail.search.NotTerm:
+    private SearchTerm term;
+
+javax.mail.search.OrTerm:
+    private SearchTerm[] terms;
+
+javax.mail.search.RecipientTerm:
+    private Message.RecipientType type;
+
+
+===================================================================
+
+3. Add MimeMultipart(String subtype, BodyPart... bps) constructor (39)
+----------------------------------------------------------------------
+
+These convenience constructors create a MimeMultipart object given an
+array or varargs list of BodyParts.
+
+    /**
+     * Construct a MimeMultipart object of the default "mixed" subtype,
+     * and with the given body parts.  More body parts may be added later.
+     *
+     * @since	JavaMail 1.5
+     */
+    public MimeMultipart(BodyPart... parts) throws MessagingException
+
+    /**
+     * Construct a MimeMultipart object of the given subtype
+     * and with the given body parts.  More body parts may be added later.
+     *
+     * @since	JavaMail 1.5
+     */
+    public MimeMultipart(String subtype, BodyPart... parts)
+				throws MessagingException
+
+
+===================================================================
+
+4. Exceptions should support exception chaining (40)
+----------------------------------------------------
+
+javax.mail.MessagingException was designed before exception chainging
+was added to Java SE, but it does support a similar concept itself,
+and that support should be made available to all subclasses.
+
+javax.mail.AuthenticationFailedException:
+   /**
+    * Constructs an AuthenticationFailedException with the specified
+    * detail message and embedded exception.  The exception is chained
+    * to this exception.
+    *
+    * @param message	The detailed error message
+    * @param e		The embedded exception
+    * @since		JavaMail 1.5
+    */
+   public AuthenticationFailedException(String message, Exception e)
+
+javax.mail.FolderClosedException:
+   /**
+    * Constructs a FolderClosedException with the specified
+    * detail message and embedded exception.  The exception is chained
+    * to this exception.
+    *
+    * @param folder 	The Folder
+    * @param message	The detailed error message
+    * @param e		The embedded exception
+    * @since		JavaMail 1.5
+    */
+   public FolderClosedException(Folder folder, String message, Exception e)
+
+javax.mail.FolderNotFoundException:
+   /**
+    * Constructs a FolderNotFoundException with the specified
+    * detail message and embedded exception.  The exception is chained
+    * to this exception.
+    *
+    * @param folder	The Folder
+    * @param s		The detailed error message
+    * @param e		The embedded exception
+    * @since		JavaMail 1.5
+    */
+   public FolderNotFoundException(Folder folder, String s, Exception e)
+
+javax.mail.IllegalWriteException:
+   /**
+    * Constructs an IllegalWriteException with the specified
+    * detail message and embedded exception.  The exception is chained
+    * to this exception.
+    *
+    * @param s		The detailed error message
+    * @param e		The embedded exception
+    * @since		JavaMail 1.5
+    */
+   public IllegalWriteException(String s, Exception e)
+
+javax.mail.MessageRemovedException:
+   /**
+    * Constructs a MessageRemovedException with the specified
+    * detail message and embedded exception.  The exception is chained
+    * to this exception.
+    *
+    * @param s		The detailed error message
+    * @param e		The embedded exception
+    * @since		JavaMail 1.5
+    */
+   public MessageRemovedException(String s, Exception e)
+
+javax.mail.MethodNotSupportedException:
+   /**
+    * Constructs a MethodNotSupportedException with the specified
+    * detail message and embedded exception.  The exception is chained
+    * to this exception.
+    *
+    * @param s		The detailed error message
+    * @param e		The embedded exception
+    * @since		JavaMail 1.5
+    */
+   public MethodNotSupportedException(String s, Exception e)
+
+javax.mail.NoSuchProviderException:
+   /**
+    * Constructs a NoSuchProviderException with the specified
+    * detail message and embedded exception.  The exception is chained
+    * to this exception.
+    *
+    * @param message	The detailed error message
+    * @param e		The embedded exception
+    * @since		JavaMail 1.5
+    */
+   public NoSuchProviderException(String message, Exception e)
+
+javax.mail.ReadOnlyFolderException:
+   /**
+    * Constructs a ReadOnlyFolderException with the specified
+    * detail message and embedded exception.  The exception is chained
+    * to this exception.
+    *
+    * @param folder 	The Folder
+    * @param message	The detailed error message
+    * @param e		The embedded exception
+    * @since		JavaMail 1.5
+    */
+   public ReadOnlyFolderException(Folder folder, String message, Exception e)
+
+javax.mail.StoreClosedException:
+   /**
+    * Constructs a StoreClosedException with the specified
+    * detail message and embedded exception.  The exception is chained
+    * to this exception.
+    *
+    * @param store	The dead Store object
+    * @param message	The detailed error message
+    * @param e		The embedded exception
+    * @since		JavaMail 1.5
+    */
+   public StoreClosedException(Store store, String message, Exception e)
+
+
+===================================================================
+
+5. ParameterList needs to support use by IMAP (41)
+--------------------------------------------------
+
+The IMAP provider has special needs when processing multi-segment
+parameters defined by RFC 2231.  This new method supports such use
+by the IMAP provider.
+
+   /**
+    * Normal users of this class will use simple parameter names.
+    * In some cases, for example, when processing IMAP protocol
+    * messages, individual segments of a multi-segment name
+    * (specified by RFC 2231) will be encountered and passed to
+    * the {@link #set} method.  After all these segments are added
+    * to this ParameterList, they need to be combined to represent
+    * the logical parameter name and value.  This method will combine
+    * all segments of multi-segment names. <p>
+    *
+    * Normal users should never need to call this method.
+    *
+    * @since	JavaMail 1.5
+    */ 
+   public void combineSegments()
+
+
+===================================================================
+
+6. ContentType and ContentDisposition toString should never return null (42)
+----------------------------------------------------------------------------
+
+The general contract of Object.toString is that it never returns null.
+The toString methods of ContentType and ContentDisposition were defined
+to return null in certain error cases.  Given the general toString contract
+it seems unlikely that anyone ever depended on these special cases, and
+it would be more useful for these classes to obey the general contract.
+These methods have been changed to return an empty string in these error
+cases.
+
+javax.mail.internet.ContentType:
+    /**
+     * Retrieve a RFC2045 style string representation of
+     * this Content-Type. Returns an empty string if
+     * the conversion failed.
+     *
+     * @return	RFC2045 style string
+     */
+    public String toString()
+
+javax.mail.internet.ContentDisposition:
+    /**
+     * Retrieve a RFC2045 style string representation of
+     * this ContentDisposition. Returns an empty string if
+     * the conversion failed.
+     *
+     * @return	RFC2045 style string
+     * @since		JavaMail 1.2
+     */
+    public String toString()
+
+
+===================================================================
+
+7. Add Transport.send(msg, username, password) method (44)
+----------------------------------------------------------
+
+It's now very common that email servers require authentication before
+sending a message, so we add these new convenience methods.
+
+    /**
+     * Send a message.  The message will be sent to all recipient
+     * addresses specified in the message (as returned from the
+     * <code>Message</code> method <code>getAllRecipients</code>).
+     * The <code>send</code> method calls the <code>saveChanges</code>
+     * method on the message before sending it. <p>
+     *
+     * Use the specified user name and password to authenticate to
+     * the mail server.
+     *
+     * @param	msg	the message to send
+     * @param	user	the user name
+     * @param	password this user's password
+     * @exception	SendFailedException if the message could not
+     *			be sent to some or any of the recipients.
+     * @exception	MessagingException
+     * @see		Message#saveChanges
+     * @see             #send(Message)
+     * @see		javax.mail.SendFailedException
+     * @since		JavaMail 1.5
+     */
+    public static void send(Message msg,
+		String user, String password) throws MessagingException
+
+    /**
+     * Send the message to the specified addresses, ignoring any
+     * recipients specified in the message itself. The
+     * <code>send</code> method calls the <code>saveChanges</code>
+     * method on the message before sending it. <p>
+     *
+     * Use the specified user name and password to authenticate to
+     * the mail server.
+     *
+     * @param	msg	the message to send
+     * @param	addresses the addresses to which to send the message
+     * @param	user	the user name
+     * @param	password this user's password
+     * @exception	SendFailedException if the message could not
+     *			be sent to some or any of the recipients.
+     * @exception	MessagingException
+     * @see		Message#saveChanges
+     * @see             #send(Message)
+     * @see		javax.mail.SendFailedException
+     * @since		JavaMail 1.5
+     */
+    public static void send(Message msg, Address[] addresses,
+		String user, String password) throws MessagingException
+
+
+===================================================================
+
+8. Add MimeMessage.setFrom(String) method (45)
+----------------------------------------------
+
+This new convenience method allows the From header to be set using a String.
+
+    /**
+     * Set the RFC 822 "From" header field. Any existing values are 
+     * replaced with the given addresses. If address is <code>null</code>,
+     * this header is removed.
+     *
+     * @param address	the sender(s) 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.
+     * @exception	MessagingException
+     * @since		JvaMail 1.5
+     */
+    public void setFrom(String address) throws MessagingException
+
+
+===================================================================
+
+9. Add Message.getSesssion() method (46)
+----------------------------------------
+
+Alow access to the Session used when the Message was created.
+
+    /**
+     * Return the Session used when this message was created.
+     *
+     * @return		the message's Session
+     * @since		JavaMail 1.5
+     */
+    public Session getSession()
+
+
+===================================================================
+
+10. MimeBodyPart.attachFile should set the disposition to ATTACHMENT (47)
+-------------------------------------------------------------------------
+
+An oversight when these methods were originally added.  Clearly attachments
+should set the disposition to ATTACHMENT.
+
+    /**
+     * Use the specified file to provide the data for this part.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The encoding will be chosen appropriately for the
+     * file data.  The disposition of this part is set to
+     * {@link Part#ATTACHMENT Part.ATTACHMENT}.
+     *
+     * @param		file		the File object to attach
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void attachFile(File file) throws IOException, MessagingException
+
+    /**
+     * Use the specified file to provide the data for this part.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The encoding will be chosen appropriately for the
+     * file data.
+     *
+     * @param		file		the name of the file to attach
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void attachFile(String file) throws IOException, MessagingException
+
+
+===================================================================
+
+11. Add MimeMessage.reply(replyToAll, setAnswered) method (48)
+--------------------------------------------------------------
+
+Add a method to control whether the ANSWERED flag is set in the original
+message when creating a reply message.
+
+    /**
+     * 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 <strong>not</strong> 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>
+     *
+     * If <code>setAnswered</code> is set, the
+     * {@link javax.mail.Flags.Flag#ANSWERED ANSWERED} flag is set
+     * in this message. <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 current implementation also sets the "References" header
+     * in the new message to include the contents of the "References"
+     * header (or, if missing, the "In-Reply-To" header) in this message,
+     * plus the contents of the "Message-Id" header of this message,
+     * as described in RFC 2822.
+     *
+     * @param	replyToAll	reply should be sent to all recipients
+     *				of this message
+     * @param	setAnswered	set the ANSWERED flag in this message?
+     * @return		the reply Message
+     * @exception	MessagingException
+     * @since		JavaMail 1.5
+     */
+    public Message reply(boolean replyToAll, boolean setAnswered)
+				throws MessagingException
+
+
+===================================================================
+
+12. Add additional "next" methods to HeaderTokenizer (49)
+---------------------------------------------------------
+
+These additional "next" methods make it easier to parse headers that don't
+obey the MIME syntax requirements.
+
+    /**
+     * Parses the next token from this String.
+     * If endOfAtom is not NUL, the token extends until the
+     * endOfAtom character is seen, or to the end of the header.
+     * This method is useful when parsing headers that don't
+     * obey the MIME specification, e.g., by failing to quote
+     * parameter values that contain spaces.
+     *
+     * @param	endOfAtom	if not NUL, character marking end of token
+     * @return		the next Token
+     * @exception	ParseException if the parse fails
+     * @since		JavaMail 1.5
+     */
+    public Token next(char endOfAtom) throws ParseException
+
+    /**
+     * Parses the next token from this String.
+     * endOfAtom is handled as above.  If keepEscapes is true,
+     * any backslash escapes are preserved in the returned string.
+     * This method is useful when parsing headers that don't
+     * obey the MIME specification, e.g., by failing to escape
+     * backslashes in the filename parameter.
+     *
+     * @param	endOfAtom	if not NUL, character marking end of token
+     * @param	keepEscapes	keep all backslashes in returned string?
+     * @return		the next Token
+     * @exception	ParseException if the parse fails
+     * @since		JavaMail 1.5
+     */
+    public Token next(char endOfAtom, boolean keepEscapes)
+				throws ParseException
+
+
+===================================================================
+
+13. Add @MailSessionDefinition and @MailSessionDefinitions for Java EE 7 (51)
+-----------------------------------------------------------------------------
+
+These new annotations support configuring JavaMail Session resources in
+Java EE 7 application servers.
+
+javax.mail.MailSessionDefinition:
+
+/**
+ * Annotation used by Java EE applications to define a <code>MailSession</code>
+ * to be registered with JNDI.  The <code>MailSession</code> may be configured
+ * by setting the annotation elements for commonly used <code>Session</code>
+ * properties.  Additional standard and vendor-specific properties may be
+ * specified using the <code>properties</code> element.
+ * <p/>
+ * The session will be registered under the name specified in the
+ * <code>name</code> element.  It may be defined to be in any valid
+ * <code>Java EE</code> namespace, and will determine the accessibility of
+ * the session from other components.
+ *
+ * @since JavaMail 1.5
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MailSessionDefinition {
+
+    /**
+     * Description of this mail session.
+     */
+    String description() default "";
+
+    /**
+     * JNDI name by which the mail session will be registered.
+     */
+    String name();
+
+    /**
+     * Store protocol name.
+     */
+    String storeProtocol() default "";
+
+    /**
+     * Transport protocol name.
+     */
+    String transportProtocol() default "";
+
+    /**
+     * Host name for the mail server.
+     */
+    String host() default "";
+
+    /**
+     * User name to use for authentication.
+     */
+    String user() default "";
+
+    /**
+     * Password to use for authentication.
+     */
+    String password() default "";
+
+    /**
+     * From address for the user.
+     */
+    String from() default "";
+
+    /**
+     * Properties to include in the Session.
+     * Properties are specified using the format:
+     * <i>propertyName=propertyValue</i> with one property per array element.
+     */
+    String[] properties() default {};
+}
+
+
+javax.mail.MailSessionDefinitions:
+
+import java.lang.annotation.Target;
+import java.lang.annotation.Retention;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Declares one or more <code>MailSessionDefinition</code> annotations.
+ *
+ * @see MailSessionDefinition
+ * @since JavaMail 1.5
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MailSessionDefinitions {
+    MailSessionDefinition[] value();
+}
+
+
+===================================================================
+
+14. Make cachedContent field protected in MimeMessage and MimeBodyPart (52)
+---------------------------------------------------------------------------
+
+Exposing this previously private field makes it easier to subclass these
+classes.
+
+    /**
+     * If our content is a Multipart or Message object, we save it
+     * the first time it's created by parsing a stream so that changes
+     * to the contained objects will not be lost. <p>
+     *
+     * If this field is not null, it's return by the {@link #getContent}
+     * method.  The {@link #getContent} method sets this field if it
+     * would return a Multipart or MimeMessage object.  This field is
+     * is cleared by the {@link #setDataHandler} method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected Object cachedContent;
+
+
+===================================================================
+
+15. Make MimeMultipart fields protected to allow subclassing (53)
+-----------------------------------------------------------------
+
+Most of these fields control how the MimeMultipart class parses messages
+that don't conform to the MIME spec.  The new initializeProperties method
+initializes these fields based on System properties.  Exposing these
+previously private fields makes it easier to subclass MimeMultipart.
+
+    /**
+     * Have we seen the final bounary line?
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean complete = true;
+
+    /**
+     * The MIME multipart preamble text, the text that
+     * occurs before the first boundary line.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected String preamble = null;
+
+    /**
+     * Flag corresponding to the "mail.mime.multipart.ignoremissingendboundary"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean ignoreMissingEndBoundary = true;
+
+    /**
+     * Flag corresponding to the
+     * "mail.mime.multipart.ignoremissingboundaryparameter"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean ignoreMissingBoundaryParameter = true;
+
+    /**
+     * Flag corresponding to the
+     * "mail.mime.multipart.ignoreexistingboundaryparameter"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean ignoreExistingBoundaryParameter = false;
+
+    /**
+     * Flag corresponding to the "mail.mime.multipart.allowempty"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean allowEmpty = false;
+
+    /**
+     * Initialize flags that control parsing behavior,
+     * based on System properties described above in
+     * the class documentation.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected void initializeProperties()
+
+The following additional System properties are defined corresponding to
+the last two fields above:
+
+mail.mime.multipart.ignoreexistingboundaryparameter:
+
+  Normally the boundary parameter in the Content-Type header of a multipart
+  body part is used to specify the separator between parts of the multipart
+  body.  This System property may be set to <code>"true"</code> to cause
+  the parser to look for a line in the multipart body that looks like a
+  boundary line and use that value as the separator between subsequent parts.
+  This may be useful in cases where a broken anti-virus product has rewritten
+  the message incorrectly such that the boundary parameter and the actual
+  boundary value no longer match.
+  The default value of this property is false.
+
+mail.mime.multipart.allowempty:
+
+  Normally, when writing out a MimeMultipart that contains no body
+  parts, or when trying to parse a multipart message with no body parts,
+  a <code>MessagingException</code> is thrown.  The MIME spec does not allow
+  multipart content with no body parts.  This
+  System property may be set to <code>"true"</code> to override this behavior.
+  When writing out such a MimeMultipart, a single empty part will be
+  included.  When reading such a multipart, a MimeMultipart will be created
+  with no body parts.
+  The default value of this property is false.
+
+
+===================================================================
+
+16. Need simple way to override MIME type and encoding of attachment (55)
+-------------------------------------------------------------------------
+
+First, we define an interface that allows a DataSource to specify the
+Content-Transfer-Encoding to use:
+
+package javax.mail;
+
+/**
+ * A {@link javax.activation.DataSource DataSource} that also implements
+ * <code>EncodingAware</code> may specify the Content-Transfer-Encoding
+ * to use for its data.  Valid Content-Transfer-Encoding values specified
+ * by RFC 2045 are "7bit", "8bit", "quoted-printable", "base64", and "binary".
+ * <p>
+ * For example, a {@link javax.activation.FileDataSource FileDataSource}
+ * could be created that forces all files to be base64 encoded: <p>
+ * <blockquote><pre>
+ *  public class Base64FileDataSource extends FileDataSource
+ *					implements EncodingAware {
+ *	public Base64FileDataSource(File file) {
+ *	    super(file);
+ *	}
+ *
+ *	// implements EncodingAware.getEncoding()
+ *	public String getEncoding() {
+ *	    return "base64";
+ *	}
+ *  }
+ * </pre></blockquote><p>
+ *
+ * @since	JavaMail 1.5
+ * @author	Bill Shannon
+ */
+
+public interface EncodingAware {
+
+    /**
+     * Return the MIME Content-Transfer-Encoding to use for this data,
+     * or null to indicate that an appropriate value should be chosen
+     * by the caller.
+     *
+     * @return		the Content-Transfer-Encoding value, or null
+     */
+    public String getEncoding();
+}
+
+Then we add new methods to MimeBodyPart:
+
+    /**
+     * Use the specified file with the specified Content-Type and
+     * Content-Transfer-Encoding to provide the data for this part.
+     * If contentType or encoding are null, appropriate values will
+     * be chosen.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The disposition of this part is set to
+     * {@link Part#ATTACHMENT Part.ATTACHMENT}.
+     *
+     * @param		file		the File object to attach
+     * @param		contentType	the Content-Type, or null
+     * @param		encoding	the Content-Transfer-Encoding, or null
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.5
+     */
+    public void attachFile(File file, String contentType, String encoding)
+				throws IOException, MessagingException
+
+    /**
+     * Use the specified file with the specified Content-Type and
+     * Content-Transfer-Encoding to provide the data for this part.
+     * If contentType or encoding are null, appropriate values will
+     * be chosen.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The disposition of this part is set to
+     * {@link Part#ATTACHMENT Part.ATTACHMENT}.
+     *
+     * @param		file		the name of the file
+     * @param		contentType	the Content-Type, or null
+     * @param		encoding	the Content-Transfer-Encoding, or null
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.5
+     */
+    public void attachFile(String file, String contentType, String encoding)
+				throws IOException, MessagingException
+
+
+===================================================================
+
+17. Enable RFC 2231 support by default (56)
+-------------------------------------------
+
+RFC 2231 support for encoded parameter values is now widely implemented,
+it's time to change the default to support this standard.  Given the
+way RFC 2231 is defined, it's extremely unlikely that this would cause
+compatibility problems with existing applications.
+
+The System properties mail.mime.decodeparameters and mail.mime.encodeparameters
+now default to true instead of false.
diff --git a/doc/spec/JavaMail-1.6-changes.txt b/doc/spec/JavaMail-1.6-changes.txt
new file mode 100644
index 0000000..a6906b6
--- /dev/null
+++ b/doc/spec/JavaMail-1.6-changes.txt
@@ -0,0 +1,677 @@
+
+		JavaMail 1.6
+		============
+
+		(Updated July 17, 2017)
+
+Following is a description of the changes to the JavaMail
+API introduced in JavaMail 1.6.  The numbers in parentheses
+are bug numbers; you can find more information about the
+bug reports at:
+
+    https://github.com/javaee/javamail/issues/<bug number>
+
+Please send comments and feedback to javamail_ww@oracle.com.
+
+JavaMail 1.6 will also require at least Java SE 7.
+
+
+===================================================================
+
+1. MailSessionDefinition should use Repeatable annotation for Java EE 8 (226)
+-----------------------------------------------------------------------------
+
+The MailSessionDefinition annotation now includes the Repeatable annotation:
+
+	@Target({ElementType.TYPE})
+	@Retention(RetentionPolicy.RUNTIME)
+	@Repeatable(MailSessionDefinitions.class)
+	public @interface MailSessionDefinition {
+	    ...
+	}
+
+The Repeatable annotation is not known to Java SE 7 and will be ignored
+(as expected) when JavaMail 1.6 is used on Java SE 7.
+
+
+===================================================================
+
+2. MimeMessage.updateHeaders should set Date header if not already set (77)
+---------------------------------------------------------------------------
+
+RFC 2822 requires a Date header.  The MimeMessage.updateHeaders method
+now sets the Date header if it's not already set:
+
+	/**
+	 * Called by the <code>saveChanges</code> method to actually
+	 * update the MIME headers.  The implementation here sets the
+	 * <code>Content-Transfer-Encoding</code> header (if needed
+	 * and not already set), the <code>Date</code> header (if
+	 * not already set), the <code>MIME-Version</code> header
+	 * and the <code>Message-ID</code> header. Also, if the content
+	 * of this message is a <code>MimeMultipart</code>, its
+	 * <code>updateHeaders</code> method is called. <p>
+	 *
+	 * If the {@link #cachedContent} field is not null (that is,
+	 * it references a Multipart or Message object), then
+	 * that object is used to set a new DataHandler, any
+	 * stream data used to create this object is discarded,
+	 * and the {@link #cachedContent} field is cleared.
+	 *
+	 * @exception       IllegalWriteException if the underlying
+	 *                  implementation does not support modification
+	 * @exception       IllegalStateException if this message is
+	 *                  obtained from a READ_ONLY folder.
+	 * @exception       MessagingException for other failures
+	 */
+	protected synchronized void updateHeaders() throws MessagingException {
+	    ...
+	}
+
+
+===================================================================
+
+3. Update public API to use generics (232)
+------------------------------------------
+
+Methods on the following APIs have been updated to use generics
+when appropriate:
+
+	javax.mail.Multipart
+	javax.mail.Part
+	javax.mail.Service
+	javax.mail.internet.InternetHeaders
+	javax.mail.internet.MimeBodyPart
+	javax.mail.internet.MimeMessage
+	javax.mail.internet.MimePart
+	javax.mail.internet.ParameterList
+
+Details follow:
+
+diff -r 1f6b2c17e291 mail/src/main/java/javax/mail/Multipart.java
+- a/mail/src/main/java/javax/mail/Multipart.java
++++ b/mail/src/main/java/javax/mail/Multipart.java
+@@ -72,2 +72,1 @@
+-    @SuppressWarnings("rawtypes")
+-    protected Vector parts = new Vector(); // Holds BodyParts
++    protected Vector<BodyPart> parts = new Vector<>(); // Holds BodyParts
+diff -r 1f6b2c17e291 mail/src/main/java/javax/mail/Part.java
+- a/mail/src/main/java/javax/mail/Part.java
++++ b/mail/src/main/java/javax/mail/Part.java
+@@ -452,2 +452,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getAllHeaders() throws MessagingException;
++    public Enumeration<Header> getAllHeaders() throws MessagingException;
+@@ -463,2 +462,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getMatchingHeaders(String[] header_names)
++    public Enumeration<Header> getMatchingHeaders(String[] header_names)
+@@ -475,2 +473,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getNonMatchingHeaders(String[] header_names) 
++    public Enumeration<Header> getNonMatchingHeaders(String[] header_names) 
+diff -r 1f6b2c17e291 mail/src/main/java/javax/mail/Service.java
+- a/mail/src/main/java/javax/mail/Service.java
++++ b/mail/src/main/java/javax/mail/Service.java
+@@ -640,2 +640,2 @@
+-    @SuppressWarnings("rawtypes")
+-    protected void queueEvent(MailEvent event, Vector vector) {
++    protected void queueEvent(MailEvent event,
++	    Vector<? extends EventListener> vector) {
+diff -r 1f6b2c17e291 mail/src/main/java/javax/mail/internet/InternetHeaders.java
+- a/mail/src/main/java/javax/mail/internet/InternetHeaders.java
++++ b/mail/src/main/java/javax/mail/internet/InternetHeaders.java
+@@ -300,2 +300,1 @@
+-    @SuppressWarnings("rawtypes")
+-    protected List headers;
++    protected List<InternetHeader> headers;
+@@ -589,2 +585,1 @@
+-    @SuppressWarnings({"rawtypes", "unchecked"})
+-    public Enumeration getAllHeaders() {
++    public Enumeration<Header> getAllHeaders() {
+@@ -600,2 +595,1 @@
+-    @SuppressWarnings({"rawtypes", "unchecked"})
+-    public Enumeration getMatchingHeaders(String[] names) {
++    public Enumeration<Header> getMatchingHeaders(String[] names) {
+@@ -611,2 +605,1 @@
+-    @SuppressWarnings({"rawtypes", "unchecked"})
+-    public Enumeration getNonMatchingHeaders(String[] names) {
++    public Enumeration<Header> getNonMatchingHeaders(String[] names) {
+@@ -649,2 +640,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getAllHeaderLines() { 
++    public Enumeration<String> getAllHeaderLines() { 
+@@ -660,2 +650,1 @@
+-    @SuppressWarnings({"rawtypes", "unchecked"})
+-    public Enumeration getMatchingHeaderLines(String[] names) {
++    public Enumeration<String> getMatchingHeaderLines(String[] names) {
+@@ -671,2 +660,1 @@
+-    @SuppressWarnings({"rawtypes", "unchecked"})
+-    public Enumeration getNonMatchingHeaderLines(String[] names) {
++    public Enumeration<String> getNonMatchingHeaderLines(String[] names) {
+diff -r 1f6b2c17e291 mail/src/main/java/javax/mail/internet/MimeBodyPart.java
+- a/mail/src/main/java/javax/mail/internet/MimeBodyPart.java
++++ b/mail/src/main/java/javax/mail/internet/MimeBodyPart.java
+@@ -1036,2 +1036,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getAllHeaders() throws MessagingException {
++    public Enumeration<Header> getAllHeaders() throws MessagingException {
+@@ -1045,2 +1044,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getMatchingHeaders(String[] names)
++    public Enumeration<Header> getMatchingHeaders(String[] names)
+@@ -1055,2 +1053,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getNonMatchingHeaders(String[] names)
++    public Enumeration<Header> getNonMatchingHeaders(String[] names)
+@@ -1073,2 +1070,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getAllHeaderLines() throws MessagingException {
++    public Enumeration<String> getAllHeaderLines() throws MessagingException {
+@@ -1083,2 +1079,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getMatchingHeaderLines(String[] names)
++    public Enumeration<String> getMatchingHeaderLines(String[] names)
+@@ -1094,2 +1089,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getNonMatchingHeaderLines(String[] names)  
++    public Enumeration<String> getNonMatchingHeaderLines(String[] names)  
+diff -r 1f6b2c17e291 mail/src/main/java/javax/mail/internet/MimeMessage.java
+- a/mail/src/main/java/javax/mail/internet/MimeMessage.java
++++ b/mail/src/main/java/javax/mail/internet/MimeMessage.java
+@@ -1992,2 +1991,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getAllHeaders() throws MessagingException {
++    public Enumeration<Header> getAllHeaders() throws MessagingException {
+@@ -2004,2 +2002,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getMatchingHeaders(String[] names)
++    public Enumeration<Header> getMatchingHeaders(String[] names)
+@@ -2017,2 +2014,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getNonMatchingHeaders(String[] names)
++    public Enumeration<Header> getNonMatchingHeaders(String[] names)
+@@ -2043,2 +2039,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getAllHeaderLines() throws MessagingException {
++    public Enumeration<String> getAllHeaderLines() throws MessagingException {
+@@ -2055,2 +2050,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getMatchingHeaderLines(String[] names)
++    public Enumeration<String> getMatchingHeaderLines(String[] names)
+@@ -2068,2 +2062,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getNonMatchingHeaderLines(String[] names)
++    public Enumeration<String> getNonMatchingHeaderLines(String[] names)
+diff -r 1f6b2c17e291 mail/src/main/java/javax/mail/internet/MimePart.java
+- a/mail/src/main/java/javax/mail/internet/MimePart.java
++++ b/mail/src/main/java/javax/mail/internet/MimePart.java
+@@ -111,2 +111,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getAllHeaderLines() throws MessagingException;
++    public Enumeration<String> getAllHeaderLines() throws MessagingException;
+@@ -123,2 +122,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getMatchingHeaderLines(String[] names)
++    public Enumeration<String> getMatchingHeaderLines(String[] names)
+@@ -136,2 +134,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getNonMatchingHeaderLines(String[] names)
++    public Enumeration<String> getNonMatchingHeaderLines(String[] names)
+diff -r 1f6b2c17e291 mail/src/main/java/javax/mail/internet/ParameterList.java
+- a/mail/src/main/java/javax/mail/internet/ParameterList.java
++++ b/mail/src/main/java/javax/mail/internet/ParameterList.java
+@@ -627,2 +627,1 @@
+-    @SuppressWarnings("rawtypes")
+-    public Enumeration getNames() {
++    public Enumeration<String> getNames() {
+
+
+===================================================================
+
+4. MailDateFormat changes for version 1.6 (174)
+-----------------------------------------------
+
+The parse(String) method currently returns null for invalid dates,
+which violates the contract of DateFormat.  It should throw ParseException.
+
+Some methods of MailDateFormat throw UnsupportedOperationException.
+The following methods should also do so:
+
+	/**
+	 * This method always throws an UnsupportedOperationException and
+	 * should not be used because RFC 2822 mandates a specific pattern.
+	 *
+	 * @throws UnsupportedOperationException if this method is invoked
+	 * @since JavaMail 1.6
+	 */
+	@Override
+	public void applyLocalizedPattern(String pattern) {
+	    throw new UnsupportedOperationException("Method "
+		    + "applyLocalizedPattern() shouldn't be called");
+	}
+
+	/**
+	 * This method always throws an UnsupportedOperationException and
+	 * should not be used because RFC 2822 mandates a specific pattern.
+	 *
+	 * @throws UnsupportedOperationException if this method is invoked
+	 * @since JavaMail 1.6
+	 */
+	@Override
+	public void applyPattern(String pattern) {
+	    throw new UnsupportedOperationException("Method "
+		    + "applyPattern() shouldn't be called");
+	}
+
+	/**
+	 * This method always throws an UnsupportedOperationException and
+	 * should not be used because RFC 2822 mandates another strategy
+	 * for interpreting 2-digits years.
+	 *
+	 * @return the start of the 100-year period into which two digit
+	 * years are parsed
+	 * @throws UnsupportedOperationException if this method is invoked
+	 * @since JavaMail 1.6
+	 */
+	@Override
+	public Date get2DigitYearStart() {
+	    throw new UnsupportedOperationException("Method "
+		    + "get2DigitYearStart() shouldn't be called");
+	}
+
+	/**
+	 * This method always throws an UnsupportedOperationException and
+	 * should not be used because RFC 2822 mandates another strategy
+	 * for interpreting 2-digits years.
+	 *
+	 * @throws UnsupportedOperationException if this method is invoked
+	 * @since JavaMail 1.6
+	 */
+	@Override
+	public void set2DigitYearStart(Date startDate) {
+	    throw new UnsupportedOperationException("Method "
+		    + "set2DigitYearStart() shouldn't be called");
+	}
+
+	/**
+	 * This method always throws an UnsupportedOperationException and
+	 * should not be used because RFC 2822 mandates specific date
+	 * format symbols.
+	 *
+	 * @throws UnsupportedOperationException if this method is invoked
+	 * @since JavaMail 1.6
+	 */
+	@Override
+	public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
+	    throw new UnsupportedOperationException("Method "
+		    + "setDateFormatSymbols() shouldn't be called");
+	}
+
+MailDateFormat should include a proper clone method:
+
+	/**
+	 * Overrides Cloneable.
+	 *
+	 * @return a clone of this instance
+	 * @since JavaMail 1.6
+	 */
+	@Override
+	public MailDateFormat clone() {
+	    return (MailDateFormat) super.clone();
+	}
+
+
+===================================================================
+
+5. Store, Transport, and Folder should implement AutoCloseable (159)
+--------------------------------------------------------------------
+
+To enable use in a try-with-resources block, the Store, Transport,
+and Folder classes should implement the java.lang.AutoCloseable interface.
+Store and Transport are subclasses of Service, which already has the
+required close() method, so having Service implement AutoCloseable is
+sufficient.  The Folder class includes a close method with a required
+"expunge" parameter so we add a new close() method with no parameter
+that behaves the same as close(true), and have Folder implement AutoCloseable.
+
+	public abstract class Service implements AutoCloseable {
+	    ...
+	}
+
+	public abstract class Folder implements AutoCloseable {
+	    ...
+ 
+	    /**
+	     * Close this Folder and expunge deleted messages. <p>
+	     *
+	     * A CLOSED ConnectionEvent is delivered to any ConnectionListeners
+	     * registered on this Folder. Note that the folder is closed even
+	     * if this method terminates abnormally by throwing a
+	     * MessagingException. <p>
+	     *
+	     * This method supports the {@link java.lang.AutoCloseable AutoCloseable}
+	     * interface. <p>
+	     *
+	     * This implementation calls <code>close(true)</code>.
+	     *
+	     * @exception	IllegalStateException if this folder is not opened
+	     * @exception       MessagingException for other failures
+	     * @see 		javax.mail.event.ConnectionEvent
+	     * @since		JavaMail 1.6
+	     */
+	    @Override
+	    public void close() throws MessagingException {
+		close(true);
+	    }
+	}
+
+
+===================================================================
+
+6. The UIDFolder interface should have a getter for UIDNEXT (104)
+-----------------------------------------------------------------
+
+The UIDFolder interface models the UID support in the IMAP protocol.
+After UIDFolder was originally created, the IMAP protocol added
+support for getting the value of the next UID that will be assigned.
+The IMAP provider in JavaMail has supported this for quite some time;
+it should be added to the UIDFolder interface:
+
+	/**
+	 * Returns the predicted UID that will be assigned to the
+	 * next message that is appended to this folder.
+	 * Messages might be appended to the folder after this value
+	 * is retrieved, causing this value to be out of date.
+	 * This value might only be updated when a folder is first opened.
+	 * Note that messages may have been appended to the folder
+	 * while it was open and thus this value may be out of
+	 * date. <p>
+	 *
+	 * If the value is unknown, -1 is returned.  <p>
+	 *
+	 * @return  the UIDNEXT value, or -1 if unknown
+	 * @exception       MessagingException for failures
+	 * @since   JavaMail 1.6
+	 */
+	public long getUIDNext() throws MessagingException;
+
+Ideally this new method added to the interface would include a default
+implementation to provide compatibility with existing classes that
+implement this method.  However, since the JavaMail 1.6 reference
+implementation targets Java SE 7, this is not possible.  It's very
+likely that the only class implementing this interface is the IMAPFolder
+class in the JavaMail reference implementation, thus this incompatibility
+is extremely unlikely to cause a problem in practice.
+
+
+===================================================================
+
+7. The UIDFolder interface should have a MAXUID constant (244)
+--------------------------------------------------------------
+
+An IMAP UID is a 32-bit unsigned integer and is represented as a Java long.
+A new constant indicates the maximum value of a UID:
+
+	/**
+	 * The largest value possible for a UID, a 32-bit unsigned integer.
+	 * This can be used to fetch all new messages by keeping track of the
+	 * last UID that was seen and using:
+	 * <blockquote><pre>
+	 *
+	 * 	Folder f = store.getFolder("whatever");
+	 *	UIDFolder uf = (UIDFolder)f;
+	 *	Message[] newMsgs =
+	 *		uf.getMessagesByUID(lastSeenUID + 1, UIDFolder.MAXUID);
+	 *
+	 * </pre></blockquote><p>
+	 *
+	 * @since JavaMail 1.6
+	 */
+	public static final long MAXUID = 0xffffffffL;
+
+
+===================================================================
+
+8. MimeMultipart should throw ParseException for parsing errors (75)
+--------------------------------------------------------------------
+
+ParseException indicates an error parsing MIME messages.  In addition
+to applying to MIME headers, it seems reasonable to expand it to cover
+multipart message parsing.  Since ParseException is a subclass of
+MessagingException, it merely reports more precisely the cause of the
+error.
+
+The description of ParseException is changed to:
+
+	  * The exception thrown due to an error in parsing RFC822 
+	  * or MIME headers, including multipart bodies.
+
+MimeMultipart documents that ParseException can be thrown from an
+existing constructor and method:
+
+	/**
+	 * Constructs a MimeMultipart object and its bodyparts from the 
+	 * given DataSource. <p>
+	 *
+	 * This constructor handles as a special case the situation where the
+	 * given DataSource is a MultipartDataSource object.  In this case, this
+	 * method just invokes the superclass (i.e., Multipart) constructor
+	 * that takes a MultipartDataSource object. <p>
+	 *
+	 * Otherwise, the DataSource is assumed to provide a MIME multipart 
+	 * byte stream.  The <code>parsed</code> flag is set to false.  When
+	 * the data for the body parts are needed, the parser extracts the
+	 * "boundary" parameter from the content type of this DataSource,
+	 * skips the 'preamble' and reads bytes till the terminating
+	 * boundary and creates MimeBodyParts for each part of the stream.
+	 *
+	 * @param	ds	DataSource, can be a MultipartDataSource
+	 * @exception	ParseException for failures parsing the message
+	 * @exception	MessagingException for other failures
+	 */
+	public MimeMultipart(DataSource ds) throws MessagingException
+
+	...
+
+	/**
+	 * Parse the InputStream from our DataSource, constructing the
+	 * appropriate MimeBodyParts.  The <code>parsed</code> flag is
+	 * set to true, and if true on entry nothing is done.  This
+	 * method is called by all other methods that need data for
+	 * the body parts, to make sure the data has been parsed.
+	 * The {@link #initializeProperties} method is called before
+	 * parsing the data.
+	 *
+	 * @exception	ParseException for failures parsing the message
+	 * @exception	MessagingException for other failures
+	 * @since	JavaMail 1.2
+	 */
+	protected synchronized void parse() throws MessagingException
+
+
+===================================================================
+
+9. Support addressing i18n via RFC 6530/6531/6532 (93)
+------------------------------------------------------
+
+To enable support for UTF-8 email addresses, the following methods
+are added to InternetAddress:
+
+	/**
+	 * Convert the given array of InternetAddress objects into
+	 * a comma separated sequence of address strings. The
+	 * resulting string contains Unicode characters. <p>
+	 *
+	 * @param addresses	array of InternetAddress objects
+	 * @exception 	ClassCastException if any address object in the 
+	 *			given array is not an InternetAddress object.
+	 *			Note that this is a RuntimeException.
+	 * @return		comma separated string of addresses
+	 * @since		JavaMail 1.6
+	 */
+	public static String toUnicodeString(Address[] addresses)
+
+	/**
+	 * Convert the given array of InternetAddress objects into
+	 * a comma separated sequence of address strings. The
+	 * resulting string contains Unicode characters. <p>
+	 *
+	 * The 'used' parameter specifies the number of character positions
+	 * already taken up in the field into which the resulting address 
+	 * sequence string is to be inserted. It is used to determine the 
+	 * line-break positions in the resulting address sequence string.
+	 *
+	 * @param addresses	array of InternetAddress objects
+	 * @param used	number of character positions already used, in
+	 *			the field into which the address string is to
+	 *			be inserted.
+	 * @exception 	ClassCastException if any address object in the 
+	 *			given array is not an InternetAddress object.
+	 *			Note that this is a RuntimeException.
+	 * @return		comma separated string of addresses
+	 * @since		JavaMail 1.6
+	 */
+	public static String toUnicodeString(Address[] addresses, int used)
+
+
+The following constructor and method are added to InternetHeaders:
+
+	/**
+	 * Read and parse the given RFC822 message stream till the 
+	 * blank line separating the header from the body. The input 
+	 * stream is left positioned at the start of the body. The 
+	 * header lines are stored internally. <p>
+	 *
+	 * For efficiency, wrap a BufferedInputStream around the actual
+	 * input stream and pass it as the parameter. <p>
+	 *
+	 * No placeholder entries are inserted; the original order of
+	 * the headers is preserved.
+	 *
+	 * @param	is 	RFC822 input stream
+	 * @param	allowutf8 	if UTF-8 encoded headers are allowed
+	 * @exception	MessagingException for any I/O error reading the stream
+	 * @since		JavaMail 1.6
+	 */
+	public InternetHeaders(InputStream is, boolean allowutf8)
+				    throws MessagingException
+
+
+	/**
+	 * Read and parse the given RFC822 message stream till the
+	 * blank line separating the header from the body. Store the
+	 * header lines inside this InternetHeaders object. The order
+	 * of header lines is preserved. <p>
+	 *
+	 * Note that the header lines are added into this InternetHeaders
+	 * object, so any existing headers in this object will not be
+	 * affected.  Headers are added to the end of the existing list
+	 * of headers, in order.
+	 *
+	 * @param	is 	RFC822 input stream
+	 * @param	allowutf8 	if UTF-8 encoded headers are allowed
+	 * @exception	MessagingException for any I/O error reading the stream
+	 * @since		JavaMail 1.6
+	 */
+	public void load(InputStream is, boolean allowutf8)
+				    throws MessagingException
+
+The following Session property can be set to enable implicit use of
+these new methods:
+
+mail.mime.allowutf8:
+
+  If set to "true", UTF-8 strings are allowed in message headers,
+  e.g., in addresses.  This should only be set if the mail server also
+  supports UTF-8.
+
+
+===================================================================
+
+10. Look for resource files in <java.home>/conf on JDK 1.9 (247)
+----------------------------------------------------------------
+
+JDK 1.9 adds a new <java.home>/conf directory to hold configuration
+files that were previously stored in <java.home>/lib.  When using
+JavaMail on JDK 1.9, it should look for its (optional) configuration
+files in the <java.home>/conf directory.
+
+The specification of the Session class is changed as follows:
+
+	/**
+	 * The Session class represents a mail session and is not subclassed.
+	 * It collects together properties and defaults used by the mail API's.
+	 * A single default session can be shared by multiple applications on
+	 * the desktop.  Unshared sessions can also be created. <p>
+	 *
+	 * The Session class provides access to the protocol providers that
+	 * implement the <code>Store</code>, <code>Transport</code>, and related
+	 * classes.  The protocol providers are configured using the following
+	 * files:
+	 * <ul>
+	 *  <li> <code>javamail.providers</code> and
+	 *      <code>javamail.default.providers</code> </li>
+	 *  <li> <code>javamail.address.map</code> and
+	 *      <code>javamail.default.address.map</code> </li>
+	 * </ul>
+	 * <p>
+	 * Each <code>javamail.</code><i>X</i> resource file is searched for
+	 * using three methods in the following order:
+	 * <ol>
+	 *  <li> <code><i>java.home</i>/<i>conf</i>/javamail.</code><i>X</i> </li>
+	 *  <li> <code>META-INF/javamail.</code><i>X</i> </li>
+	 *  <li> <code>META-INF/javamail.default.</code><i>X</i> </li>
+	 * </ol>
+	 * <p>
+	 * (Where <i>java.home</i> is the value of the "java.home" System
+	 * property and <i>conf</i> is the directory named "conf" if it exists,
+	 * otherwise the directory named "lib"; the "conf" directory was
+	 * introduced in JDK 1.9.)
+	 * <p>
+	 * The first method allows the user to include their own version of the
+	 * resource file by placing it in the <i>conf</i> directory where the
+	 * <code>java.home</code> property points.  The second method allows an
+	 * application that uses the JavaMail APIs to include their own resource
+	 * files in their application's or jar file's <code>META-INF</code>
+	 * directory.  The <code>javamail.default.</code><i>X</i> default files
+	 * are part of the JavaMail <code>mail.jar</code> file and should not be
+	 * supplied by users. <p>
+
+
+===================================================================
+
+11. Flags convenience methods (249)
+-----------------------------------
+
+When copying messages from one server to another, it's sometimes necessary
+to adjust the message flags based on the capabilities of the target server.
+It would be convenient if the Flags class had methods to clear any flags
+not supported by the target server, to clear all user flags, and to clear
+all system flags.
+
+	/**
+	 * Remove any flags <strong>not</strong> in the given Flags object.
+	 * Useful for clearing flags not supported by a server.  If the
+	 * given Flags object includes the Flags.Flag.USER flag, all user
+	 * flags in this Flags object are retained.
+	 *
+	 * @param	f	the flags to keep
+	 * @return		true if this Flags object changed
+	 * @since		JavaMail 1.6
+	 */
+	public boolean retainAll(Flags f)
+
+	/**
+	 * Clear all of the system flags.
+	 *
+	 * @since	JavaMail 1.6
+	 */
+	public void clearSystemFlags()
+
+	/**
+	 * Clear all of the user flags.
+	 *
+	 * @since	JavaMail 1.6
+	 */
+	public void clearUserFlags()
diff --git a/dsn/pom.xml b/dsn/pom.xml
new file mode 100644
index 0000000..c61952c
--- /dev/null
+++ b/dsn/pom.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>dsn</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API dsn support</name>
+
+    <properties>
+	<mail.packages.export>
+	    com.sun.mail.dsn; version=${mail.osgiversion}
+	</mail.packages.export>
+    </properties>
+
+    <build>
+	<plugins>
+	    <!--
+		Configure SpotBugs to run with "mvn findbugs:findbugs"
+		and generate XML output that can be used by the Hudson
+		FindBugs plugin.
+	    -->
+	    <plugin>
+		<groupId>com.github.spotbugs</groupId>
+		<artifactId>spotbugs-maven-plugin</artifactId>
+		<configuration>
+		    <skip>false</skip>
+		    <threshold>${findbugs.threshold}</threshold>
+		    <!--
+		    <excludeFilterFile>
+			${project.basedir}/exclude.xml
+		    </excludeFilterFile>
+		    -->
+		    <findbugsXmlWithMessages>true</findbugsXmlWithMessages>
+		</configuration>
+	    </plugin>
+	</plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+	<dependency>
+	    <groupId>junit</groupId>
+	    <artifactId>junit</artifactId>
+	    <version>4.12</version>
+	    <scope>test</scope>
+	    <optional>true</optional>
+	</dependency>
+    </dependencies>
+
+    <reporting>
+	<plugins>
+	    <!--
+		Configure SpotBugs to run with "mvn site" and
+		generate html output that can be viewed directly.
+	    -->
+	    <plugin>
+		<groupId>org.codehaus.mojo</groupId>
+		<artifactId>findbugs-maven-plugin</artifactId>
+		<configuration>
+		    <skip>false</skip>
+		    <threshold>${findbugs.threshold}</threshold>
+		    <!--
+		    <excludeFilterFile>exclude.xml</excludeFilterFile>
+		    -->
+		</configuration>
+	    </plugin>
+	</plugins>
+    </reporting>
+</project>
diff --git a/dsn/src/main/java/com/sun/mail/dsn/DeliveryStatus.java b/dsn/src/main/java/com/sun/mail/dsn/DeliveryStatus.java
new file mode 100644
index 0000000..c20e6eb
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/DeliveryStatus.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.io.*;
+import java.util.*;
+import java.util.logging.Level;
+
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.util.LineOutputStream;	// XXX
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+
+/**
+ * A message/delivery-status message content, as defined in
+ * <A HREF="http://www.ietf.org/rfc/rfc3464.txt" TARGET="_top">RFC 3464</A>.
+ *
+ * @since	JavaMail 1.4
+ */
+public class DeliveryStatus extends Report {
+
+    private static MailLogger logger = new MailLogger(
+	DeliveryStatus.class,
+	"DEBUG DSN",
+	PropUtil.getBooleanSystemProperty("mail.dsn.debug", false),
+	System.out);
+
+    /**
+     * The DSN fields for the message.
+     */
+    protected InternetHeaders messageDSN;
+
+    /**
+     * The DSN fields for each recipient.
+     */
+    protected InternetHeaders[] recipientDSN;
+
+    /**
+     * Construct a delivery status notification with no content.
+     *
+     * @exception	MessagingException for failures
+     */
+    public DeliveryStatus() throws MessagingException {
+	super("delivery-status");
+	messageDSN = new InternetHeaders();
+	recipientDSN = new InternetHeaders[0];
+    }
+
+    /**
+     * Construct a delivery status notification by parsing the
+     * supplied input stream.
+     *
+     * @param	is	the input stream
+     * @exception	IOException for I/O errors reading the stream
+     * @exception	MessagingException for other failures
+     */
+    public DeliveryStatus(InputStream is)
+				throws MessagingException, IOException {
+	super("delivery-status");
+	messageDSN = new InternetHeaders(is);
+	logger.fine("got messageDSN");
+	Vector<InternetHeaders> v = new Vector<>();
+	try {
+	    while (is.available() > 0) {
+		InternetHeaders h = new InternetHeaders(is);
+		logger.fine("got recipientDSN");
+		v.addElement(h);
+	    }
+	} catch (EOFException ex) {
+	    logger.log(Level.FINE, "got EOFException", ex);
+	}
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("recipientDSN size " + v.size());
+	recipientDSN = new InternetHeaders[v.size()];
+	v.copyInto(recipientDSN);
+    }
+
+    /**
+     * Return all the per-message fields in the delivery status notification.
+     * The fields are defined as:
+     *
+     * <pre>
+     *    per-message-fields =
+     *          [ original-envelope-id-field CRLF ]
+     *          reporting-mta-field CRLF
+     *          [ dsn-gateway-field CRLF ]
+     *          [ received-from-mta-field CRLF ]
+     *          [ arrival-date-field CRLF ]
+     *          *( extension-field CRLF )
+     * </pre>
+     *
+     * @return	the per-message DSN fields
+     */
+    // XXX - could parse each of these fields
+    public InternetHeaders getMessageDSN() {
+	return messageDSN;
+    }
+
+    /**
+     * Set the per-message fields in the delivery status notification.
+     *
+     * @param	messageDSN	the per-message DSN fields
+     */
+    public void setMessageDSN(InternetHeaders messageDSN) {
+	this.messageDSN = messageDSN;
+    }
+
+    /**
+     * Return the number of recipients for which we have
+     * per-recipient delivery status notification information.
+     *
+     * @return	the number of recipients
+     */
+    public int getRecipientDSNCount() {
+	return recipientDSN.length;
+    }
+
+    /**
+     * Return the delivery status notification information for
+     * the specified recipient.
+     *
+     * @param	n	the recipient number
+     * @return	the DSN fields for the recipient
+     */
+    public InternetHeaders getRecipientDSN(int n) {
+	return recipientDSN[n];
+    }
+
+    /**
+     * Add deliver status notification information for another
+     * recipient.
+     *
+     * @param	h	the DSN fields for the recipient
+     */
+    public void addRecipientDSN(InternetHeaders h) {
+	InternetHeaders[] rh = new InternetHeaders[recipientDSN.length + 1];
+	System.arraycopy(recipientDSN, 0, rh, 0, recipientDSN.length);
+	recipientDSN = rh;
+	recipientDSN[recipientDSN.length - 1] = h;
+    }
+
+    public void writeTo(OutputStream os) throws IOException {
+	// see if we already have a LOS
+	LineOutputStream los = null;
+	if (os instanceof LineOutputStream) {
+	    los = (LineOutputStream) os;
+	} else {
+	    los = new LineOutputStream(os);
+	}
+
+	writeInternetHeaders(messageDSN, los);
+	los.writeln();
+	for (int i = 0; i < recipientDSN.length; i++) {
+	    writeInternetHeaders(recipientDSN[i], los);
+	    los.writeln();
+	}
+    }
+
+    private static void writeInternetHeaders(InternetHeaders h,
+				LineOutputStream los) throws IOException {
+	Enumeration e = h.getAllHeaderLines();
+	while (e.hasMoreElements())
+	    los.writeln((String)e.nextElement());
+    }
+
+    public String toString() {
+	return "DeliveryStatus: Reporting-MTA=" +
+	    messageDSN.getHeader("Reporting-MTA", null) + ", #Recipients=" +
+	    recipientDSN.length;
+    }
+}
diff --git a/dsn/src/main/java/com/sun/mail/dsn/DispositionNotification.java b/dsn/src/main/java/com/sun/mail/dsn/DispositionNotification.java
new file mode 100644
index 0000000..30f6e9d
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/DispositionNotification.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.io.*;
+import java.util.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.util.LineOutputStream;	// XXX
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+
+/**
+ * A message/disposition-notification message content, as defined in
+ * <A HREF="http://www.ietf.org/rfc/rfc3798.txt" TARGET="_top">RFC 3798</A>.
+ *
+ * @since	JavaMail 1.4.2
+ */
+public class DispositionNotification extends Report {
+
+    private static MailLogger logger = new MailLogger(
+	DeliveryStatus.class,
+	"DEBUG DSN",
+	PropUtil.getBooleanSystemProperty("mail.dsn.debug", false),
+	System.out);
+
+    /**
+     * The disposition notification content fields.
+     */
+    protected InternetHeaders notifications;
+
+    /**
+     * Construct a disposition notification with no content.
+     *
+     * @exception	MessagingException for failures
+     */
+    public DispositionNotification() throws MessagingException {
+	super("disposition-notification");
+	notifications = new InternetHeaders();
+    }
+
+    /**
+     * Construct a disposition notification by parsing the
+     * supplied input stream.
+     *
+     * @param	is	the input stream
+     * @exception	IOException for I/O errors reading the stream
+     * @exception	MessagingException for other failures
+     */
+    public DispositionNotification(InputStream is)
+				throws MessagingException, IOException {
+	super("disposition-notification");
+	notifications = new InternetHeaders(is);
+	logger.fine("got MDN notification content");
+    }
+
+    /**
+     * Return all the disposition notification fields in the
+     * disposition notification.
+     * The fields are defined as:
+     *
+     * <pre>
+     *    disposition-notification-content =
+     *		[ reporting-ua-field CRLF ]
+     *		[ mdn-gateway-field CRLF ]
+     *		[ original-recipient-field CRLF ]
+     *		final-recipient-field CRLF
+     *		[ original-message-id-field CRLF ]
+     *		disposition-field CRLF
+     *		*( failure-field CRLF )
+     *		*( error-field CRLF )
+     *		*( warning-field CRLF )
+     *		*( extension-field CRLF )
+     * </pre>
+     *
+     * @return	the DSN fields
+     */
+    // XXX - could parse each of these fields
+    public InternetHeaders getNotifications() {
+	return notifications;
+    }
+
+    /**
+     * Set the disposition notification fields in the
+     * disposition notification.
+     *
+     * @param	notifications	the DSN fields
+     */
+    public void setNotifications(InternetHeaders notifications) {
+	this.notifications = notifications;
+    }
+
+    public void writeTo(OutputStream os) throws IOException {
+	// see if we already have a LOS
+	LineOutputStream los = null;
+	if (os instanceof LineOutputStream) {
+	    los = (LineOutputStream) os;
+	} else {
+	    los = new LineOutputStream(os);
+	}
+
+	writeInternetHeaders(notifications, los);
+	los.writeln();
+    }
+
+    private static void writeInternetHeaders(InternetHeaders h,
+				LineOutputStream los) throws IOException {
+	Enumeration e = h.getAllHeaderLines();
+	while (e.hasMoreElements())
+	    los.writeln((String)e.nextElement());
+    }
+
+    public String toString() {
+	return "DispositionNotification: Reporting-UA=" +
+	    notifications.getHeader("Reporting-UA", null);
+    }
+}
diff --git a/dsn/src/main/java/com/sun/mail/dsn/MessageHeaders.java b/dsn/src/main/java/com/sun/mail/dsn/MessageHeaders.java
new file mode 100644
index 0000000..9acdea0
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/MessageHeaders.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.io.*;
+
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * A special MimeMessage object that contains only message headers,
+ * no content.  Used to represent the MIME type text/rfc822-headers.
+ *
+ * @since	JavaMail 1.4
+ */
+public class MessageHeaders extends MimeMessage {
+
+    /**
+     * Construct a MessageHeaders object.
+     *
+     * @exception	MessagingException for failures
+     */
+    public MessageHeaders() throws MessagingException {
+	super((Session)null);
+	content = new byte[0];
+    }
+
+    /**
+     * Constructs a MessageHeaders object from the given InputStream.
+     *
+     * @param	is	InputStream
+     * @exception	MessagingException for failures
+     */
+    public MessageHeaders(InputStream is) throws MessagingException {
+	super(null, is);
+	content = new byte[0];
+    }
+
+    /**
+     * Constructs a MessageHeaders object using the given InternetHeaders.
+     *
+     * @param	headers	InternetHeaders to use
+     * @exception	MessagingException for failures
+     */
+    public MessageHeaders(InternetHeaders headers) throws MessagingException {
+	super((Session)null);
+	this.headers = headers;
+	content = new byte[0];
+    }
+
+    /**
+     * Return the size of this message.
+     * Always returns zero.
+     */
+    public int getSize() {
+	return 0;
+    }
+
+    public InputStream getInputStream() {
+	return new ByteArrayInputStream(content);
+    }
+
+    protected InputStream getContentStream() {
+	return new ByteArrayInputStream(content);
+    }
+
+    /**
+     * Can't set any content for a MessageHeaders object.
+     *
+     * @exception	MessagingException	always
+     */
+    public void setDataHandler(DataHandler dh) throws MessagingException {
+	throw new MessagingException("Can't set content for MessageHeaders");
+    }
+
+}
diff --git a/dsn/src/main/java/com/sun/mail/dsn/MultipartReport.java b/dsn/src/main/java/com/sun/mail/dsn/MultipartReport.java
new file mode 100644
index 0000000..ee19efe
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/MultipartReport.java
@@ -0,0 +1,430 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.io.*;
+import java.util.Vector;
+
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * A multipart/report message content, as defined in
+ * <A HREF="http://www.ietf.org/rfc/rfc3462.txt" TARGET="_top">RFC 3462</A>.
+ * A multipart/report content is a container for mail reports
+ * of any kind, and is most often used to return a delivery
+ * status report or a disposition notification report. <p>
+ *
+ * A MultipartReport object is a special type of MimeMultipart
+ * object with a restricted set of body parts.  A MultipartReport
+ * object contains:
+ * <ul>
+ * <li>[Required] A human readable text message describing the
+ * reason the report was generated.</li>
+ * <li>[Required] A {@link Report} object containing the
+ * details for why the report was generated.</li>
+ * <li>[Optional] A returned copy of the entire message, or just
+ * its headers, which caused the generation of this report.
+ * </ul>
+ * Many of the normal MimeMultipart operations are restricted to
+ * ensure that the MultipartReport object always follows this
+ * structure.
+ *
+ * @since	JavaMail 1.4
+ */
+public class MultipartReport extends MimeMultipart {
+    protected boolean constructed; // true when done with constructor
+
+    /**
+     * Construct a multipart/report object with no content.
+     *
+     * @exception	MessagingException for failures
+     */
+    public MultipartReport() throws MessagingException {
+	super("report");
+	// always at least two body parts
+	MimeBodyPart mbp = new MimeBodyPart();
+	setBodyPart(mbp, 0);
+	mbp = new MimeBodyPart();
+	setBodyPart(mbp, 1);
+	constructed = true;
+    }
+
+    /**
+     * Construct a multipart/report object with the specified plain
+     * text and report type (DeliveryStatus or DispositionNotification)
+     * to be returned to the user.
+     *
+     * @param	text	the plain text
+     * @param	report	the Report object
+     * @exception	MessagingException for failures
+     */
+    public MultipartReport(String text, Report report)
+				throws MessagingException {
+	super("report");
+	ContentType ct = new ContentType(contentType);
+	String reportType = report.getType();
+	ct.setParameter("report-type", reportType);
+	contentType = ct.toString();
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setText(text);
+	setBodyPart(mbp, 0);
+	mbp = new MimeBodyPart();
+	ct = new ContentType("message", reportType, null);
+	mbp.setContent(report, ct.toString());
+	setBodyPart(mbp, 1);
+	constructed = true;
+    }
+
+    /**
+     * Construct a multipart/report object with the specified plain
+     * text, report, and original message to be returned to the user.
+     *
+     * @param	text	the plain text
+     * @param	report	the Report object
+     * @param	msg	the message this report is about
+     * @exception	MessagingException for failures
+     */
+    public MultipartReport(String text, Report report, MimeMessage msg)
+				throws MessagingException {
+	this(text, report);
+	if (msg != null) {
+	    MimeBodyPart mbp = new MimeBodyPart();
+	    mbp.setContent(msg, "message/rfc822");
+	    setBodyPart(mbp, 2);
+	}
+    }
+
+    /**
+     * Construct a multipart/report object with the specified plain
+     * text, report, and headers from the original message
+     * to be returned to the user.
+     *
+     * @param	text	the plain text
+     * @param	report	the Report object
+     * @param	hdr	the headers of the message this report is about
+     * @exception	MessagingException for failures
+     */
+    public MultipartReport(String text, Report report, InternetHeaders hdr)
+				throws MessagingException {
+	this(text, report);
+	if (hdr != null) {
+	    MimeBodyPart mbp = new MimeBodyPart();
+	    mbp.setContent(new MessageHeaders(hdr), "text/rfc822-headers");
+	    setBodyPart(mbp, 2);
+	}
+    }
+
+    /**
+     * Constructs a MultipartReport object and its bodyparts from the 
+     * given DataSource. <p>
+     *
+     * @param	ds	DataSource, can be a MultipartDataSource
+     * @exception	MessagingException for failures
+     */
+    public MultipartReport(DataSource ds) throws MessagingException {
+	super(ds);
+	parse();
+	constructed = true;
+	/*
+	 * Can't fail to construct object because some programs just
+	 * want to treat this as a Multipart and examine the parts.
+	 *
+	if (getCount() < 2 || getCount() > 3)	// XXX allow extra parts
+	    throw new MessagingException(
+		"Wrong number of parts in multipart/report: " + getCount());
+	 */
+    }
+
+    /**
+     * Get the plain text to be presented to the user, if there is any.
+     * Rarely, the message may contain only HTML text, or no text at
+     * all.  If the text body part of this multipart/report object is
+     * of type text/plain, or if it is of type multipart/alternative
+     * and contains a text/plain part, the text from that part is
+     * returned.  Otherwise, null is return and the {@link #getTextBodyPart
+     * getTextBodyPart} method may be used to extract the data.
+     *
+     * @return	the text
+     * @exception	MessagingException for failures
+     */
+    public synchronized String getText() throws MessagingException {
+	try {
+	    BodyPart bp = getBodyPart(0);
+	    if (bp.isMimeType("text/plain"))
+		return (String)bp.getContent();
+	    if (bp.isMimeType("multipart/alternative")) {
+		Multipart mp = (Multipart)bp.getContent();
+		for (int i = 0; i < mp.getCount(); i++) {
+		    bp = mp.getBodyPart(i);
+		    if (bp.isMimeType("text/plain"))
+			return (String)bp.getContent();
+		}
+	    }
+	} catch (IOException ex) {
+	    throw new MessagingException("Exception getting text content", ex);
+	}
+	return null;
+    }
+
+    /**
+     * Set the message to be presented to the user as just a text/plain
+     * part containing the specified text.
+     *
+     * @param	text	the text
+     * @exception	MessagingException for failures
+     */
+    public synchronized void setText(String text) throws MessagingException {
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setText(text);
+	setBodyPart(mbp, 0);
+    }
+
+    /**
+     * Return the body part containing the message to be presented to
+     * the user, usually just a text/plain part.
+     *
+     * @return	the body part containing the text
+     * @exception	MessagingException for failures
+     */
+    public synchronized MimeBodyPart getTextBodyPart()
+				throws MessagingException {
+	return (MimeBodyPart)getBodyPart(0);
+    }
+
+    /**
+     * Set the body part containing the text to be presented to the
+     * user.  Usually this a text/plain part, but it might also be
+     * a text/html part or a multipart/alternative part containing
+     * text/plain and text/html parts.  Any type is allowed here
+     * but these types are most common.
+     *
+     * @param	mbp	the body part containing the text
+     * @exception	MessagingException for failures
+     */
+    public synchronized void setTextBodyPart(MimeBodyPart mbp)
+				throws MessagingException {
+	setBodyPart(mbp, 0);
+    }
+
+    /**
+     * Get the report associated with this multipart/report.
+     *
+     * @return	the Report object
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.4.2
+     */
+    public synchronized Report getReport() throws MessagingException {
+	if (getCount() < 2)
+	    return null;
+	BodyPart bp = getBodyPart(1);
+	try {
+	    Object content = bp.getContent();
+	    if (!(content instanceof Report))
+		return null;
+	    return (Report)content;
+	} catch (IOException ex) {
+	    throw new MessagingException("IOException getting Report", ex);
+	}
+    }
+
+    /**
+     * Set the report associated with this multipart/report.
+     *
+     * @param	report	the Report object
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.4.2
+     */
+    public synchronized void setReport(Report report)
+				throws MessagingException {
+	MimeBodyPart mbp = new MimeBodyPart();
+	ContentType ct = new ContentType(contentType);
+	String reportType = report.getType();
+	ct.setParameter("report-type", reportType);
+	contentType = ct.toString();
+	ct = new ContentType("message", reportType, null);
+	mbp.setContent(report, ct.toString());
+	setBodyPart(mbp, 1);
+    }
+
+    /**
+     * Get the delivery status associated with this multipart/report.
+     *
+     * @return	the delivery status
+     * @exception	MessagingException for failures
+     * @deprecated	use getReport instead
+     */
+    @Deprecated
+    public synchronized DeliveryStatus getDeliveryStatus()
+				throws MessagingException {
+	if (getCount() < 2)
+	    return null;
+	BodyPart bp = getBodyPart(1);
+	if (!bp.isMimeType("message/delivery-status"))
+	    return null;
+	try {
+	    return (DeliveryStatus)bp.getContent();
+	} catch (IOException ex) {
+	    throw new MessagingException("IOException getting DeliveryStatus",
+					ex);
+	}
+    }
+
+    /**
+     * Set the delivery status associated with this multipart/report.
+     *
+     * @param	status the deliver status
+     * @exception	MessagingException for failures
+     * @deprecated	use setReport instead
+     */
+    public synchronized void setDeliveryStatus(DeliveryStatus status)
+				throws MessagingException {
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setContent(status, "message/delivery-status");
+	setBodyPart(mbp, 1);
+	ContentType ct = new ContentType(contentType);
+	ct.setParameter("report-type", "delivery-status");
+	contentType = ct.toString();
+    }
+
+    /**
+     * Get the original message that is being returned along with this
+     * multipart/report.  If no original message is included, null is
+     * returned.  In some cases only the headers of the original
+     * message will be returned as an object of type MessageHeaders.
+     *
+     * @return	the returned message
+     * @exception	MessagingException for failures
+     */
+    public synchronized MimeMessage getReturnedMessage()
+				throws MessagingException {
+	if (getCount() < 3)
+	    return null;
+	BodyPart bp = getBodyPart(2);
+	if (!bp.isMimeType("message/rfc822") &&
+		!bp.isMimeType("text/rfc822-headers"))
+	    return null;
+	try {
+	    return (MimeMessage)bp.getContent();
+	} catch (IOException ex) {
+	    throw new MessagingException("IOException getting ReturnedMessage",
+					ex);
+	}
+    }
+
+    /**
+     * Set the original message to be returned as part of the
+     * multipart/report.  If msg is null, any previously set
+     * returned message or headers is removed.
+     *
+     * @param	msg	the returned message
+     * @exception	MessagingException for failures
+     */
+    public synchronized void setReturnedMessage(MimeMessage msg)
+				throws MessagingException {
+	if (msg == null) {
+	    super.removeBodyPart(2);
+	    return;
+	}
+	MimeBodyPart mbp = new MimeBodyPart();
+	if (msg instanceof MessageHeaders)
+	    mbp.setContent(msg, "text/rfc822-headers");
+	else
+	    mbp.setContent(msg, "message/rfc822");
+	setBodyPart(mbp, 2);
+    }
+
+    private synchronized void setBodyPart(BodyPart part, int index) 
+				throws MessagingException {
+	if (parts == null)	// XXX - can never happen?
+	    parts = new Vector<BodyPart>();
+
+	if (index < parts.size())
+	    super.removeBodyPart(index);
+	super.addBodyPart(part, index);
+    }
+
+
+    // Override Multipart methods to preserve integrity of multipart/report.
+
+    /**
+     * Set the subtype.  Throws MessagingException.
+     *
+     * @param	subtype		Subtype
+     * @exception	MessagingException	always; can't change subtype
+     */
+    public synchronized void setSubType(String subtype) 
+			throws MessagingException {
+	throw new MessagingException("Can't change subtype of MultipartReport");
+    }
+
+    /**
+     * Remove the specified part from the multipart message.
+     * Not allowed on a multipart/report object.
+     *
+     * @param   part	The part to remove
+     * @exception	MessagingException always
+     */
+    public boolean removeBodyPart(BodyPart part) throws MessagingException {
+	throw new MessagingException(
+	    "Can't remove body parts from multipart/report");
+    }
+
+    /**
+     * Remove the part at specified location (starting from 0).
+     * Not allowed on a multipart/report object.
+     *
+     * @param   index	Index of the part to remove
+     * @exception	MessagingException	always
+     */
+    public void removeBodyPart(int index) throws MessagingException {
+	throw new MessagingException(
+	    "Can't remove body parts from multipart/report");
+    }
+
+    /**
+     * Adds a Part to the multipart.
+     * Not allowed on a multipart/report object.
+     *
+     * @param  part  The Part to be appended
+     * @exception       MessagingException	always
+     */
+    public synchronized void addBodyPart(BodyPart part) 
+		throws MessagingException {
+	// Once constructor is done, don't allow this anymore.
+	if (!constructed)
+	    super.addBodyPart(part);
+	else
+	    throw new MessagingException(
+		"Can't add body parts to multipart/report 1");
+    }
+
+    /**
+     * Adds a BodyPart at position <code>index</code>.
+     * Not allowed on a multipart/report object.
+     *
+     * @param  part  The BodyPart to be inserted
+     * @param  index Location where to insert the part
+     * @exception       MessagingException	always
+     */
+    public synchronized void addBodyPart(BodyPart part, int index) 
+				throws MessagingException {
+	throw new MessagingException(
+	    "Can't add body parts to multipart/report 2");
+    }
+}
diff --git a/dsn/src/main/java/com/sun/mail/dsn/Report.java b/dsn/src/main/java/com/sun/mail/dsn/Report.java
new file mode 100644
index 0000000..b103d56
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/Report.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+/**
+ * An abstract report type, to be included in a MultipartReport.
+ * Subclasses define specific report types, such as {@link DeliveryStatus}
+ * and {@link DispositionNotification}.
+ *
+ * @since	JavaMail 1.4.2
+ */
+public abstract class Report {
+    protected String type;	// the MIME subtype of the report
+
+    /**
+     * Construct a report of the indicated MIME subtype.
+     * The primary MIME type is always "message".
+     *
+     * @param	type	the MIME subtype
+     */
+    protected Report(String type) {
+	this.type = type;
+    }
+
+    /**
+     * Get the MIME subtype of the report.
+     * The primary MIME type is always "message".
+     *
+     * @return	the MIME subtype
+     */
+    public String getType() {
+	return type;
+    }
+}
diff --git a/dsn/src/main/java/com/sun/mail/dsn/message_deliverystatus.java b/dsn/src/main/java/com/sun/mail/dsn/message_deliverystatus.java
new file mode 100644
index 0000000..a59ac8a
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/message_deliverystatus.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.io.*;
+import java.util.Properties;
+import java.awt.datatransfer.DataFlavor;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+
+/**
+ * DataContentHandler for message/delivery-status MIME type.
+ * Applications should not use this class directly, it's used indirectly
+ * through the JavaBeans Activation Framework.
+ *
+ * @since	JavaMail 1.4
+ */
+public class message_deliverystatus implements DataContentHandler {
+
+    ActivationDataFlavor ourDataFlavor = new ActivationDataFlavor(
+	DeliveryStatus.class,
+	"message/delivery-status", 
+	"Delivery Status");
+
+    /**
+     * return the DataFlavors for this <code>DataContentHandler</code>
+     * @return The DataFlavors.
+     */
+    public DataFlavor[] getTransferDataFlavors() {
+	return new DataFlavor[] { ourDataFlavor };
+    }
+
+    /**
+     * return the Transfer Data of type DataFlavor from InputStream
+     * @param df The DataFlavor.
+     * @param ds The DataSource corresponding to the data.
+     * @return a Message object
+     */
+    public Object getTransferData(DataFlavor df, DataSource ds)
+				throws IOException {
+	// make sure we can handle this DataFlavor
+	if (ourDataFlavor.equals(df))
+	    return getContent(ds);
+	else
+	    return null;
+    }
+    
+    /**
+     * Return the content.
+     */
+    public Object getContent(DataSource ds) throws IOException {
+	// create a new DeliveryStatus
+	try {
+	    /*
+	    Session session;
+	    if (ds instanceof MessageAware) {
+		javax.mail.MessageContext mc =
+			((MessageAware)ds).getMessageContext();
+		session = mc.getSession();
+	    } else {
+		// Hopefully a rare case.  Also hopefully the application
+		// has created a default Session that can just be returned
+		// here.  If not, the one we create here is better than
+		// nothing, but overall not a really good answer.
+		session = Session.getDefaultInstance(new Properties(), null);
+	    }
+	    return new DeliveryStatus(session, ds.getInputStream());
+	    */
+	    return new DeliveryStatus(ds.getInputStream());
+	} catch (MessagingException me) {
+	    throw new IOException("Exception creating DeliveryStatus in " +
+		    "message/delivery-status DataContentHandler: " +
+		    me.toString());
+	}
+    }
+    
+    /**
+     */
+    public void writeTo(Object obj, String mimeType, OutputStream os) 
+			throws IOException {
+	// if the object is a DeliveryStatus, we know how to write that out
+	if (obj instanceof DeliveryStatus) {
+	    DeliveryStatus ds = (DeliveryStatus)obj;
+	    ds.writeTo(os);
+	    
+	} else {
+	    throw new IOException("unsupported object");
+	}
+    }
+}
diff --git a/dsn/src/main/java/com/sun/mail/dsn/message_dispositionnotification.java b/dsn/src/main/java/com/sun/mail/dsn/message_dispositionnotification.java
new file mode 100644
index 0000000..6eba802
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/message_dispositionnotification.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.io.*;
+import java.util.Properties;
+import java.awt.datatransfer.DataFlavor;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+
+/**
+ * DataContentHandler for message/disposition-notification MIME type.
+ * Applications should not use this class directly, it's used indirectly
+ * through the JavaBeans Activation Framework.
+ *
+ * @since	JavaMail 1.4.2
+ */
+public class message_dispositionnotification implements DataContentHandler {
+
+    ActivationDataFlavor ourDataFlavor = new ActivationDataFlavor(
+	DispositionNotification.class,
+	"message/disposition-notification", 
+	"Disposition Notification");
+
+    /**
+     * return the DataFlavors for this <code>DataContentHandler</code>
+     * @return The DataFlavors.
+     */
+    public DataFlavor[] getTransferDataFlavors() {
+	return new DataFlavor[] { ourDataFlavor };
+    }
+
+    /**
+     * return the Transfer Data of type DataFlavor from InputStream
+     * @param df The DataFlavor.
+     * @param ds The DataSource corresponding to the data.
+     * @return a Message object
+     */
+    public Object getTransferData(DataFlavor df, DataSource ds)
+				throws IOException {
+	// make sure we can handle this DataFlavor
+	if (ourDataFlavor.equals(df))
+	    return getContent(ds);
+	else
+	    return null;
+    }
+    
+    /**
+     * Return the content.
+     */
+    public Object getContent(DataSource ds) throws IOException {
+	// create a new DispositionNotification
+	try {
+	    /*
+	    Session session;
+	    if (ds instanceof MessageAware) {
+		javax.mail.MessageContext mc =
+			((MessageAware)ds).getMessageContext();
+		session = mc.getSession();
+	    } else {
+		// Hopefully a rare case.  Also hopefully the application
+		// has created a default Session that can just be returned
+		// here.  If not, the one we create here is better than
+		// nothing, but overall not a really good answer.
+		session = Session.getDefaultInstance(new Properties(), null);
+	    }
+	    return new DispositionNotification(session, ds.getInputStream());
+	    */
+	    return new DispositionNotification(ds.getInputStream());
+	} catch (MessagingException me) {
+	    throw new IOException(
+		    "Exception creating DispositionNotification in " +
+		    "message/disposition-notification DataContentHandler: " +
+		    me.toString());
+	}
+    }
+    
+    /**
+     */
+    public void writeTo(Object obj, String mimeType, OutputStream os) 
+			throws IOException {
+	// if it's a DispositionNotification, we know how to write that out
+	if (obj instanceof DispositionNotification) {
+	    DispositionNotification dn = (DispositionNotification)obj;
+	    dn.writeTo(os);
+	} else {
+	    throw new IOException("unsupported object");
+	}
+    }
+}
diff --git a/dsn/src/main/java/com/sun/mail/dsn/multipart_report.java b/dsn/src/main/java/com/sun/mail/dsn/multipart_report.java
new file mode 100644
index 0000000..4fbccfa
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/multipart_report.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.io.*;
+import java.awt.datatransfer.DataFlavor;
+import javax.activation.*;
+import javax.mail.MessagingException;
+import javax.mail.internet.*;
+
+
+/**
+ * DataContentHandler for multipart/report MIME type.
+ * Applications should not use this class directly, it's used indirectly
+ * through the JavaBeans Activation Framework.
+ *
+ * @since	JavaMail 1.4
+ */
+public class multipart_report implements DataContentHandler {
+    private ActivationDataFlavor myDF = new ActivationDataFlavor(
+	    MultipartReport.class,
+	    "multipart/report", 
+	    "Multipart Report");
+
+    /**
+     * Return the DataFlavors for this <code>DataContentHandler</code>.
+     *
+     * @return The DataFlavors
+     */
+    public DataFlavor[] getTransferDataFlavors() { // throws Exception;
+	return new DataFlavor[] { myDF };
+    }
+
+    /**
+     * Return the Transfer Data of type DataFlavor from InputStream.
+     *
+     * @param df The DataFlavor
+     * @param ds The DataSource corresponding to the data
+     * @return String object
+     */
+    public Object getTransferData(DataFlavor df, DataSource ds)
+				throws IOException {
+	// use myDF.equals to be sure to get ActivationDataFlavor.equals,
+	// which properly ignores Content-Type parameters in comparison
+	if (myDF.equals(df))
+	    return getContent(ds);
+	else
+	    return null;
+    }
+    
+    /**
+     * Return the content.
+     */
+    public Object getContent(DataSource ds) throws IOException {
+	try {
+	    return new MultipartReport(ds); 
+	} catch (MessagingException e) {
+	    IOException ioex =
+		new IOException("Exception while constructing MultipartReport");
+	    ioex.initCause(e);
+	    throw ioex;
+	}
+    }
+    
+    /**
+     * Write the object to the output stream, using the specific MIME type.
+     */
+    public void writeTo(Object obj, String mimeType, OutputStream os) 
+			throws IOException {
+	if (obj instanceof MultipartReport) {
+	    try {
+		((MultipartReport)obj).writeTo(os);
+	    } catch (MessagingException e) {
+		throw new IOException(e.toString());
+	    }
+	}
+    }
+}
diff --git a/dsn/src/main/java/com/sun/mail/dsn/package.html b/dsn/src/main/java/com/sun/mail/dsn/package.html
new file mode 100644
index 0000000..0f96802
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/package.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.dsn package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+Support for creating and parsing Delivery Status Notifications.
+Refer to <A HREF="http://www.ietf.org/rfc/rfc3462.txt" TARGET="_top">
+RFC 3462</A>
+and <A HREF="http://www.ietf.org/rfc/rfc3464.txt" TARGET="_top">RFC 3464</A>
+for more information.
+</P>
+<P>
+A Delivery Status Notification is a MIME message with a Content-Type
+of <code>multipart/report</code>.
+A {@link com.sun.mail.dsn.MultipartReport MultipartReport} object
+represents the content of such a message.
+The MultipartReport object contains several parts that represent the
+information in a delivery status notification.
+The first part is usually a <code>text/plain</code> part that
+describes the reason for the notification.
+The second part is a <code>message/delivery-status</code> part,
+which is represented by a
+{@link com.sun.mail.dsn.DeliveryStatus DeliveryStatus} object, and contains
+details about the notification.
+The third part is either an entire copy of the original message
+that is returned, represented by a
+{@link javax.mail.internet.MimeMessage MimeMessage} object, or
+just the headers of the original message, represented by a
+{@link com.sun.mail.dsn.MessageHeaders MessageHeaders} object.
+</P>
+<P>
+To use the classes in this package, include <code>dsn.jar</code>
+in your class path.
+</P>
+<P>
+Classes in this package log debugging information using
+{@link java.util.logging} as described in the following table:
+</P>
+<TABLE BORDER SUMMARY="com.sun.mail.dsn Loggers">
+<TR>
+<TH>Logger Name</TH>
+<TH>Logging Level</TH>
+<TH>Purpose</TH>
+</TR>
+
+<TR>
+<TD>com.sun.mail.dsn</TD>
+<TD>FINER</TD>
+<TD>General debugging output</TD>
+</TR>
+</TABLE>
+
+<P>
+<strong>WARNING:</strong> The APIs unique to this package should be
+considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+</P>
+
+</BODY>
+</HTML>
diff --git a/dsn/src/main/java/com/sun/mail/dsn/text_rfc822headers.java b/dsn/src/main/java/com/sun/mail/dsn/text_rfc822headers.java
new file mode 100644
index 0000000..e60782f
--- /dev/null
+++ b/dsn/src/main/java/com/sun/mail/dsn/text_rfc822headers.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.io.*;
+import java.awt.datatransfer.DataFlavor;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * DataContentHandler for text/rfc822-headers MIME type.
+ * Applications should not use this class directly, it's used indirectly
+ * through the JavaBeans Activation Framework.
+ *
+ * @since	JavaMail 1.4
+ *
+ */
+public class text_rfc822headers implements DataContentHandler {
+    private static ActivationDataFlavor myDF = new ActivationDataFlavor(
+	MessageHeaders.class,
+	"text/rfc822-headers",
+	"RFC822 headers");
+    private static ActivationDataFlavor myDFs = new ActivationDataFlavor(
+	java.lang.String.class,
+	"text/rfc822-headers",
+	"RFC822 headers");
+
+    /**
+     * Return the DataFlavors for this <code>DataContentHandler</code>.
+     *
+     * @return The DataFlavors
+     */
+    public DataFlavor[] getTransferDataFlavors() {
+	return new DataFlavor[] { myDF, myDFs };
+    }
+
+    /**
+     * Return the Transfer Data of type DataFlavor from InputStream.
+     *
+     * @param df The DataFlavor
+     * @param ds The DataSource corresponding to the data
+     * @return String object
+     */
+    public Object getTransferData(DataFlavor df, DataSource ds) 
+			throws IOException {
+	// use myDF.equals to be sure to get ActivationDataFlavor.equals,
+	// which properly ignores Content-Type parameters in comparison
+	if (myDF.equals(df))
+	    return getContent(ds);
+	else if (myDFs.equals(df))
+	    return getStringContent(ds);
+	else
+	    return null;
+    }
+
+    public Object getContent(DataSource ds) throws IOException {
+	try {
+	    return new MessageHeaders(ds.getInputStream());
+	} catch (MessagingException mex) {
+//System.out.println("Exception creating MessageHeaders: " + mex);
+	    throw new IOException("Exception creating MessageHeaders: " + mex);
+	}
+    }
+
+    private Object getStringContent(DataSource ds) throws IOException {
+	String enc = null;
+	InputStreamReader is = null;
+	
+	try {
+	    enc = getCharset(ds.getContentType());
+	    is = new InputStreamReader(ds.getInputStream(), enc);
+	} catch (IllegalArgumentException iex) {
+	    /*
+	     * An unknown charset of the form ISO-XXX-XXX will cause
+	     * the JDK to throw an IllegalArgumentException.  The
+	     * JDK will attempt to create a classname using this string,
+	     * but valid classnames must not contain the character '-',
+	     * and this results in an IllegalArgumentException, rather than
+	     * the expected UnsupportedEncodingException.  Yikes.
+	     */
+	    throw new UnsupportedEncodingException(enc);
+	}
+
+	try {
+	    int pos = 0;
+	    int count;
+	    char buf[] = new char[1024];
+
+	    while ((count = is.read(buf, pos, buf.length - pos)) != -1) {
+		pos += count;
+		if (pos >= buf.length) {
+		    int size = buf.length;
+		    if (size < 256*1024)
+			size += size;
+		    else
+			size += 256*1024;
+		    char tbuf[] = new char[size];
+		    System.arraycopy(buf, 0, tbuf, 0, pos);
+		    buf = tbuf;
+		}
+	    }
+	    return new String(buf, 0, pos);
+	} finally {
+	    try {
+		is.close();
+	    } catch (IOException ex) {
+		// ignore it
+	    }
+	}
+    }
+
+    /**
+     * Write the object to the output stream, using the specified MIME type.
+     */
+    public void writeTo(Object obj, String type, OutputStream os) 
+			throws IOException {
+	if (obj instanceof MessageHeaders) {
+	    MessageHeaders mh = (MessageHeaders)obj;
+	    try {
+		mh.writeTo(os);
+	    } catch (MessagingException mex) {
+		Exception ex = mex.getNextException();
+		if (ex instanceof IOException)
+		    throw (IOException)ex;
+		else
+		    throw new IOException("Exception writing headers: " + mex);
+	    }
+	    return;
+	}
+	if (!(obj instanceof String))
+	    throw new IOException("\"" + myDFs.getMimeType() +
+		"\" DataContentHandler requires String object, " +
+		"was given object of type " + obj.getClass().toString());
+
+	String enc = null;
+	OutputStreamWriter osw = null;
+
+	try {
+	    enc = getCharset(type);
+	    osw = new OutputStreamWriter(os, enc);
+	} catch (IllegalArgumentException iex) {
+	    /*
+	     * An unknown charset of the form ISO-XXX-XXX will cause
+	     * the JDK to throw an IllegalArgumentException.  The
+	     * JDK will attempt to create a classname using this string,
+	     * but valid classnames must not contain the character '-',
+	     * and this results in an IllegalArgumentException, rather than
+	     * the expected UnsupportedEncodingException.  Yikes.
+	     */
+	    throw new UnsupportedEncodingException(enc);
+	}
+
+	String s = (String)obj;
+	osw.write(s, 0, s.length());
+	osw.flush();
+    }
+
+    private String getCharset(String type) {
+	try {
+	    ContentType ct = new ContentType(type);
+	    String charset = ct.getParameter("charset");
+	    if (charset == null)
+		// If the charset parameter is absent, use US-ASCII.
+		charset = "us-ascii";
+	    return MimeUtility.javaCharset(charset);
+	} catch (Exception ex) {
+	    return null;
+	}
+    }
+}
diff --git a/dsn/src/main/resources/META-INF/MANIFEST.MF b/dsn/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..c24177b
--- /dev/null
+++ b/dsn/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Extension-Name: com.sun.mail.dsn
+Specification-Title: com.sun.mail.dsn
+Specification-Version: ${mail.spec.version}
+Specification-Vendor: ${project.organization.name}
+Implementation-Title: com.sun.mail.dsn
+Implementation-Version: ${mail.version}
+Implementation-Vendor: ${project.organization.name}
+Implementation-Vendor-Id: com.sun
diff --git a/dsn/src/main/resources/META-INF/mailcap b/dsn/src/main/resources/META-INF/mailcap
new file mode 100644
index 0000000..d42de7c
--- /dev/null
+++ b/dsn/src/main/resources/META-INF/mailcap
@@ -0,0 +1,8 @@
+#
+#
+# JavaMail content-handlers:
+#
+multipart/report;;	x-java-content-handler=com.sun.mail.dsn.multipart_report
+message/delivery-status;; x-java-content-handler=com.sun.mail.dsn.message_deliverystatus
+message/disposition-notification;; x-java-content-handler=com.sun.mail.dsn.message_dispositionnotification
+text/rfc822-headers;;	x-java-content-handler=com.sun.mail.dsn.text_rfc822headers
diff --git a/dsn/src/test/java/com/sun/mail/dsn/MultipartReportTest.java b/dsn/src/test/java/com/sun/mail/dsn/MultipartReportTest.java
new file mode 100644
index 0000000..cedafe6
--- /dev/null
+++ b/dsn/src/test/java/com/sun/mail/dsn/MultipartReportTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.InternetHeaders;
+
+import org.junit.*;
+import static org.junit.Assert.assertTrue;
+
+/**
+ */
+public class MultipartReportTest {
+ 
+    private static Session session = Session.getInstance(new Properties());
+
+    @Test
+    public void testWrongIndexBug() throws Exception {
+	MimeMessage msg = new MimeMessage(session);
+
+	// create the Multipart and its parts to it
+	MultipartReport mp = new MultipartReport();
+	mp.setText("test Multipart Report\n");
+
+	DeliveryStatus ds = new DeliveryStatus();
+	InternetHeaders mdsn = new InternetHeaders();
+	mdsn.setHeader("Reporting-MTA", "test");
+	ds.setMessageDSN(mdsn);
+	InternetHeaders rdsn = new InternetHeaders();
+	rdsn.setHeader("Final-Recipient", "joe");
+	rdsn.setHeader("Action", "none");
+	rdsn.setHeader("Status", "none");
+	ds.addRecipientDSN(rdsn);
+	mp.setReport(ds);
+	msg.setContent(mp);
+	msg.saveChanges();
+	msg.writeTo(new NullOutputStream());
+	// anything other than an exception is success
+	assertTrue("MultipartReport constructed", true);
+    }
+}
diff --git a/dsn/src/test/java/com/sun/mail/dsn/NullOutputStream.java b/dsn/src/test/java/com/sun/mail/dsn/NullOutputStream.java
new file mode 100644
index 0000000..1b2a7a7
--- /dev/null
+++ b/dsn/src/test/java/com/sun/mail/dsn/NullOutputStream.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.dsn;
+
+import java.io.*;
+
+/**
+ * An OutputStream that throws away all data written to it.
+ */
+public class NullOutputStream extends OutputStream {
+
+    public void write(int b) throws IOException {
+    }
+
+    public void write(byte[] b) throws IOException {
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+    }
+}
diff --git a/gimap/exclude.xml b/gimap/exclude.xml
new file mode 100644
index 0000000..e95e3a8
--- /dev/null
+++ b/gimap/exclude.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<!-- FindBugs exclude list for JavaMail gimap provider -->
+
+<FindBugsFilter>
+    <!--
+	These returned arrays are never exposed to applications so it's
+	safe that it doesn't return a copy.
+    -->
+    <Match>
+	<Class name="com.sun.mail.gimap.protocol.GmailProtocol"/>
+	<Method name="getFetchItems"/>
+	<Bug pattern="EI_EXPOSE_REP"/>
+    </Match>
+
+    <!--
+	The setLabels method needs to be synchronized because of its
+	use of the protocol object.  The getLabels method depends on
+	the getItem method to do the synchronization.
+    -->
+    <Match>
+	<Class name="com.sun.mail.gimap.GmailMessage"/>
+	<Method name="getLabels"/>
+	<Bug pattern="UG_SYNC_SET_UNSYNC_GET"/>
+    </Match>
+
+</FindBugsFilter>
diff --git a/gimap/pom.xml b/gimap/pom.xml
new file mode 100644
index 0000000..13f25eb
--- /dev/null
+++ b/gimap/pom.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>gimap</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API Gmail IMAP provider</name>
+
+    <properties>
+	<mail.packages.export>
+	    com.sun.mail.gimap; version=${mail.osgiversion}
+	</mail.packages.export>
+	<findbugs.exclude>
+	    ${project.basedir}/exclude.xml
+	</findbugs.exclude>
+    </properties>
+
+    <build>
+	<plugins>
+	    <!--
+		Configure SpotBugs to run with "mvn spotbugs:spotbugs"
+		and generate XML output that can be used by the Hudson
+		FindBugs plugin.
+	    -->
+	    <plugin>
+		<groupId>com.github.spotbugs</groupId>
+		<artifactId>spotbugs-maven-plugin</artifactId>
+		<configuration>
+		    <skip>false</skip>
+		    <threshold>${findbugs.threshold}</threshold>
+		    <excludeFilterFile>${findbugs.exclude}</excludeFilterFile>
+		    <findbugsXmlWithMessages>true</findbugsXmlWithMessages>
+		</configuration>
+	    </plugin>
+	</plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+    </dependencies>
+
+    <reporting>
+	<plugins>
+	    <!--
+		Configure SpotBugs to run with "mvn site" and
+		generate html output that can be viewed directly.
+	    -->
+	    <plugin>
+		<groupId>org.codehaus.mojo</groupId>
+		<artifactId>findbugs-maven-plugin</artifactId>
+		<configuration>
+		    <skip>false</skip>
+		    <threshold>${findbugs.threshold}</threshold>
+		    <!--
+		    <excludeFilterFile>exclude.xml</excludeFilterFile>
+		    -->
+		</configuration>
+	    </plugin>
+	</plugins>
+    </reporting>
+</project>
diff --git a/gimap/src/main/java/com/sun/mail/gimap/GmailFolder.java b/gimap/src/main/java/com/sun/mail/gimap/GmailFolder.java
new file mode 100644
index 0000000..cdbde00
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailFolder.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import java.io.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.gimap.protocol.*;
+
+/**
+ * A Gmail folder.  Defines new FetchProfile items and
+ * uses GmailMessage to store additional Gmail message attributes.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+public class GmailFolder extends IMAPFolder {
+    /**
+     * A fetch profile item for fetching headers.
+     * This inner class extends the <code>FetchProfile.Item</code>
+     * class to add new FetchProfile item types, specific to Gmail.
+     *
+     * @see FetchProfile
+     */
+    public static class FetchProfileItem extends FetchProfile.Item {
+	protected FetchProfileItem(String name) {
+	    super(name);
+	}
+
+	/**
+	 * MSGID is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the Gmail unique message ID for messages
+	 * in the specified range are desired to be prefetched. <p>
+	 * 
+	 * An example of how a client uses this is below:
+	 * <blockquote><pre>
+	 *
+	 * 	FetchProfile fp = new FetchProfile();
+	 *	fp.add(GmailFolder.FetchProfileItem.MSGID);
+	 *	folder.fetch(msgs, fp);
+	 *
+	 * </pre></blockquote>
+	 */ 
+	public static final FetchProfileItem MSGID = 
+		new FetchProfileItem("X-GM-MSGID");
+
+	/**
+	 * THRID is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the Gmail unique thread ID for messages
+	 * in the specified range are desired to be prefetched. <p>
+	 * 
+	 * An example of how a client uses this is below:
+	 * <blockquote><pre>
+	 *
+	 * 	FetchProfile fp = new FetchProfile();
+	 *	fp.add(GmailFolder.FetchProfileItem.THRID);
+	 *	folder.fetch(msgs, fp);
+	 *
+	 * </pre></blockquote>
+	 */ 
+	public static final FetchProfileItem THRID = 
+		new FetchProfileItem("X-GM-THRID");
+
+	/**
+	 * LABELS is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the Gmail labels for messages
+	 * in the specified range are desired to be prefetched. <p>
+	 * 
+	 * An example of how a client uses this is below:
+	 * <blockquote><pre>
+	 *
+	 * 	FetchProfile fp = new FetchProfile();
+	 *	fp.add(GmailFolder.FetchProfileItem.LABELS);
+	 *	folder.fetch(msgs, fp);
+	 *
+	 * </pre></blockquote>
+	 */ 
+	public static final FetchProfileItem LABELS = 
+		new FetchProfileItem("X-GM-LABELS");
+    }
+
+    /**
+     * Set the specified labels for the given array of messages.
+     *
+     * @param	msgs	the messages
+     * @param	labels	the labels to add or remove
+     * @param	set	true to add, false to remove
+     * @exception	MessagingException	for failures
+     * @since	JavaMail 1.5.5
+     */
+    public synchronized void setLabels(Message[] msgs,
+				String[] labels, boolean set)
+				throws MessagingException {
+	checkOpened();
+
+	if (msgs.length == 0) // boundary condition
+	    return;
+
+	synchronized(messageCacheLock) {
+	    try {
+		IMAPProtocol ip = getProtocol();
+		assert ip instanceof GmailProtocol;
+		GmailProtocol p = (GmailProtocol)ip;
+		MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
+		if (ms == null)
+		    throw new MessageRemovedException(
+					"Messages have been removed");
+		p.storeLabels(ms, labels, set);
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(this, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+    }
+
+    /**
+     * Set the specified labels for the given range of message numbers.
+     *
+     * @param	start	first message number
+     * @param	end	last message number
+     * @param	labels	the labels to add or remove
+     * @param	set	true to add, false to remove
+     * @exception	MessagingException	for failures
+     * @since	JavaMail 1.5.5
+     */
+    public synchronized void setLabels(int start, int end,
+				String[] labels, boolean set)
+				throws MessagingException {
+	checkOpened();
+	Message[] msgs = new Message[end - start + 1];
+	int i = 0;
+	for (int n = start; n <= end; n++)
+	    msgs[i++] = getMessage(n);
+	setLabels(msgs, labels, set);
+    }
+
+    /**
+     * Set the specified labels for the given array of message numbers.
+     *
+     * @param	msgnums	the message numbers
+     * @param	labels	the labels to add or remove
+     * @param	set	true to add, false to remove
+     * @exception	MessagingException	for failures
+     * @since	JavaMail 1.5.5
+     */
+    public synchronized void setLabels(int[] msgnums,
+				String[] labels, boolean set)
+				throws MessagingException {
+	checkOpened();
+	Message[] msgs = new Message[msgnums.length];
+	for (int i = 0; i < msgnums.length; i++)
+	    msgs[i] = getMessage(msgnums[i]);
+	setLabels(msgs, labels, set);
+    }
+
+    /**
+     * Constructor used to create a possibly non-existent folder.
+     *
+     * @param fullName	fullname of this folder
+     * @param separator the default separator character for this 
+     *			folder's namespace
+     * @param store	the Store
+     * @param isNamespace does this name represent a namespace?
+     */
+    protected GmailFolder(String fullName, char separator, IMAPStore store,
+				Boolean isNamespace) {
+	super(fullName, separator, store, isNamespace);
+    }
+
+    /**
+     * Constructor used to create an existing folder.
+     *
+     * @param	li	the ListInfo for this folder
+     * @param	store	the store containing this folder
+     */
+    protected GmailFolder(ListInfo li, IMAPStore store) {
+	super(li, store);
+    }
+
+    /**
+     * Create a new IMAPMessage object to represent the given message number.
+     */
+    protected IMAPMessage newIMAPMessage(int msgnum) {
+	return new GmailMessage(this, msgnum);
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/GmailMessage.java b/gimap/src/main/java/com/sun/mail/gimap/GmailMessage.java
new file mode 100644
index 0000000..52e1a7a
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailMessage.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import java.io.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.util.*;
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.gimap.protocol.*;
+
+/**
+ * A Gmail message.  Adds methods to access Gmail-specific per-message data.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+public class GmailMessage extends IMAPMessage {
+    /**
+     * Constructor.
+     *
+     * @param	folder	the containing folder
+     * @param	msgnum	the message sequence number
+     */
+    protected GmailMessage(IMAPFolder folder, int msgnum) {
+	super(folder, msgnum);
+    }
+
+    /**
+     * Constructor, for use by IMAPNestedMessage.
+     *
+     * @param	session	the Session
+     */
+    protected GmailMessage(Session session) {
+	super(session);
+    }
+
+    /**
+     * Return the Gmail unique message ID.
+     *
+     * @return	the message ID
+     * @exception	MessagingException for failures
+     */
+    public long getMsgId() throws MessagingException {
+	Long msgid = (Long)getItem(GmailProtocol.MSGID_ITEM);
+	if (msgid != null)
+	    return msgid.longValue();
+	else
+	    return -1;
+    }
+
+    /**
+     * Return the Gmail unique thread ID.
+     *
+     * @return	the thread ID
+     * @exception	MessagingException for failures
+     */
+    public long getThrId() throws MessagingException {
+	Long thrid = (Long)getItem(GmailProtocol.THRID_ITEM);
+	if (thrid != null)
+	    return thrid.longValue();
+	else
+	    return -1;
+    }
+
+    /**
+     * Return the Gmail labels associated with this message.
+     *
+     * @return	array of labels, or empty array if none
+     * @exception	MessagingException for failures
+     */
+    public String[] getLabels() throws MessagingException {
+	String[] labels = (String[])getItem(GmailProtocol.LABELS_ITEM);
+	if (labels != null)
+	    return (String[])(labels.clone());
+	else
+	    return new String[0];
+    }
+
+    /**
+     * Set/Unset the given labels on this message.
+     *
+     * @param	labels	the labels to add or remove
+     * @param	set	true to add labels, false to remove
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.5.5
+     */
+    public synchronized void setLabels(String[] labels, boolean set)
+			throws MessagingException {
+        // Acquire MessageCacheLock, to freeze seqnum.
+        synchronized(getMessageCacheLock()) {
+	    try {
+		IMAPProtocol ip = getProtocol();
+		assert ip instanceof GmailProtocol;
+		GmailProtocol p = (GmailProtocol)ip;
+		checkExpunged(); // Insure that this message is not expunged
+		p.storeLabels(getSequenceNumber(), labels, set);
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+    }
+
+    /**
+     * Clear any cached labels for this message.
+     * The Gmail labels for a messge will be cached when first accessed
+     * using either the fetch method or the getLabels method.  Gmail provides
+     * no notification when the labels have been changed by another application
+     * so applications may need to clear the cache if accessing the labels for
+     * a message more than once while the Folder is open.
+     *
+     * @since JavaMail 1.5.6
+     */
+    public synchronized void clearCachedLabels() {
+	if (items != null)
+	    items.remove(GmailProtocol.LABELS_ITEM.getName());
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/GmailMsgIdTerm.java b/gimap/src/main/java/com/sun/mail/gimap/GmailMsgIdTerm.java
new file mode 100644
index 0000000..9b3a60d
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailMsgIdTerm.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import javax.mail.Message;
+import javax.mail.search.StringTerm;
+
+/**
+ * This class implements searching for the Gmail message ID.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+public final class GmailMsgIdTerm extends LongTerm {
+
+    private static final long serialVersionUID = -7090909401071626866L;
+
+    /**
+     * Constructor.
+     *
+     * @param msgId  the message ID
+     */
+    public GmailMsgIdTerm(long msgId) {
+	super(msgId);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the Message number is matched with this Message
+     * @return		true if the match succeeds, otherwise false
+     */
+    public boolean match(Message msg) {
+	long msgId;
+
+	try {
+	    if (msg instanceof GmailMessage)
+		msgId = ((GmailMessage)msg).getMsgId();
+	    else
+		return false;
+	} catch (Exception e) {
+	    return false;
+	}
+
+	return super.match(msgId);
+    }
+
+    /**
+     * Equality comparison.
+     *
+     * @param	obj	the object to compare with
+     * @return		true if equal
+     */
+    public boolean equals(Object obj) {
+	if (!(obj instanceof GmailMsgIdTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/GmailProvider.java b/gimap/src/main/java/com/sun/mail/gimap/GmailProvider.java
new file mode 100644
index 0000000..8a56263
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import javax.mail.Provider;
+
+/**
+ * The Gmail IMAP protocol provider.
+ */
+public class GmailProvider extends Provider {
+    public GmailProvider() {
+	super(Provider.Type.STORE, "gimap", GmailStore.class.getName(),
+	    "Oracle", null);
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/GmailRawSearchTerm.java b/gimap/src/main/java/com/sun/mail/gimap/GmailRawSearchTerm.java
new file mode 100644
index 0000000..6626467
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailRawSearchTerm.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import javax.mail.Message;
+import javax.mail.search.StringTerm;
+
+/**
+ * This class implements searching using the Gmail X-GM-RAW extension.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+public final class GmailRawSearchTerm extends StringTerm {
+
+    private static final long serialVersionUID = 6284730140424242662L;
+
+    /**
+     * Constructor.
+     *
+     * @param pattern  the pattern to search for
+     */
+    public GmailRawSearchTerm(String pattern) {
+	// Note: comparison is case-insensitive
+	super(pattern);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the pattern match is applied to this Message's 
+     *			subject header
+     * @exception	RuntimeException	this can't be supported locally
+     */
+    public boolean match(Message msg) {
+	throw new RuntimeException("GmailRawSearchTerm not supported locally");
+    }
+
+    /**
+     * Equality comparison.
+     */
+    public boolean equals(Object obj) {
+	if (!(obj instanceof GmailRawSearchTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/GmailSSLProvider.java b/gimap/src/main/java/com/sun/mail/gimap/GmailSSLProvider.java
new file mode 100644
index 0000000..ed86523
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailSSLProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import javax.mail.Provider;
+
+/**
+ * The Gmail IMAP protocol provider.
+ */
+public class GmailSSLProvider extends Provider {
+    public GmailSSLProvider() {
+	super(Provider.Type.STORE, "gimaps", GmailSSLStore.class.getName(),
+	    "Oracle", null);
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/GmailSSLStore.java b/gimap/src/main/java/com/sun/mail/gimap/GmailSSLStore.java
new file mode 100644
index 0000000..df855f4
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailSSLStore.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import javax.mail.*;
+
+/**
+ * Support "gimaps" protocol name.
+ * Unnecessary, since Gmail always uses SSL, but someone might
+ * expect this name to work.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+public class GmailSSLStore extends GmailStore {
+    /**
+     * Constructor that takes a Session object and a URLName that
+     * represents a specific IMAP server.
+     *
+     * @param	session	the Session
+     * @param	url	the URLName of this store
+     */
+    public GmailSSLStore(Session session, URLName url) {
+	super(session, url, "gimaps", true);
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/GmailStore.java b/gimap/src/main/java/com/sun/mail/gimap/GmailStore.java
new file mode 100644
index 0000000..fb651af
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailStore.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import java.io.IOException;
+
+import javax.mail.*;
+
+import com.sun.mail.iap.ProtocolException;
+import com.sun.mail.imap.IMAPStore;
+import com.sun.mail.imap.IMAPFolder;
+import com.sun.mail.imap.protocol.IMAPProtocol;
+import com.sun.mail.imap.protocol.ListInfo;
+import com.sun.mail.gimap.protocol.GmailProtocol;
+
+/**
+ * A Gmail Store.  Defaults to imap.gmail.com with SSL.
+ * Uses a GmailProtocol and Gmail Folder to support Gmail extensions.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+public class GmailStore extends IMAPStore {
+    /**
+     * Constructor that takes a Session object and a URLName that
+     * represents a specific IMAP server.
+     *
+     * @param	session	the Session
+     * @param	url	the URLName of this store
+     */
+    public GmailStore(Session session, URLName url) {
+	this(session, url, "gimap", true);
+    }
+
+    /**
+     * Constructor used by GmailSSLStore subclass.
+     *
+     * @param	session	the Session
+     * @param	url	the URLName of this store
+     * @param	name	the protocol name
+     * @param	isSSL	use SSL to connect?
+     */
+    protected GmailStore(Session session, URLName url,
+                                String name, boolean isSSL) {
+	super(session, url, name, true);	// Gmail requires SSL
+    }
+
+    protected boolean protocolConnect(String host, int pport,
+				String user, String password)
+				throws MessagingException {
+	if (host == null)
+	    host = "imap.gmail.com";		// default to Gmail host
+	return super.protocolConnect(host, pport, user, password);
+    }
+
+    protected IMAPProtocol newIMAPProtocol(String host, int port)
+				throws IOException, ProtocolException {
+	return new GmailProtocol(name, host, port, 
+					    session.getProperties(),
+					    isSSL,
+					    logger
+					   );
+    }
+
+    /**
+     * Create an IMAPFolder object.
+     */
+    protected IMAPFolder newIMAPFolder(String fullName, char separator,
+				Boolean isNamespace) {
+	return new GmailFolder(fullName, separator, this, isNamespace);
+    }
+
+    /**
+     * Create an IMAPFolder object.
+     */
+    protected IMAPFolder newIMAPFolder(ListInfo li) {
+	return new GmailFolder(li, this);
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/GmailThrIdTerm.java b/gimap/src/main/java/com/sun/mail/gimap/GmailThrIdTerm.java
new file mode 100644
index 0000000..9101d73
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/GmailThrIdTerm.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import javax.mail.Message;
+import javax.mail.search.StringTerm;
+
+/**
+ * This class implements searching for the Gmail thread ID.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+public final class GmailThrIdTerm extends LongTerm {
+
+    private static final long serialVersionUID = -4549268502183739744L;
+
+    /**
+     * Constructor.
+     *
+     * @param thrId  the thread ID
+     */
+    public GmailThrIdTerm(long thrId) {
+	super(thrId);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the Message number is matched with this Message
+     * @return		true if the match succeeds, otherwise false
+     */
+    public boolean match(Message msg) {
+	long thrId;
+
+	try {
+	    if (msg instanceof GmailMessage)
+		thrId = ((GmailMessage)msg).getThrId();
+	    else
+		return false;
+	} catch (Exception e) {
+	    return false;
+	}
+
+	return super.match(thrId);
+    }
+
+    /**
+     * Equality comparison.
+     *
+     * @param	obj	the object to compare with
+     * @return		true if equal
+     */
+    public boolean equals(Object obj) {
+	if (!(obj instanceof GmailThrIdTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/LongTerm.java b/gimap/src/main/java/com/sun/mail/gimap/LongTerm.java
new file mode 100644
index 0000000..2ec189d
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/LongTerm.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap;
+
+import javax.mail.search.SearchTerm;
+
+/**
+ * This class implements a long integer search term.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+abstract class LongTerm extends SearchTerm {
+    /**
+     * The number.
+     *
+     * @serial
+     */
+    protected long number;
+
+    private static final long serialVersionUID = 5285147193246128043L;
+
+    protected LongTerm(long number) {
+	this.number = number;
+    }
+
+    /**
+     * Return the number to compare with.
+     *
+     * @return	the number
+     */
+    public long getNumber() {
+	return number;
+    }
+
+    protected boolean match(long i) {
+	return i == number;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    public boolean equals(Object obj) {
+	if (!(obj instanceof LongTerm))
+	    return false;
+	LongTerm t = (LongTerm)obj;
+	return t.number == this.number && super.equals(obj);
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    public int hashCode() {
+	return (int)number + super.hashCode();
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/package.html b/gimap/src/main/java/com/sun/mail/gimap/package.html
new file mode 100644
index 0000000..9161372
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/package.html
@@ -0,0 +1,105 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.gimap package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+An EXPERIMENTAL IMAP protocol provider that supports the
+<A HREF="https://developers.google.com/google-apps/gmail/imap_extensions"
+TARGET="_top">
+Gmail-specific IMAP protocol extensions
+</A>.
+This provider supports all the features of the IMAP provider, plus
+additional Gmail-specific features.
+This provider can be used by including gimap.jar in your CLASSPATH
+along with mail.jar, and by using the "gimap" protocol instead of
+the "imap" protocol.
+Remember that this means that all properties should be named "mail.gimap.*",
+but that otherwise this provider supports all the same properties as the
+IMAP protocol provider.
+The Gmail IMAP provider defaults to using SSL to connect to "imap.gmail.com".
+</P>
+<P>
+In general, applications should not need to use the classes in this
+package directly.  Instead, they should use the APIs defined by the
+<code>javax.mail</code> package (and subpackages).  Applications should
+never construct instances of <code>GmailStore</code> or
+<code>GmailFolder</code> directly.  Instead, they should use the
+<code>Session</code> method <code>getStore</code> to acquire an
+appropriate <code>Store</code> object, and from that acquire
+<code>Folder</code> objects.
+</P>
+<P>
+Message objects returned by this provider may be cast to GmailMessage
+to access Gmail-specific data, e.g., using the methods GmailMessage.getMsgId(),
+GmailMessage.getThrId(), and GmailMessage.getLabels().
+For example:
+</P>
+<PRE>
+    GmailMessage gmsg = (GmailMessage)msg;
+    System.out.println("Gmail message ID is " + gmsg.getMsgId());
+    String[] labels = gmsg.getLabels();
+    for (String s : labels)
+	System.out.println("Gmail message label: " + s);
+</PRE>
+<P>
+Gmail-specific data may be prefetched using the GmailFolder.FetchProfileItems
+MSGID, THRID, and LABELS.
+For example:
+</P>
+<PRE>
+    FetchProfile fp = new FetchProfile();
+    fp.add(GmailFolder.FetchProfileItem.MSGID);
+    folder.fetch(fp);
+</PRE>
+<P>
+You can search using Gmail-specific data using the GmailMsgIdTerm,
+GmailThrIdTerm, and GmailRawSearchTerm search terms.
+For example:
+</P>
+<PRE>
+    // find the message with this Gmail unique message ID
+    long msgid = ...;
+    Message[] msgs = folder.search(new GmailMsgIdTerm(msgid));
+</PRE>
+<P>
+You can access the Gmail extended attributes (returned by XLIST) for a
+folder using the IMAPFolder.getAttributes() method.
+For example:
+</P>
+<PRE>
+    IMAPFolder ifolder = (IMAPFolder)folder;
+    String[] attrs = ifolder.getAttributes();
+    for (String s : attrs)
+	System.out.println("Folder attribute: " + s);
+</PRE>
+<P>
+<strong>WARNING:</strong> The APIs unique to this package should be
+considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+</P>
+
+</BODY>
+</HTML>
diff --git a/gimap/src/main/java/com/sun/mail/gimap/protocol/GmailProtocol.java b/gimap/src/main/java/com/sun/mail/gimap/protocol/GmailProtocol.java
new file mode 100644
index 0000000..c3e3fb1
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/protocol/GmailProtocol.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap.protocol;
+
+import java.io.*;
+import java.util.*;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.gimap.GmailFolder.FetchProfileItem;
+
+import com.sun.mail.util.MailLogger;
+
+/**
+ * Extend IMAP support to handle Gmail-specific protocol extensions.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+public class GmailProtocol extends IMAPProtocol {
+    
+    /*
+     * Define the Gmail-specific FETCH items.
+     */
+    public static final FetchItem MSGID_ITEM =
+	new FetchItem("X-GM-MSGID", FetchProfileItem.MSGID) {
+	    public Object parseItem(FetchResponse r) {
+		return Long.valueOf(r.readLong());
+	    }
+	};
+    public static final FetchItem THRID_ITEM =
+	new FetchItem("X-GM-THRID", FetchProfileItem.THRID) {
+	    public Object parseItem(FetchResponse r) {
+		return Long.valueOf(r.readLong());
+	    }
+	};
+    public static final FetchItem LABELS_ITEM =
+	new FetchItem("X-GM-LABELS", FetchProfileItem.LABELS) {
+	    public Object parseItem(FetchResponse r) {
+		return r.readAtomStringList();
+	    }
+	};
+
+    private static final FetchItem[] myFetchItems = {
+	MSGID_ITEM,
+	THRID_ITEM,
+	LABELS_ITEM
+    };
+
+    private FetchItem[] fetchItems = null;
+
+    /**
+     * Connect to Gmail.
+     *
+     * @param name	the protocol name
+     * @param host	host to connect to
+     * @param port	portnumber to connect to
+     * @param props	Properties object used by this protocol
+     * @param isSSL	use SSL?
+     * @param logger	for log messages
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     */
+    public GmailProtocol(String name, String host, int port, 
+			Properties props, boolean isSSL, MailLogger logger)
+			throws IOException, ProtocolException {
+	super(name, host, port, props, isSSL, logger);
+
+	// check to see if this is really Gmail
+	if (!hasCapability("X-GM-EXT-1")) {
+	    logger.fine("WARNING! Not connected to Gmail!");
+	    // XXX - could call "disconnect()" here and make this a fatal error
+	} else {
+	    logger.fine("connected to Gmail");
+	}
+    }
+
+    /**
+     * Return the additional fetch items supported by the Gmail protocol.
+     * Combines our fetch items with those supported by the superclass.
+     */
+    public FetchItem[] getFetchItems() {
+	if (fetchItems != null)
+	    return fetchItems;
+	FetchItem[] sfi = super.getFetchItems();
+	if (sfi == null || sfi.length == 0)
+	    fetchItems = myFetchItems;
+	else {
+	    fetchItems = new FetchItem[sfi.length + myFetchItems.length];
+	    System.arraycopy(sfi, 0, fetchItems, 0, sfi.length);
+	    System.arraycopy(myFetchItems, 0, fetchItems, sfi.length,
+							myFetchItems.length);
+	}
+	return fetchItems;
+    }
+
+    /**
+     * Set the specified labels on this message.
+     *
+     * @param	msgsets	the message sets
+     * @param	labels	the labels
+     * @param	set	true to set, false to clear
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.5.5
+     */
+    public void storeLabels(MessageSet[] msgsets, String[] labels, boolean set)
+			throws ProtocolException {
+	storeLabels(MessageSet.toString(msgsets), labels, set);
+    }
+
+    /**
+     * Set the specified labels on this message.
+     *
+     * @param	start	the first message number
+     * @param	end	the last message number
+     * @param	labels	the labels
+     * @param	set	true to set, false to clear
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.5.5
+     */
+    public void storeLabels(int start, int end, String[] labels, boolean set)
+			throws ProtocolException {
+	storeLabels(String.valueOf(start) + ":" + String.valueOf(end),
+		   labels, set);
+    }
+
+    /**
+     * Set the specified labels on this message.
+     *
+     * @param	msg	the message number
+     * @param	labels	the labels
+     * @param	set	true to set, false to clear
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.5.5
+     */
+    public void storeLabels(int msg, String[] labels, boolean set)
+			throws ProtocolException { 
+	storeLabels(String.valueOf(msg), labels, set);
+    }
+
+    private void storeLabels(String msgset, String[] labels, boolean set)
+			throws ProtocolException {
+	Response[] r;
+	if (set)
+	    r = command("STORE " + msgset + " +X-GM-LABELS",
+			 createLabelList(labels));
+	else
+	    r = command("STORE " + msgset + " -X-GM-LABELS",
+			createLabelList(labels));
+	
+	// Dispatch untagged responses
+	notifyResponseHandlers(r);
+	handleResult(r[r.length-1]);
+    }
+
+    // XXX - assume Gmail always supports UTF-8
+    private Argument createLabelList(String[] labels) {
+	Argument args = new Argument();	
+	Argument itemArgs = new Argument();
+	for (int i = 0, len = labels.length; i < len; i++)
+	    itemArgs.writeString(labels[i], StandardCharsets.UTF_8);
+	args.writeArgument(itemArgs);
+	return args;
+    }
+
+    /**
+     * Return a GmailSearchSequence.
+     */
+    protected SearchSequence getSearchSequence() {
+	if (searchSequence == null)
+	    searchSequence = new GmailSearchSequence(this);
+	return searchSequence;
+    }
+}
diff --git a/gimap/src/main/java/com/sun/mail/gimap/protocol/GmailSearchSequence.java b/gimap/src/main/java/com/sun/mail/gimap/protocol/GmailSearchSequence.java
new file mode 100644
index 0000000..8689726
--- /dev/null
+++ b/gimap/src/main/java/com/sun/mail/gimap/protocol/GmailSearchSequence.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.gimap.protocol;
+
+import java.io.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.search.*;
+
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.gimap.*;
+
+/**
+ * Support Gmail-specific search extensions.
+ *
+ * @since JavaMail 1.4.6
+ * @author Bill Shannon
+ */
+
+public class GmailSearchSequence extends SearchSequence {
+    public GmailSearchSequence(IMAPProtocol p) {
+	super(p);
+    }
+
+    public Argument generateSequence(SearchTerm term, String charset)
+				throws SearchException, IOException {
+	if (term instanceof GmailMsgIdTerm)
+	    return gmailMsgidSearch((GmailMsgIdTerm)term);
+	else if (term instanceof GmailThrIdTerm)
+	    return gmailThridSearch((GmailThrIdTerm)term);
+	else if (term instanceof GmailRawSearchTerm)
+	    return gmailRawSearch((GmailRawSearchTerm)term, charset);
+	else
+	    return super.generateSequence(term, charset);
+    }
+
+    protected Argument gmailMsgidSearch(GmailMsgIdTerm term)
+				throws IOException {
+	Argument result = new Argument();
+	result.writeAtom("X-GM-MSGID");
+	result.writeNumber(term.getNumber());
+	return result;
+    }
+
+    protected Argument gmailThridSearch(GmailThrIdTerm term)
+				throws IOException {
+	Argument result = new Argument();
+	result.writeAtom("X-GM-THRID");
+	result.writeNumber(term.getNumber());
+	return result;
+    }
+
+    protected Argument gmailRawSearch(GmailRawSearchTerm term, String charset)
+				throws IOException {
+	Argument result = new Argument();
+	result.writeAtom("X-GM-RAW");
+	result.writeString(term.getPattern(), charset);
+	return result;
+    }
+}
diff --git a/gimap/src/main/java/module-info.java b/gimap/src/main/java/module-info.java
new file mode 100644
index 0000000..cda246e
--- /dev/null
+++ b/gimap/src/main/java/module-info.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+module com.sun.mail.gimap {
+    exports com.sun.mail.gimap;
+    exports com.sun.mail.gimap.protocol;
+    provides javax.mail.Provider with
+	com.sun.mail.gimap.GmailProvider, com.sun.mail.gimap.GmailSSLProvider;
+
+    requires jakarta.mail;
+    requires java.logging;
+    requires java.security.sasl;
+    requires com.sun.mail.imap;
+}
diff --git a/gimap/src/main/resources/META-INF/MANIFEST.MF b/gimap/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..6ccf342
--- /dev/null
+++ b/gimap/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Extension-Name: com.sun.mail.gimap
+Specification-Title: com.sun.mail.gimap
+Specification-Version: ${mail.spec.version}
+Specification-Vendor: ${project.organization.name}
+Implementation-Title: com.sun.mail.gimap
+Implementation-Version: ${mail.version}
+Implementation-Vendor: ${project.organization.name}
+Implementation-Vendor-Id: com.sun
diff --git a/gimap/src/main/resources/META-INF/javamail.providers b/gimap/src/main/resources/META-INF/javamail.providers
new file mode 100644
index 0000000..e9d3890
--- /dev/null
+++ b/gimap/src/main/resources/META-INF/javamail.providers
@@ -0,0 +1,2 @@
+protocol=gimap; type=store; class=com.sun.mail.gimap.GmailStore; vendor=Oracle;
+protocol=gimaps; type=store; class=com.sun.mail.gimap.GmailSSLStore; vendor=Oracle;
diff --git a/gimap/src/main/resources/META-INF/services/javax.mail.Provider b/gimap/src/main/resources/META-INF/services/javax.mail.Provider
new file mode 100644
index 0000000..bf49a14
--- /dev/null
+++ b/gimap/src/main/resources/META-INF/services/javax.mail.Provider
@@ -0,0 +1,2 @@
+com.sun.mail.gimap.GmailProvider
+com.sun.mail.gimap.GmailSSLProvider
diff --git a/imap/pom.xml b/imap/pom.xml
new file mode 100644
index 0000000..d45590f
--- /dev/null
+++ b/imap/pom.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>parent-distrib</artifactId>
+	<version>1.6.3</version>
+	<relativePath>../parent-distrib/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>imap</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API imap provider</name>
+
+    <properties>
+	<mail.extraClasses>com/sun/mail/iap/**</mail.extraClasses>
+	<mail.packages.export>
+	    com.sun.mail.imap; version=${mail.osgiversion},
+	    com.sun.mail.imap.protocol; version=${mail.osgiversion},
+	    com.sun.mail.iap; version=${mail.osgiversion}
+	</mail.packages.export>
+    </properties>
+</project>
diff --git a/imap/src/main/java/module-info.java b/imap/src/main/java/module-info.java
new file mode 100644
index 0000000..37a3c34
--- /dev/null
+++ b/imap/src/main/java/module-info.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+module com.sun.mail.imap {
+    exports com.sun.mail.iap;
+    exports com.sun.mail.imap;
+    exports com.sun.mail.imap.protocol;
+    provides javax.mail.Provider with
+	com.sun.mail.imap.IMAPProvider, com.sun.mail.imap.IMAPSSLProvider;
+
+    requires jakarta.mail;
+    requires java.logging;
+    requires java.security.sasl;
+}
diff --git a/imap/src/main/resources/META-INF/MANIFEST.MF b/imap/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..64f8156
--- /dev/null
+++ b/imap/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Extension-Name: com.sun.mail.imap
+Specification-Title: com.sun.mail.imap
+Specification-Version: ${mail.spec.version}
+Specification-Vendor: ${project.organization.name}
+Implementation-Title: com.sun.mail.imap
+Implementation-Version: ${mail.version}
+Implementation-Vendor: ${project.organization.name}
+Implementation-Vendor-Id: com.sun
diff --git a/imap/src/main/resources/META-INF/javamail.providers b/imap/src/main/resources/META-INF/javamail.providers
new file mode 100644
index 0000000..305dfa5
--- /dev/null
+++ b/imap/src/main/resources/META-INF/javamail.providers
@@ -0,0 +1,3 @@
+# JavaMail IMAP provider Oracle
+protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Oracle;
+protocol=imaps; type=store; class=com.sun.mail.imap.IMAPSSLStore; vendor=Oracle;
diff --git a/imap/src/main/resources/META-INF/services/javax.mail.Provider b/imap/src/main/resources/META-INF/services/javax.mail.Provider
new file mode 100644
index 0000000..932e992
--- /dev/null
+++ b/imap/src/main/resources/META-INF/services/javax.mail.Provider
@@ -0,0 +1,2 @@
+com.sun.mail.imap.IMAPProvider
+com.sun.mail.imap.IMAPSSLProvider
diff --git a/javadoc/pom.xml b/javadoc/pom.xml
new file mode 100644
index 0000000..8aeaed3
--- /dev/null
+++ b/javadoc/pom.xml
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>javadoc</artifactId>
+    <packaging>pom</packaging>
+    <version>1.6.3</version>
+    <name>JavaMail API javadocs</name>
+    <description>${project.name}</description>
+
+    <build>
+        <plugins>
+	    <!--
+		To allow us to generate javadocs that only include some
+		classes in certain packages, we need to copy the sources
+		to another location and run javadoc against that subset
+		of the sources.  This ant task does the copy.
+	    -->
+	    <plugin>
+                <artifactId>maven-antrun-plugin</artifactId>
+		<inherited>false</inherited>
+                <executions>
+		    <execution>
+			<phase>package</phase>
+			<configuration>
+			    <tasks>
+
+				<copy todir="target/javadoc">
+				    <fileset dir="../mail/src/main/java">
+					<include name="**/*.html"/>
+					<include name="javax/mail/**"/>
+				    </fileset>
+				    <fileset dir="../mail/src/main/java"
+					includes="
+			com/sun/mail/imap/package.html,
+			com/sun/mail/imap/IMAPFolder.java,
+			com/sun/mail/imap/IMAPMessage.java,
+			com/sun/mail/imap/IMAPStore.java,
+			com/sun/mail/imap/IMAPSSLStore.java
+			com/sun/mail/imap/ACL.java,
+			com/sun/mail/imap/Rights.java,
+			com/sun/mail/imap/Quota.java,
+			com/sun/mail/imap/SortTerm.java,
+			com/sun/mail/imap/ResyncData.java,
+			com/sun/mail/imap/OlderTerm.java,
+			com/sun/mail/imap/YoungerTerm.java,
+			com/sun/mail/imap/MessageVanishedEvent.java,
+			com/sun/mail/imap/ModifiedSinceTerm.java,
+			com/sun/mail/imap/IdleManager.java,
+			com/sun/mail/imap/ReferralException.java,
+			com/sun/mail/pop3/POP3Store.java,
+			com/sun/mail/pop3/POP3SSLStore.java,
+			com/sun/mail/pop3/POP3Folder.java,
+			com/sun/mail/pop3/POP3Message.java,
+			com/sun/mail/smtp/SMTPMessage.java,
+			com/sun/mail/smtp/SMTPAddressFailedException.java,
+			com/sun/mail/smtp/SMTPAddressSucceededException.java,
+			com/sun/mail/smtp/SMTPSendFailedException.java,
+			com/sun/mail/smtp/SMTPSenderFailedException.java,
+			com/sun/mail/smtp/SMTPTransport.java,
+			com/sun/mail/smtp/SMTPSSLTransport.java,
+			com/sun/mail/util/MailConnectException.java,
+			com/sun/mail/util/MailSSLSocketFactory.java,
+			com/sun/mail/util/ReadableMime.java,
+			com/sun/mail/util/logging/*.java
+					"/>
+				    <fileset dir="../dsn/src/main/java"
+					includes="
+			com/sun/mail/dsn/package.html,
+			com/sun/mail/dsn/DeliveryStatus.java,
+			com/sun/mail/dsn/DispositionNotification.java,
+			com/sun/mail/dsn/MessageHeaders.java,
+			com/sun/mail/dsn/MultipartReport.java,
+			com/sun/mail/dsn/Report.java
+					"/>
+				    <fileset dir="../gimap/src/main/java"
+					includes="
+			com/sun/mail/gimap/package.html,
+			com/sun/mail/gimap/*.java
+					"/>
+				</copy>
+
+			    </tasks>
+			</configuration>
+			<goals>
+			    <goal>run</goal>
+			</goals>
+		    </execution>
+                </executions>
+            </plugin>                                 
+
+	    <plugin>
+		<artifactId>maven-javadoc-plugin</artifactId>
+		<inherited>false</inherited>
+                <executions>
+		    <execution>
+			<phase>package</phase>
+			<goals>
+			    <goal>javadoc</goal>
+			</goals>
+			<configuration>
+			    <additionalJOption>-J-Xmx256m</additionalJOption>
+			    <author>false</author>
+			    <description>
+				JavaMail API documentation
+			    </description>
+			    <doctitle>
+				JavaMail API documentation
+			    </doctitle>
+			    <windowtitle>
+				JavaMail API documentation
+			    </windowtitle>
+			    <splitindex>true</splitindex>
+			    <use>true</use>
+			    <notimestamp>true</notimestamp>
+			    <serialwarn>true</serialwarn>
+			    <quiet>true</quiet>
+			    <overview>
+				${basedir}/target/javadoc/overview.html
+			    </overview>
+			    <bottom>
+<![CDATA[Copyright &#169; 1996-2018,
+    <a href="http://www.oracle.com">Oracle</a>
+    and/or its affiliates. All Rights Reserved.
+    Use is subject to
+    <a href="{@docRoot}/doc-files/speclicense.html" target="_top">license terms</a>.
+]]>
+			    </bottom>
+			    <groups>
+				<group>
+				    <title>JavaMail API Packages</title>
+				    <packages>javax.*</packages>
+				</group>
+				<group>
+				    <title>RI-specific Packages</title>
+				    <packages>com.sun.*</packages>
+				</group>
+			    </groups>
+			    <subpackages>
+javax:com.sun.mail.imap:com.sun.mail.pop3:com.sun.mail.smtp:com.sun.mail.dsn:com.sun.mail.util:com.sun.mail.gimap
+			    </subpackages>
+			    <excludePackageNames>
+				com.sun.mail.imap.protocol:com.sun.mail.gimap.protocol
+			    </excludePackageNames>
+			    <sourcepath>${basedir}/target/javadoc</sourcepath>
+			    <!--
+				Links to Java SE javadocs.
+
+				XXX - links need to include a trailing "/."
+				      because Maven strips off a trailing "/"
+				      before passing the option to the javadoc
+				      command, which then strips off the last
+				      name if it doesn't end with "/".
+			    -->
+			    <links>
+				<link>http://docs.oracle.com/javase/7/docs/api/.</link>
+			    </links>
+			</configuration>
+		    </execution>
+                </executions>
+	    </plugin>
+	</plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>dsn</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>gimap</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/logging/pom.xml b/logging/pom.xml
new file mode 100644
index 0000000..5471b8a
--- /dev/null
+++ b/logging/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>logging</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API logging demo</name>
+
+    <build>
+        <plugins>
+	    <!--
+		Need to disable the maven-bundle-plugin because the
+		new version doesn't like classes in the default package.
+	    -->
+	    <plugin>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>maven-bundle-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>osgi-manifest</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Because the maven-jar-plugin depends on the manifest file
+		created by the maven-bundle-plugin, we need to disable it too.
+	    -->
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>default-jar</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/logging/src/main/java/FileErrorManager.java b/logging/src/main/java/FileErrorManager.java
new file mode 100644
index 0000000..e91a246
--- /dev/null
+++ b/logging/src/main/java/FileErrorManager.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2018 Jason Mehrens. All Rights Reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.logging.ErrorManager;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+
+/**
+ * An error manager used to store mime messages from the <tt>MailHandler</tt>
+ * to the file system when the email server is unavailable or unreachable. The
+ * code to manually setup this error manager can be as simple as the following:
+ * <pre>
+ *      File dir = new File("path to dir");
+ *      FileErrorManager em = new FileErrorManager(dir);
+ * </pre>
+ *
+ * <p>
+ * <b>Configuration:</b>
+ * The code to setup this error manager via the logging properties can be as
+ * simple as the following:
+ * <pre>
+ *      #Default FileErrorManager settings.
+ *      FileErrorManager.pattern = path to directory
+ * </pre>
+ *
+ * If properties are not defined, or contain invalid values, then the specified
+ * default values are used.
+ * <ul>
+ * <li>FileErrorManager.pattern the absolute file path to the directory which
+ * will store any failed email messages. (defaults to the value of the system
+ * property <tt>java.io.tmpdir</tt>)
+ * </ul>
+ *
+ * @author Jason Mehrens
+ */
+public class FileErrorManager extends ErrorManager {
+
+    /**
+     * Stores the LogManager.
+     */
+    private static final LogManager manager = LogManager.getLogManager();
+    /**
+     * Used to report errors that this error manager fails to report.
+     */
+    private final ErrorManager next = new ErrorManager();
+    /**
+     * Directory of the email store.
+     */
+    private final File emailStore;
+
+    /**
+     * Creates a new error manager. Files are stored in the users temp
+     * directory.
+     *
+     * @exception SecurityException if unable to access system properties or if
+     * a security manager is present and unable to read or write to users temp
+     * directory.
+     */
+    public FileErrorManager() {
+        this.emailStore = getEmailStore();
+        init();
+    }
+
+    /**
+     * Creates a new error manager.
+     *
+     * @param dir a directory to store the email files.
+     * @throws NullPointerException if <tt>dir</tt> is <tt>null</tt>
+     * @throws IllegalArgumentException if <tt>dir</tt> is a
+     * <tt>java.io.File</tt> subclass, not a directory, or is not an absolute
+     * path.
+     * @throws SecurityException if a security manager is present and unable to
+     * read or write to a given directory.
+     */
+    public FileErrorManager(File dir) {
+        this.emailStore = dir;
+        init();
+    }
+
+    /**
+     * If the message parameter is a raw email, and passes the store term, then
+     * this method will store the email to the file system. If the message
+     * parameter is not a raw email then the message is forwarded to the super
+     * class. If an email is written to the file system without error, then the
+     * original reported error is ignored.
+     *
+     * @param msg String raw email or plain error message.
+     * @param ex Exception that occurred in the mail handler.
+     * @param code int error manager code.
+     */
+    @Override
+    public void error(String msg, Exception ex, int code) {
+        if (isRawEmail(msg)) {
+            try {
+                storeEmail(msg);
+            } catch (final IOException | RuntimeException IOE) {
+                next.error(msg, ex, code);
+                super.error(emailStore.toString(), IOE, ErrorManager.GENERIC_FAILURE);
+            }
+        } else {
+            next.error(msg, ex, code);
+        }
+    }
+
+    /**
+     * Performs the initialization for this object.
+     */
+    private void init() {
+        if (next == null) {
+            throw new NullPointerException(ErrorManager.class.getName());
+        }
+
+        File dir = this.emailStore;
+        if (dir.getClass() != File.class) { //For security reasons.
+            throw new IllegalArgumentException(dir.getClass().getName());
+        }
+
+        if (!dir.isDirectory()) {
+            throw new IllegalArgumentException("File must be a directory.");
+        }
+
+        if (!dir.canWrite()) { //Can throw under a security manager.
+            super.error(dir.getAbsolutePath(),
+                    new SecurityException("write"), ErrorManager.OPEN_FAILURE);
+        }
+
+        //For now, only absolute paths are allowed.
+        if (!dir.isAbsolute()) {
+            throw new IllegalArgumentException("Only absolute paths are allowed.");
+        }
+
+        if (!dir.canRead()) { //Can throw under a security manager.
+            super.error(dir.getAbsolutePath(),
+                    new SecurityException("read"), ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * Creates a common temp file prefix.
+     *
+     * @return the file prefix.
+     */
+    private String prefixName() {
+        return "FileErrorManager";
+    }
+
+    /**
+     * Creates a common temp file suffix.
+     *
+     * @return the file suffix.
+     */
+    private String suffixName() {
+        return ".eml";
+    }
+
+    /**
+     * Determines if the given message is a MIME message or just free text.
+     *
+     * @param msg the message to examine.
+     * @return true if MIME message otherwise false.
+     */
+    private boolean isRawEmail(String msg) {
+        if (msg != null && msg.length() > 0) {
+            return !msg.startsWith(Level.SEVERE.getName());
+        }
+        return false;
+    }
+
+    /**
+     * Stores the given string in a file.
+     *
+     * @param email the message to store.
+     * @throws IOException if there is a problem.
+     */
+    private void storeEmail(String email) throws IOException {
+        File tmp = null;
+        FileOutputStream out = null;
+        for (;;) {
+            tmp = File.createTempFile(prefixName(), suffixName(), emailStore);
+            try {
+                out = new FileOutputStream(tmp);
+                break;
+            } catch (FileNotFoundException FNFE) {
+                if (!tmp.exists()) { //retry if file is locked
+                    throw FNFE;
+                }
+            }
+        }
+
+        try (PrintStream ps = new PrintStream(wrap(out), false, "UTF-8")) {
+            ps.print(email);
+            ps.flush();
+            tmp = null; //Don't delete 'tmp' if all bytes were written.
+        } finally {
+            close(out);
+            delete(tmp); //Only deletes if not null.
+        }
+    }
+
+    /**
+     * Null safe close method.
+     *
+     * @param out closes the given stream.
+     */
+    private void close(OutputStream out) {
+        if (out != null) {
+            try {
+                out.close();
+            } catch (IOException IOE) {
+                super.error(out.toString(), IOE, ErrorManager.CLOSE_FAILURE);
+            }
+        }
+    }
+
+    /**
+     * Null safe delete method.
+     *
+     * @param tmp the file to delete.
+     */
+    private void delete(File tmp) {
+        if (tmp != null) {
+            try {
+                if (!tmp.delete() && tmp.exists()) {
+                    try {
+                        try {
+                            tmp.deleteOnExit();
+                        } catch (final LinkageError shutdown) {
+                            throw new RuntimeException(shutdown);
+                        }
+                    } catch (final RuntimeException shutdown) {
+                        if (!tmp.delete()) {
+                            super.error(tmp.getAbsolutePath(), shutdown,
+                                    ErrorManager.CLOSE_FAILURE);
+                        }
+                    }
+                }
+            } catch (SecurityException SE) {
+                super.error(tmp.toString(), SE, ErrorManager.CLOSE_FAILURE);
+            }
+        }
+    }
+
+    /**
+     * Gets the location of the email store.
+     *
+     * @return the File location.
+     */
+    private File getEmailStore() {
+        String dir = manager.getProperty(
+                getClass().getName().concat(".pattern"));
+        if (dir == null) {
+            dir = AccessController.doPrivileged(new PrivilegedAction<String>() {
+
+                @Override
+                public String run() {
+                    return System.getProperty("java.io.tmpdir", ".");
+                }
+            });
+        }
+        return new File(dir);
+    }
+
+    /**
+     * Wraps the given stream as a NewLineOutputStream.
+     *
+     * @param out the stream to wrap.
+     * @return the original or wrapped output stream.
+     */
+    @SuppressWarnings("UseSpecificCatch")
+    private OutputStream wrap(OutputStream out) {
+        assert out != null;
+        Class<?> k;
+        try {
+            k = Class.forName("NewlineOutputStream");
+            if (OutputStream.class.isAssignableFrom(k)) {
+                Constructor<?> c = k.getConstructor(OutputStream.class);
+                return (OutputStream) c.newInstance(out);
+            } else {
+                super.error("Unable to switch newlines",
+                        new ClassNotFoundException(k.getName()),
+                        ErrorManager.GENERIC_FAILURE);
+            }
+        } catch (RuntimeException re) {
+            super.error("Unable to switch newlines",
+                    re, ErrorManager.GENERIC_FAILURE);
+        } catch (Exception ex) {
+            super.error("Unable to switch newlines",
+                    ex, ErrorManager.GENERIC_FAILURE);
+        } catch (LinkageError le) {
+            super.error("Unable to switch newlines",
+                    new ClassNotFoundException("", le),
+                    ErrorManager.GENERIC_FAILURE);
+        }
+        return out;
+    }
+}
diff --git a/logging/src/main/java/MailHandlerDemo.java b/logging/src/main/java/MailHandlerDemo.java
new file mode 100644
index 0000000..22c918f
--- /dev/null
+++ b/logging/src/main/java/MailHandlerDemo.java
@@ -0,0 +1,647 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2018 Jason Mehrens. All Rights Reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import com.sun.mail.util.logging.CollectorFormatter;
+import com.sun.mail.util.logging.DurationFilter;
+import com.sun.mail.util.logging.MailHandler;
+import com.sun.mail.util.logging.SeverityComparator;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.lang.management.ManagementFactory;
+import java.util.*;
+import java.util.logging.*;
+import javax.activation.DataHandler;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * Demo for the different configurations for the MailHandler. If the logging
+ * properties file or class is not specified then this demo will apply some
+ * default settings to store emails in the user's temp directory.
+ *
+ * @author Jason Mehrens
+ */
+public class MailHandlerDemo {
+
+    /**
+     * This class name.
+     */
+    private static final String CLASS_NAME = MailHandlerDemo.class.getName();
+    /**
+     * The logger for this class name.
+     */
+    private static final Logger LOGGER = Logger.getLogger(CLASS_NAME);
+
+    /**
+     * Runs the demo.
+     *
+     * @param args the command line arguments
+     * @throws IOException if there is a problem.
+     */
+    public static void main(String[] args) throws IOException {
+        List<String> l = Arrays.asList(args);
+        if (l.contains("/?") || l.contains("-?") || l.contains("-help")) {
+            LOGGER.info("Usage: java MailHandlerDemo "
+                    + "[[-all] | [-body] | [-custom] | [-debug] | [-low] "
+                    + "| [-simple] | [-pushlevel] | [-pushfilter] "
+                    + "| [-pushnormal] | [-pushonly]] "
+                    + "\n\n"
+                    + "-all\t\t: Execute all demos.\n"
+                    + "-body\t\t: An email with all records and only a body.\n"
+                    + "-custom\t\t: An email with attachments and dynamic names.\n"
+                    + "-debug\t\t: Output basic debug information about the JVM "
+                    + "and log configuration.\n"
+                    + "-low\t\t: Generates multiple emails due to low capacity."
+                    + "\n"
+                    + "-simple\t\t: An email with all records with body and "
+                    + "an attachment.\n"
+                    + "-pushlevel\t: Generates high priority emails when the"
+                    + " push level is triggered and normal priority when "
+                    + "flushed.\n"
+                    + "-pushFilter\t: Generates high priority emails when the "
+                    + "push level and the push filter is triggered and normal "
+                    + "priority emails when flushed.\n"
+                    + "-pushnormal\t: Generates multiple emails when the "
+                    + "MemoryHandler push level is triggered.  All generated "
+                    + "email are sent as normal priority.\n"
+                    + "-pushonly\t: Generates multiple emails when the "
+                    + "MemoryHandler push level is triggered.  Generates high "
+                    + "priority emails when the push level is triggered and "
+                    + "normal priority when flushed.\n");
+        } else {
+            final boolean debug = init(l); //may create log messages.
+            try {
+                LOGGER.log(Level.FINEST, "This is the finest part of the demo.",
+                        new MessagingException("Fake JavaMail issue."));
+                LOGGER.log(Level.FINER, "This is the finer part of the demo.",
+                        new NullPointerException("Fake bug."));
+                LOGGER.log(Level.FINE, "This is the fine part of the demo.");
+                LOGGER.log(Level.CONFIG, "Logging config file is {0}.",
+                        getConfigLocation());
+                LOGGER.log(Level.INFO, "Your temp directory is {0}, "
+                        + "please wait...", getTempDir());
+
+                try { //Waste some time for the custom formatter.
+                    Thread.sleep(3L * 1000L);
+                } catch (InterruptedException ex) {
+                    Thread.currentThread().interrupt();
+                }
+
+                LOGGER.log(Level.WARNING, "This is a warning.",
+                        new FileNotFoundException("Fake file chooser issue."));
+                LOGGER.log(Level.SEVERE, "The end of the demo.",
+                        new IOException("Fake access denied issue."));
+            } finally {
+                closeHandlers();
+            }
+
+            //Force parse errors.  This does have side effects.
+            if (debug && getConfigLocation() != null) {
+                LogManager.getLogManager().readConfiguration();
+            }
+        }
+    }
+
+    /**
+     * Used debug problems with the logging.properties. The system property
+     * java.security.debug=access,stack can be used to trace access to the
+     * LogManager reset.
+     *
+     * @param prefix a string to prefix the output.
+     * @param err any PrintStream or null for System.out.
+     */
+    @SuppressWarnings("UseOfSystemOutOrSystemErr")
+    private static void checkConfig(String prefix, PrintStream err) {
+        if (prefix == null || prefix.trim().length() == 0) {
+            prefix = "DEBUG";
+        }
+
+        if (err == null) {
+            err = System.out;
+        }
+
+        try {
+            err.println(prefix + ": java.version="
+                    + System.getProperty("java.version"));
+            err.println(prefix + ": LOGGER=" + LOGGER.getLevel());
+            err.println(prefix + ": JVM id "
+                    + ManagementFactory.getRuntimeMXBean().getName());
+            err.println(prefix + ": java.security.debug="
+                    + System.getProperty("java.security.debug"));
+            SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                err.println(prefix + ": SecurityManager.class="
+                        + sm.getClass().getName());
+                err.println(prefix + ": SecurityManager classLoader="
+                        + toString(sm.getClass().getClassLoader()));
+                err.println(prefix + ": SecurityManager.toString=" + sm);
+            } else {
+                err.println(prefix + ": SecurityManager.class=null");
+                err.println(prefix + ": SecurityManager.toString=null");
+                err.println(prefix + ": SecurityManager classLoader=null");
+            }
+
+            String policy = System.getProperty("java.security.policy");
+            if (policy != null) {
+                File f = new File(policy);
+                err.println(prefix + ": AbsolutePath=" + f.getAbsolutePath());
+                err.println(prefix + ": CanonicalPath=" + f.getCanonicalPath());
+                err.println(prefix + ": length=" + f.length());
+                err.println(prefix + ": canRead=" + f.canRead());
+                err.println(prefix + ": lastModified="
+                        + new java.util.Date(f.lastModified()));
+            }
+
+            LogManager manager = LogManager.getLogManager();
+            String key = "java.util.logging.config.file";
+            String cfg = System.getProperty(key);
+            if (cfg != null) {
+                err.println(prefix + ": " + cfg);
+                File f = new File(cfg);
+                err.println(prefix + ": AbsolutePath=" + f.getAbsolutePath());
+                err.println(prefix + ": CanonicalPath=" + f.getCanonicalPath());
+                err.println(prefix + ": length=" + f.length());
+                err.println(prefix + ": canRead=" + f.canRead());
+                err.println(prefix + ": lastModified="
+                        + new java.util.Date(f.lastModified()));
+            } else {
+                err.println(prefix + ": " + key
+                        + " is not set as a system property.");
+            }
+            err.println(prefix + ": LogManager.class="
+                    + manager.getClass().getName());
+            err.println(prefix + ": LogManager classLoader="
+                    + toString(manager.getClass().getClassLoader()));
+            err.println(prefix + ": LogManager.toString=" + manager);
+            err.println(prefix + ": MailHandler classLoader="
+                    + toString(MailHandler.class.getClassLoader()));
+            err.println(prefix + ": Context ClassLoader="
+                    + toString(Thread.currentThread().getContextClassLoader()));
+            err.println(prefix + ": Session ClassLoader="
+                    + toString(Session.class.getClassLoader()));
+            err.println(prefix + ": DataHandler ClassLoader="
+                    + toString(DataHandler.class.getClassLoader()));
+
+            final String p = MailHandler.class.getName();
+            key = p.concat(".mail.to");
+            String to = manager.getProperty(key);
+            err.println(prefix + ": TO=" + to);
+            if (to != null) {
+                err.println(prefix + ": TO="
+                        + Arrays.toString(InternetAddress.parse(to, true)));
+            }
+
+            key = p.concat(".mail.from");
+            String from = manager.getProperty(key);
+            if (from == null || from.length() == 0) {
+                Session session = Session.getInstance(new Properties());
+                InternetAddress local = InternetAddress.getLocalAddress(session);
+                err.println(prefix + ": FROM=" + local);
+            } else {
+                err.println(prefix + ": FROM="
+                        + Arrays.asList(InternetAddress.parse(from, false)));
+                err.println(prefix + ": FROM="
+                        + Arrays.asList(InternetAddress.parse(from, true)));
+            }
+
+            synchronized (manager) {
+                final Enumeration<String> e = manager.getLoggerNames();
+                while (e.hasMoreElements()) {
+                    final Logger l = manager.getLogger(e.nextElement());
+                    if (l != null) {
+                        final Handler[] handlers = l.getHandlers();
+                        if (handlers.length > 0) {
+                            err.println(prefix + ": " + l.getClass().getName()
+                                    + ", " + l.getName());
+                            for (Handler h : handlers) {
+                                err.println(prefix + ":\t" + toString(prefix, err, h));
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (Throwable error) {
+            err.print(prefix + ": ");
+            error.printStackTrace(err);
+        }
+        err.flush();
+    }
+
+    /**
+     * Gets the class loader list.
+     *
+     * @param cl the class loader or null.
+     * @return the class loader list.
+     */
+    private static String toString(ClassLoader cl) {
+        StringBuilder buf = new StringBuilder();
+        buf.append(cl);
+        while (cl != null) {
+            cl = cl.getParent();
+            buf.append("<-").append(cl);
+        }
+        return buf.toString();
+    }
+
+    /**
+     * Gets a formatting string describing the given handler.
+     *
+     * @param prefix the output prefix.
+     * @param err the error stream.
+     * @param h the handler.
+     * @return the formatted string.
+     */
+    private static String toString(String prefix, PrintStream err, Handler h) {
+        StringBuilder buf = new StringBuilder();
+        buf.append(h.getClass().getName());
+        try {
+            if (h instanceof MailHandler) {
+                MailHandler mh = (MailHandler) h;
+                buf.append(", ").append(mh.getSubject());
+            }
+        } catch (SecurityException error) {
+            err.print(prefix + ": ");
+            error.printStackTrace(err);
+        }
+
+        try {
+            buf.append(", ").append(h.getFormatter());
+        } catch (SecurityException error) {
+            err.print(prefix + ": ");
+            error.printStackTrace(err);
+        }
+
+        try {
+            if (h instanceof MailHandler) {
+                MailHandler mh = (MailHandler) h;
+                buf.append(", ").append(Arrays.toString(
+                        mh.getAttachmentFormatters()));
+            }
+        } catch (SecurityException error) {
+            err.print(prefix + ": ");
+            error.printStackTrace(err);
+        }
+
+        try {
+            buf.append(", ").append(h.getLevel());
+        } catch (SecurityException error) {
+            err.print(prefix + ": ");
+            error.printStackTrace(err);
+        }
+
+        try {
+            buf.append(", ").append(h.getFilter());
+        } catch (SecurityException error) {
+            err.print(prefix + ": ");
+            error.printStackTrace(err);
+        }
+
+        try {
+            buf.append(", ").append(h.getErrorManager());
+        } catch (SecurityException error) {
+            err.print(prefix + ": ");
+            error.printStackTrace(err);
+        }
+
+        buf.append(", ").append(toString(h.getClass().getClassLoader()));
+        return buf.toString();
+    }
+
+    /**
+     * Example for body only messages. On close the remaining messages are sent.      <code>
+     * ##logging.properties
+     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
+     * com.sun.mail.util.logging.MailHandler.subject=Body only demo
+     * ##
+     * </code>
+     */
+    private static void initBodyOnly() {
+        MailHandler h = new MailHandler();
+        h.setSubject("Body only demo");
+        LOGGER.addHandler(h);
+    }
+
+    /**
+     * Example showing that when the mail handler reaches capacity it will
+     * format and send the current records. Capacity is used to roughly limit
+     * the size of an outgoing message. On close any remaining messages are
+     * sent.      <code>
+     * ##logging.properties
+     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
+     * com.sun.mail.util.logging.MailHandler.subject=Low capacity demo
+     * com.sun.mail.util.logging.MailHandler.capacity=5
+     * ##
+     * </code>
+     */
+    private static void initLowCapacity() {
+        MailHandler h = new MailHandler(5);
+        h.setSubject("Low capacity demo");
+        LOGGER.addHandler(h);
+    }
+
+    /**
+     * Example for body only messages. On close any remaining messages are sent.      <code>
+     * ##logging.properties
+     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
+     * com.sun.mail.util.logging.MailHandler.subject=Body and attachment demo
+     * com.sun.mail.util.logging.MailHandler.attachment.formatters=java.util.logging.XMLFormatter
+     * com.sun.mail.util.logging.MailHandler.attachment.names=data.xml
+     * ##
+     * </code>
+     */
+    private static void initSimpleAttachment() {
+        MailHandler h = new MailHandler();
+        h.setSubject("Body and attachment demo");
+        h.setAttachmentFormatters(new XMLFormatter());
+        h.setAttachmentNames("data.xml");
+        LOGGER.addHandler(h);
+    }
+
+    /**
+     * Example setup for priority messages by level. If the push level is
+     * triggered the message is high priority. Otherwise, on close any remaining
+     * messages are sent.      <code>
+     * ##logging.properties
+     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
+     * com.sun.mail.util.logging.MailHandler.subject=Push level demo
+     * com.sun.mail.util.logging.MailHandler.pushLevel=WARNING
+     * ##
+     * </code>
+     */
+    private static void initWithPushLevel() {
+        MailHandler h = new MailHandler();
+        h.setSubject("Push level demo");
+        h.setPushLevel(Level.WARNING);
+        LOGGER.addHandler(h);
+    }
+
+    /**
+     * Example for priority messages by generation rate. If the push filter is
+     * triggered the message is high priority. Otherwise, on close any remaining
+     * messages are sent. If the capacity is set to the      <code>
+     * ##logging.properties
+     * MailHandlerDemo.handlers=com.sun.mail.util.logging.MailHandler
+     * com.sun.mail.util.logging.MailHandler.subject=Push filter demo
+     * com.sun.mail.util.logging.MailHandler.pushLevel=ALL
+     * com.sun.mail.util.logging.MailHandler.pushFilter=com.sun.mail.util.logging.DurationFilter
+     * com.sun.mail.util.logging.DurationFilter.records=2
+     * com.sun.mail.util.logging.DurationFilter.duration=1 * 60 * 1000
+     * ##
+     * </code>
+     */
+    private static void initWithPushFilter() {
+        MailHandler h = new MailHandler();
+        h.setSubject("Push filter demo");
+        h.setPushLevel(Level.ALL);
+        h.setPushFilter(new DurationFilter(2, 1L * 60L * 1000L));
+        LOGGER.addHandler(h);
+    }
+
+    /**
+     * Example for circular buffer behavior. The level, push level, and capacity
+     * are set the same so that the memory handler push results in a mail
+     * handler push. All messages are high priority. On close any remaining
+     * records are discarded because they never reach the mail handler.      <code>
+     * ##logging.properties
+     * MailHandlerDemo.handlers=java.util.logging.MemoryHandler
+     * java.util.logging.MemoryHandler.target=com.sun.mail.util.logging.MailHandler
+     * com.sun.mail.util.logging.MailHandler.level=ALL
+     * java.util.logging.MemoryHandler.level=ALL
+     * java.util.logging.MemoryHandler.push=WARNING
+     * com.sun.mail.util.logging.MailHandler.subject=Push only demo
+     * com.sun.mail.util.logging.MailHandler.pushLevel=WARNING
+     * ##
+     * </code>
+     */
+    private static void initPushOnly() {
+        final int capacity = 3;
+        final Level pushLevel = Level.WARNING;
+        final MailHandler h = new MailHandler(capacity);
+        h.setPushLevel(pushLevel);
+        h.setSubject("Push only demo");
+        MemoryHandler m = new MemoryHandler(h, capacity, pushLevel);
+        h.setLevel(m.getLevel());
+        LOGGER.addHandler(m);
+        pushOnlyHandler = h;
+    }
+
+    /**
+     * Holds on to the push only handler. Only declared here to apply fallback
+     * settings.
+     */
+    private static Handler pushOnlyHandler;
+
+    /**
+     * Example for circular buffer behavior as normal priority. The push level,
+     * and capacity are set the same so that the memory handler push results in
+     * a mail handler push. All messages are normal priority. On close any
+     * remaining records are discarded because they never reach the mail
+     * handler. Use the LogManager config option or extend the MemoryHandler to
+     * emulate this behavior via the logging.properties.
+     */
+    private static void initPushNormal() {
+        final int capacity = 3;
+        final MailHandler h = new MailHandler(capacity);
+        h.setSubject("Push normal demo");
+        MemoryHandler m = new MemoryHandler(h, capacity, Level.WARNING) {
+
+            @Override
+            public void push() {
+                super.push();  //push to target.
+                super.flush(); //make the target send the email.
+            }
+        };
+        LOGGER.addHandler(m);
+        pushNormalHandler = h;
+    }
+
+    /**
+     * Holds on to the push normal handler. Only declared here to apply fallback
+     * settings.
+     */
+    private static Handler pushNormalHandler;
+
+    /**
+     * Example for various kinds of custom sorting, formatting, and filtering
+     * for multiple attachment messages. The subject will contain the most
+     * severe record and a count of remaining records. The log records are
+     * ordered from most severe to least severe. The body uses a custom
+     * formatter that includes a summary by date and time. The attachment use
+     * XML and plain text formats. Each attachment has a different set of
+     * filtering. The attachment names are generated from either a fixed name or
+     * are built using the number and type of the records formatted. On close
+     * any remaining messages are sent. Use the LogManager config option or
+     * extend the MemoryHandler to emulate this behavior via the
+     * logging.properties.
+     */
+    private static void initCustomAttachments() {
+        MailHandler h = new MailHandler();
+
+        //Sort records by severity keeping the severe messages at the top.
+        h.setComparator(Collections.reverseOrder(new SeverityComparator()));
+
+        //Use subject to provide a hint as to what is in the email.
+        h.setSubject(new CollectorFormatter());
+
+        //Make the body give a simple summary of what happened.
+        h.setFormatter(new SummaryFormatter());
+
+        //Create 3 attachments.
+        h.setAttachmentFormatters(new XMLFormatter(),
+                new XMLFormatter(), new SimpleFormatter());
+
+        //Filter each attachment differently.
+        h.setAttachmentFilters(null,
+                new DurationFilter(3L, 1000L),
+                new DurationFilter(1L, 15L * 60L * 1000L));
+
+        //Creating the attachment name formatters.
+        h.setAttachmentNames(new CollectorFormatter("all.xml"),
+                new CollectorFormatter("{3} records and {5} errors.xml"),
+                new CollectorFormatter("{5,choice,0#no errors|1#1 error|1<"
+                        + "{5,number,integer} errors}.txt"));
+
+        LOGGER.addHandler(h);
+    }
+
+    /**
+     * Sets up the demos that will run.
+     *
+     * @param l the list of arguments.
+     * @return true if debug is on.
+     */
+    private static boolean init(List<String> l) {
+        l = new ArrayList<String>(l);
+        Session session = Session.getInstance(System.getProperties());
+        boolean all = l.remove("-all") || l.isEmpty();
+        if (l.remove("-body") || all) {
+            initBodyOnly();
+        }
+
+        if (l.remove("-custom") || all) {
+            initCustomAttachments();
+        }
+
+        if (l.remove("-low") || all) {
+            initLowCapacity();
+        }
+
+        if (l.remove("-pushfilter") || all) {
+            initWithPushFilter();
+        }
+
+        if (l.remove("-pushlevel") || all) {
+            initWithPushLevel();
+        }
+
+        if (l.remove("-pushnormal") || all) {
+            initPushNormal();
+        }
+
+        if (l.remove("-pushonly") || all) {
+            initPushOnly();
+        }
+
+        if (l.remove("-simple") || all) {
+            initSimpleAttachment();
+        }
+
+        boolean fallback = applyFallbackSettings();
+        boolean debug = l.remove("-debug") || session.getDebug();
+        if (debug) {
+            checkConfig(CLASS_NAME, session.getDebugOut());
+        }
+
+        if (!l.isEmpty()) {
+            LOGGER.log(Level.SEVERE, "Unknown commands: {0}", l);
+        }
+
+        if (fallback) {
+            LOGGER.info("Check your user temp dir for output.");
+        }
+        return debug;
+    }
+
+    /**
+     * Close and remove all handlers added to the class logger.
+     */
+    private static void closeHandlers() {
+        Handler[] handlers = LOGGER.getHandlers();
+        for (Handler h : handlers) {
+            h.close();
+            LOGGER.removeHandler(h);
+        }
+    }
+
+    /**
+     * Apply some fallback settings if no configuration file was specified.
+     *
+     * @return true if fallback settings were applied.
+     */
+    private static boolean applyFallbackSettings() {
+        if (getConfigLocation() == null) {
+            LOGGER.setLevel(Level.ALL);
+            Handler[] handlers = LOGGER.getHandlers();
+            for (Handler h : handlers) {
+                fallbackSettings(h);
+            }
+            fallbackSettings(pushOnlyHandler);
+            fallbackSettings(pushNormalHandler);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Common fallback settings for a single handler.
+     *
+     * @param h the handler.
+     */
+    private static void fallbackSettings(Handler h) {
+        if (h != null) {
+            h.setErrorManager(new FileErrorManager());
+            h.setLevel(Level.ALL);
+        }
+    }
+
+    /**
+     * Gets the system temp directory.
+     *
+     * @return the system temp directory.
+     */
+    private static String getTempDir() {
+        return System.getProperty("java.io.tmpdir");
+    }
+
+    /**
+     * Gets the configuration file or class name.
+     *
+     * @return the file name or class name.
+     */
+    private static String getConfigLocation() {
+        String file = System.getProperty("java.util.logging.config.file");
+        if (file == null) {
+            return System.getProperty("java.util.logging.config.class");
+        }
+        return file;
+    }
+
+    /**
+     * No objects are allowed.
+     * @throws IllegalAccessException always.
+     */
+    private MailHandlerDemo() throws IllegalAccessException {
+        throw new IllegalAccessException();
+    }
+}
diff --git a/logging/src/main/java/README.txt b/logging/src/main/java/README.txt
new file mode 100644
index 0000000..c6b7add
--- /dev/null
+++ b/logging/src/main/java/README.txt
@@ -0,0 +1,103 @@
+Logging Demo
+------------
+
+Notes:
+======
+
+This should not be taken as a demo of how to use the logging API, but
+rather how to use the features of the MailHandler.
+
+To run the demo:
+================
+
+    1.  The demo requires Java version 1.5 or newer.
+	We *strongly* encourage you to use the latest version of J2SE,
+	which you can download from
+        http://www.oracle.com/technetwork/java/javase/downloads.
+
+    2.  Set your CLASSPATH to include the "mail.jar" and "activation.jar".
+
+	For JDK 1.1 on UNIX:
+
+	export CLASSPATH=/u/me/download/mail.jar:/u/me/download/activation.jar.
+
+	For JDK 1.2 and newer on UNIX:
+
+	export CLASSPATH=/u/me/download/mail.jar:/u/me/download/activation.jar:.
+
+    3.  Go to the demo/logging directory
+
+    4.  Compile all the files using your Java compiler.  For example:
+
+	  javac *.java
+
+    5.  Not required but, you should edit the maildemo.properties and change the
+        mail.to address and mail.host to your mail server ip or host name.
+
+    6.  Run the demo. For example:
+
+	  java -Dmail.debug=false -Djava.util.logging.config.file=/u/me/download/javamail/demo/maildemo.properties MailHandlerDemo
+
+
+
+Overview of the Classes
+=======================
+
+Main Classes:
+
+	MailHandlerDemo       = The main method creates log messages
+				for the MailHander to capture.  The
+				initXXX methods describe some of the
+				common setup code for different types
+				of email messages.
+
+                       Usage: java MailHandlerDemo [[-all] | [-body] | [-debug]
+                                | [-low] | [-simple] | [-pushlevel]
+                                | [-pushfilter] | [-pushnormal]| [-pushonly]]
+
+                        Options:
+                            -all    : Execute all demos.
+                            -body   : An email with all records and only a body.
+                            -custom : An email with attachments and dynamic names.
+                            -debug  : Output basic debug information about the
+                                        JVM and log configuration.
+                            -low    : Generates multiple emails due to low
+                                        capacity.
+                            -simple : An email with all records with body and an
+                                        attachment.
+                            -pushlevel	: Generates high priority emails when
+                                            the push level is triggered and
+                                            normal priority when flushed.
+                            -pushFilter	: Generates high priority emails when
+                                            the push level and the push filter
+                                            is triggered and normal priority
+                                            emails when flushed.
+                            -pushnormal	: Generates multiple emails when the
+                                            MemoryHandler push level is
+                                            triggered.  All generated email are
+                                            sent as normal priority.
+                            -pushonly	: Generates multiple emails when the
+                                            MemoryHandler push level is
+                                            triggered.  Generates high priority
+                                            emails when the push level is
+                                            triggered and normal priority when
+                                            flushed.
+
+
+	FileErrorManager      = Used to store email messages to the
+				local file system when mail transport
+				fails.  This is installed as a
+				fallback in case the logging config is
+				not specified.
+
+	SummaryFormatter      = An example compact formatter with summary
+                                for use with the body of an email message.
+
+Support files:
+
+	maildemo.properties   = A sample LogManager properties file for
+				the MailHandlerDemo.
+
+	maildemo.policy       = A sample security policy file to use with
+                                the MailHandlerDemo.  This can be used to
+                                enable security tracing.
diff --git a/logging/src/main/java/SummaryFormatter.java b/logging/src/main/java/SummaryFormatter.java
new file mode 100644
index 0000000..b821364
--- /dev/null
+++ b/logging/src/main/java/SummaryFormatter.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2018 Jason Mehrens. All Rights Reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import com.sun.mail.util.logging.CollectorFormatter;
+import com.sun.mail.util.logging.CompactFormatter;
+import java.util.logging.Formatter;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+
+/**
+ * A compact formatter used to summarize an error report.
+ *
+ * @author Jason Mehrens
+ */
+public final class SummaryFormatter extends Formatter {
+
+    /**
+     * The line formatter.
+     */
+    private final CompactFormatter format;
+    /**
+     * The footer formatter.
+     */
+    private final CollectorFormatter footer;
+
+    /**
+     * Creates the formatter.
+     */
+    public SummaryFormatter() {
+        format = new CompactFormatter("[%4$s]\t%5$s %6$s%n");
+        footer = new CollectorFormatter("\nThese {3} messages occurred between "
+                + "{7,time,EEE, MMM dd HH:mm:ss:S ZZZ yyyy} and "
+                + "{8,time,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}\n", format, null);
+    }
+
+    /**
+     * Gets the header information.
+     *
+     * @param h the handler or null.
+     * @return the header.
+     */
+    @Override
+    public String getHead(Handler h) {
+        footer.getHead(h);
+        return format.getHead(h);
+    }
+
+    /**
+     * Formats the given record.
+     *
+     * @param record the log record.
+     * @return the formatted record.
+     * @throws NullPointerException if record is null.
+     */
+    public String format(LogRecord record) {
+        String data = format.format(record);
+        footer.format(record); //Track record times for footer.
+        return data;
+    }
+
+    /**
+     * Gets and resets the footer information.
+     *
+     * @param h the handler or null.
+     * @return the footer.
+     */
+    @Override
+    public String getTail(Handler h) {
+        format.getTail(h);
+        return footer.getTail(h);
+    }
+}
diff --git a/logging/src/main/java/maildemo.policy b/logging/src/main/java/maildemo.policy
new file mode 100644
index 0000000..d7d7980
--- /dev/null
+++ b/logging/src/main/java/maildemo.policy
@@ -0,0 +1,19 @@
+/*

+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2014, 2018 Jason Mehrens. All Rights Reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Distribution License v. 1.0, which is available at

+ * http://www.eclipse.org/org/documents/edl-v10.php.

+ *

+ * SPDX-License-Identifier: BSD-3-Clause

+ */

+grant {

+  permission java.util.logging.LoggingPermission "control";

+  permission java.util.PropertyPermission "*", "read, write";

+  permission java.net.SocketPermission "*", "resolve, connect";

+  permission java.lang.RuntimePermission "accessClassInPackage.sun.util.logging.resources";

+  permission java.io.FilePermission "<<ALL FILES>>", "read, write";

+  permission java.lang.RuntimePermission "getClassLoader";

+};

+

diff --git a/logging/src/main/java/maildemo.properties b/logging/src/main/java/maildemo.properties
new file mode 100644
index 0000000..314a05a
--- /dev/null
+++ b/logging/src/main/java/maildemo.properties
@@ -0,0 +1,51 @@
+#

+# Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.

+# Copyright (c) 2009, 2018 Jason Mehrens. All Rights Reserved.

+#

+# This program and the accompanying materials are made available under the

+# terms of the Eclipse Distribution License v. 1.0, which is available at

+# http://www.eclipse.org/org/documents/edl-v10.php.

+#

+# SPDX-License-Identifier: BSD-3-Clause

+

+

+

+

+# This can be used by setting the system property

+# -Djava.util.logging.config.file=path to this file

+

+# Taken from the JDK defaults.

+handlers= java.util.logging.ConsoleHandler

+.level= INFO

+java.util.logging.ConsoleHandler.level = INFO

+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

+

+

+# Set the mail handler demo logger level

+MailHandlerDemo.level = ALL

+

+# Configure the MailHandler.

+com.sun.mail.util.logging.MailHandler.level = ALL

+com.sun.mail.util.logging.MailHandler.mail.host = my-mail-server

+com.sun.mail.util.logging.MailHandler.mail.from = me@example.com

+com.sun.mail.util.logging.MailHandler.mail.to = me@example.com

+com.sun.mail.util.logging.MailHandler.verify = local

+

+# Add attachments if needed.

+#com.sun.mail.util.logging.MailHandler.attachment.formatters = java.util.logging.SimpleFormatter, java.util.logging.XMLFormatter

+

+# No filters.

+#com.sun.mail.util.logging.MailHandler.attachment.filters = null, null

+

+# Formatter class name or strings.

+#com.sun.mail.util.logging.MailHandler.attachment.names = simple.txt, error.xml

+

+

+# Store messages on error by installing the FileErrorManager (demo code).

+com.sun.mail.util.logging.MailHandler.errorManager = FileErrorManager

+

+# Configure the FileErrorManager for demo (not required).

+# FileErrorManager.pattern = path-to-dir

+

+# Debug mail transport issues.

+mail.debug = false

diff --git a/mail.sig b/mail.sig
new file mode 100644
index 0000000..fdddb33
--- /dev/null
+++ b/mail.sig
@@ -0,0 +1,1451 @@
+#Signature file v4.3
+#Version 
+
+CLSS public abstract javax.mail.Address
+cons public <init>()
+intf java.io.Serializable
+meth public abstract boolean equals(java.lang.Object)
+meth public abstract java.lang.String getType()
+meth public abstract java.lang.String toString()
+supr java.lang.Object
+hfds serialVersionUID
+
+CLSS public javax.mail.AuthenticationFailedException
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.Exception)
+supr javax.mail.MessagingException
+hfds serialVersionUID
+
+CLSS public abstract javax.mail.Authenticator
+cons public <init>()
+meth protected final int getRequestingPort()
+meth protected final java.lang.String getDefaultUserName()
+meth protected final java.lang.String getRequestingPrompt()
+meth protected final java.lang.String getRequestingProtocol()
+meth protected final java.net.InetAddress getRequestingSite()
+meth protected javax.mail.PasswordAuthentication getPasswordAuthentication()
+supr java.lang.Object
+hfds requestingPort,requestingPrompt,requestingProtocol,requestingSite,requestingUserName
+
+CLSS public abstract javax.mail.BodyPart
+cons public <init>()
+fld protected javax.mail.Multipart parent
+intf javax.mail.Part
+meth public javax.mail.Multipart getParent()
+supr java.lang.Object
+
+CLSS public abstract interface javax.mail.EncodingAware
+meth public abstract java.lang.String getEncoding()
+
+CLSS public javax.mail.FetchProfile
+cons public <init>()
+innr public static Item
+meth public boolean contains(java.lang.String)
+meth public boolean contains(javax.mail.FetchProfile$Item)
+meth public java.lang.String[] getHeaderNames()
+meth public javax.mail.FetchProfile$Item[] getItems()
+meth public void add(java.lang.String)
+meth public void add(javax.mail.FetchProfile$Item)
+supr java.lang.Object
+hfds headers,specials
+
+CLSS public static javax.mail.FetchProfile$Item
+ outer javax.mail.FetchProfile
+cons protected <init>(java.lang.String)
+fld public final static javax.mail.FetchProfile$Item CONTENT_INFO
+fld public final static javax.mail.FetchProfile$Item ENVELOPE
+fld public final static javax.mail.FetchProfile$Item FLAGS
+fld public final static javax.mail.FetchProfile$Item SIZE
+meth public java.lang.String toString()
+supr java.lang.Object
+hfds name
+
+CLSS public javax.mail.Flags
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(javax.mail.Flags$Flag)
+cons public <init>(javax.mail.Flags)
+innr public final static Flag
+intf java.io.Serializable
+intf java.lang.Cloneable
+meth public boolean contains(java.lang.String)
+meth public boolean contains(javax.mail.Flags$Flag)
+meth public boolean contains(javax.mail.Flags)
+meth public boolean equals(java.lang.Object)
+meth public boolean retainAll(javax.mail.Flags)
+meth public int hashCode()
+meth public java.lang.Object clone()
+meth public java.lang.String toString()
+meth public java.lang.String[] getUserFlags()
+meth public javax.mail.Flags$Flag[] getSystemFlags()
+meth public void add(java.lang.String)
+meth public void add(javax.mail.Flags$Flag)
+meth public void add(javax.mail.Flags)
+meth public void clearSystemFlags()
+meth public void clearUserFlags()
+meth public void remove(java.lang.String)
+meth public void remove(javax.mail.Flags$Flag)
+meth public void remove(javax.mail.Flags)
+supr java.lang.Object
+hfds ANSWERED_BIT,DELETED_BIT,DRAFT_BIT,FLAGGED_BIT,RECENT_BIT,SEEN_BIT,USER_BIT,serialVersionUID,system_flags,user_flags
+
+CLSS public final static javax.mail.Flags$Flag
+ outer javax.mail.Flags
+fld public final static javax.mail.Flags$Flag ANSWERED
+fld public final static javax.mail.Flags$Flag DELETED
+fld public final static javax.mail.Flags$Flag DRAFT
+fld public final static javax.mail.Flags$Flag FLAGGED
+fld public final static javax.mail.Flags$Flag RECENT
+fld public final static javax.mail.Flags$Flag SEEN
+fld public final static javax.mail.Flags$Flag USER
+supr java.lang.Object
+hfds bit
+
+CLSS public abstract javax.mail.Folder
+cons protected <init>(javax.mail.Store)
+fld protected int mode
+fld protected javax.mail.Store store
+fld public final static int HOLDS_FOLDERS = 2
+fld public final static int HOLDS_MESSAGES = 1
+fld public final static int READ_ONLY = 1
+fld public final static int READ_WRITE = 2
+intf java.lang.AutoCloseable
+meth protected void finalize() throws java.lang.Throwable
+meth protected void notifyConnectionListeners(int)
+meth protected void notifyFolderListeners(int)
+meth protected void notifyFolderRenamedListeners(javax.mail.Folder)
+meth protected void notifyMessageAddedListeners(javax.mail.Message[])
+meth protected void notifyMessageChangedListeners(int,javax.mail.Message)
+meth protected void notifyMessageRemovedListeners(boolean,javax.mail.Message[])
+meth public abstract boolean create(int) throws javax.mail.MessagingException
+meth public abstract boolean delete(boolean) throws javax.mail.MessagingException
+meth public abstract boolean exists() throws javax.mail.MessagingException
+meth public abstract boolean hasNewMessages() throws javax.mail.MessagingException
+meth public abstract boolean isOpen()
+meth public abstract boolean renameTo(javax.mail.Folder) throws javax.mail.MessagingException
+meth public abstract char getSeparator() throws javax.mail.MessagingException
+meth public abstract int getMessageCount() throws javax.mail.MessagingException
+meth public abstract int getType() throws javax.mail.MessagingException
+meth public abstract java.lang.String getFullName()
+meth public abstract java.lang.String getName()
+meth public abstract javax.mail.Flags getPermanentFlags()
+meth public abstract javax.mail.Folder getFolder(java.lang.String) throws javax.mail.MessagingException
+meth public abstract javax.mail.Folder getParent() throws javax.mail.MessagingException
+meth public abstract javax.mail.Folder[] list(java.lang.String) throws javax.mail.MessagingException
+meth public abstract javax.mail.Message getMessage(int) throws javax.mail.MessagingException
+meth public abstract javax.mail.Message[] expunge() throws javax.mail.MessagingException
+meth public abstract void appendMessages(javax.mail.Message[]) throws javax.mail.MessagingException
+meth public abstract void close(boolean) throws javax.mail.MessagingException
+meth public abstract void open(int) throws javax.mail.MessagingException
+meth public boolean isSubscribed()
+meth public int getDeletedMessageCount() throws javax.mail.MessagingException
+meth public int getMode()
+meth public int getNewMessageCount() throws javax.mail.MessagingException
+meth public int getUnreadMessageCount() throws javax.mail.MessagingException
+meth public java.lang.String toString()
+meth public javax.mail.Folder[] list() throws javax.mail.MessagingException
+meth public javax.mail.Folder[] listSubscribed() throws javax.mail.MessagingException
+meth public javax.mail.Folder[] listSubscribed(java.lang.String) throws javax.mail.MessagingException
+meth public javax.mail.Message[] getMessages() throws javax.mail.MessagingException
+meth public javax.mail.Message[] getMessages(int,int) throws javax.mail.MessagingException
+meth public javax.mail.Message[] getMessages(int[]) throws javax.mail.MessagingException
+meth public javax.mail.Message[] search(javax.mail.search.SearchTerm) throws javax.mail.MessagingException
+meth public javax.mail.Message[] search(javax.mail.search.SearchTerm,javax.mail.Message[]) throws javax.mail.MessagingException
+meth public javax.mail.Store getStore()
+meth public javax.mail.URLName getURLName() throws javax.mail.MessagingException
+meth public void addConnectionListener(javax.mail.event.ConnectionListener)
+meth public void addFolderListener(javax.mail.event.FolderListener)
+meth public void addMessageChangedListener(javax.mail.event.MessageChangedListener)
+meth public void addMessageCountListener(javax.mail.event.MessageCountListener)
+meth public void close() throws javax.mail.MessagingException
+meth public void copyMessages(javax.mail.Message[],javax.mail.Folder) throws javax.mail.MessagingException
+meth public void fetch(javax.mail.Message[],javax.mail.FetchProfile) throws javax.mail.MessagingException
+meth public void removeConnectionListener(javax.mail.event.ConnectionListener)
+meth public void removeFolderListener(javax.mail.event.FolderListener)
+meth public void removeMessageChangedListener(javax.mail.event.MessageChangedListener)
+meth public void removeMessageCountListener(javax.mail.event.MessageCountListener)
+meth public void setFlags(int,int,javax.mail.Flags,boolean) throws javax.mail.MessagingException
+meth public void setFlags(int[],javax.mail.Flags,boolean) throws javax.mail.MessagingException
+meth public void setFlags(javax.mail.Message[],javax.mail.Flags,boolean) throws javax.mail.MessagingException
+meth public void setSubscribed(boolean) throws javax.mail.MessagingException
+supr java.lang.Object
+hfds connectionListeners,folderListeners,messageChangedListeners,messageCountListeners,q
+
+CLSS public javax.mail.FolderClosedException
+cons public <init>(javax.mail.Folder)
+cons public <init>(javax.mail.Folder,java.lang.String)
+cons public <init>(javax.mail.Folder,java.lang.String,java.lang.Exception)
+meth public javax.mail.Folder getFolder()
+supr javax.mail.MessagingException
+hfds folder,serialVersionUID
+
+CLSS public javax.mail.FolderNotFoundException
+cons public <init>()
+cons public <init>(java.lang.String,javax.mail.Folder)
+cons public <init>(javax.mail.Folder)
+cons public <init>(javax.mail.Folder,java.lang.String)
+cons public <init>(javax.mail.Folder,java.lang.String,java.lang.Exception)
+meth public javax.mail.Folder getFolder()
+supr javax.mail.MessagingException
+hfds folder,serialVersionUID
+
+CLSS public javax.mail.Header
+cons public <init>(java.lang.String,java.lang.String)
+fld protected java.lang.String name
+fld protected java.lang.String value
+meth public java.lang.String getName()
+meth public java.lang.String getValue()
+supr java.lang.Object
+
+CLSS public javax.mail.IllegalWriteException
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.Exception)
+supr javax.mail.MessagingException
+hfds serialVersionUID
+
+CLSS public abstract interface !annotation javax.mail.MailSessionDefinition
+ anno 0 java.lang.annotation.Repeatable(java.lang.Class<? extends java.lang.annotation.Annotation> value=class javax.mail.MailSessionDefinitions)
+ anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE])
+intf java.lang.annotation.Annotation
+meth public abstract !hasdefault java.lang.String description() value= ""
+meth public abstract !hasdefault java.lang.String from() value= ""
+meth public abstract !hasdefault java.lang.String host() value= ""
+meth public abstract !hasdefault java.lang.String password() value= ""
+meth public abstract !hasdefault java.lang.String storeProtocol() value= ""
+meth public abstract !hasdefault java.lang.String transportProtocol() value= ""
+meth public abstract !hasdefault java.lang.String user() value= ""
+meth public abstract !hasdefault java.lang.String[] properties() value= []
+meth public abstract java.lang.String name()
+
+CLSS public abstract interface !annotation javax.mail.MailSessionDefinitions
+ anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE])
+intf java.lang.annotation.Annotation
+meth public abstract javax.mail.MailSessionDefinition[] value()
+
+CLSS public abstract javax.mail.Message
+cons protected <init>()
+cons protected <init>(javax.mail.Folder,int)
+cons protected <init>(javax.mail.Session)
+fld protected boolean expunged
+fld protected int msgnum
+fld protected javax.mail.Folder folder
+fld protected javax.mail.Session session
+innr public static RecipientType
+intf javax.mail.Part
+meth protected void setExpunged(boolean)
+meth protected void setMessageNumber(int)
+meth public abstract java.lang.String getSubject() throws javax.mail.MessagingException
+meth public abstract java.util.Date getReceivedDate() throws javax.mail.MessagingException
+meth public abstract java.util.Date getSentDate() throws javax.mail.MessagingException
+meth public abstract javax.mail.Address[] getFrom() throws javax.mail.MessagingException
+meth public abstract javax.mail.Address[] getRecipients(javax.mail.Message$RecipientType) throws javax.mail.MessagingException
+meth public abstract javax.mail.Flags getFlags() throws javax.mail.MessagingException
+meth public abstract javax.mail.Message reply(boolean) throws javax.mail.MessagingException
+meth public abstract void addFrom(javax.mail.Address[]) throws javax.mail.MessagingException
+meth public abstract void addRecipients(javax.mail.Message$RecipientType,javax.mail.Address[]) throws javax.mail.MessagingException
+meth public abstract void saveChanges() throws javax.mail.MessagingException
+meth public abstract void setFlags(javax.mail.Flags,boolean) throws javax.mail.MessagingException
+meth public abstract void setFrom() throws javax.mail.MessagingException
+meth public abstract void setFrom(javax.mail.Address) throws javax.mail.MessagingException
+meth public abstract void setRecipients(javax.mail.Message$RecipientType,javax.mail.Address[]) throws javax.mail.MessagingException
+meth public abstract void setSentDate(java.util.Date) throws javax.mail.MessagingException
+meth public abstract void setSubject(java.lang.String) throws javax.mail.MessagingException
+meth public boolean isExpunged()
+meth public boolean isSet(javax.mail.Flags$Flag) throws javax.mail.MessagingException
+meth public boolean match(javax.mail.search.SearchTerm) throws javax.mail.MessagingException
+meth public int getMessageNumber()
+meth public javax.mail.Address[] getAllRecipients() throws javax.mail.MessagingException
+meth public javax.mail.Address[] getReplyTo() throws javax.mail.MessagingException
+meth public javax.mail.Folder getFolder()
+meth public javax.mail.Session getSession()
+meth public void addRecipient(javax.mail.Message$RecipientType,javax.mail.Address) throws javax.mail.MessagingException
+meth public void setFlag(javax.mail.Flags$Flag,boolean) throws javax.mail.MessagingException
+meth public void setRecipient(javax.mail.Message$RecipientType,javax.mail.Address) throws javax.mail.MessagingException
+meth public void setReplyTo(javax.mail.Address[]) throws javax.mail.MessagingException
+supr java.lang.Object
+
+CLSS public static javax.mail.Message$RecipientType
+ outer javax.mail.Message
+cons protected <init>(java.lang.String)
+fld protected java.lang.String type
+fld public final static javax.mail.Message$RecipientType BCC
+fld public final static javax.mail.Message$RecipientType CC
+fld public final static javax.mail.Message$RecipientType TO
+intf java.io.Serializable
+meth protected java.lang.Object readResolve() throws java.io.ObjectStreamException
+meth public java.lang.String toString()
+supr java.lang.Object
+hfds serialVersionUID
+
+CLSS public abstract interface javax.mail.MessageAware
+meth public abstract javax.mail.MessageContext getMessageContext()
+
+CLSS public javax.mail.MessageContext
+cons public <init>(javax.mail.Part)
+meth public javax.mail.Message getMessage()
+meth public javax.mail.Part getPart()
+meth public javax.mail.Session getSession()
+supr java.lang.Object
+hfds part
+
+CLSS public javax.mail.MessageRemovedException
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.Exception)
+supr javax.mail.MessagingException
+hfds serialVersionUID
+
+CLSS public javax.mail.MessagingException
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.Exception)
+meth public boolean setNextException(java.lang.Exception)
+meth public java.lang.Exception getNextException()
+meth public java.lang.String toString()
+meth public java.lang.Throwable getCause()
+supr java.lang.Exception
+hfds next,serialVersionUID
+
+CLSS public javax.mail.MethodNotSupportedException
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.Exception)
+supr javax.mail.MessagingException
+hfds serialVersionUID
+
+CLSS public abstract javax.mail.Multipart
+cons protected <init>()
+fld protected java.lang.String contentType
+fld protected java.util.Vector<javax.mail.BodyPart> parts
+fld protected javax.mail.Part parent
+meth protected void setMultipartDataSource(javax.mail.MultipartDataSource) throws javax.mail.MessagingException
+meth public abstract void writeTo(java.io.OutputStream) throws java.io.IOException,javax.mail.MessagingException
+meth public boolean removeBodyPart(javax.mail.BodyPart) throws javax.mail.MessagingException
+meth public int getCount() throws javax.mail.MessagingException
+meth public java.lang.String getContentType()
+meth public javax.mail.BodyPart getBodyPart(int) throws javax.mail.MessagingException
+meth public javax.mail.Part getParent()
+meth public void addBodyPart(javax.mail.BodyPart) throws javax.mail.MessagingException
+meth public void addBodyPart(javax.mail.BodyPart,int) throws javax.mail.MessagingException
+meth public void removeBodyPart(int) throws javax.mail.MessagingException
+meth public void setParent(javax.mail.Part)
+supr java.lang.Object
+
+CLSS public abstract interface javax.mail.MultipartDataSource
+intf javax.activation.DataSource
+meth public abstract int getCount()
+meth public abstract javax.mail.BodyPart getBodyPart(int) throws javax.mail.MessagingException
+
+CLSS public javax.mail.NoSuchProviderException
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.Exception)
+supr javax.mail.MessagingException
+hfds serialVersionUID
+
+CLSS public abstract interface javax.mail.Part
+fld public final static java.lang.String ATTACHMENT = "attachment"
+fld public final static java.lang.String INLINE = "inline"
+meth public abstract boolean isMimeType(java.lang.String) throws javax.mail.MessagingException
+meth public abstract int getLineCount() throws javax.mail.MessagingException
+meth public abstract int getSize() throws javax.mail.MessagingException
+meth public abstract java.io.InputStream getInputStream() throws java.io.IOException,javax.mail.MessagingException
+meth public abstract java.lang.Object getContent() throws java.io.IOException,javax.mail.MessagingException
+meth public abstract java.lang.String getContentType() throws javax.mail.MessagingException
+meth public abstract java.lang.String getDescription() throws javax.mail.MessagingException
+meth public abstract java.lang.String getDisposition() throws javax.mail.MessagingException
+meth public abstract java.lang.String getFileName() throws javax.mail.MessagingException
+meth public abstract java.lang.String[] getHeader(java.lang.String) throws javax.mail.MessagingException
+meth public abstract java.util.Enumeration<javax.mail.Header> getAllHeaders() throws javax.mail.MessagingException
+meth public abstract java.util.Enumeration<javax.mail.Header> getMatchingHeaders(java.lang.String[]) throws javax.mail.MessagingException
+meth public abstract java.util.Enumeration<javax.mail.Header> getNonMatchingHeaders(java.lang.String[]) throws javax.mail.MessagingException
+meth public abstract javax.activation.DataHandler getDataHandler() throws javax.mail.MessagingException
+meth public abstract void addHeader(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public abstract void removeHeader(java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setContent(java.lang.Object,java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setContent(javax.mail.Multipart) throws javax.mail.MessagingException
+meth public abstract void setDataHandler(javax.activation.DataHandler) throws javax.mail.MessagingException
+meth public abstract void setDescription(java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setDisposition(java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setFileName(java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setHeader(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setText(java.lang.String) throws javax.mail.MessagingException
+meth public abstract void writeTo(java.io.OutputStream) throws java.io.IOException,javax.mail.MessagingException
+
+CLSS public final javax.mail.PasswordAuthentication
+cons public <init>(java.lang.String,java.lang.String)
+meth public java.lang.String getPassword()
+meth public java.lang.String getUserName()
+supr java.lang.Object
+hfds password,userName
+
+CLSS public javax.mail.Provider
+cons public <init>(javax.mail.Provider$Type,java.lang.String,java.lang.String,java.lang.String,java.lang.String)
+innr public static Type
+meth public java.lang.String getClassName()
+meth public java.lang.String getProtocol()
+meth public java.lang.String getVendor()
+meth public java.lang.String getVersion()
+meth public java.lang.String toString()
+meth public javax.mail.Provider$Type getType()
+supr java.lang.Object
+hfds className,protocol,type,vendor,version
+
+CLSS public static javax.mail.Provider$Type
+ outer javax.mail.Provider
+fld public final static javax.mail.Provider$Type STORE
+fld public final static javax.mail.Provider$Type TRANSPORT
+meth public java.lang.String toString()
+supr java.lang.Object
+hfds type
+
+CLSS public javax.mail.Quota
+cons public <init>(java.lang.String)
+fld public java.lang.String quotaRoot
+fld public javax.mail.Quota$Resource[] resources
+innr public static Resource
+meth public void setResourceLimit(java.lang.String,long)
+supr java.lang.Object
+
+CLSS public static javax.mail.Quota$Resource
+ outer javax.mail.Quota
+cons public <init>(java.lang.String,long,long)
+fld public java.lang.String name
+fld public long limit
+fld public long usage
+supr java.lang.Object
+
+CLSS public abstract interface javax.mail.QuotaAwareStore
+meth public abstract javax.mail.Quota[] getQuota(java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setQuota(javax.mail.Quota) throws javax.mail.MessagingException
+
+CLSS public javax.mail.ReadOnlyFolderException
+cons public <init>(javax.mail.Folder)
+cons public <init>(javax.mail.Folder,java.lang.String)
+cons public <init>(javax.mail.Folder,java.lang.String,java.lang.Exception)
+meth public javax.mail.Folder getFolder()
+supr javax.mail.MessagingException
+hfds folder,serialVersionUID
+
+CLSS public javax.mail.SendFailedException
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.Exception)
+cons public <init>(java.lang.String,java.lang.Exception,javax.mail.Address[],javax.mail.Address[],javax.mail.Address[])
+fld protected javax.mail.Address[] invalid
+fld protected javax.mail.Address[] validSent
+fld protected javax.mail.Address[] validUnsent
+meth public javax.mail.Address[] getInvalidAddresses()
+meth public javax.mail.Address[] getValidSentAddresses()
+meth public javax.mail.Address[] getValidUnsentAddresses()
+supr javax.mail.MessagingException
+hfds serialVersionUID
+
+CLSS public abstract javax.mail.Service
+cons protected <init>(javax.mail.Session,javax.mail.URLName)
+fld protected boolean debug
+fld protected javax.mail.Session session
+fld protected volatile javax.mail.URLName url
+intf java.lang.AutoCloseable
+meth protected boolean protocolConnect(java.lang.String,int,java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth protected void finalize() throws java.lang.Throwable
+meth protected void notifyConnectionListeners(int)
+meth protected void queueEvent(javax.mail.event.MailEvent,java.util.Vector<? extends java.util.EventListener>)
+meth protected void setConnected(boolean)
+meth protected void setURLName(javax.mail.URLName)
+meth public boolean isConnected()
+meth public java.lang.String toString()
+meth public javax.mail.URLName getURLName()
+meth public void addConnectionListener(javax.mail.event.ConnectionListener)
+meth public void close() throws javax.mail.MessagingException
+meth public void connect() throws javax.mail.MessagingException
+meth public void connect(java.lang.String,int,java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void connect(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void connect(java.lang.String,java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void removeConnectionListener(javax.mail.event.ConnectionListener)
+supr java.lang.Object
+hfds connected,connectionListeners,q
+
+CLSS public final javax.mail.Session
+meth public boolean getDebug()
+meth public java.io.PrintStream getDebugOut()
+meth public java.lang.String getProperty(java.lang.String)
+meth public java.util.Properties getProperties()
+meth public javax.mail.Folder getFolder(javax.mail.URLName) throws javax.mail.MessagingException
+meth public javax.mail.PasswordAuthentication getPasswordAuthentication(javax.mail.URLName)
+meth public javax.mail.PasswordAuthentication requestPasswordAuthentication(java.net.InetAddress,int,java.lang.String,java.lang.String,java.lang.String)
+meth public javax.mail.Provider getProvider(java.lang.String) throws javax.mail.NoSuchProviderException
+meth public javax.mail.Provider[] getProviders()
+meth public javax.mail.Store getStore() throws javax.mail.NoSuchProviderException
+meth public javax.mail.Store getStore(java.lang.String) throws javax.mail.NoSuchProviderException
+meth public javax.mail.Store getStore(javax.mail.Provider) throws javax.mail.NoSuchProviderException
+meth public javax.mail.Store getStore(javax.mail.URLName) throws javax.mail.NoSuchProviderException
+meth public javax.mail.Transport getTransport() throws javax.mail.NoSuchProviderException
+meth public javax.mail.Transport getTransport(java.lang.String) throws javax.mail.NoSuchProviderException
+meth public javax.mail.Transport getTransport(javax.mail.Address) throws javax.mail.NoSuchProviderException
+meth public javax.mail.Transport getTransport(javax.mail.Provider) throws javax.mail.NoSuchProviderException
+meth public javax.mail.Transport getTransport(javax.mail.URLName) throws javax.mail.NoSuchProviderException
+meth public static javax.mail.Session getDefaultInstance(java.util.Properties)
+meth public static javax.mail.Session getDefaultInstance(java.util.Properties,javax.mail.Authenticator)
+meth public static javax.mail.Session getInstance(java.util.Properties)
+meth public static javax.mail.Session getInstance(java.util.Properties,javax.mail.Authenticator)
+meth public void addProvider(javax.mail.Provider)
+meth public void setDebug(boolean)
+meth public void setDebugOut(java.io.PrintStream)
+meth public void setPasswordAuthentication(javax.mail.URLName,javax.mail.PasswordAuthentication)
+meth public void setProtocolForAddress(java.lang.String,java.lang.String)
+meth public void setProvider(javax.mail.Provider) throws javax.mail.NoSuchProviderException
+supr java.lang.Object
+hfds addressMap,authTable,authenticator,confDir,debug,defaultSession,logger,out,props,providers,providersByClassName,providersByProtocol,q
+
+CLSS public abstract javax.mail.Store
+cons protected <init>(javax.mail.Session,javax.mail.URLName)
+meth protected void notifyFolderListeners(int,javax.mail.Folder)
+meth protected void notifyFolderRenamedListeners(javax.mail.Folder,javax.mail.Folder)
+meth protected void notifyStoreListeners(int,java.lang.String)
+meth public abstract javax.mail.Folder getDefaultFolder() throws javax.mail.MessagingException
+meth public abstract javax.mail.Folder getFolder(java.lang.String) throws javax.mail.MessagingException
+meth public abstract javax.mail.Folder getFolder(javax.mail.URLName) throws javax.mail.MessagingException
+meth public javax.mail.Folder[] getPersonalNamespaces() throws javax.mail.MessagingException
+meth public javax.mail.Folder[] getSharedNamespaces() throws javax.mail.MessagingException
+meth public javax.mail.Folder[] getUserNamespaces(java.lang.String) throws javax.mail.MessagingException
+meth public void addFolderListener(javax.mail.event.FolderListener)
+meth public void addStoreListener(javax.mail.event.StoreListener)
+meth public void removeFolderListener(javax.mail.event.FolderListener)
+meth public void removeStoreListener(javax.mail.event.StoreListener)
+supr javax.mail.Service
+hfds folderListeners,storeListeners
+
+CLSS public javax.mail.StoreClosedException
+cons public <init>(javax.mail.Store)
+cons public <init>(javax.mail.Store,java.lang.String)
+cons public <init>(javax.mail.Store,java.lang.String,java.lang.Exception)
+meth public javax.mail.Store getStore()
+supr javax.mail.MessagingException
+hfds serialVersionUID,store
+
+CLSS public abstract javax.mail.Transport
+cons public <init>(javax.mail.Session,javax.mail.URLName)
+meth protected void notifyTransportListeners(int,javax.mail.Address[],javax.mail.Address[],javax.mail.Address[],javax.mail.Message)
+meth public abstract void sendMessage(javax.mail.Message,javax.mail.Address[]) throws javax.mail.MessagingException
+meth public static void send(javax.mail.Message) throws javax.mail.MessagingException
+meth public static void send(javax.mail.Message,java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public static void send(javax.mail.Message,javax.mail.Address[]) throws javax.mail.MessagingException
+meth public static void send(javax.mail.Message,javax.mail.Address[],java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void addTransportListener(javax.mail.event.TransportListener)
+meth public void removeTransportListener(javax.mail.event.TransportListener)
+supr javax.mail.Service
+hfds transportListeners
+
+CLSS public abstract interface javax.mail.UIDFolder
+fld public final static long LASTUID = -1
+fld public final static long MAXUID = 4294967295
+innr public static FetchProfileItem
+meth public abstract javax.mail.Message getMessageByUID(long) throws javax.mail.MessagingException
+meth public abstract javax.mail.Message[] getMessagesByUID(long,long) throws javax.mail.MessagingException
+meth public abstract javax.mail.Message[] getMessagesByUID(long[]) throws javax.mail.MessagingException
+meth public abstract long getUID(javax.mail.Message) throws javax.mail.MessagingException
+meth public abstract long getUIDNext() throws javax.mail.MessagingException
+meth public abstract long getUIDValidity() throws javax.mail.MessagingException
+
+CLSS public static javax.mail.UIDFolder$FetchProfileItem
+ outer javax.mail.UIDFolder
+cons protected <init>(java.lang.String)
+fld public final static javax.mail.UIDFolder$FetchProfileItem UID
+supr javax.mail.FetchProfile$Item
+
+CLSS public javax.mail.URLName
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.String,int,java.lang.String,java.lang.String,java.lang.String)
+cons public <init>(java.net.URL)
+fld protected java.lang.String fullURL
+meth protected void parseString(java.lang.String)
+meth public boolean equals(java.lang.Object)
+meth public int getPort()
+meth public int hashCode()
+meth public java.lang.String getFile()
+meth public java.lang.String getHost()
+meth public java.lang.String getPassword()
+meth public java.lang.String getProtocol()
+meth public java.lang.String getRef()
+meth public java.lang.String getUsername()
+meth public java.lang.String toString()
+meth public java.net.URL getURL() throws java.net.MalformedURLException
+supr java.lang.Object
+hfds caseDiff,doEncode,dontNeedEncoding,file,hashCode,host,hostAddress,hostAddressKnown,password,port,protocol,ref,username
+
+CLSS public abstract javax.mail.event.ConnectionAdapter
+cons public <init>()
+intf javax.mail.event.ConnectionListener
+meth public void closed(javax.mail.event.ConnectionEvent)
+meth public void disconnected(javax.mail.event.ConnectionEvent)
+meth public void opened(javax.mail.event.ConnectionEvent)
+supr java.lang.Object
+
+CLSS public javax.mail.event.ConnectionEvent
+cons public <init>(java.lang.Object,int)
+fld protected int type
+fld public final static int CLOSED = 3
+fld public final static int DISCONNECTED = 2
+fld public final static int OPENED = 1
+meth public int getType()
+meth public void dispatch(java.lang.Object)
+supr javax.mail.event.MailEvent
+hfds serialVersionUID
+
+CLSS public abstract interface javax.mail.event.ConnectionListener
+intf java.util.EventListener
+meth public abstract void closed(javax.mail.event.ConnectionEvent)
+meth public abstract void disconnected(javax.mail.event.ConnectionEvent)
+meth public abstract void opened(javax.mail.event.ConnectionEvent)
+
+CLSS public abstract javax.mail.event.FolderAdapter
+cons public <init>()
+intf javax.mail.event.FolderListener
+meth public void folderCreated(javax.mail.event.FolderEvent)
+meth public void folderDeleted(javax.mail.event.FolderEvent)
+meth public void folderRenamed(javax.mail.event.FolderEvent)
+supr java.lang.Object
+
+CLSS public javax.mail.event.FolderEvent
+cons public <init>(java.lang.Object,javax.mail.Folder,int)
+cons public <init>(java.lang.Object,javax.mail.Folder,javax.mail.Folder,int)
+fld protected int type
+fld protected javax.mail.Folder folder
+fld protected javax.mail.Folder newFolder
+fld public final static int CREATED = 1
+fld public final static int DELETED = 2
+fld public final static int RENAMED = 3
+meth public int getType()
+meth public javax.mail.Folder getFolder()
+meth public javax.mail.Folder getNewFolder()
+meth public void dispatch(java.lang.Object)
+supr javax.mail.event.MailEvent
+hfds serialVersionUID
+
+CLSS public abstract interface javax.mail.event.FolderListener
+intf java.util.EventListener
+meth public abstract void folderCreated(javax.mail.event.FolderEvent)
+meth public abstract void folderDeleted(javax.mail.event.FolderEvent)
+meth public abstract void folderRenamed(javax.mail.event.FolderEvent)
+
+CLSS public abstract javax.mail.event.MailEvent
+cons public <init>(java.lang.Object)
+meth public abstract void dispatch(java.lang.Object)
+supr java.util.EventObject
+hfds serialVersionUID
+
+CLSS public javax.mail.event.MessageChangedEvent
+cons public <init>(java.lang.Object,int,javax.mail.Message)
+fld protected int type
+fld protected javax.mail.Message msg
+fld public final static int ENVELOPE_CHANGED = 2
+fld public final static int FLAGS_CHANGED = 1
+meth public int getMessageChangeType()
+meth public javax.mail.Message getMessage()
+meth public void dispatch(java.lang.Object)
+supr javax.mail.event.MailEvent
+hfds serialVersionUID
+
+CLSS public abstract interface javax.mail.event.MessageChangedListener
+intf java.util.EventListener
+meth public abstract void messageChanged(javax.mail.event.MessageChangedEvent)
+
+CLSS public abstract javax.mail.event.MessageCountAdapter
+cons public <init>()
+intf javax.mail.event.MessageCountListener
+meth public void messagesAdded(javax.mail.event.MessageCountEvent)
+meth public void messagesRemoved(javax.mail.event.MessageCountEvent)
+supr java.lang.Object
+
+CLSS public javax.mail.event.MessageCountEvent
+cons public <init>(javax.mail.Folder,int,boolean,javax.mail.Message[])
+fld protected boolean removed
+fld protected int type
+fld protected javax.mail.Message[] msgs
+fld public final static int ADDED = 1
+fld public final static int REMOVED = 2
+meth public boolean isRemoved()
+meth public int getType()
+meth public javax.mail.Message[] getMessages()
+meth public void dispatch(java.lang.Object)
+supr javax.mail.event.MailEvent
+hfds serialVersionUID
+
+CLSS public abstract interface javax.mail.event.MessageCountListener
+intf java.util.EventListener
+meth public abstract void messagesAdded(javax.mail.event.MessageCountEvent)
+meth public abstract void messagesRemoved(javax.mail.event.MessageCountEvent)
+
+CLSS public javax.mail.event.StoreEvent
+cons public <init>(javax.mail.Store,int,java.lang.String)
+fld protected int type
+fld protected java.lang.String message
+fld public final static int ALERT = 1
+fld public final static int NOTICE = 2
+meth public int getMessageType()
+meth public java.lang.String getMessage()
+meth public void dispatch(java.lang.Object)
+supr javax.mail.event.MailEvent
+hfds serialVersionUID
+
+CLSS public abstract interface javax.mail.event.StoreListener
+intf java.util.EventListener
+meth public abstract void notification(javax.mail.event.StoreEvent)
+
+CLSS public abstract javax.mail.event.TransportAdapter
+cons public <init>()
+intf javax.mail.event.TransportListener
+meth public void messageDelivered(javax.mail.event.TransportEvent)
+meth public void messageNotDelivered(javax.mail.event.TransportEvent)
+meth public void messagePartiallyDelivered(javax.mail.event.TransportEvent)
+supr java.lang.Object
+
+CLSS public javax.mail.event.TransportEvent
+cons public <init>(javax.mail.Transport,int,javax.mail.Address[],javax.mail.Address[],javax.mail.Address[],javax.mail.Message)
+fld protected int type
+fld protected javax.mail.Address[] invalid
+fld protected javax.mail.Address[] validSent
+fld protected javax.mail.Address[] validUnsent
+fld protected javax.mail.Message msg
+fld public final static int MESSAGE_DELIVERED = 1
+fld public final static int MESSAGE_NOT_DELIVERED = 2
+fld public final static int MESSAGE_PARTIALLY_DELIVERED = 3
+meth public int getType()
+meth public javax.mail.Address[] getInvalidAddresses()
+meth public javax.mail.Address[] getValidSentAddresses()
+meth public javax.mail.Address[] getValidUnsentAddresses()
+meth public javax.mail.Message getMessage()
+meth public void dispatch(java.lang.Object)
+supr javax.mail.event.MailEvent
+hfds serialVersionUID
+
+CLSS public abstract interface javax.mail.event.TransportListener
+intf java.util.EventListener
+meth public abstract void messageDelivered(javax.mail.event.TransportEvent)
+meth public abstract void messageNotDelivered(javax.mail.event.TransportEvent)
+meth public abstract void messagePartiallyDelivered(javax.mail.event.TransportEvent)
+
+CLSS public javax.mail.internet.AddressException
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.String)
+cons public <init>(java.lang.String,java.lang.String,int)
+fld protected int pos
+fld protected java.lang.String ref
+meth public int getPos()
+meth public java.lang.String getRef()
+meth public java.lang.String toString()
+supr javax.mail.internet.ParseException
+hfds serialVersionUID
+
+CLSS public javax.mail.internet.ContentDisposition
+cons public <init>()
+cons public <init>(java.lang.String) throws javax.mail.internet.ParseException
+cons public <init>(java.lang.String,javax.mail.internet.ParameterList)
+meth public java.lang.String getDisposition()
+meth public java.lang.String getParameter(java.lang.String)
+meth public java.lang.String toString()
+meth public javax.mail.internet.ParameterList getParameterList()
+meth public void setDisposition(java.lang.String)
+meth public void setParameter(java.lang.String,java.lang.String)
+meth public void setParameterList(javax.mail.internet.ParameterList)
+supr java.lang.Object
+hfds disposition,list
+
+CLSS public javax.mail.internet.ContentType
+cons public <init>()
+cons public <init>(java.lang.String) throws javax.mail.internet.ParseException
+cons public <init>(java.lang.String,java.lang.String,javax.mail.internet.ParameterList)
+meth public boolean match(java.lang.String)
+meth public boolean match(javax.mail.internet.ContentType)
+meth public java.lang.String getBaseType()
+meth public java.lang.String getParameter(java.lang.String)
+meth public java.lang.String getPrimaryType()
+meth public java.lang.String getSubType()
+meth public java.lang.String toString()
+meth public javax.mail.internet.ParameterList getParameterList()
+meth public void setParameter(java.lang.String,java.lang.String)
+meth public void setParameterList(javax.mail.internet.ParameterList)
+meth public void setPrimaryType(java.lang.String)
+meth public void setSubType(java.lang.String)
+supr java.lang.Object
+hfds list,primaryType,subType
+
+CLSS public javax.mail.internet.HeaderTokenizer
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.String)
+cons public <init>(java.lang.String,java.lang.String,boolean)
+fld public final static java.lang.String MIME = "()<>@,;:\u005c\u0022\u0009 []/?="
+fld public final static java.lang.String RFC822 = "()<>@,;:\u005c\u0022\u0009 .[]"
+innr public static Token
+meth public java.lang.String getRemainder()
+meth public javax.mail.internet.HeaderTokenizer$Token next() throws javax.mail.internet.ParseException
+meth public javax.mail.internet.HeaderTokenizer$Token next(char) throws javax.mail.internet.ParseException
+meth public javax.mail.internet.HeaderTokenizer$Token next(char,boolean) throws javax.mail.internet.ParseException
+meth public javax.mail.internet.HeaderTokenizer$Token peek() throws javax.mail.internet.ParseException
+supr java.lang.Object
+hfds EOFToken,currentPos,delimiters,maxPos,nextPos,peekPos,skipComments,string
+
+CLSS public static javax.mail.internet.HeaderTokenizer$Token
+ outer javax.mail.internet.HeaderTokenizer
+cons public <init>(int,java.lang.String)
+fld public final static int ATOM = -1
+fld public final static int COMMENT = -3
+fld public final static int EOF = -4
+fld public final static int QUOTEDSTRING = -2
+meth public int getType()
+meth public java.lang.String getValue()
+supr java.lang.Object
+hfds type,value
+
+CLSS public javax.mail.internet.InternetAddress
+cons public <init>()
+cons public <init>(java.lang.String) throws javax.mail.internet.AddressException
+cons public <init>(java.lang.String,boolean) throws javax.mail.internet.AddressException
+cons public <init>(java.lang.String,java.lang.String) throws java.io.UnsupportedEncodingException
+cons public <init>(java.lang.String,java.lang.String,java.lang.String) throws java.io.UnsupportedEncodingException
+fld protected java.lang.String address
+fld protected java.lang.String encodedPersonal
+fld protected java.lang.String personal
+intf java.lang.Cloneable
+meth public boolean equals(java.lang.Object)
+meth public boolean isGroup()
+meth public int hashCode()
+meth public java.lang.Object clone()
+meth public java.lang.String getAddress()
+meth public java.lang.String getPersonal()
+meth public java.lang.String getType()
+meth public java.lang.String toString()
+meth public java.lang.String toUnicodeString()
+meth public javax.mail.internet.InternetAddress[] getGroup(boolean) throws javax.mail.internet.AddressException
+meth public static java.lang.String toString(javax.mail.Address[])
+meth public static java.lang.String toString(javax.mail.Address[],int)
+meth public static java.lang.String toUnicodeString(javax.mail.Address[])
+meth public static java.lang.String toUnicodeString(javax.mail.Address[],int)
+meth public static javax.mail.internet.InternetAddress getLocalAddress(javax.mail.Session)
+meth public static javax.mail.internet.InternetAddress[] parse(java.lang.String) throws javax.mail.internet.AddressException
+meth public static javax.mail.internet.InternetAddress[] parse(java.lang.String,boolean) throws javax.mail.internet.AddressException
+meth public static javax.mail.internet.InternetAddress[] parseHeader(java.lang.String,boolean) throws javax.mail.internet.AddressException
+meth public void setAddress(java.lang.String)
+meth public void setPersonal(java.lang.String) throws java.io.UnsupportedEncodingException
+meth public void setPersonal(java.lang.String,java.lang.String) throws java.io.UnsupportedEncodingException
+meth public void validate() throws javax.mail.internet.AddressException
+supr javax.mail.Address
+hfds allowUtf8,ignoreBogusGroupName,rfc822phrase,serialVersionUID,specialsNoDot,specialsNoDotNoAt,useCanonicalHostName
+
+CLSS public javax.mail.internet.InternetHeaders
+cons public <init>()
+cons public <init>(java.io.InputStream) throws javax.mail.MessagingException
+cons public <init>(java.io.InputStream,boolean) throws javax.mail.MessagingException
+fld protected java.util.List<javax.mail.internet.InternetHeaders$InternetHeader> headers
+innr protected final static InternetHeader
+meth public java.lang.String getHeader(java.lang.String,java.lang.String)
+meth public java.lang.String[] getHeader(java.lang.String)
+meth public java.util.Enumeration<java.lang.String> getAllHeaderLines()
+meth public java.util.Enumeration<java.lang.String> getMatchingHeaderLines(java.lang.String[])
+meth public java.util.Enumeration<java.lang.String> getNonMatchingHeaderLines(java.lang.String[])
+meth public java.util.Enumeration<javax.mail.Header> getAllHeaders()
+meth public java.util.Enumeration<javax.mail.Header> getMatchingHeaders(java.lang.String[])
+meth public java.util.Enumeration<javax.mail.Header> getNonMatchingHeaders(java.lang.String[])
+meth public void addHeader(java.lang.String,java.lang.String)
+meth public void addHeaderLine(java.lang.String)
+meth public void load(java.io.InputStream) throws javax.mail.MessagingException
+meth public void load(java.io.InputStream,boolean) throws javax.mail.MessagingException
+meth public void removeHeader(java.lang.String)
+meth public void setHeader(java.lang.String,java.lang.String)
+supr java.lang.Object
+hfds ignoreWhitespaceLines
+hcls MatchEnum,MatchHeaderEnum,MatchStringEnum
+
+CLSS protected final static javax.mail.internet.InternetHeaders$InternetHeader
+ outer javax.mail.internet.InternetHeaders
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.String)
+meth public java.lang.String getValue()
+supr javax.mail.Header
+hfds line
+
+CLSS public javax.mail.internet.MailDateFormat
+cons public <init>()
+meth public java.lang.StringBuffer format(java.util.Date,java.lang.StringBuffer,java.text.FieldPosition)
+meth public java.util.Date get2DigitYearStart()
+meth public java.util.Date parse(java.lang.String,java.text.ParsePosition)
+meth public javax.mail.internet.MailDateFormat clone()
+meth public void applyLocalizedPattern(java.lang.String)
+meth public void applyPattern(java.lang.String)
+meth public void set2DigitYearStart(java.util.Date)
+meth public void setCalendar(java.util.Calendar)
+meth public void setDateFormatSymbols(java.text.DateFormatSymbols)
+meth public void setNumberFormat(java.text.NumberFormat)
+supr java.text.SimpleDateFormat
+hfds LEAP_SECOND,LOGGER,PATTERN,UNKNOWN_DAY_NAME,UTC,serialVersionUID
+hcls AbstractDateParser,Rfc2822LenientParser,Rfc2822StrictParser
+
+CLSS public javax.mail.internet.MimeBodyPart
+cons public <init>()
+cons public <init>(java.io.InputStream) throws javax.mail.MessagingException
+cons public <init>(javax.mail.internet.InternetHeaders,byte[]) throws javax.mail.MessagingException
+fld protected byte[] content
+fld protected java.io.InputStream contentStream
+fld protected java.lang.Object cachedContent
+fld protected javax.activation.DataHandler dh
+fld protected javax.mail.internet.InternetHeaders headers
+intf javax.mail.internet.MimePart
+meth protected java.io.InputStream getContentStream() throws javax.mail.MessagingException
+meth protected void updateHeaders() throws javax.mail.MessagingException
+meth public boolean isMimeType(java.lang.String) throws javax.mail.MessagingException
+meth public int getLineCount() throws javax.mail.MessagingException
+meth public int getSize() throws javax.mail.MessagingException
+meth public java.io.InputStream getInputStream() throws java.io.IOException,javax.mail.MessagingException
+meth public java.io.InputStream getRawInputStream() throws javax.mail.MessagingException
+meth public java.lang.Object getContent() throws java.io.IOException,javax.mail.MessagingException
+meth public java.lang.String getContentID() throws javax.mail.MessagingException
+meth public java.lang.String getContentMD5() throws javax.mail.MessagingException
+meth public java.lang.String getContentType() throws javax.mail.MessagingException
+meth public java.lang.String getDescription() throws javax.mail.MessagingException
+meth public java.lang.String getDisposition() throws javax.mail.MessagingException
+meth public java.lang.String getEncoding() throws javax.mail.MessagingException
+meth public java.lang.String getFileName() throws javax.mail.MessagingException
+meth public java.lang.String getHeader(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public java.lang.String[] getContentLanguage() throws javax.mail.MessagingException
+meth public java.lang.String[] getHeader(java.lang.String) throws javax.mail.MessagingException
+meth public java.util.Enumeration<java.lang.String> getAllHeaderLines() throws javax.mail.MessagingException
+meth public java.util.Enumeration<java.lang.String> getMatchingHeaderLines(java.lang.String[]) throws javax.mail.MessagingException
+meth public java.util.Enumeration<java.lang.String> getNonMatchingHeaderLines(java.lang.String[]) throws javax.mail.MessagingException
+meth public java.util.Enumeration<javax.mail.Header> getAllHeaders() throws javax.mail.MessagingException
+meth public java.util.Enumeration<javax.mail.Header> getMatchingHeaders(java.lang.String[]) throws javax.mail.MessagingException
+meth public java.util.Enumeration<javax.mail.Header> getNonMatchingHeaders(java.lang.String[]) throws javax.mail.MessagingException
+meth public javax.activation.DataHandler getDataHandler() throws javax.mail.MessagingException
+meth public void addHeader(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void addHeaderLine(java.lang.String) throws javax.mail.MessagingException
+meth public void attachFile(java.io.File) throws java.io.IOException,javax.mail.MessagingException
+meth public void attachFile(java.io.File,java.lang.String,java.lang.String) throws java.io.IOException,javax.mail.MessagingException
+meth public void attachFile(java.lang.String) throws java.io.IOException,javax.mail.MessagingException
+meth public void attachFile(java.lang.String,java.lang.String,java.lang.String) throws java.io.IOException,javax.mail.MessagingException
+meth public void removeHeader(java.lang.String) throws javax.mail.MessagingException
+meth public void saveFile(java.io.File) throws java.io.IOException,javax.mail.MessagingException
+meth public void saveFile(java.lang.String) throws java.io.IOException,javax.mail.MessagingException
+meth public void setContent(java.lang.Object,java.lang.String) throws javax.mail.MessagingException
+meth public void setContent(javax.mail.Multipart) throws javax.mail.MessagingException
+meth public void setContentID(java.lang.String) throws javax.mail.MessagingException
+meth public void setContentLanguage(java.lang.String[]) throws javax.mail.MessagingException
+meth public void setContentMD5(java.lang.String) throws javax.mail.MessagingException
+meth public void setDataHandler(javax.activation.DataHandler) throws javax.mail.MessagingException
+meth public void setDescription(java.lang.String) throws javax.mail.MessagingException
+meth public void setDescription(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void setDisposition(java.lang.String) throws javax.mail.MessagingException
+meth public void setFileName(java.lang.String) throws javax.mail.MessagingException
+meth public void setHeader(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void setText(java.lang.String) throws javax.mail.MessagingException
+meth public void setText(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void setText(java.lang.String,java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void writeTo(java.io.OutputStream) throws java.io.IOException,javax.mail.MessagingException
+supr javax.mail.BodyPart
+hfds allowutf8,cacheMultipart,decodeFileName,encodeFileName,ignoreMultipartEncoding,setContentTypeFileName,setDefaultTextCharset
+hcls EncodedFileDataSource,MimePartDataHandler
+
+CLSS public javax.mail.internet.MimeMessage
+cons protected <init>(javax.mail.Folder,int)
+cons protected <init>(javax.mail.Folder,java.io.InputStream,int) throws javax.mail.MessagingException
+cons protected <init>(javax.mail.Folder,javax.mail.internet.InternetHeaders,byte[],int) throws javax.mail.MessagingException
+cons public <init>(javax.mail.Session)
+cons public <init>(javax.mail.Session,java.io.InputStream) throws javax.mail.MessagingException
+cons public <init>(javax.mail.internet.MimeMessage) throws javax.mail.MessagingException
+fld protected boolean modified
+fld protected boolean saved
+fld protected byte[] content
+fld protected java.io.InputStream contentStream
+fld protected java.lang.Object cachedContent
+fld protected javax.activation.DataHandler dh
+fld protected javax.mail.Flags flags
+fld protected javax.mail.internet.InternetHeaders headers
+innr public static RecipientType
+intf javax.mail.internet.MimePart
+meth protected java.io.InputStream getContentStream() throws javax.mail.MessagingException
+meth protected javax.mail.internet.InternetHeaders createInternetHeaders(java.io.InputStream) throws javax.mail.MessagingException
+meth protected javax.mail.internet.MimeMessage createMimeMessage(javax.mail.Session) throws javax.mail.MessagingException
+meth protected void parse(java.io.InputStream) throws javax.mail.MessagingException
+meth protected void updateHeaders() throws javax.mail.MessagingException
+meth protected void updateMessageID() throws javax.mail.MessagingException
+meth public boolean isMimeType(java.lang.String) throws javax.mail.MessagingException
+meth public boolean isSet(javax.mail.Flags$Flag) throws javax.mail.MessagingException
+meth public int getLineCount() throws javax.mail.MessagingException
+meth public int getSize() throws javax.mail.MessagingException
+meth public java.io.InputStream getInputStream() throws java.io.IOException,javax.mail.MessagingException
+meth public java.io.InputStream getRawInputStream() throws javax.mail.MessagingException
+meth public java.lang.Object getContent() throws java.io.IOException,javax.mail.MessagingException
+meth public java.lang.String getContentID() throws javax.mail.MessagingException
+meth public java.lang.String getContentMD5() throws javax.mail.MessagingException
+meth public java.lang.String getContentType() throws javax.mail.MessagingException
+meth public java.lang.String getDescription() throws javax.mail.MessagingException
+meth public java.lang.String getDisposition() throws javax.mail.MessagingException
+meth public java.lang.String getEncoding() throws javax.mail.MessagingException
+meth public java.lang.String getFileName() throws javax.mail.MessagingException
+meth public java.lang.String getHeader(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public java.lang.String getMessageID() throws javax.mail.MessagingException
+meth public java.lang.String getSubject() throws javax.mail.MessagingException
+meth public java.lang.String[] getContentLanguage() throws javax.mail.MessagingException
+meth public java.lang.String[] getHeader(java.lang.String) throws javax.mail.MessagingException
+meth public java.util.Date getReceivedDate() throws javax.mail.MessagingException
+meth public java.util.Date getSentDate() throws javax.mail.MessagingException
+meth public java.util.Enumeration<java.lang.String> getAllHeaderLines() throws javax.mail.MessagingException
+meth public java.util.Enumeration<java.lang.String> getMatchingHeaderLines(java.lang.String[]) throws javax.mail.MessagingException
+meth public java.util.Enumeration<java.lang.String> getNonMatchingHeaderLines(java.lang.String[]) throws javax.mail.MessagingException
+meth public java.util.Enumeration<javax.mail.Header> getAllHeaders() throws javax.mail.MessagingException
+meth public java.util.Enumeration<javax.mail.Header> getMatchingHeaders(java.lang.String[]) throws javax.mail.MessagingException
+meth public java.util.Enumeration<javax.mail.Header> getNonMatchingHeaders(java.lang.String[]) throws javax.mail.MessagingException
+meth public javax.activation.DataHandler getDataHandler() throws javax.mail.MessagingException
+meth public javax.mail.Address getSender() throws javax.mail.MessagingException
+meth public javax.mail.Address[] getAllRecipients() throws javax.mail.MessagingException
+meth public javax.mail.Address[] getFrom() throws javax.mail.MessagingException
+meth public javax.mail.Address[] getRecipients(javax.mail.Message$RecipientType) throws javax.mail.MessagingException
+meth public javax.mail.Address[] getReplyTo() throws javax.mail.MessagingException
+meth public javax.mail.Flags getFlags() throws javax.mail.MessagingException
+meth public javax.mail.Message reply(boolean) throws javax.mail.MessagingException
+meth public javax.mail.Message reply(boolean,boolean) throws javax.mail.MessagingException
+meth public void addFrom(javax.mail.Address[]) throws javax.mail.MessagingException
+meth public void addHeader(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void addHeaderLine(java.lang.String) throws javax.mail.MessagingException
+meth public void addRecipients(javax.mail.Message$RecipientType,java.lang.String) throws javax.mail.MessagingException
+meth public void addRecipients(javax.mail.Message$RecipientType,javax.mail.Address[]) throws javax.mail.MessagingException
+meth public void removeHeader(java.lang.String) throws javax.mail.MessagingException
+meth public void saveChanges() throws javax.mail.MessagingException
+meth public void setContent(java.lang.Object,java.lang.String) throws javax.mail.MessagingException
+meth public void setContent(javax.mail.Multipart) throws javax.mail.MessagingException
+meth public void setContentID(java.lang.String) throws javax.mail.MessagingException
+meth public void setContentLanguage(java.lang.String[]) throws javax.mail.MessagingException
+meth public void setContentMD5(java.lang.String) throws javax.mail.MessagingException
+meth public void setDataHandler(javax.activation.DataHandler) throws javax.mail.MessagingException
+meth public void setDescription(java.lang.String) throws javax.mail.MessagingException
+meth public void setDescription(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void setDisposition(java.lang.String) throws javax.mail.MessagingException
+meth public void setFileName(java.lang.String) throws javax.mail.MessagingException
+meth public void setFlags(javax.mail.Flags,boolean) throws javax.mail.MessagingException
+meth public void setFrom() throws javax.mail.MessagingException
+meth public void setFrom(java.lang.String) throws javax.mail.MessagingException
+meth public void setFrom(javax.mail.Address) throws javax.mail.MessagingException
+meth public void setHeader(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void setRecipients(javax.mail.Message$RecipientType,java.lang.String) throws javax.mail.MessagingException
+meth public void setRecipients(javax.mail.Message$RecipientType,javax.mail.Address[]) throws javax.mail.MessagingException
+meth public void setReplyTo(javax.mail.Address[]) throws javax.mail.MessagingException
+meth public void setSender(javax.mail.Address) throws javax.mail.MessagingException
+meth public void setSentDate(java.util.Date) throws javax.mail.MessagingException
+meth public void setSubject(java.lang.String) throws javax.mail.MessagingException
+meth public void setSubject(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void setText(java.lang.String) throws javax.mail.MessagingException
+meth public void setText(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void setText(java.lang.String,java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public void writeTo(java.io.OutputStream) throws java.io.IOException,javax.mail.MessagingException
+meth public void writeTo(java.io.OutputStream,java.lang.String[]) throws java.io.IOException,javax.mail.MessagingException
+supr javax.mail.Message
+hfds allowutf8,answeredFlag,mailDateFormat,strict
+
+CLSS public static javax.mail.internet.MimeMessage$RecipientType
+ outer javax.mail.internet.MimeMessage
+cons protected <init>(java.lang.String)
+fld public final static javax.mail.internet.MimeMessage$RecipientType NEWSGROUPS
+meth protected java.lang.Object readResolve() throws java.io.ObjectStreamException
+supr javax.mail.Message$RecipientType
+hfds serialVersionUID
+
+CLSS public javax.mail.internet.MimeMultipart
+cons public !varargs <init>(java.lang.String,javax.mail.BodyPart[]) throws javax.mail.MessagingException
+cons public !varargs <init>(javax.mail.BodyPart[]) throws javax.mail.MessagingException
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(javax.activation.DataSource) throws javax.mail.MessagingException
+fld protected boolean allowEmpty
+fld protected boolean complete
+fld protected boolean ignoreExistingBoundaryParameter
+fld protected boolean ignoreMissingBoundaryParameter
+fld protected boolean ignoreMissingEndBoundary
+fld protected boolean parsed
+fld protected java.lang.String preamble
+fld protected javax.activation.DataSource ds
+meth protected javax.mail.internet.InternetHeaders createInternetHeaders(java.io.InputStream) throws javax.mail.MessagingException
+meth protected javax.mail.internet.MimeBodyPart createMimeBodyPart(java.io.InputStream) throws javax.mail.MessagingException
+meth protected javax.mail.internet.MimeBodyPart createMimeBodyPart(javax.mail.internet.InternetHeaders,byte[]) throws javax.mail.MessagingException
+meth protected void initializeProperties()
+meth protected void parse() throws javax.mail.MessagingException
+meth protected void updateHeaders() throws javax.mail.MessagingException
+meth public boolean isComplete() throws javax.mail.MessagingException
+meth public boolean removeBodyPart(javax.mail.BodyPart) throws javax.mail.MessagingException
+meth public int getCount() throws javax.mail.MessagingException
+meth public java.lang.String getPreamble() throws javax.mail.MessagingException
+meth public javax.mail.BodyPart getBodyPart(int) throws javax.mail.MessagingException
+meth public javax.mail.BodyPart getBodyPart(java.lang.String) throws javax.mail.MessagingException
+meth public void addBodyPart(javax.mail.BodyPart) throws javax.mail.MessagingException
+meth public void addBodyPart(javax.mail.BodyPart,int) throws javax.mail.MessagingException
+meth public void removeBodyPart(int) throws javax.mail.MessagingException
+meth public void setPreamble(java.lang.String) throws javax.mail.MessagingException
+meth public void setSubType(java.lang.String) throws javax.mail.MessagingException
+meth public void writeTo(java.io.OutputStream) throws java.io.IOException,javax.mail.MessagingException
+supr javax.mail.Multipart
+
+CLSS public abstract interface javax.mail.internet.MimePart
+intf javax.mail.Part
+meth public abstract java.lang.String getContentID() throws javax.mail.MessagingException
+meth public abstract java.lang.String getContentMD5() throws javax.mail.MessagingException
+meth public abstract java.lang.String getEncoding() throws javax.mail.MessagingException
+meth public abstract java.lang.String getHeader(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public abstract java.lang.String[] getContentLanguage() throws javax.mail.MessagingException
+meth public abstract java.util.Enumeration<java.lang.String> getAllHeaderLines() throws javax.mail.MessagingException
+meth public abstract java.util.Enumeration<java.lang.String> getMatchingHeaderLines(java.lang.String[]) throws javax.mail.MessagingException
+meth public abstract java.util.Enumeration<java.lang.String> getNonMatchingHeaderLines(java.lang.String[]) throws javax.mail.MessagingException
+meth public abstract void addHeaderLine(java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setContentLanguage(java.lang.String[]) throws javax.mail.MessagingException
+meth public abstract void setContentMD5(java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setText(java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setText(java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public abstract void setText(java.lang.String,java.lang.String,java.lang.String) throws javax.mail.MessagingException
+
+CLSS public javax.mail.internet.MimePartDataSource
+cons public <init>(javax.mail.internet.MimePart)
+fld protected javax.mail.internet.MimePart part
+intf javax.activation.DataSource
+intf javax.mail.MessageAware
+meth public java.io.InputStream getInputStream() throws java.io.IOException
+meth public java.io.OutputStream getOutputStream() throws java.io.IOException
+meth public java.lang.String getContentType()
+meth public java.lang.String getName()
+meth public javax.mail.MessageContext getMessageContext()
+supr java.lang.Object
+hfds context
+
+CLSS public javax.mail.internet.MimeUtility
+fld public final static int ALL = -1
+meth public static java.io.InputStream decode(java.io.InputStream,java.lang.String) throws javax.mail.MessagingException
+meth public static java.io.OutputStream encode(java.io.OutputStream,java.lang.String) throws javax.mail.MessagingException
+meth public static java.io.OutputStream encode(java.io.OutputStream,java.lang.String,java.lang.String) throws javax.mail.MessagingException
+meth public static java.lang.String decodeText(java.lang.String) throws java.io.UnsupportedEncodingException
+meth public static java.lang.String decodeWord(java.lang.String) throws java.io.UnsupportedEncodingException,javax.mail.internet.ParseException
+meth public static java.lang.String encodeText(java.lang.String) throws java.io.UnsupportedEncodingException
+meth public static java.lang.String encodeText(java.lang.String,java.lang.String,java.lang.String) throws java.io.UnsupportedEncodingException
+meth public static java.lang.String encodeWord(java.lang.String) throws java.io.UnsupportedEncodingException
+meth public static java.lang.String encodeWord(java.lang.String,java.lang.String,java.lang.String) throws java.io.UnsupportedEncodingException
+meth public static java.lang.String fold(int,java.lang.String)
+meth public static java.lang.String getDefaultJavaCharset()
+meth public static java.lang.String getEncoding(javax.activation.DataHandler)
+meth public static java.lang.String getEncoding(javax.activation.DataSource)
+meth public static java.lang.String javaCharset(java.lang.String)
+meth public static java.lang.String mimeCharset(java.lang.String)
+meth public static java.lang.String quote(java.lang.String,java.lang.String)
+meth public static java.lang.String unfold(java.lang.String)
+supr java.lang.Object
+hfds ALL_ASCII,MOSTLY_ASCII,MOSTLY_NONASCII,allowUtf8,decodeStrict,defaultJavaCharset,defaultMIMECharset,encodeEolStrict,foldEncodedWords,foldText,ignoreUnknownEncoding,java2mime,mime2java,nonAsciiCharsetMap
+
+CLSS public javax.mail.internet.NewsAddress
+cons public <init>()
+cons public <init>(java.lang.String)
+cons public <init>(java.lang.String,java.lang.String)
+fld protected java.lang.String host
+fld protected java.lang.String newsgroup
+meth public boolean equals(java.lang.Object)
+meth public int hashCode()
+meth public java.lang.String getHost()
+meth public java.lang.String getNewsgroup()
+meth public java.lang.String getType()
+meth public java.lang.String toString()
+meth public static java.lang.String toString(javax.mail.Address[])
+meth public static javax.mail.internet.NewsAddress[] parse(java.lang.String) throws javax.mail.internet.AddressException
+meth public void setHost(java.lang.String)
+meth public void setNewsgroup(java.lang.String)
+supr javax.mail.Address
+hfds serialVersionUID
+
+CLSS public javax.mail.internet.ParameterList
+cons public <init>()
+cons public <init>(java.lang.String) throws javax.mail.internet.ParseException
+meth public int size()
+meth public java.lang.String get(java.lang.String)
+meth public java.lang.String toString()
+meth public java.lang.String toString(int)
+meth public java.util.Enumeration<java.lang.String> getNames()
+meth public void combineSegments()
+meth public void remove(java.lang.String)
+meth public void set(java.lang.String,java.lang.String)
+meth public void set(java.lang.String,java.lang.String,java.lang.String)
+supr java.lang.Object
+hfds applehack,decodeParameters,decodeParametersStrict,encodeParameters,hex,lastName,list,multisegmentNames,parametersStrict,slist,splitLongParameters,windowshack
+hcls LiteralValue,MultiValue,ParamEnum,ToStringBuffer,Value
+
+CLSS public javax.mail.internet.ParseException
+cons public <init>()
+cons public <init>(java.lang.String)
+supr javax.mail.MessagingException
+hfds serialVersionUID
+
+CLSS public javax.mail.internet.PreencodedMimeBodyPart
+cons public <init>(java.lang.String)
+meth protected void updateHeaders() throws javax.mail.MessagingException
+meth public java.lang.String getEncoding() throws javax.mail.MessagingException
+meth public void writeTo(java.io.OutputStream) throws java.io.IOException,javax.mail.MessagingException
+supr javax.mail.internet.MimeBodyPart
+hfds encoding
+
+CLSS public abstract interface javax.mail.internet.SharedInputStream
+meth public abstract java.io.InputStream newStream(long,long)
+meth public abstract long getPosition()
+
+CLSS public abstract javax.mail.search.AddressStringTerm
+cons protected <init>(java.lang.String)
+meth protected boolean match(javax.mail.Address)
+meth public boolean equals(java.lang.Object)
+supr javax.mail.search.StringTerm
+hfds serialVersionUID
+
+CLSS public abstract javax.mail.search.AddressTerm
+cons protected <init>(javax.mail.Address)
+fld protected javax.mail.Address address
+meth protected boolean match(javax.mail.Address)
+meth public boolean equals(java.lang.Object)
+meth public int hashCode()
+meth public javax.mail.Address getAddress()
+supr javax.mail.search.SearchTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.AndTerm
+cons public <init>(javax.mail.search.SearchTerm,javax.mail.search.SearchTerm)
+cons public <init>(javax.mail.search.SearchTerm[])
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+meth public int hashCode()
+meth public javax.mail.search.SearchTerm[] getTerms()
+supr javax.mail.search.SearchTerm
+hfds serialVersionUID,terms
+
+CLSS public final javax.mail.search.BodyTerm
+cons public <init>(java.lang.String)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+supr javax.mail.search.StringTerm
+hfds serialVersionUID
+
+CLSS public abstract javax.mail.search.ComparisonTerm
+cons public <init>()
+fld protected int comparison
+fld public final static int EQ = 3
+fld public final static int GE = 6
+fld public final static int GT = 5
+fld public final static int LE = 1
+fld public final static int LT = 2
+fld public final static int NE = 4
+meth public boolean equals(java.lang.Object)
+meth public int hashCode()
+supr javax.mail.search.SearchTerm
+hfds serialVersionUID
+
+CLSS public abstract javax.mail.search.DateTerm
+cons protected <init>(int,java.util.Date)
+fld protected java.util.Date date
+meth protected boolean match(java.util.Date)
+meth public boolean equals(java.lang.Object)
+meth public int getComparison()
+meth public int hashCode()
+meth public java.util.Date getDate()
+supr javax.mail.search.ComparisonTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.FlagTerm
+cons public <init>(javax.mail.Flags,boolean)
+meth public boolean equals(java.lang.Object)
+meth public boolean getTestSet()
+meth public boolean match(javax.mail.Message)
+meth public int hashCode()
+meth public javax.mail.Flags getFlags()
+supr javax.mail.search.SearchTerm
+hfds flags,serialVersionUID,set
+
+CLSS public final javax.mail.search.FromStringTerm
+cons public <init>(java.lang.String)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+supr javax.mail.search.AddressStringTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.FromTerm
+cons public <init>(javax.mail.Address)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+supr javax.mail.search.AddressTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.HeaderTerm
+cons public <init>(java.lang.String,java.lang.String)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+meth public int hashCode()
+meth public java.lang.String getHeaderName()
+supr javax.mail.search.StringTerm
+hfds headerName,serialVersionUID
+
+CLSS public abstract javax.mail.search.IntegerComparisonTerm
+cons protected <init>(int,int)
+fld protected int number
+meth protected boolean match(int)
+meth public boolean equals(java.lang.Object)
+meth public int getComparison()
+meth public int getNumber()
+meth public int hashCode()
+supr javax.mail.search.ComparisonTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.MessageIDTerm
+cons public <init>(java.lang.String)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+supr javax.mail.search.StringTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.MessageNumberTerm
+cons public <init>(int)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+supr javax.mail.search.IntegerComparisonTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.NotTerm
+cons public <init>(javax.mail.search.SearchTerm)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+meth public int hashCode()
+meth public javax.mail.search.SearchTerm getTerm()
+supr javax.mail.search.SearchTerm
+hfds serialVersionUID,term
+
+CLSS public final javax.mail.search.OrTerm
+cons public <init>(javax.mail.search.SearchTerm,javax.mail.search.SearchTerm)
+cons public <init>(javax.mail.search.SearchTerm[])
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+meth public int hashCode()
+meth public javax.mail.search.SearchTerm[] getTerms()
+supr javax.mail.search.SearchTerm
+hfds serialVersionUID,terms
+
+CLSS public final javax.mail.search.ReceivedDateTerm
+cons public <init>(int,java.util.Date)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+supr javax.mail.search.DateTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.RecipientStringTerm
+cons public <init>(javax.mail.Message$RecipientType,java.lang.String)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+meth public int hashCode()
+meth public javax.mail.Message$RecipientType getRecipientType()
+supr javax.mail.search.AddressStringTerm
+hfds serialVersionUID,type
+
+CLSS public final javax.mail.search.RecipientTerm
+cons public <init>(javax.mail.Message$RecipientType,javax.mail.Address)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+meth public int hashCode()
+meth public javax.mail.Message$RecipientType getRecipientType()
+supr javax.mail.search.AddressTerm
+hfds serialVersionUID,type
+
+CLSS public javax.mail.search.SearchException
+cons public <init>()
+cons public <init>(java.lang.String)
+supr javax.mail.MessagingException
+hfds serialVersionUID
+
+CLSS public abstract javax.mail.search.SearchTerm
+cons public <init>()
+intf java.io.Serializable
+meth public abstract boolean match(javax.mail.Message)
+supr java.lang.Object
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.SentDateTerm
+cons public <init>(int,java.util.Date)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+supr javax.mail.search.DateTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.SizeTerm
+cons public <init>(int,int)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+supr javax.mail.search.IntegerComparisonTerm
+hfds serialVersionUID
+
+CLSS public abstract javax.mail.search.StringTerm
+cons protected <init>(java.lang.String)
+cons protected <init>(java.lang.String,boolean)
+fld protected boolean ignoreCase
+fld protected java.lang.String pattern
+meth protected boolean match(java.lang.String)
+meth public boolean equals(java.lang.Object)
+meth public boolean getIgnoreCase()
+meth public int hashCode()
+meth public java.lang.String getPattern()
+supr javax.mail.search.SearchTerm
+hfds serialVersionUID
+
+CLSS public final javax.mail.search.SubjectTerm
+cons public <init>(java.lang.String)
+meth public boolean equals(java.lang.Object)
+meth public boolean match(javax.mail.Message)
+supr javax.mail.search.StringTerm
+hfds serialVersionUID
+
+CLSS public javax.mail.util.ByteArrayDataSource
+cons public <init>(byte[],java.lang.String)
+cons public <init>(java.io.InputStream,java.lang.String) throws java.io.IOException
+cons public <init>(java.lang.String,java.lang.String) throws java.io.IOException
+intf javax.activation.DataSource
+meth public java.io.InputStream getInputStream() throws java.io.IOException
+meth public java.io.OutputStream getOutputStream() throws java.io.IOException
+meth public java.lang.String getContentType()
+meth public java.lang.String getName()
+meth public void setName(java.lang.String)
+supr java.lang.Object
+hfds data,len,name,type
+hcls DSByteArrayOutputStream
+
+CLSS public javax.mail.util.SharedByteArrayInputStream
+cons public <init>(byte[])
+cons public <init>(byte[],int,int)
+fld protected int start
+intf javax.mail.internet.SharedInputStream
+meth public java.io.InputStream newStream(long,long)
+meth public long getPosition()
+supr java.io.ByteArrayInputStream
+
+CLSS public javax.mail.util.SharedFileInputStream
+cons public <init>(java.io.File) throws java.io.IOException
+cons public <init>(java.io.File,int) throws java.io.IOException
+cons public <init>(java.lang.String) throws java.io.IOException
+cons public <init>(java.lang.String,int) throws java.io.IOException
+fld protected int bufsize
+fld protected java.io.RandomAccessFile in
+fld protected long bufpos
+fld protected long datalen
+fld protected long start
+intf javax.mail.internet.SharedInputStream
+meth protected void finalize() throws java.lang.Throwable
+meth public boolean markSupported()
+meth public int available() throws java.io.IOException
+meth public int read() throws java.io.IOException
+meth public int read(byte[],int,int) throws java.io.IOException
+meth public java.io.InputStream newStream(long,long)
+meth public long getPosition()
+meth public long skip(long) throws java.io.IOException
+meth public void close() throws java.io.IOException
+meth public void mark(int)
+meth public void reset() throws java.io.IOException
+supr java.io.BufferedInputStream
+hfds defaultBufferSize,master,sf
+hcls SharedFile
+
diff --git a/mail/exclude.xml b/mail/exclude.xml
new file mode 100644
index 0000000..b5fa45c
--- /dev/null
+++ b/mail/exclude.xml
@@ -0,0 +1,727 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<!-- FindBugs exclude list for JavaMail -->
+
+<FindBugsFilter>
+    <!--
+	FindBugs *really* wants us to use zero length arrays instead
+	of null.  Unfortunately, there's a bunch of places we can't
+	do this for various reasons:
+	  - The API specifies null and we can't make an incompatible change.
+	  - We use null to indicate no header and an empty array to indicate
+	    the header exists but has no entries.
+	  - We use null to indicate NIL in the protocol and an empty array
+	    to indicate an empty list "()" in the protocol.
+	This error occurs often enough that we just ignore it everywhere.
+    -->
+    <Match>
+	<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+    </Match>
+    <!--
+	There are a bunch of places where FindBugs complains about
+	exposing internal representations.  We exclude cases where
+	this only happens in internal classes that are never visible
+	through the public JavaMail API, or cases where the user
+	passes in an object (usually an array) and can only hurt
+	themselves by modifying the array while a method is in progress,
+	or where the implementation is passing the data back to the user
+	(e.g., in an Exception) and never uses it again.
+    -->
+    <Match>
+	<Or>
+	    <Class name="javax.mail.SendFailedException"/>
+	    <Class name="javax.mail.event.MessageCountEvent"/>
+	    <Class name="javax.mail.event.TransportEvent"/>
+	    <Class name="com.sun.mail.iap.ByteArray"/>
+	    <Class name="com.sun.mail.imap.MessageVanishedEvent"/>
+	    <Class name="com.sun.mail.imap.protocol.FetchResponse"/>
+	</Or>
+	<Or>
+	    <Bug pattern="EI_EXPOSE_REP"/>
+	    <Bug pattern="EI_EXPOSE_REP2"/>
+	</Or>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.smtp.SMTPTransport"/>
+	<Method name="sendMessage"/>
+	<!-- passed in Address array -->
+	<Bug pattern="EI_EXPOSE_REP2"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.internet.MimeBodyPart"/>
+	<Method name="&lt;init&gt;"/> <!-- match constructor -->
+	<!-- passed in byte array -->
+	<Bug pattern="EI_EXPOSE_REP2"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.util.ByteArrayDataSource"/>
+	<Method name="&lt;init&gt;"/> <!-- match constructor -->
+	<!-- passed in byte array -->
+	<Bug pattern="EI_EXPOSE_REP2"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.util.MailSSLSocketFactory"/>
+	<Method name="setTrustManagers"/>
+	<!-- passed in TrustManager array -->
+	<Bug pattern="EI_EXPOSE_REP2"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.util.MailSSLSocketFactory"/>
+	<Method name="getTrustManagers"/>
+	<!-- returned TrustManager array -->
+	<Bug pattern="EI_EXPOSE_REP"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.INTERNALDATE"/>
+	<Method name="getDate"/>
+	<!--
+	    Returned Date object; it's only ever stored in
+	    IMAPMessage.receivedDate and is always used to
+	    construct a new Date object before returning to
+	    users.
+	-->
+	<Bug pattern="EI_EXPOSE_REP"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.imap.CopyUID"/>
+	<Method name="&lt;init&gt;"/> <!-- match constructor -->
+	<Bug pattern="EI_EXPOSE_REP2"/>
+    </Match>
+
+    <!--
+	A few places where it complains about wait not being in a loop.
+	This purposely doesn't loop so that the application
+	calling idle can check whether the idle should continue.
+    -->
+    <Match>
+	<!-- an anonymous inner class of the idle method -->
+	<Class name="~com\.sun\.mail\.imap\.IMAPFolder.*"/>
+	<Method name="doCommand"/>
+	<Bug pattern="WA_NOT_IN_LOOP"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.imap.IMAPStore"/>
+	<Method name="idle"/>
+	<Bug pattern="WA_NOT_IN_LOOP"/>
+    </Match>
+
+    <!--
+	A few places where we catch Exception even though it's not
+	explicitly thrown.  We need to make sure that if anything
+	goes wrong we clean things up.  Perhaps these should be
+	converted to a finally block and a boolean "success" flag?
+	Most of these are related to reflection or ClassLoader
+	operations, which can fail for all sorts of unexpected reasons.
+	Some of these could be converted to use multi-catch with JDK 1.7.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.IMAPStore"/>
+	<Or>
+	    <Method name="&lt;init&gt;"/> <!-- match constructor -->
+	    <Method name="getProtocol"/>
+	    <Method name="getStoreProtocol"/>
+	    <Method name="newIMAPFolder"/>
+	</Or>
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.IMAPProtocol"/>
+	<Or>
+	    <Method name="authlogin"/>
+	    <Method name="authntlm"/>
+	    <Method name="authplain"/>
+	    <Method name="authoauth2"/>
+	    <Method name="sasllogin"/>
+	</Or>
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.IMAPSaslAuthenticator"/>
+	<Method name="authenticate"/>
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.pop3.POP3Store"/>
+	<Method name="&lt;init&gt;"/> <!-- match constructor -->
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.smtp.SMTPTransport"/>
+	<Or>
+	    <Method name="isConnected"/>
+	    <Method name="sasllogin"/>
+	</Or>
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.pop3.POP3Folder"/>
+	<Method name="createMessage"/>
+	<Or>
+	    <Bug pattern="REC_CATCH_EXCEPTION"/>
+	    <Bug pattern="DE_MIGHT_IGNORE"/>
+	</Or>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.util.SocketFetcher"/>
+	<Or>
+	    <Method name="getSocket"/>
+	    <Method name="startTLS"/>
+	    <Method name="matchCert"/>
+	</Or>
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.util.MimeUtil"/>
+	<Method name="cleanContentType"/>
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.Session"/>
+	<Or>
+	    <Method name="getService"/>
+	    <Method name="loadAllResources"/>
+	</Or>
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.internet.MimeUtility"/>
+	<Or>
+	    <Method name="&lt;clinit&gt;"/> <!-- match static initializer -->
+	    <Method name="getEncoding"/>
+	</Or>
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.imap.IMAPFolder"/>
+	<Method name="handleIdle"/>
+	<Bug pattern="DE_MIGHT_IGNORE"/>
+    </Match>
+
+    <!--
+	FindBugs complains about a possible double check of headersLoaded,
+	but it seems to be just wrong; I don't see it.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.IMAPMessage"/>
+	<Method name="loadHeaders"/>
+	<Bug pattern="DC_DOUBLECHECK"/>
+    </Match>
+
+    <!--
+	These IMAP-specific subclasses of standard classes don't override
+	equals because all they add are constructors and optimized access
+	methods; everything else, including the way to test for equality,
+	is the same.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.FLAGS"/>
+	<Bug pattern="EQ_DOESNT_OVERRIDE_EQUALS"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.IMAPAddress"/>
+	<!-- defined in ENVELOPE.java -->
+	<Bug pattern="EQ_DOESNT_OVERRIDE_EQUALS"/>
+    </Match>
+
+    <!--
+	FindBugs complains of an unitialized read of the "capabilities"
+	field.  Since the superclass might initialize the field (via a
+	call into the overridden processGreeting method), any initialization
+	done in the IMAPProtocol class will undo any initialization done
+	as a side effect of calling the superclass constructor.  Thus, we
+	need to depend on the field being initialized to the default value.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.IMAPProtocol"/>
+	<Method name="&lt;init&gt;"/> <!-- match constructor -->
+	<Bug pattern="UR_UNINIT_READ"/>
+    </Match>
+
+    <!--
+	This use of string concatenation only occurs when creating a
+	string for an error message in an exception.  The simpler code
+	is better here; performance is not an issue.
+    -->
+    <Match>
+	<Class name="com.sun.mail.util.BASE64DecoderStream"/>
+	<Method name="recentChars"/>
+	<Bug pattern="SBSC_USE_STRINGBUFFER_CONCATENATION"/>
+    </Match>
+
+    <!--
+	Yes, the "next" element in my linked list isn't ever actually
+	used, but it feels weird to only have a "prev" element.
+    -->
+    <Match>
+	<Class name="javax.mail.EventQueue$QueueElement"/>
+	<Field name="next"/>
+	<Bug pattern="URF_UNREAD_FIELD"/>
+    </Match>
+
+    <!--
+	Stupid Serializable EventObject class causes FindBugs to complain
+	about transient fields in subclasses.  I don't know why it's
+	complaining about these fields but not others, but since I don't
+	really expect anyone to serialize these events I'm just ignoring
+	this complaint.  Ditto Exception fields.
+    -->
+    <Match>
+	<Class name="javax.mail.event.TransportEvent"/>
+	<Or>
+	    <Field name="invalid"/>
+	    <Field name="validSent"/>
+	    <Field name="validUnsent"/>
+	</Or>
+	<Bug pattern="SE_TRANSIENT_FIELD_NOT_RESTORED"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.event.FolderEvent"/>
+	<Or>
+	    <Field name="folder"/>
+	    <Field name="newFolder"/>
+	</Or>
+	<Bug pattern="SE_TRANSIENT_FIELD_NOT_RESTORED"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.event.MessageChangedEvent"/>
+	<Field name="msg"/>
+	<Bug pattern="SE_TRANSIENT_FIELD_NOT_RESTORED"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.event.MessageCountEvent"/>
+	<Field name="msgs"/>
+	<Bug pattern="SE_TRANSIENT_FIELD_NOT_RESTORED"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.event.TransportEvent"/>
+	<Field name="msg"/>
+	<Bug pattern="SE_TRANSIENT_FIELD_NOT_RESTORED"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.SendFailedException"/>
+	<Or>
+	    <Field name="invalid"/>
+	    <Field name="validSent"/>
+	    <Field name="validUnsent"/>
+	</Or>
+	<Bug pattern="SE_TRANSIENT_FIELD_NOT_RESTORED"/>
+    </Match>
+
+    <!--
+	These string comparisons are just optimizations.
+    -->
+    <Match>
+	<Or>
+	    <Class name="javax.mail.URLName"/>
+	    <Class name="javax.mail.internet.InternetAddress"/>
+	</Or>
+	<Method name="equals"/>
+	<Bug pattern="ES_COMPARING_STRINGS_WITH_EQ"/>
+    </Match>
+
+    <!--
+	This string comparison using == is to determine whether the
+	String object is a different String object.
+    -->
+    <Match>
+	<Class name="javax.mail.internet.MimeUtility"/>
+	<Method name="decodeText"/>
+	<Bug pattern="ES_COMPARING_STRINGS_WITH_EQ"/>
+    </Match>
+
+    <!--
+	This string comparison using == is to determine whether the
+	String object is the original default String object.
+    -->
+    <Match>
+	<Class name="com.sun.mail.smtp.SMTPTransport"/>
+	<Method name="authenticate"/>
+	<Bug pattern="ES_COMPARING_STRINGS_WITH_EQ"/>
+    </Match>
+
+    <!--
+	ByteArrayInputStream.available guarantees to return the full number
+	of bytes left in the buffer, and ByteArrayInputStream.read guarantees
+	to read all the bytes, so we don't really need to check the return
+	value.  Ignore this complaint.
+    -->
+    <Match>
+	<Class name="com.sun.mail.util.ASCIIUtility"/>
+	<Method name="getBytes"/>
+	<Bug pattern="DLS_DEAD_LOCAL_STORE"/>
+    </Match>
+
+    <!--
+	We extract the "lang" field of an encoded string but we don't
+	currently do anything with it.  Ignore this complaint.
+    -->
+    <Match>
+	<Class name="javax.mail.internet.ParameterList"/>
+	<Method name="decodeValue"/>
+	<Bug pattern="DLS_DEAD_LOCAL_STORE"/>
+    </Match>
+
+    <!--
+	The call ParameterList.set(null, "DONE") is a kludge used by the
+	IMAP provider to indicate that it's done setting parameters.
+	In other cases we *want* a null name to cause a NullPointerException.
+    -->
+    <Match>
+	<Class name="javax.mail.internet.ParameterList"/>
+	<Method name="set"/>
+	<Bug pattern="NP_NULL_ON_SOME_PATH"/>
+    </Match>
+
+    <!--
+	We purposely don't close these streams, which are just wrappers
+	around the original stream that needs to remain open.
+    -->
+    <Match>
+	<Class name="javax.mail.internet.MimeMultipart"/>
+	<Or>
+	    <Method name="parse"/>
+	    <Method name="parsebm"/>
+	</Or>
+	<Bug pattern="OS_OPEN_STREAM"/>
+    </Match>
+
+    <!--
+	When deleting the temp file fails, there's really nothing to be done.
+    -->
+    <Match>
+	<Class name="com.sun.mail.pop3.TempFile"/>
+	<Method name="close"/>
+	<Bug pattern="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE"/>
+    </Match>
+
+    <!--
+	In IMAPFolder.close, I believe the protocol field can be set to null
+	as a result of a protocol error that invokes a callback that calls
+	cleanup, thus I don't believe these null checks are redundant.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.IMAPFolder"/>
+	<Method name="close"/>
+	<Bug pattern="RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"/>
+    </Match>
+
+    <!--
+	Can't fix these errors in toString until JavaMail 1.5 because
+	it's part of the spec.  Sigh.
+    -->
+    <Match>
+	<Or>
+	    <Class name="javax.mail.internet.ContentDisposition"/>
+	    <Class name="javax.mail.internet.ContentType"/>
+	</Or>
+	<Method name="toString"/>
+	<Bug pattern="NP_TOSTRING_COULD_RETURN_NULL"/>
+    </Match>
+
+    <!--
+	IMAPFolder.uidTable is only ever manipulated when the
+	messageCacheLock is held, but FindBugs can't figure that out.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.IMAPFolder"/>
+	<Field name="uidTable"/>
+	<Bug pattern="IS2_INCONSISTENT_SYNC"/>
+    </Match>
+
+    <!--
+	IMAPFolder.doExpungeNotification is only ever manipulated when the
+	messageCacheLock is held, but FindBugs can't figure that out.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.IMAPFolder"/>
+	<Field name="doExpungeNotification"/>
+	<Bug pattern="IS2_INCONSISTENT_SYNC"/>
+    </Match>
+
+    <!--
+	The static mailDateFormat field is only used to access the parse
+	method, which is known to be thread safe, so we can ignore this error.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.INTERNALDATE"/>
+	<Bug pattern="STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCE"/>
+    </Match>
+
+    <!--
+	These errors are in code imported from the JDK, where it seems
+	to be purposely using the platform default encoding.  It's safer
+	to just leave this alone and ignore the errors.
+    -->
+    <Match>
+	<Class name="javax.mail.URLName"/>
+	<Bug pattern="DM_DEFAULT_ENCODING"/>
+    </Match>
+
+    <!--
+	We're purposely using the platform default encoding because
+	we're trying to discover what the platform default encoding *is*!
+    -->
+    <Match>
+	<Class name="javax.mail.internet.MimeUtility"/>
+	<Method name="getDefaultJavaCharset"/>
+	<Bug pattern="DM_DEFAULT_ENCODING"/>
+    </Match>
+
+    <!--
+	We're purposely using the platform default encoding when a
+	charset hasn't been specified.
+    -->
+    <Match>
+	<Class name="javax.mail.internet.ParameterList"/>
+	<Method name="combineMultisegmentNames"/>
+	<Bug pattern="DM_DEFAULT_ENCODING"/>
+    </Match>
+
+    <!--
+	We're purposely using the platform default encoding because
+	we're converting bytes that were written using the platform
+	default encoding back to a String.
+    -->
+    <Match>
+	<Class name="com.sun.mail.util.LogOutputStream"/>
+	<Method name="logBuf"/>
+	<Bug pattern="DM_DEFAULT_ENCODING"/>
+    </Match>
+
+    <!--
+	MimeBodyPart.headers is set in the constructor.  IMAPBodyPart has
+	perhaps paranoid defensive code to set it if it's not set.  That
+	code is protected by the object lock, but all of the reads of the
+	field are not, thus FindBugs complains.  For now I think it's
+	better to leave the defensive code in and ignore this error.
+    -->
+    <Match>
+	<Class name="javax.mail.internet.MimeBodyPart"/>
+	<Field name="headers"/>
+	<Bug pattern="IS2_INCONSISTENT_SYNC"/>
+    </Match>
+
+    <!--
+	Refactoring these anonymous inner classes to be static classes
+	makes the code less readable.  Too bad there's no way for an
+	anonymous inner class to be static.
+	XXX - these class names aren't going to be stable as the code changes
+    -->
+    <Match>
+	<Or>
+	    <Class name="com.sun.mail.imap.DefaultFolder$1"/>
+	    <Class name="com.sun.mail.imap.DefaultFolder$2"/>
+	    <Class name="com.sun.mail.imap.IMAPFolder$1"/>
+	    <Class name="com.sun.mail.imap.IMAPFolder$4"/>
+	    <Class name="com.sun.mail.imap.IMAPFolder$7"/>
+	    <Class name="com.sun.mail.imap.IMAPFolder$13"/>
+	    <Class name="com.sun.mail.imap.IMAPFolder$20"/>
+	    <Class name="com.sun.mail.imap.IdleManager$2"/>
+	</Or>
+	<Bug pattern="SIC_INNER_SHOULD_BE_STATIC_ANON"/>
+    </Match>
+
+    <!--
+	SpotBugs doesn't recognize the anonymous inner classes above.
+	https://github.com/spotbugs/discuss/issues/22
+	This regular expression version has the same effect, and
+	avoids the potential issue with the class names changing.
+    -->
+    <Match>
+	<Or>
+	    <Class name="~com\.sun\.mail\.imap\.DefaultFolder.*"/>
+	    <Class name="~com\.sun\.mail\.imap\.IMAPFolder.*"/>
+	    <Class name="~com\.sun\.mail\.imap\.IdleManager.*"/>
+	</Or>
+	<Bug pattern="SIC_INNER_SHOULD_BE_STATIC_ANON"/>
+    </Match>
+
+    <!--
+	Even though only this array reference is volatile, the array is
+	used in such a way that it's safe.  The aray elements are created
+	and initialized before the array is assigned to this array
+	reference, and after being assigned neither the array nor the
+	array elements are changed.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.IMAPFolder"/>
+	<Field name="attributes"/>
+	<Bug pattern="VO_VOLATILE_REFERENCE_TO_ARRAY"/>
+    </Match>
+
+    <!--
+	This code is easier to understand with empty "if" clauses;
+	the "match" method is being called for its side effects.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.FetchResponse"/>
+	<Method name="parseItem"/>
+	<Bug pattern="UCF_USELESS_CONTROL_FLOW"/>
+    </Match>
+
+    <!--
+	Even though these fields are unread for now, we'll keep them
+	since they represent data in the protocol message being parsed.
+    -->
+    <Match>
+	<Or>
+	    <Class name="com.sun.mail.imap.protocol.ENVELOPE"/>
+	    <Class name="com.sun.mail.imap.protocol.INTERNALDATE"/>
+	    <Class name="com.sun.mail.imap.protocol.RFC822SIZE"/>
+	</Or>
+	<Field name="msgno"/>
+	<Bug pattern="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.MailboxInfo"/>
+	<Field name="first"/>
+	<Bug pattern="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD"/>
+    </Match>
+    <!--
+	These unread fields are part of the public API.
+    -->
+    <Match>
+	<Class name="javax.mail.Service"/>
+	<Field name="debug"/>
+	<Bug pattern="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD"/>
+    </Match>
+    <Match>
+	<Class name="javax.mail.Quota$Resource"/>
+	<Field name="usage"/>
+	<Bug pattern="URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD"/>
+    </Match>
+
+    <!--
+	We use a static MailDateFormat instance, which uses a static
+	GregorianCalendar instance.  The MailDateFormat.parse method
+	uses only local data except for the GregorianCalendar instance,
+	which is invoked through a static synchronized method.
+    -->
+    <Match>
+	<Class name="com.sun.mail.imap.protocol.ENVELOPE"/>
+	<Bug pattern="STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCE"/>
+    </Match>
+
+    <!--
+	A special String instance is used to indicate that the POP3
+	UID is unknown.  The uid field is initialized to this String
+	instance, which we then compare with later to see if the uid
+	has been set.
+    -->
+    <Match>
+	<Class name="com.sun.mail.pop3.POP3Folder"/>
+	<Method name="getUID"/>
+	<Bug pattern="ES_COMPARING_STRINGS_WITH_EQ"/>
+    </Match>
+    <!--
+	As above, a special String instance is used to indicate whether
+	certain fields have been set.
+    -->
+    <Match>
+	<Class name="com.sun.mail.smtp.SMTPTransport"/>
+	<Or>
+	    <Method name="getAuthorizationId"/>
+	    <Method name="getNTLMDomain"/>
+	    <Method name="getSASLRealm"/>
+	</Or>
+	<Bug pattern="ES_COMPARING_STRINGS_WITH_EQ"/>
+    </Match>
+
+    <!--
+	I know Exception.toString is never supposed to return null,
+	but this extra check is cheap insurance.
+    -->
+    <Match>
+	<Class name="javax.mail.MessagingException"/>
+	<Method name="toString"/>
+	<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
+    </Match>
+    <!--
+	I know ClassLoader.getResources is never supposed to return null,
+	but this extra check is cheap insurance.
+    -->
+    <Match>
+	<Class name="~javax\.mail\.Session\$.*"/>
+	<Method name="run"/>
+	<Bug pattern="RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE"/>
+    </Match>
+
+    <!--
+	These names may be confusing, but we're stuck with them.
+    -->
+    <Match>
+	<Class name="javax.mail.PasswordAuthentication"/>
+	<Method name="getUserName"/>
+	<Bug pattern="NM_CONFUSING"/>
+    </Match>
+
+    <!--
+	This trivial inner class extends ArrayList, but is never serialized,
+	so it doesn't really need a serialVersionUID.
+    -->
+    <Match>
+	<Class name="javax.mail.internet.ParameterList$MultiValue"/>
+	<Bug pattern="SE_NO_SERIALVERSIONID"/>
+    </Match>
+
+    <!--
+	These array references are declared volatile because they're
+	treated as copy-on-write.
+    -->
+    <Match>
+	<Class name="com.sun.mail.util.logging.LogManagerProperties"/>
+	<Field name="REFLECT_NAMES"/>
+	<Bug pattern="VO_VOLATILE_REFERENCE_TO_ARRAY"/>
+    </Match>
+    <Match>
+	<Class name="com.sun.mail.util.logging.MailHandler"/>
+	<Field name="attachmentFilters"/>
+	<Bug pattern="VO_VOLATILE_REFERENCE_TO_ARRAY"/>
+    </Match>
+
+    <!--
+	The Authenticator fields are only accessible by subclasses,
+	which are only called through the one synchronized public
+	method, assuming the subclass doesn't do something crazy and
+	expose them itself.
+    -->
+    <Match>
+	<Class name="javax.mail.Authenticator"/>
+	<Bug pattern="IS2_INCONSISTENT_SYNC"/>
+    </Match>
+
+    <!--
+	Ignore failures to close connection.
+    -->
+    <Match>
+	<Class name="com.sun.mail.smtp.SMTPTransport"/>
+	<Method name="protocolConnect"/>
+	<Bug pattern="DE_MIGHT_IGNORE"/>
+    </Match>
+
+    <!--
+	This inner class doesn't need "this", but it creates another
+	inner class that *does* need "this".
+    -->
+    <Match>
+	<Class name="com.sun.mail.smtp.SMTPTransport$BDATOutputStream"/>
+	<Bug pattern="SIC_INNER_SHOULD_BE_STATIC_NEEDS_THIS"/>
+    </Match>
+</FindBugsFilter>
diff --git a/mail/pom.xml b/mail/pom.xml
new file mode 100644
index 0000000..1020ed0
--- /dev/null
+++ b/mail/pom.xml
@@ -0,0 +1,214 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>jakarta.mail</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API</name>
+
+    <properties>
+	<mail.extensionName>
+	    jakarta.mail
+	</mail.extensionName>
+	<mail.moduleName>
+	    jakarta.mail
+	</mail.moduleName>
+	<mail.specificationTitle>
+	    JavaMail(TM) API Design Specification
+	</mail.specificationTitle>
+	<mail.implementationTitle>
+	    javax.mail
+	</mail.implementationTitle>
+	<mail.packages.export>
+	    javax.mail.*; version=${mail.spec.version},
+	    com.sun.mail.imap; version=${mail.osgiversion},
+	    com.sun.mail.imap.protocol; version=${mail.osgiversion},
+	    com.sun.mail.iap; version=${mail.osgiversion},
+	    com.sun.mail.pop3; version=${mail.osgiversion},
+	    com.sun.mail.smtp; version=${mail.osgiversion},
+	    com.sun.mail.util; version=${mail.osgiversion},
+	    com.sun.mail.util.logging; version=${mail.osgiversion},
+	    com.sun.mail.handlers; version=${mail.osgiversion}
+	</mail.packages.export>
+	<mail.probeFile>
+	    META-INF/gfprobe-provider.xml
+	</mail.probeFile>
+	<findbugs.skip>
+	    false
+	</findbugs.skip>
+	<findbugs.exclude>
+	    ${project.basedir}/exclude.xml
+	</findbugs.exclude>
+    </properties>
+
+    <profiles>
+	<!--
+	    A special profile for compiling with the real JDK 1.7 compiler.
+	    Exclude MailSessionDefinition and MailSessionDefinitions since
+	    the former requires JDK 1.8 and the latter depends on the former.
+	-->
+	<profile>
+	    <id>1.7</id>
+	    <build>
+		<plugins>
+		    <plugin>
+			<artifactId>maven-compiler-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>default-compile</id>
+				<configuration>
+				    <excludes>
+					<exclude>
+					  javax/mail/MailSessionDefinition.java
+					</exclude>
+					<exclude>
+					  javax/mail/MailSessionDefinitions.java
+					</exclude>
+					<exclude>
+					  module-info.java
+					</exclude>
+				    </excludes>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		</plugins>
+	    </build>
+	</profile>
+
+	<!--
+	    A special profile used when compiling with the real JDK 9 compiler.
+	    Override the release setting from the parent's "9" profile so
+	    that we produce JDK 1.7 compatible class files.
+	-->
+	<profile>
+	    <id>9</id>
+	    <activation>
+		<jdk>9</jdk>
+	    </activation>
+	    <build>
+		<plugins>
+		    <plugin>
+			<artifactId>maven-compiler-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>default-compile</id>
+				<configuration>
+				    <release combine.self="override"></release>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		</plugins>
+	    </build>
+	</profile>
+    </profiles>
+
+    <build>
+	<resources>
+	    <resource>
+		<directory>src/main/resources</directory>
+		<filtering>true</filtering>
+	    </resource>
+	</resources>
+	<plugins>
+	    <!--
+		Configure compiler plugin to print lint warnings.
+		Need to exclude options warning because bootstrap
+		classpath isn't set (on purpose).
+		Need to exclude path warnings because Maven includes
+		source directories that don't exist (e.g., generated-sources).
+	    -->
+	    <plugin>
+		<artifactId>maven-compiler-plugin</artifactId>
+		<configuration>
+		    <source>1.7</source>
+		    <target>1.7</target>
+		    <compilerArgs>
+			<arg>-Xlint</arg>
+			<arg>-Xlint:-options</arg>
+			<arg>-Xlint:-path</arg>
+			<arg>-Werror</arg>
+		    </compilerArgs>
+		    <showDeprecation>true</showDeprecation>
+		    <showWarnings>true</showWarnings>
+		    <excludes>
+			<exclude>
+			  module-info.java
+			</exclude>
+		    </excludes>
+		</configuration>
+	    </plugin>
+
+	    <!--
+		Configure test plugin to find *TestSuite classes.
+	    -->
+	    <plugin>
+		<groupId>org.apache.maven.plugins</groupId>
+		<artifactId>maven-surefire-plugin</artifactId>
+		<configuration>
+		    <includes>
+			<include>**/*Test.java</include>
+			<include>**/*TestSuite.java</include>
+		    </includes>
+		</configuration>
+	    </plugin>
+
+	    <!--
+		Add the Automatic-Module-Name manifest header for JDK 9.
+	    -->
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<configuration>
+		    <archive>
+			<manifestEntries>
+			    <Automatic-Module-Name>
+				${mail.moduleName}
+			    </Automatic-Module-Name>
+			</manifestEntries>
+		    </archive>
+		</configuration>
+	    </plugin>
+	</plugins>
+    </build>
+
+    <dependencies>
+	<dependency>
+	    <groupId>com.sun.activation</groupId>
+	    <artifactId>jakarta.activation</artifactId>
+	</dependency>
+	<dependency>
+	    <groupId>junit</groupId>
+	    <artifactId>junit</artifactId>
+	    <version>4.12</version>
+	    <scope>test</scope>
+	    <optional>true</optional>
+	</dependency>
+    </dependencies>
+</project>
diff --git a/mail/src/main/java/com/sun/mail/auth/MD4.java b/mail/src/main/java/com/sun/mail/auth/MD4.java
new file mode 100644
index 0000000..4b98b90
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/auth/MD4.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 2005, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/*
+ * Copied from OpenJDK with permission.
+ */
+
+package com.sun.mail.auth;
+
+import java.security.*;
+
+//import static sun.security.provider.ByteArrayAccess.*;
+
+/**
+ * The MD4 class is used to compute an MD4 message digest over a given
+ * buffer of bytes. It is an implementation of the RSA Data Security Inc
+ * MD4 algorithim as described in internet RFC 1320.
+ *
+ * @author      Andreas Sterbenz
+ * @author      Bill Shannon (adapted for JavaMail)
+ */
+public final class MD4 {
+
+    // state of this object
+    private final int[] state;
+    // temporary buffer, used by implCompress()
+    private final int[] x;
+
+    // size of the input to the compression function in bytes
+    private static final int blockSize = 64;
+
+    // buffer to store partial blocks, blockSize bytes large
+    private final byte[] buffer = new byte[blockSize];
+    // offset into buffer
+    private int bufOfs;
+
+    // number of bytes processed so far.
+    // also used as a flag to indicate reset status
+    // -1: need to call engineReset() before next call to update()
+    //  0: is already reset
+    private long bytesProcessed;
+
+    // rotation constants
+    private static final int S11 = 3;
+    private static final int S12 = 7;
+    private static final int S13 = 11;
+    private static final int S14 = 19;
+    private static final int S21 = 3;
+    private static final int S22 = 5;
+    private static final int S23 = 9;
+    private static final int S24 = 13;
+    private static final int S31 = 3;
+    private static final int S32 = 9;
+    private static final int S33 = 11;
+    private static final int S34 = 15;
+
+    private static final byte[] padding;
+
+    static {
+        padding = new byte[136];
+        padding[0] = (byte)0x80;
+    }
+
+    /**
+     * Standard constructor, creates a new MD4 instance.
+     */
+    public MD4() {
+        state = new int[4];
+        x = new int[16];
+        implReset();
+    }
+
+    /**
+     * Compute and return the message digest of the input byte array.
+     *
+     * @param	in	the input byte array
+     * @return	the message digest byte array
+     */
+    public byte[] digest(byte[] in) {
+	implReset();
+	engineUpdate(in, 0, in.length);
+	byte[] out = new byte[16];
+	implDigest(out, 0);
+	return out;
+    }
+
+    /**
+     * Reset the state of this object.
+     */
+    private void implReset() {
+        // Load magic initialization constants.
+        state[0] = 0x67452301;
+        state[1] = 0xefcdab89;
+        state[2] = 0x98badcfe;
+        state[3] = 0x10325476;
+        bufOfs = 0;
+        bytesProcessed = 0;
+    }
+
+    /**
+     * Perform the final computations, any buffered bytes are added
+     * to the digest, the count is added to the digest, and the resulting
+     * digest is stored.
+     */
+    private void implDigest(byte[] out, int ofs) {
+        long bitsProcessed = bytesProcessed << 3;
+
+        int index = (int)bytesProcessed & 0x3f;
+        int padLen = (index < 56) ? (56 - index) : (120 - index);
+        engineUpdate(padding, 0, padLen);
+
+        //i2bLittle4((int)bitsProcessed, buffer, 56);
+        //i2bLittle4((int)(bitsProcessed >>> 32), buffer, 60);
+	buffer[56] = (byte)bitsProcessed;
+	buffer[57] = (byte)(bitsProcessed>>8);
+	buffer[58] = (byte)(bitsProcessed>>16);
+	buffer[59] = (byte)(bitsProcessed>>24);
+	buffer[60] = (byte)(bitsProcessed>>32);
+	buffer[61] = (byte)(bitsProcessed>>40);
+	buffer[62] = (byte)(bitsProcessed>>48);
+	buffer[63] = (byte)(bitsProcessed>>56);
+        implCompress(buffer, 0);
+
+        //i2bLittle(state, 0, out, ofs, 16);
+	for (int i = 0; i < state.length; i++) {
+	    int x = state[i];
+	    out[ofs++] = (byte)x;
+	    out[ofs++] = (byte)(x>>8);
+	    out[ofs++] = (byte)(x>>16);
+	    out[ofs++] = (byte)(x>>24);
+	}
+    }
+
+    private void engineUpdate(byte[] b, int ofs, int len) {
+        if (len == 0) {
+            return;
+        }
+        if ((ofs < 0) || (len < 0) || (ofs > b.length - len)) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        if (bytesProcessed < 0) {
+            implReset();
+        }
+        bytesProcessed += len;
+        // if buffer is not empty, we need to fill it before proceeding
+        if (bufOfs != 0) {
+            int n = Math.min(len, blockSize - bufOfs);
+            System.arraycopy(b, ofs, buffer, bufOfs, n);
+            bufOfs += n;
+            ofs += n;
+            len -= n;
+            if (bufOfs >= blockSize) {
+                // compress completed block now
+                implCompress(buffer, 0);
+                bufOfs = 0;
+            }
+        }
+        // compress complete blocks
+        while (len >= blockSize) {
+            implCompress(b, ofs);
+            len -= blockSize;
+            ofs += blockSize;
+        }
+        // copy remainder to buffer
+        if (len > 0) {
+            System.arraycopy(b, ofs, buffer, 0, len);
+            bufOfs = len;
+        }
+    }
+
+    private static int FF(int a, int b, int c, int d, int x, int s) {
+        a += ((b & c) | ((~b) & d)) + x;
+        return ((a << s) | (a >>> (32 - s)));
+    }
+
+    private static int GG(int a, int b, int c, int d, int x, int s) {
+        a += ((b & c) | (b & d) | (c & d)) + x + 0x5a827999;
+        return ((a << s) | (a >>> (32 - s)));
+    }
+
+    private static int HH(int a, int b, int c, int d, int x, int s) {
+        a += ((b ^ c) ^ d) + x + 0x6ed9eba1;
+        return ((a << s) | (a >>> (32 - s)));
+    }
+
+    /**
+     * This is where the functions come together as the generic MD4
+     * transformation operation. It consumes 64
+     * bytes from the buffer, beginning at the specified offset.
+     */
+    private void implCompress(byte[] buf, int ofs) {
+        //b2iLittle64(buf, ofs, x);
+	for (int xfs = 0; xfs < x.length; xfs++) {
+	    x[xfs] = (buf[ofs] & 0xff) | ((buf[ofs+1] & 0xff) << 8) |
+		((buf[ofs+2] & 0xff) << 16) | ((buf[ofs+3] & 0xff) << 24);
+	    ofs += 4;
+	}
+
+        int a = state[0];
+        int b = state[1];
+        int c = state[2];
+        int d = state[3];
+
+        /* Round 1 */
+        a = FF (a, b, c, d, x[ 0], S11); /* 1 */
+        d = FF (d, a, b, c, x[ 1], S12); /* 2 */
+        c = FF (c, d, a, b, x[ 2], S13); /* 3 */
+        b = FF (b, c, d, a, x[ 3], S14); /* 4 */
+        a = FF (a, b, c, d, x[ 4], S11); /* 5 */
+        d = FF (d, a, b, c, x[ 5], S12); /* 6 */
+        c = FF (c, d, a, b, x[ 6], S13); /* 7 */
+        b = FF (b, c, d, a, x[ 7], S14); /* 8 */
+        a = FF (a, b, c, d, x[ 8], S11); /* 9 */
+        d = FF (d, a, b, c, x[ 9], S12); /* 10 */
+        c = FF (c, d, a, b, x[10], S13); /* 11 */
+        b = FF (b, c, d, a, x[11], S14); /* 12 */
+        a = FF (a, b, c, d, x[12], S11); /* 13 */
+        d = FF (d, a, b, c, x[13], S12); /* 14 */
+        c = FF (c, d, a, b, x[14], S13); /* 15 */
+        b = FF (b, c, d, a, x[15], S14); /* 16 */
+
+        /* Round 2 */
+        a = GG (a, b, c, d, x[ 0], S21); /* 17 */
+        d = GG (d, a, b, c, x[ 4], S22); /* 18 */
+        c = GG (c, d, a, b, x[ 8], S23); /* 19 */
+        b = GG (b, c, d, a, x[12], S24); /* 20 */
+        a = GG (a, b, c, d, x[ 1], S21); /* 21 */
+        d = GG (d, a, b, c, x[ 5], S22); /* 22 */
+        c = GG (c, d, a, b, x[ 9], S23); /* 23 */
+        b = GG (b, c, d, a, x[13], S24); /* 24 */
+        a = GG (a, b, c, d, x[ 2], S21); /* 25 */
+        d = GG (d, a, b, c, x[ 6], S22); /* 26 */
+        c = GG (c, d, a, b, x[10], S23); /* 27 */
+        b = GG (b, c, d, a, x[14], S24); /* 28 */
+        a = GG (a, b, c, d, x[ 3], S21); /* 29 */
+        d = GG (d, a, b, c, x[ 7], S22); /* 30 */
+        c = GG (c, d, a, b, x[11], S23); /* 31 */
+        b = GG (b, c, d, a, x[15], S24); /* 32 */
+
+        /* Round 3 */
+        a = HH (a, b, c, d, x[ 0], S31); /* 33 */
+        d = HH (d, a, b, c, x[ 8], S32); /* 34 */
+        c = HH (c, d, a, b, x[ 4], S33); /* 35 */
+        b = HH (b, c, d, a, x[12], S34); /* 36 */
+        a = HH (a, b, c, d, x[ 2], S31); /* 37 */
+        d = HH (d, a, b, c, x[10], S32); /* 38 */
+        c = HH (c, d, a, b, x[ 6], S33); /* 39 */
+        b = HH (b, c, d, a, x[14], S34); /* 40 */
+        a = HH (a, b, c, d, x[ 1], S31); /* 41 */
+        d = HH (d, a, b, c, x[ 9], S32); /* 42 */
+        c = HH (c, d, a, b, x[ 5], S33); /* 43 */
+        b = HH (b, c, d, a, x[13], S34); /* 44 */
+        a = HH (a, b, c, d, x[ 3], S31); /* 45 */
+        d = HH (d, a, b, c, x[11], S32); /* 46 */
+        c = HH (c, d, a, b, x[ 7], S33); /* 47 */
+        b = HH (b, c, d, a, x[15], S34); /* 48 */
+
+        state[0] += a;
+        state[1] += b;
+        state[2] += c;
+        state[3] += d;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/auth/Ntlm.java b/mail/src/main/java/com/sun/mail/auth/Ntlm.java
new file mode 100644
index 0000000..46a05a4
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/auth/Ntlm.java
@@ -0,0 +1,343 @@
+/*
+ * Copyright (c) 2005, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+/*
+ * Copied from OpenJDK with permission.
+ */
+
+package com.sun.mail.auth;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.io.PrintStream;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Locale;
+import java.util.logging.Level;
+import javax.crypto.Cipher;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.DESKeySpec;
+
+import com.sun.mail.util.BASE64DecoderStream;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.MailLogger;
+
+
+/**
+ * NTLMAuthentication:
+ *
+ * @author Michael McMahon
+ * @author Bill Shannon (adapted for JavaMail)
+ */
+public class Ntlm {
+
+    private byte[] type1;
+    private byte[] type3;
+
+    private SecretKeyFactory fac;
+    private Cipher cipher;
+    private MD4 md4;
+    private String hostname;
+    private String ntdomain;
+    private String username;
+    private String password;
+
+    private MailLogger logger;
+
+    private void init0() {
+        type1 = new byte[256];
+        type3 = new byte[256];
+        System.arraycopy(new byte[] {'N','T','L','M','S','S','P',0,1}, 0,
+			    type1, 0, 9);
+        type1[12] = (byte) 3;
+        type1[13] = (byte) 0xb2;
+        type1[28] = (byte) 0x20;
+        System.arraycopy(new byte[] {'N','T','L','M','S','S','P',0,3}, 0,
+			    type3, 0, 9);
+        type3[12] = (byte) 0x18;
+        type3[14] = (byte) 0x18;
+        type3[20] = (byte) 0x18;
+        type3[22] = (byte) 0x18;
+        type3[32] = (byte) 0x40;
+        type3[60] = (byte) 1;
+        type3[61] = (byte) 0x82;
+
+        try {
+            fac = SecretKeyFactory.getInstance("DES");
+            cipher = Cipher.getInstance("DES/ECB/NoPadding");
+            md4 = new MD4();
+        } catch (NoSuchPaddingException e) {
+            assert false;
+        } catch (NoSuchAlgorithmException e) {
+            assert false;
+        }
+    };
+
+    /**
+     * Create an NTLM authenticator.
+     * Username may be specified as domain\\username in the Authenticator.
+     * If this notation is not used, then the domain will be taken
+     * from the ntdomain parameter.
+     *
+     * @param	ntdomain	the NT domain
+     * @param	hostname	the host name
+     * @param	username	the user name
+     * @param	password	the password
+     * @param	logger		the MailLogger
+     */
+    public Ntlm(String ntdomain, String hostname, String username,
+				String password, MailLogger logger) {
+	int i = hostname.indexOf('.');
+	if (i != -1) {
+	    hostname = hostname.substring(0, i);
+	}
+        i = username.indexOf('\\');
+        if (i != -1) {
+            ntdomain = username.substring(0, i).toUpperCase(Locale.ENGLISH);
+            username = username.substring(i+1);
+        } else if (ntdomain == null) {
+	    ntdomain = "";
+	}
+	this.ntdomain = ntdomain;
+	this.hostname = hostname;
+	this.username = username;
+	this.password = password;
+	this.logger = logger.getLogger(this.getClass(), "DEBUG NTLM");
+        init0();
+    }
+
+    private void copybytes(byte[] dest, int destpos, String src, String enc) {
+        try {
+            byte[] x = src.getBytes(enc);
+            System.arraycopy(x, 0, dest, destpos, x.length);
+        } catch (UnsupportedEncodingException e) {
+            assert false;
+        }
+    }
+
+    public String generateType1Msg(int flags) {
+	// XXX - should set "flags" in generated message
+        int dlen = ntdomain.length();
+        type1[16]= (byte) (dlen % 256);
+        type1[17]= (byte) (dlen / 256);
+        type1[18] = type1[16];
+        type1[19] = type1[17];
+	if (dlen == 0)
+	    type1[13] &= ~0x10;
+
+        int hlen = hostname.length();
+        type1[24]= (byte) (hlen % 256);
+        type1[25]= (byte) (hlen / 256);
+        type1[26] = type1[24];
+        type1[27] = type1[25];
+
+        copybytes(type1, 32, hostname, "iso-8859-1");
+        copybytes(type1, hlen+32, ntdomain, "iso-8859-1");
+        type1[20] = (byte) ((hlen+32) % 256);
+        type1[21] = (byte) ((hlen+32) / 256);
+
+        byte[] msg = new byte[32 + hlen + dlen];
+        System.arraycopy(type1, 0, msg, 0, 32 + hlen + dlen);
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("type 1 message: " + toHex(msg));
+
+        String result = null;
+	try {
+	    result = new String(BASE64EncoderStream.encode(msg), "iso-8859-1");
+        } catch (UnsupportedEncodingException e) {
+            assert false;
+        }
+        return result;
+    }
+
+    /**
+     * Convert a 7 byte array to an 8 byte array (for a des key with parity).
+     * Input starts at offset off.
+     */
+    private byte[] makeDesKey(byte[] input, int off) {
+        int[] in = new int[input.length];
+        for (int i = 0; i < in.length; i++) {
+            in[i] = input[i] < 0 ? input[i] + 256: input[i];
+        }
+        byte[] out = new byte[8];
+        out[0] = (byte)in[off+0];
+        out[1] = (byte)(((in[off+0] << 7) & 0xFF) | (in[off+1] >> 1));
+        out[2] = (byte)(((in[off+1] << 6) & 0xFF) | (in[off+2] >> 2));
+        out[3] = (byte)(((in[off+2] << 5) & 0xFF) | (in[off+3] >> 3));
+        out[4] = (byte)(((in[off+3] << 4) & 0xFF) | (in[off+4] >> 4));
+        out[5] = (byte)(((in[off+4] << 3) & 0xFF) | (in[off+5] >> 5));
+        out[6] = (byte)(((in[off+5] << 2) & 0xFF) | (in[off+6] >> 6));
+        out[7] = (byte)((in[off+6] << 1) & 0xFF);
+        return out;
+    }
+
+    private byte[] calcLMHash() throws GeneralSecurityException {
+        byte[] magic = {0x4b, 0x47, 0x53, 0x21, 0x40, 0x23, 0x24, 0x25};
+        byte[] pwb = null;
+	try {
+	    pwb = password.toUpperCase(Locale.ENGLISH).getBytes("iso-8859-1");
+	} catch (UnsupportedEncodingException ex) {
+	    // should never happen
+	    assert false;
+	}
+        byte[] pwb1 = new byte[14];
+        int len = password.length();
+        if (len > 14)
+            len = 14;
+        System.arraycopy(pwb, 0, pwb1, 0, len); /* Zero padded */
+
+        DESKeySpec dks1 = new DESKeySpec(makeDesKey(pwb1, 0));
+        DESKeySpec dks2 = new DESKeySpec(makeDesKey(pwb1, 7));
+
+        SecretKey key1 = fac.generateSecret(dks1);
+        SecretKey key2 = fac.generateSecret(dks2);
+        cipher.init(Cipher.ENCRYPT_MODE, key1);
+        byte[] out1 = cipher.doFinal(magic, 0, 8);
+        cipher.init(Cipher.ENCRYPT_MODE, key2);
+        byte[] out2 = cipher.doFinal(magic, 0, 8);
+
+        byte[] result = new byte [21];
+        System.arraycopy(out1, 0, result, 0, 8);
+        System.arraycopy(out2, 0, result, 8, 8);
+        return result;
+    }
+
+    private byte[] calcNTHash() throws GeneralSecurityException {
+        byte[] pw = null;
+        try {
+            pw = password.getBytes("UnicodeLittleUnmarked");
+        } catch (UnsupportedEncodingException e) {
+            assert false;
+        }
+        byte[] out = md4.digest(pw);
+        byte[] result = new byte[21];
+        System.arraycopy(out, 0, result, 0, 16);
+        return result;
+    }
+
+    /*
+     * Key is a 21 byte array.  Split it into 3 7 byte chunks,
+     * convert each to 8 byte DES keys, encrypt the text arg with
+     * each key and return the three results in a sequential [].
+     */
+    private byte[] calcResponse(byte[] key, byte[] text)
+				throws GeneralSecurityException {
+        assert key.length == 21;
+        DESKeySpec dks1 = new DESKeySpec(makeDesKey(key, 0));
+        DESKeySpec dks2 = new DESKeySpec(makeDesKey(key, 7));
+        DESKeySpec dks3 = new DESKeySpec(makeDesKey(key, 14));
+        SecretKey key1 = fac.generateSecret(dks1);
+        SecretKey key2 = fac.generateSecret(dks2);
+        SecretKey key3 = fac.generateSecret(dks3);
+        cipher.init(Cipher.ENCRYPT_MODE, key1);
+        byte[] out1 = cipher.doFinal(text, 0, 8);
+        cipher.init(Cipher.ENCRYPT_MODE, key2);
+        byte[] out2 = cipher.doFinal(text, 0, 8);
+        cipher.init(Cipher.ENCRYPT_MODE, key3);
+        byte[] out3 = cipher.doFinal(text, 0, 8);
+        byte[] result = new byte[24];
+        System.arraycopy(out1, 0, result, 0, 8);
+        System.arraycopy(out2, 0, result, 8, 8);
+        System.arraycopy(out3, 0, result, 16, 8);
+        return result;
+    }
+
+    public String generateType3Msg(String challenge) {
+	try {
+        /* First decode the type2 message to get the server nonce */
+        /* nonce is located at type2[24] for 8 bytes */
+
+        byte[] type2 = null;
+	try {
+	    type2 = BASE64DecoderStream.decode(challenge.getBytes("us-ascii"));
+	} catch (UnsupportedEncodingException ex) {
+	    // should never happen
+	    assert false;
+	}
+        byte[] nonce = new byte[8];
+        System.arraycopy(type2, 24, nonce, 0, 8);
+
+        int ulen = username.length()*2;
+        type3[36] = type3[38] = (byte) (ulen % 256);
+        type3[37] = type3[39] = (byte) (ulen / 256);
+        int dlen = ntdomain.length()*2;
+        type3[28] = type3[30] = (byte) (dlen % 256);
+        type3[29] = type3[31] = (byte) (dlen / 256);
+        int hlen = hostname.length()*2;
+        type3[44] = type3[46] = (byte) (hlen % 256);
+        type3[45] = type3[47] = (byte) (hlen / 256);
+
+        int l = 64;
+        copybytes(type3, l, ntdomain, "UnicodeLittleUnmarked");
+        type3[32] = (byte) (l % 256);
+        type3[33] = (byte) (l / 256);
+        l += dlen;
+        copybytes(type3, l, username, "UnicodeLittleUnmarked");
+        type3[40] = (byte) (l % 256);
+        type3[41] = (byte) (l / 256);
+        l += ulen;
+        copybytes(type3, l, hostname, "UnicodeLittleUnmarked");
+        type3[48] = (byte) (l % 256);
+        type3[49] = (byte) (l / 256);
+        l += hlen;
+
+        byte[] lmhash = calcLMHash();
+        byte[] lmresponse = calcResponse(lmhash, nonce);
+        byte[] nthash = calcNTHash();
+        byte[] ntresponse = calcResponse(nthash, nonce);
+        System.arraycopy(lmresponse, 0, type3, l, 24);
+        type3[16] = (byte) (l % 256);
+        type3[17] = (byte) (l / 256);
+        l += 24;
+        System.arraycopy(ntresponse, 0, type3, l, 24);
+        type3[24] = (byte) (l % 256);
+        type3[25] = (byte) (l / 256);
+        l += 24;
+        type3[56] = (byte) (l % 256);
+        type3[57] = (byte) (l / 256);
+
+        byte[] msg = new byte[l];
+        System.arraycopy(type3, 0, msg, 0, l);
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("type 3 message: " + toHex(msg));
+
+        String result = null;
+	try {
+	    result = new String(BASE64EncoderStream.encode(msg), "iso-8859-1");
+        } catch (UnsupportedEncodingException e) {
+            assert false;
+        }
+        return result;
+
+	} catch (GeneralSecurityException ex) {
+	    // should never happen
+	    logger.log(Level.FINE, "GeneralSecurityException", ex);
+	    return "";	// will fail later
+	}
+    }
+
+    private static char[] hex =
+	{ '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
+
+    private static String toHex(byte[] b) {
+	StringBuilder sb = new StringBuilder(b.length * 3);
+	for (int i = 0; i < b.length; i++)
+	    sb.append(hex[(b[i]>>4)&0xF]).append(hex[b[i]&0xF]).append(' ');
+	return sb.toString();
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/auth/OAuth2SaslClient.java b/mail/src/main/java/com/sun/mail/auth/OAuth2SaslClient.java
new file mode 100644
index 0000000..2ec95d4
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/auth/OAuth2SaslClient.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.auth;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Map;
+import java.security.Provider;
+import java.security.Security;
+import javax.security.sasl.*;
+import javax.security.auth.callback.*;
+
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * JavaMail SASL client for OAUTH2.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc6749">
+ *	RFC 6749 - OAuth 2.0 Authorization Framework</a>
+ * @see <a href="http://tools.ietf.org/html/rfc6750">
+ *	RFC 6750 - OAuth 2.0 Authorization Framework: Bearer Token Usage</a>
+ * @author Bill Shannon
+ */
+public class OAuth2SaslClient implements SaslClient {
+    private CallbackHandler cbh;
+    //private Map<String,?> props;	// XXX - not currently used
+    private boolean complete = false;
+
+    public OAuth2SaslClient(Map<String,?> props, CallbackHandler cbh) {
+	//this.props = props;
+	this.cbh = cbh;
+    }
+
+    @Override
+    public String getMechanismName() {
+	return "XOAUTH2";
+    }
+
+    @Override
+    public boolean hasInitialResponse() {
+	return true;
+    }
+
+    @Override
+    public byte[] evaluateChallenge(byte[] challenge) throws SaslException {
+	if (complete)
+	    return new byte[0];
+
+	NameCallback ncb = new NameCallback("User name:");
+	PasswordCallback pcb = new PasswordCallback("OAuth token:", false);
+	try {
+	    cbh.handle(new Callback[] { ncb, pcb });
+	} catch (UnsupportedCallbackException ex) {
+	    throw new SaslException("Unsupported callback", ex);
+	} catch (IOException ex) {
+	    throw new SaslException("Callback handler failed", ex);
+	}
+
+	/*
+	 * The OAuth token isn't really a password, and JavaMail doesn't
+	 * use char[] for passwords, so we don't worry about storing the
+	 * token in strings.
+	 */
+	String user = ncb.getName();
+	String token = new String(pcb.getPassword());
+	pcb.clearPassword();
+	String resp = "user=" + user + "\001auth=Bearer " + token + "\001\001";
+	byte[] response;
+	try {
+	    response = resp.getBytes("utf-8");
+	} catch (UnsupportedEncodingException ex) {
+	    // fall back to ASCII
+	    response = ASCIIUtility.getBytes(resp);
+	}
+	complete = true;
+	return response;
+    }
+
+    @Override
+    public boolean isComplete() {
+	return complete;
+    }
+
+    @Override
+    public byte[] unwrap(byte[] incoming, int offset, int len)
+				throws SaslException {
+	throw new IllegalStateException("OAUTH2 unwrap not supported");
+    }
+
+    @Override
+    public byte[] wrap(byte[] outgoing, int offset, int len)
+				throws SaslException {
+	throw new IllegalStateException("OAUTH2 wrap not supported");
+    }
+
+    @Override
+    public Object getNegotiatedProperty(String propName) {
+	if (!complete)
+	    throw new IllegalStateException("OAUTH2 getNegotiatedProperty");
+	return null;
+    }
+
+    @Override
+    public void dispose() throws SaslException {
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/auth/OAuth2SaslClientFactory.java b/mail/src/main/java/com/sun/mail/auth/OAuth2SaslClientFactory.java
new file mode 100644
index 0000000..8450391
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/auth/OAuth2SaslClientFactory.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.auth;
+
+import java.util.*;
+import java.security.Provider;
+import java.security.Security;
+import javax.security.sasl.*;
+import javax.security.auth.callback.*;
+
+/**
+ * JavaMail SASL client factory for OAUTH2.
+ *
+ * @author Bill Shannon
+ */
+public class OAuth2SaslClientFactory implements SaslClientFactory {
+
+    private static final String PROVIDER_NAME = "JavaMail-OAuth2";
+    private static final String MECHANISM_NAME = "SaslClientFactory.XOAUTH2";
+
+    static class OAuth2Provider extends Provider {
+	private static final long serialVersionUID = -5371795551562287059L;
+
+	public OAuth2Provider() {
+	    super(PROVIDER_NAME, 1.0, "XOAUTH2 SASL Mechanism");
+	    put(MECHANISM_NAME, OAuth2SaslClientFactory.class.getName());
+	}
+    }
+
+    @Override
+    public SaslClient createSaslClient(String[] mechanisms,
+				String authorizationId, String protocol,
+				String serverName, Map<String,?> props,
+				CallbackHandler cbh) throws SaslException {
+	for (String m : mechanisms) {
+	    if (m.equals("XOAUTH2"))
+		return new OAuth2SaslClient(props, cbh);
+	}
+	return null;
+    }
+
+    @Override
+    public String[] getMechanismNames(Map<String,?> props) {
+	return new String[] { "XOAUTH2" };
+    }
+
+    /**
+     * Initialize this OAUTH2 provider, but only if there isn't one already.
+     * If we're not allowed to add this provider, just give up silently.
+     */
+    public static void init() {
+	try {
+	    if (Security.getProvider(PROVIDER_NAME) == null)
+		Security.addProvider(new OAuth2Provider());
+	} catch (SecurityException ex) {
+	    // oh well...
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/auth/package.html b/mail/src/main/java/com/sun/mail/auth/package.html
new file mode 100644
index 0000000..476f2ee
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/auth/package.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.auth package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+This package includes internal authentication support classes and
+<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/com/sun/mail/handlers/handler_base.java b/mail/src/main/java/com/sun/mail/handlers/handler_base.java
new file mode 100644
index 0000000..5b4edae
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/handlers/handler_base.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import java.io.IOException;
+import java.awt.datatransfer.DataFlavor;
+import javax.activation.*;
+
+/**
+ * Base class for other DataContentHandlers.
+ */
+public abstract class handler_base implements DataContentHandler {
+
+    /**
+     * Return an array of ActivationDataFlavors that we support.
+     * Usually there will be only one.
+     *
+     * @return	array of ActivationDataFlavors that we support
+     */
+    protected abstract ActivationDataFlavor[] getDataFlavors();
+
+    /**
+     * Given the flavor that matched, return the appropriate type of object.
+     * Usually there's only one flavor so just call getContent.
+     *
+     * @param	aFlavor	the ActivationDataFlavor
+     * @param	ds	DataSource containing the data
+     * @return	the object
+     * @exception	IOException	for errors reading the data
+     */
+    protected Object getData(ActivationDataFlavor aFlavor, DataSource ds)
+				throws IOException {
+	return getContent(ds);
+    }
+
+    /**
+     * Return the DataFlavors for this <code>DataContentHandler</code>.
+     *
+     * @return The DataFlavors
+     */
+    @Override
+    public DataFlavor[] getTransferDataFlavors() {
+	ActivationDataFlavor[] adf = getDataFlavors();
+	if (adf.length == 1)	// the common case
+	    return new DataFlavor[] { adf[0] };
+	DataFlavor[] df = new DataFlavor[adf.length];
+	System.arraycopy(adf, 0, df, 0, adf.length);
+	return df;
+    }
+
+    /**
+     * Return the Transfer Data of type DataFlavor from InputStream.
+     *
+     * @param	df	The DataFlavor
+     * @param	ds	The DataSource corresponding to the data
+     * @return	the object
+     * @exception	IOException	for errors reading the data
+     */
+    @Override
+    public Object getTransferData(DataFlavor df, DataSource ds) 
+			throws IOException {
+	ActivationDataFlavor[] adf = getDataFlavors();
+	for (int i = 0; i < adf.length; i++) {
+	    // use ActivationDataFlavor.equals, which properly
+	    // ignores Content-Type parameters in comparison
+	    if (adf[i].equals(df))
+		return getData(adf[i], ds);
+	}
+	return null;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/handlers/image_gif.java b/mail/src/main/java/com/sun/mail/handlers/image_gif.java
new file mode 100644
index 0000000..0c75c8b
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/handlers/image_gif.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import java.io.*;
+import java.awt.*;
+import javax.activation.*;
+
+/**
+ * DataContentHandler for image/gif.
+ */
+public class image_gif extends handler_base {
+    private static ActivationDataFlavor[] myDF = {
+	new ActivationDataFlavor(Image.class, "image/gif", "GIF Image")
+    };
+
+    @Override
+    protected ActivationDataFlavor[] getDataFlavors() {
+	return myDF;
+    }
+
+    @Override
+    public Object getContent(DataSource ds) throws IOException {
+	InputStream is = ds.getInputStream();
+	int pos = 0;
+	int count;
+	byte buf[] = new byte[1024];
+
+	while ((count = is.read(buf, pos, buf.length - pos)) != -1) {
+	    pos += count;
+	    if (pos >= buf.length) {
+		int size = buf.length;
+		if (size < 256*1024)
+		    size += size;
+		else
+		    size += 256*1024;
+		byte tbuf[] = new byte[size];
+		System.arraycopy(buf, 0, tbuf, 0, pos);
+		buf = tbuf;
+	    }
+	}
+	Toolkit tk = Toolkit.getDefaultToolkit();
+	return tk.createImage(buf, 0, pos);
+    }
+
+    /**
+     * Write the object to the output stream, using the specified MIME type.
+     */
+    @Override
+    public void writeTo(Object obj, String type, OutputStream os)
+			throws IOException {
+	if (!(obj instanceof Image))
+	    throw new IOException("\"" + getDataFlavors()[0].getMimeType() +
+		"\" DataContentHandler requires Image object, " +
+		"was given object of type " + obj.getClass().toString());
+
+	throw new IOException(getDataFlavors()[0].getMimeType() +
+				" encoding not supported");
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/handlers/image_jpeg.java b/mail/src/main/java/com/sun/mail/handlers/image_jpeg.java
new file mode 100644
index 0000000..149d129
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/handlers/image_jpeg.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import java.awt.Image;
+import javax.activation.ActivationDataFlavor;
+
+/**
+ * DataContentHandler for image/jpeg.
+ */
+public class image_jpeg extends image_gif {
+    private static ActivationDataFlavor[] myDF = {
+	new ActivationDataFlavor(Image.class, "image/jpeg", "JPEG Image")
+    };
+
+    @Override
+    protected ActivationDataFlavor[] getDataFlavors() {
+	return myDF;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/handlers/message_rfc822.java b/mail/src/main/java/com/sun/mail/handlers/message_rfc822.java
new file mode 100644
index 0000000..07b4c81
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/handlers/message_rfc822.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import java.io.*;
+import java.util.Properties;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+
+/**
+ * @author	Christopher Cotton
+ */
+
+
+public class message_rfc822 extends handler_base {
+
+    private static ActivationDataFlavor[] ourDataFlavor = {
+	new ActivationDataFlavor(Message.class, "message/rfc822", "Message")
+    };
+
+    @Override
+    protected ActivationDataFlavor[] getDataFlavors() {
+	return ourDataFlavor;
+    }
+
+    /**
+     * Return the content.
+     */
+    @Override
+    public Object getContent(DataSource ds) throws IOException {
+	// create a new MimeMessage
+	try {
+	    Session session;
+	    if (ds instanceof MessageAware) {
+		MessageContext mc = ((MessageAware)ds).getMessageContext();
+		session = mc.getSession();
+	    } else {
+		// Hopefully a rare case.  Also hopefully the application
+		// has created a default Session that can just be returned
+		// here.  If not, the one we create here is better than
+		// nothing, but overall not a really good answer.
+		session = Session.getDefaultInstance(new Properties(), null);
+	    }
+	    return new MimeMessage(session, ds.getInputStream());
+	} catch (MessagingException me) {
+	    IOException ioex =
+		new IOException("Exception creating MimeMessage in " +
+		    "message/rfc822 DataContentHandler");
+	    ioex.initCause(me);
+	    throw ioex;
+	}
+    }
+    
+    /**
+     * Write the object as a byte stream.
+     */
+    @Override
+    public void writeTo(Object obj, String mimeType, OutputStream os) 
+			throws IOException {
+	// if the object is a message, we know how to write that out
+	if (obj instanceof Message) {
+	    Message m = (Message)obj;
+	    try {
+		m.writeTo(os);
+	    } catch (MessagingException me) {
+		IOException ioex = new IOException("Exception writing message");
+		ioex.initCause(me);
+		throw ioex;
+	    }
+	} else {
+	    throw new IOException("unsupported object");
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/handlers/multipart_mixed.java b/mail/src/main/java/com/sun/mail/handlers/multipart_mixed.java
new file mode 100644
index 0000000..2e2bd2e
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/handlers/multipart_mixed.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import java.io.*;
+import javax.activation.*;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.internet.MimeMultipart;
+
+
+public class multipart_mixed extends handler_base {
+    private static ActivationDataFlavor[] myDF = {
+	new ActivationDataFlavor(Multipart.class,
+				    "multipart/mixed", "Multipart")
+    };
+
+    @Override
+    protected ActivationDataFlavor[] getDataFlavors() {
+	return myDF;
+    }
+
+    /**
+     * Return the content.
+     */
+    @Override
+    public Object getContent(DataSource ds) throws IOException {
+	try {
+	    return new MimeMultipart(ds); 
+	} catch (MessagingException e) {
+	    IOException ioex =
+		new IOException("Exception while constructing MimeMultipart");
+	    ioex.initCause(e);
+	    throw ioex;
+	}
+    }
+    
+    /**
+     * Write the object to the output stream, using the specific MIME type.
+     */
+    @Override
+    public void writeTo(Object obj, String mimeType, OutputStream os) 
+			throws IOException {
+	if (obj instanceof Multipart) {
+	    try {
+		((Multipart)obj).writeTo(os);
+	    } catch (MessagingException e) {
+		IOException ioex =
+		    new IOException("Exception writing Multipart");
+		ioex.initCause(e);
+		throw ioex;
+	    }
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/handlers/package.html b/mail/src/main/java/com/sun/mail/handlers/package.html
new file mode 100644
index 0000000..0aafa21
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/handlers/package.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.handlers package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+This package includes internal data handler support classes and
+<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/com/sun/mail/handlers/text_html.java b/mail/src/main/java/com/sun/mail/handlers/text_html.java
new file mode 100644
index 0000000..24ef058
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/handlers/text_html.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import javax.activation.ActivationDataFlavor;
+
+/**
+ * DataContentHandler for text/html.
+ *
+ */
+public class text_html extends text_plain {
+    private static ActivationDataFlavor[] myDF = {
+	new ActivationDataFlavor(String.class, "text/html", "HTML String")
+    };
+
+    @Override
+    protected ActivationDataFlavor[] getDataFlavors() {
+	return myDF;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/handlers/text_plain.java b/mail/src/main/java/com/sun/mail/handlers/text_plain.java
new file mode 100644
index 0000000..da0beab
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/handlers/text_plain.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import java.io.*;
+import javax.activation.*;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.MimeUtility;
+
+/**
+ * DataContentHandler for text/plain.
+ *
+ */
+public class text_plain extends handler_base {
+    private static ActivationDataFlavor[] myDF = {
+	new ActivationDataFlavor(String.class, "text/plain", "Text String")
+    };
+
+    /**
+     * An OuputStream wrapper that doesn't close the underlying stream.
+     */
+    private static class NoCloseOutputStream extends FilterOutputStream {
+	public NoCloseOutputStream(OutputStream os) {
+	    super(os);
+	}
+
+	@Override
+	public void close() {
+	    // do nothing
+	}
+    }
+
+    @Override
+    protected ActivationDataFlavor[] getDataFlavors() {
+	return myDF;
+    }
+
+    @Override
+    public Object getContent(DataSource ds) throws IOException {
+	String enc = null;
+	InputStreamReader is = null;
+	
+	try {
+	    enc = getCharset(ds.getContentType());
+	    is = new InputStreamReader(ds.getInputStream(), enc);
+	} catch (IllegalArgumentException iex) {
+	    /*
+	     * An unknown charset of the form ISO-XXX-XXX will cause
+	     * the JDK to throw an IllegalArgumentException.  The
+	     * JDK will attempt to create a classname using this string,
+	     * but valid classnames must not contain the character '-',
+	     * and this results in an IllegalArgumentException, rather than
+	     * the expected UnsupportedEncodingException.  Yikes.
+	     */
+	    throw new UnsupportedEncodingException(enc);
+	}
+
+	try {
+	    int pos = 0;
+	    int count;
+	    char buf[] = new char[1024];
+
+	    while ((count = is.read(buf, pos, buf.length - pos)) != -1) {
+		pos += count;
+		if (pos >= buf.length) {
+		    int size = buf.length;
+		    if (size < 256*1024)
+			size += size;
+		    else
+			size += 256*1024;
+		    char tbuf[] = new char[size];
+		    System.arraycopy(buf, 0, tbuf, 0, pos);
+		    buf = tbuf;
+		}
+	    }
+	    return new String(buf, 0, pos);
+	} finally {
+	    try {
+		is.close();
+	    } catch (IOException ex) {
+		// ignore it
+	    }
+	}
+    }
+    
+    /**
+     * Write the object to the output stream, using the specified MIME type.
+     */
+    @Override
+    public void writeTo(Object obj, String type, OutputStream os) 
+			throws IOException {
+	if (!(obj instanceof String))
+	    throw new IOException("\"" + getDataFlavors()[0].getMimeType() +
+		"\" DataContentHandler requires String object, " +
+		"was given object of type " + obj.getClass().toString());
+
+	String enc = null;
+	OutputStreamWriter osw = null;
+
+	try {
+	    enc = getCharset(type);
+	    osw = new OutputStreamWriter(new NoCloseOutputStream(os), enc);
+	} catch (IllegalArgumentException iex) {
+	    /*
+	     * An unknown charset of the form ISO-XXX-XXX will cause
+	     * the JDK to throw an IllegalArgumentException.  The
+	     * JDK will attempt to create a classname using this string,
+	     * but valid classnames must not contain the character '-',
+	     * and this results in an IllegalArgumentException, rather than
+	     * the expected UnsupportedEncodingException.  Yikes.
+	     */
+	    throw new UnsupportedEncodingException(enc);
+	}
+
+	String s = (String)obj;
+	osw.write(s, 0, s.length());
+	/*
+	 * Have to call osw.close() instead of osw.flush() because
+	 * some charset converts, such as the iso-2022-jp converter,
+	 * don't output the "shift out" sequence unless they're closed.
+	 * The NoCloseOutputStream wrapper prevents the underlying
+	 * stream from being closed.
+	 */
+	osw.close();
+    }
+
+    private String getCharset(String type) {
+	try {
+	    ContentType ct = new ContentType(type);
+	    String charset = ct.getParameter("charset");
+	    if (charset == null)
+		// If the charset parameter is absent, use US-ASCII.
+		charset = "us-ascii";
+	    return MimeUtility.javaCharset(charset);
+	} catch (Exception ex) {
+	    return null;
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/handlers/text_xml.java b/mail/src/main/java/com/sun/mail/handlers/text_xml.java
new file mode 100644
index 0000000..1f03017
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/handlers/text_xml.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import javax.activation.ActivationDataFlavor;
+import javax.activation.DataSource;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.ParseException;
+import javax.xml.transform.Source;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.stream.StreamResult;
+import javax.xml.transform.stream.StreamSource;
+
+/**
+ * DataContentHandler for text/xml.
+ *
+ * @author Anil Vijendran
+ * @author Bill Shannon
+ */
+public class text_xml extends text_plain {
+
+    private static final ActivationDataFlavor[] flavors = {
+	new ActivationDataFlavor(String.class, "text/xml", "XML String"),
+	new ActivationDataFlavor(String.class, "application/xml", "XML String"),
+	new ActivationDataFlavor(StreamSource.class, "text/xml", "XML"),
+	new ActivationDataFlavor(StreamSource.class, "application/xml", "XML")
+    };
+
+    @Override
+    protected ActivationDataFlavor[] getDataFlavors() {
+	return flavors;
+    }
+
+    @Override
+    protected Object getData(ActivationDataFlavor aFlavor, DataSource ds)
+				throws IOException {
+	if (aFlavor.getRepresentationClass() == String.class)
+	    return super.getContent(ds);
+	else if (aFlavor.getRepresentationClass() == StreamSource.class)
+	    return new StreamSource(ds.getInputStream());
+	else
+	    return null;        // XXX - should never happen
+    }
+
+    /**
+     */
+    @Override
+    public void writeTo(Object obj, String mimeType, OutputStream os)
+				    throws IOException {
+	if (!isXmlType(mimeType))
+	    throw new IOException(
+		"Invalid content type \"" + mimeType + "\" for text/xml DCH");
+	if (obj instanceof String) {
+	    super.writeTo(obj, mimeType, os);
+	    return;
+	}
+	if (!(obj instanceof DataSource || obj instanceof Source)) {
+	     throw new IOException("Invalid Object type = "+obj.getClass()+
+		". XmlDCH can only convert DataSource or Source to XML.");
+	}
+
+	try {
+	    Transformer transformer =
+		TransformerFactory.newInstance().newTransformer();
+	    StreamResult result = new StreamResult(os);
+	    if (obj instanceof DataSource) {
+		// Streaming transform applies only to
+		// javax.xml.transform.StreamSource
+		transformer.transform(
+		    new StreamSource(((DataSource)obj).getInputStream()),
+		    result);
+	    } else {
+		transformer.transform((Source)obj, result);
+	    }
+	} catch (TransformerException ex) {
+	    IOException ioex = new IOException(
+		"Unable to run the JAXP transformer on a stream "
+		    + ex.getMessage());
+	    ioex.initCause(ex);
+	    throw ioex;
+	} catch (RuntimeException ex) {
+	    IOException ioex = new IOException(
+		"Unable to run the JAXP transformer on a stream "
+		    + ex.getMessage());
+	    ioex.initCause(ex);
+	    throw ioex;
+	}
+    }
+
+    private boolean isXmlType(String type) {
+	try {
+	    ContentType ct = new ContentType(type);
+	    return ct.getSubType().equals("xml") &&
+		    (ct.getPrimaryType().equals("text") ||
+		    ct.getPrimaryType().equals("application"));
+	} catch (ParseException ex) {
+	    return false;
+	} catch (RuntimeException ex) {
+	    return false;
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/Argument.java b/mail/src/main/java/com/sun/mail/iap/Argument.java
new file mode 100644
index 0000000..677c63d
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/Argument.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.*;
+import java.nio.charset.Charset;
+
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class Argument {
+    protected List<Object> items;
+
+    /**
+     * Constructor
+     */
+    public Argument() {
+	items = new ArrayList<>(1);
+    }
+
+    /**
+     * Append the given Argument to this Argument. All items
+     * from the source argument are copied into this destination
+     * argument.
+     *
+     * @param	arg	the Argument to append
+     * @return		this
+     */
+    public Argument append(Argument arg) {
+	items.addAll(arg.items);
+	return this;
+    }
+
+    /**
+     * Write out given string as an ASTRING, depending on the type
+     * of the characters inside the string. The string should
+     * contain only ASCII characters. <p>
+     *
+     * XXX: Hmm .. this should really be called writeASCII()
+     *
+     * @param	s	String to write out
+     * @return		this
+     */
+    public Argument writeString(String s) {
+	items.add(new AString(ASCIIUtility.getBytes(s)));
+	return this;
+    }
+
+    /**
+     * Convert the given string into bytes in the specified
+     * charset, and write the bytes out as an ASTRING
+     *
+     * @param	s	String to write out
+     * @param	charset	the charset
+     * @return		this
+     * @exception	UnsupportedEncodingException	for bad charset
+     */
+    public Argument writeString(String s, String charset)
+		throws UnsupportedEncodingException {
+	if (charset == null) // convenience
+	    writeString(s);
+	else
+	    items.add(new AString(s.getBytes(charset)));
+	return this;
+    }
+
+    /**
+     * Convert the given string into bytes in the specified
+     * charset, and write the bytes out as an ASTRING
+     *
+     * @param	s	String to write out
+     * @param	charset	the charset
+     * @return		this
+     * @since	JavaMail 1.6.0
+     */
+    public Argument writeString(String s, Charset charset) {
+	if (charset == null) // convenience
+	    writeString(s);
+	else
+	    items.add(new AString(s.getBytes(charset)));
+	return this;
+    }
+
+    /**
+     * Write out given string as an NSTRING, depending on the type
+     * of the characters inside the string. The string should
+     * contain only ASCII characters. <p>
+     *
+     * @param	s	String to write out
+     * @return		this
+     * @since	JavaMail 1.5.1
+     */
+    public Argument writeNString(String s) {
+	if (s == null)
+	    items.add(new NString(null));
+	else
+	    items.add(new NString(ASCIIUtility.getBytes(s)));
+	return this;
+    }
+
+    /**
+     * Convert the given string into bytes in the specified
+     * charset, and write the bytes out as an NSTRING
+     *
+     * @param	s	String to write out
+     * @param	charset	the charset
+     * @return		this
+     * @exception	UnsupportedEncodingException	for bad charset
+     * @since	JavaMail 1.5.1
+     */
+    public Argument writeNString(String s, String charset)
+		throws UnsupportedEncodingException {
+	if (s == null)
+	    items.add(new NString(null));
+	else if (charset == null) // convenience
+	    writeString(s);
+	else
+	    items.add(new NString(s.getBytes(charset)));
+	return this;
+    }
+
+    /**
+     * Convert the given string into bytes in the specified
+     * charset, and write the bytes out as an NSTRING
+     *
+     * @param	s	String to write out
+     * @param	charset	the charset
+     * @return		this
+     * @since	JavaMail 1.6.0
+     */
+    public Argument writeNString(String s, Charset charset) {
+	if (s == null)
+	    items.add(new NString(null));
+	else if (charset == null) // convenience
+	    writeString(s);
+	else
+	    items.add(new NString(s.getBytes(charset)));
+	return this;
+    }
+
+    /**
+     * Write out given byte[] as a Literal.
+     * @param b  byte[] to write out
+     * @return	this
+     */
+    public Argument writeBytes(byte[] b)  {
+	items.add(b);
+	return this;
+    }
+
+    /**
+     * Write out given ByteArrayOutputStream as a Literal.
+     * @param b  ByteArrayOutputStream to be written out.
+     * @return	this
+     */
+    public Argument writeBytes(ByteArrayOutputStream b)  {
+	items.add(b);
+	return this;
+    }
+
+    /**
+     * Write out given data as a literal.
+     * @param b  Literal representing data to be written out.
+     * @return	this
+     */
+    public Argument writeBytes(Literal b)  {
+	items.add(b);
+	return this;
+    }
+
+    /**
+     * Write out given string as an Atom. Note that an Atom can contain only
+     * certain US-ASCII characters.  No validation is done on the characters 
+     * in the string.
+     * @param s  String
+     * @return	this
+     */
+    public Argument writeAtom(String s) {
+	items.add(new Atom(s));
+	return this;
+    }
+
+    /**
+     * Write out number.
+     * @param i number
+     * @return	this
+     */
+    public Argument writeNumber(int i) {
+	items.add(Integer.valueOf(i));
+	return this;
+    }
+
+    /**
+     * Write out number.
+     * @param i number
+     * @return	this
+     */
+    public Argument writeNumber(long i) {
+	items.add(Long.valueOf(i));
+	return this;
+    }
+
+    /**
+     * Write out as parenthesised list.
+     *
+     * @param	c	the Argument
+     * @return	this
+     */
+    public Argument writeArgument(Argument c) {
+	items.add(c);
+	return this;
+    }
+
+    /*
+     * Write out all the buffered items into the output stream.
+     */
+    public void write(Protocol protocol) 
+		throws IOException, ProtocolException {
+	int size = items != null ? items.size() : 0;
+	DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
+
+	for (int i=0; i < size; i++) {
+	    if (i > 0)	// write delimiter if not the first item
+		os.write(' ');
+
+	    Object o = items.get(i);
+	    if (o instanceof Atom) {
+		os.writeBytes(((Atom)o).string);
+	    } else if (o instanceof Number) {
+		os.writeBytes(((Number)o).toString());
+	    } else if (o instanceof AString) {
+		astring(((AString)o).bytes, protocol);
+	    } else if (o instanceof NString) {
+		nstring(((NString)o).bytes, protocol);
+	    } else if (o instanceof byte[]) {
+		literal((byte[])o, protocol);
+	    } else if (o instanceof ByteArrayOutputStream) {
+		literal((ByteArrayOutputStream)o, protocol);
+	    } else if (o instanceof Literal) {
+		literal((Literal)o, protocol);
+	    } else if (o instanceof Argument) {
+		os.write('('); // open parans
+		((Argument)o).write(protocol);
+		os.write(')'); // close parans
+	    }
+	}
+    }
+
+    /**
+     * Write out given String as either an Atom, QuotedString or Literal
+     */
+    private void astring(byte[] bytes, Protocol protocol) 
+			throws IOException, ProtocolException {
+	nastring(bytes, protocol, false);
+    }
+
+    /**
+     * Write out given String as either NIL, QuotedString, or Literal.
+     */
+    private void nstring(byte[] bytes, Protocol protocol) 
+			throws IOException, ProtocolException {
+	if (bytes == null) {
+	    DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
+	    os.writeBytes("NIL");
+	} else
+	    nastring(bytes, protocol, true);
+    }
+
+    private void nastring(byte[] bytes, Protocol protocol, boolean doQuote) 
+			throws IOException, ProtocolException {
+	DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
+	int len = bytes.length;
+
+	// If length is greater than 1024 bytes, send as literal
+	if (len > 1024) {
+	    literal(bytes, protocol);
+	    return;
+	}
+
+        // if 0 length, send as quoted-string
+        boolean quote = len == 0 ? true : doQuote;
+	boolean escape = false;
+	boolean utf8 = protocol.supportsUtf8();
+
+	byte b;
+	for (int i = 0; i < len; i++) {
+	    b = bytes[i];
+	    if (b == '\0' || b == '\r' || b == '\n' ||
+		    (!utf8 && ((b & 0xff) > 0177))) {
+		// NUL, CR or LF means the bytes need to be sent as literals
+		literal(bytes, protocol);
+		return;
+	    }
+	    if (b == '*' || b == '%' || b == '(' || b == ')' || b == '{' ||
+		    b == '"' || b == '\\' ||
+		    ((b & 0xff) <= ' ') || ((b & 0xff) > 0177)) {
+		quote = true;
+		if (b == '"' || b == '\\') // need to escape these characters
+		    escape = true;
+	    }
+	}
+
+	/*
+	 * Make sure the (case-independent) string "NIL" is always quoted,
+	 * so as not to be confused with a real NIL (handled above in nstring).
+	 * This is more than is necessary, but it's rare to begin with and
+	 * this makes it safer than doing the test in nstring above in case
+	 * some code calls writeString when it should call writeNString.
+	 */
+	if (!quote && bytes.length == 3 &&
+		(bytes[0] == 'N' || bytes[0] == 'n') &&
+		(bytes[1] == 'I' || bytes[1] == 'i') &&
+		(bytes[2] == 'L' || bytes[2] == 'l'))
+	    quote = true;
+
+	if (quote) // start quote
+	    os.write('"');
+
+        if (escape) {
+            // already quoted
+            for (int i = 0; i < len; i++) {
+                b = bytes[i];
+                if (b == '"' || b == '\\')
+                    os.write('\\');
+                os.write(b);
+            }
+        } else 
+            os.write(bytes);
+ 
+
+	if (quote) // end quote
+	    os.write('"');
+    }
+
+    /**
+     * Write out given byte[] as a literal
+     */
+    private void literal(byte[] b, Protocol protocol) 
+			throws IOException, ProtocolException {
+	startLiteral(protocol, b.length).write(b);
+    }
+
+    /**
+     * Write out given ByteArrayOutputStream as a literal.
+     */
+    private void literal(ByteArrayOutputStream b, Protocol protocol) 
+			throws IOException, ProtocolException {
+	b.writeTo(startLiteral(protocol, b.size()));
+    }
+
+    /**
+     * Write out given Literal as a literal.
+     */
+    private void literal(Literal b, Protocol protocol) 
+			throws IOException, ProtocolException {
+	b.writeTo(startLiteral(protocol, b.size()));
+    }
+
+    private OutputStream startLiteral(Protocol protocol, int size) 
+			throws IOException, ProtocolException {
+	DataOutputStream os = (DataOutputStream)protocol.getOutputStream();
+	boolean nonSync = protocol.supportsNonSyncLiterals();
+
+	os.write('{');
+	os.writeBytes(Integer.toString(size));
+	if (nonSync) // server supports non-sync literals
+	    os.writeBytes("+}\r\n");
+	else
+	    os.writeBytes("}\r\n");
+	os.flush();
+
+	// If we are using synchronized literals, wait for the server's
+	// continuation signal
+	if (!nonSync) {
+	    for (; ;) {
+		Response r = protocol.readResponse();
+		if (r.isContinuation())
+		    break;
+		if (r.isTagged())
+		    throw new LiteralException(r);
+		// XXX - throw away untagged responses;
+		//	 violates IMAP spec, hope no servers do this
+	    }
+	}
+	return os;
+    }
+}
+
+class Atom {
+    String string;
+
+    Atom(String s) {
+	string = s;
+    }
+}
+
+class AString {
+    byte[] bytes;
+
+    AString(byte[] b) {
+	bytes = b;
+    }
+}
+
+class NString {
+    byte[] bytes;
+
+    NString(byte[] b) {
+	bytes = b;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/BadCommandException.java b/mail/src/main/java/com/sun/mail/iap/BadCommandException.java
new file mode 100644
index 0000000..4a1b33b
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/BadCommandException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class BadCommandException extends ProtocolException {
+
+    private static final long serialVersionUID = 5769722539397237515L;
+
+    /**
+     * Constructs an BadCommandException with no detail message.
+     */
+    public BadCommandException() {
+	super();
+    }
+
+    /**
+     * Constructs an BadCommandException with the specified detail message.
+     * @param s		the detail message
+     */
+    public BadCommandException(String s) {
+	super(s);
+    }
+
+    /**
+     * Constructs an BadCommandException with the specified Response.
+     * @param r		the Response
+     */
+    public BadCommandException(Response r) {
+	super(r);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/ByteArray.java b/mail/src/main/java/com/sun/mail/iap/ByteArray.java
new file mode 100644
index 0000000..e05110b
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/ByteArray.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.ByteArrayInputStream;
+
+/**
+ * A simple wrapper around a byte array, with a start position and
+ * count of bytes.
+ *
+ * @author  John Mani
+ */
+
+public class ByteArray {
+    private byte[] bytes; // the byte array
+    private int start;	  // start position
+    private int count;	  // count of bytes
+
+    /**
+     * Constructor
+     *
+     * @param	b	the byte array to wrap
+     * @param	start	start position in byte array
+     * @param	count	number of bytes in byte array
+     */
+    public ByteArray(byte[] b, int start, int count) {
+	bytes = b;
+	this.start = start;
+	this.count = count;
+    }
+
+    /**
+     * Constructor that creates a byte array of the specified size.
+     *
+     * @param	size	the size of the ByteArray
+     * @since	JavaMail 1.4.1
+     */
+    public ByteArray(int size) {
+	this(new byte[size], 0, size);
+    }
+
+    /**
+     * Returns the internal byte array. Note that this is a live
+     * reference to the actual data, not a copy.
+     *
+     * @return	the wrapped byte array
+     */
+    public byte[] getBytes() {
+	return bytes;
+    }
+
+    /**
+     * Returns a new byte array that is a copy of the data.
+     *
+     * @return	a new byte array with the bytes from start for count
+     */
+    public byte[] getNewBytes() {
+	byte[] b = new byte[count];
+	System.arraycopy(bytes, start, b, 0, count);
+	return b;
+    }
+
+    /**
+     * Returns the start position
+     *
+     * @return	the start position
+     */
+    public int getStart() {
+	return start;
+    }
+
+    /**
+     * Returns the count of bytes
+     *
+     * @return	the number of bytes
+     */
+    public int getCount() {
+	return count;
+    }
+
+    /**
+     * Set the count of bytes.
+     *
+     * @param	count	the number of bytes
+     * @since	JavaMail 1.4.1
+     */
+    public void setCount(int count) {
+	this.count = count;
+    }
+
+    /**
+     * Returns a ByteArrayInputStream.
+     *
+     * @return	the ByteArrayInputStream
+     */
+    public ByteArrayInputStream toByteArrayInputStream() {
+	return new ByteArrayInputStream(bytes, start, count);
+    }
+
+    /**
+     * Grow the byte array by incr bytes.
+     *
+     * @param	incr	how much to grow
+     * @since	JavaMail 1.4.1
+     */
+    public void grow(int incr) {
+	byte[] nbuf = new byte[bytes.length + incr];
+	System.arraycopy(bytes, 0, nbuf, 0, bytes.length);
+	bytes = nbuf;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/CommandFailedException.java b/mail/src/main/java/com/sun/mail/iap/CommandFailedException.java
new file mode 100644
index 0000000..8c141e7
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/CommandFailedException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class CommandFailedException extends ProtocolException {
+
+    private static final long serialVersionUID = 793932807880443631L;
+
+    /**
+     * Constructs an CommandFailedException with no detail message.
+     */
+    public CommandFailedException() {
+	super();
+    }
+
+    /**
+     * Constructs an CommandFailedException with the specified detail message.
+     * @param s		the detail message
+     */
+    public CommandFailedException(String s) {
+	super(s);
+    }
+
+    /**
+     * Constructs an CommandFailedException with the specified Response.
+     * @param r		the Response.
+     */
+    public CommandFailedException(Response r) {
+	super(r);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/ConnectionException.java b/mail/src/main/java/com/sun/mail/iap/ConnectionException.java
new file mode 100644
index 0000000..12dd496
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/ConnectionException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class ConnectionException extends ProtocolException {
+    private transient Protocol p;
+
+    private static final long serialVersionUID = 5749739604257464727L;
+
+    /**
+     * Constructs an ConnectionException with no detail message.
+     */
+    public ConnectionException() {
+	super();
+    }
+
+    /**
+     * Constructs an ConnectionException with the specified detail message.
+     *
+     * @param s		the detail message
+     */
+    public ConnectionException(String s) {
+	super(s);
+    }
+
+    /**
+     * Constructs an ConnectionException with the specified Response.
+     *
+     * @param	p	the Protocol object
+     * @param	r	the Response
+     */
+    public ConnectionException(Protocol p, Response r) {
+	super(r);
+	this.p = p;
+    }
+
+    public Protocol getProtocol() {
+	return p;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/Literal.java b/mail/src/main/java/com/sun/mail/iap/Literal.java
new file mode 100644
index 0000000..14c592b
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/Literal.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.*;
+
+/**
+ * An interface for objects that provide data dynamically for use in
+ * a literal protocol element.
+ *
+ * @author  Bill Shannon
+ */
+
+public interface Literal {
+    /**
+     * Return the size of the data.
+     *
+     * @return	the size of the data
+     */
+    public int size();
+
+    /**
+     * Write the data to the OutputStream.
+     *
+     * @param	os	the output stream
+     * @exception	IOException	for I/O errors
+     */
+    public void writeTo(OutputStream os) throws IOException;
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/LiteralException.java b/mail/src/main/java/com/sun/mail/iap/LiteralException.java
new file mode 100644
index 0000000..660747f
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/LiteralException.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author Bill Shannon
+ */
+
+public class LiteralException extends ProtocolException {
+
+    private static final long serialVersionUID = -6919179828339609913L;
+
+    /**
+     * Constructs a LiteralException with the specified Response object.
+     *
+     * @param	r	the response object
+     */
+    public LiteralException(Response r) {
+	super(r.toString());
+	response = r;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/ParsingException.java b/mail/src/main/java/com/sun/mail/iap/ParsingException.java
new file mode 100644
index 0000000..a1e7168
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/ParsingException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class ParsingException extends ProtocolException {
+
+    private static final long serialVersionUID = 7756119840142724839L;
+
+    /**
+     * Constructs an ParsingException with no detail message.
+     */
+    public ParsingException() {
+	super();
+    }
+
+    /**
+     * Constructs an ParsingException with the specified detail message.
+     * @param s		the detail message
+     */
+    public ParsingException(String s) {
+	super(s);
+    }
+
+    /**
+     * Constructs an ParsingException with the specified Response.
+     * @param r		the Response
+     */
+    public ParsingException(Response r) {
+	super(r);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/Protocol.java b/mail/src/main/java/com/sun/mail/iap/Protocol.java
new file mode 100644
index 0000000..1339532
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/Protocol.java
@@ -0,0 +1,671 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.util.Properties;
+import java.io.*;
+import java.nio.channels.SocketChannel;
+import java.net.*;
+import javax.net.ssl.SSLSocket;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.zip.Deflater;
+import java.util.zip.DeflaterOutputStream;
+import java.util.zip.Inflater;
+import java.util.zip.InflaterInputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.SocketFetcher;
+import com.sun.mail.util.TraceInputStream;
+import com.sun.mail.util.TraceOutputStream;
+
+/**
+ * General protocol handling code for IMAP-like protocols. <p>
+ *
+ * The Protocol object is multithread safe.
+ *
+ * @author  John Mani
+ * @author  Max Spivak
+ * @author  Bill Shannon
+ */
+
+public class Protocol {
+    protected String host;
+    private Socket socket;
+    // in case we turn on TLS, we'll need these later
+    protected boolean quote;
+    protected MailLogger logger;
+    protected MailLogger traceLogger;
+    protected Properties props;
+    protected String prefix;
+
+    private TraceInputStream traceInput;	// the Tracer
+    private volatile ResponseInputStream input;
+
+    private TraceOutputStream traceOutput;	// the Tracer
+    private volatile DataOutputStream output;
+
+    private int tagCounter = 0;
+    private final String tagPrefix;
+
+    private String localHostName;
+
+    private final List<ResponseHandler> handlers
+	    = new CopyOnWriteArrayList<>();
+
+    private volatile long timestamp;
+
+    // package private, to allow testing
+    static final AtomicInteger tagNum = new AtomicInteger();
+
+    private static final byte[] CRLF = { (byte)'\r', (byte)'\n'};
+ 
+    /**
+     * Constructor. <p>
+     * 
+     * Opens a connection to the given host at given port.
+     *
+     * @param host	host to connect to
+     * @param port	portnumber to connect to
+     * @param props     Properties object used by this protocol
+     * @param prefix 	Prefix to prepend to property keys
+     * @param isSSL 	use SSL?
+     * @param logger 	log messages here
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     */
+    public Protocol(String host, int port, 
+		    Properties props, String prefix,
+		    boolean isSSL, MailLogger logger)
+		    throws IOException, ProtocolException {
+	boolean connected = false;		// did constructor succeed?
+	tagPrefix = computePrefix(props, prefix);
+	try {
+	    this.host = host;
+	    this.props = props;
+	    this.prefix = prefix;
+	    this.logger = logger;
+	    traceLogger = logger.getSubLogger("protocol", null);
+
+	    socket = SocketFetcher.getSocket(host, port, props, prefix, isSSL);
+	    quote = PropUtil.getBooleanProperty(props,
+					"mail.debug.quote", false);
+
+	    initStreams();
+
+	    // Read server greeting
+	    processGreeting(readResponse());
+
+	    timestamp = System.currentTimeMillis();
+ 
+	    connected = true;	// must be last statement in constructor
+	} finally {
+	    /*
+	     * If we get here because an exception was thrown, we need
+	     * to disconnect to avoid leaving a connected socket that
+	     * no one will be able to use because this object was never
+	     * completely constructed.
+	     */
+	    if (!connected)
+		disconnect();
+	}
+    }
+
+    private void initStreams() throws IOException {
+	traceInput = new TraceInputStream(socket.getInputStream(), traceLogger);
+	traceInput.setQuote(quote);
+	input = new ResponseInputStream(traceInput);
+
+	traceOutput =
+	    new TraceOutputStream(socket.getOutputStream(), traceLogger);
+	traceOutput.setQuote(quote);
+	output = new DataOutputStream(new BufferedOutputStream(traceOutput));
+    }
+
+    /**
+     * Compute the tag prefix to be used for this connection.
+     * Start with "A" - "Z", then "AA" - "ZZ", and finally "AAA" - "ZZZ".
+     * Wrap around after that.
+     */
+    private String computePrefix(Properties props, String prefix) {
+	// XXX - in case someone depends on the tag prefix
+	if (PropUtil.getBooleanProperty(props,
+				    prefix + ".reusetagprefix", false))
+	    return "A";
+	// tag prefix, wrap around after three letters
+	int n = tagNum.getAndIncrement() % (26*26*26 + 26*26 + 26);
+	String tagPrefix;
+	if (n < 26)
+	    tagPrefix = new String(new char[] { (char)('A' + n) });
+	else if (n < (26*26 + 26)) {
+	    n -= 26;
+	    tagPrefix = new String(new char[] {
+			    (char)('A' + n/26), (char)('A' + n%26) });
+	} else {
+	    n -= (26*26 + 26);
+	    tagPrefix = new String(new char[] {
+		(char)('A' + n/(26*26)),
+		(char)('A' + (n%(26*26))/26),
+		(char)('A' + n%26) });
+	}
+	return tagPrefix;
+    }
+
+    /**
+     * Constructor for debugging.
+     *
+     * @param in	the InputStream to read from
+     * @param out	the PrintStream to write to
+     * @param props     Properties object used by this protocol
+     * @param debug	true to enable debugging output
+     * @exception	IOException	for I/O errors
+     */
+    public Protocol(InputStream in, PrintStream out, Properties props,
+				boolean debug) throws IOException {
+	this.host = "localhost";
+	this.props = props;
+	this.quote = false;
+	tagPrefix = computePrefix(props, "mail.imap");
+	logger = new MailLogger(this.getClass(), "DEBUG", debug, System.out);
+	traceLogger = logger.getSubLogger("protocol", null);
+
+	// XXX - inlined initStreams, won't allow later startTLS
+	traceInput = new TraceInputStream(in, traceLogger);
+	traceInput.setQuote(quote);
+	input = new ResponseInputStream(traceInput);
+
+	traceOutput = new TraceOutputStream(out, traceLogger);
+	traceOutput.setQuote(quote);
+	output = new DataOutputStream(new BufferedOutputStream(traceOutput));
+
+        timestamp = System.currentTimeMillis();
+    }
+
+    /**
+     * Returns the timestamp.
+     *
+     * @return	the timestamp
+     */
+    public long getTimestamp() {
+        return timestamp;
+    }
+ 
+    /**
+     * Adds a response handler.
+     *
+     * @param	h	the response handler
+     */
+    public void addResponseHandler(ResponseHandler h) {
+	handlers.add(h);
+    }
+
+    /**
+     * Removed the specified response handler.
+     *
+     * @param	h	the response handler
+     */
+    public void removeResponseHandler(ResponseHandler h) {
+	handlers.remove(h);
+    }
+
+    /**
+     * Notify response handlers
+     *
+     * @param	responses	the responses
+     */
+    public void notifyResponseHandlers(Response[] responses) {
+	if (handlers.isEmpty()) {
+	    return;
+	}
+
+	for (Response r : responses) {
+	    if (r != null) {
+		for (ResponseHandler rh : handlers) {
+		    if (rh != null) {
+			rh.handleResponse(r);
+		    }
+		}
+	    }
+	}
+    }
+
+    protected void processGreeting(Response r) throws ProtocolException {
+	if (r.isBYE())
+	    throw new ConnectionException(this, r);
+    }
+
+    /**
+     * Return the Protocol's InputStream.
+     *
+     * @return	the input stream
+     */
+    protected ResponseInputStream getInputStream() {
+	return input;
+    }
+
+    /**
+     * Return the Protocol's OutputStream
+     *
+     * @return	the output stream
+     */
+    protected OutputStream getOutputStream() {
+	return output;
+    }
+
+    /**
+     * Returns whether this Protocol supports non-synchronizing literals
+     * Default is false. Subclasses should override this if required
+     *
+     * @return	true if the server supports non-synchronizing literals
+     */
+    protected synchronized boolean supportsNonSyncLiterals() {
+	return false;
+    }
+
+    public Response readResponse() 
+		throws IOException, ProtocolException {
+	return new Response(this);
+    }
+
+    /**
+     * Is another response available in our buffer?
+     *
+     * @return	true if another response is in the buffer
+     * @since	JavaMail 1.5.4
+     */
+    public boolean hasResponse() {
+	/*
+	 * XXX - Really should peek ahead in the buffer to see
+	 * if there's a *complete* response available, but if there
+	 * isn't who's going to read more data into the buffer 
+	 * until there is?
+	 */
+	try {
+	    return input.available() > 0;
+	} catch (IOException ex) {
+	}
+	return false;
+    }
+
+    /**
+     * Return a buffer to be used to read a response.
+     * The default implementation returns null, which causes
+     * a new buffer to be allocated for every response.
+     *
+     * @return	the buffer to use
+     * @since	JavaMail 1.4.1
+     */
+    protected ByteArray getResponseBuffer() {
+	return null;
+    }
+
+    public String writeCommand(String command, Argument args) 
+		throws IOException, ProtocolException {
+	// assert Thread.holdsLock(this);
+	// can't assert because it's called from constructor
+	String tag = tagPrefix + Integer.toString(tagCounter++); // unique tag
+
+	output.writeBytes(tag + " " + command);
+    
+	if (args != null) {
+	    output.write(' ');
+	    args.write(this);
+	}
+
+	output.write(CRLF);
+	output.flush();
+	return tag;
+    }
+
+    /**
+     * Send a command to the server. Collect all responses until either
+     * the corresponding command completion response or a BYE response 
+     * (indicating server failure).  Return all the collected responses.
+     *
+     * @param	command	the command
+     * @param	args	the arguments
+     * @return		array of Response objects returned by the server
+     */
+    public synchronized Response[] command(String command, Argument args) {
+	commandStart(command);
+	List<Response> v = new ArrayList<>();
+	boolean done = false;
+	String tag = null;
+
+	// write the command
+	try {
+	    tag = writeCommand(command, args);
+	} catch (LiteralException lex) {
+	    v.add(lex.getResponse());
+	    done = true;
+	} catch (Exception ex) {
+	    // Convert this into a BYE response
+	    v.add(Response.byeResponse(ex));
+	    done = true;
+	}
+
+	Response byeResp = null;
+	while (!done) {
+	    Response r = null;
+	    try {
+		r = readResponse();
+	    } catch (IOException ioex) {
+		if (byeResp == null)	// convert this into a BYE response
+		    byeResp = Response.byeResponse(ioex);
+		// else, connection closed after BYE was sent
+		break;
+	    } catch (ProtocolException pex) {
+		logger.log(Level.FINE, "ignoring bad response", pex);
+		continue; // skip this response
+	    }
+
+	    if (r.isBYE()) {
+		byeResp = r;
+		continue;
+	    }
+
+	    v.add(r);
+
+	    // If this is a matching command completion response, we are done
+	    if (r.isTagged() && r.getTag().equals(tag))
+		done = true;
+	}
+
+	if (byeResp != null)
+		v.add(byeResp);	// must be last
+	Response[] responses = new Response[v.size()];
+	v.toArray(responses);
+        timestamp = System.currentTimeMillis();
+	commandEnd();
+	return responses;
+    }
+
+    /**
+     * Convenience routine to handle OK, NO, BAD and BYE responses.
+     *
+     * @param	response	the response
+     * @exception	ProtocolException	for protocol failures
+     */
+    public void handleResult(Response response) throws ProtocolException {
+	if (response.isOK())
+	    return;
+	else if (response.isNO())
+	    throw new CommandFailedException(response);
+	else if (response.isBAD())
+	    throw new BadCommandException(response);
+	else if (response.isBYE()) {
+	    disconnect();
+	    throw new ConnectionException(this, response);
+	}
+    }
+
+    /**
+     * Convenience routine to handle simple IAP commands
+     * that do not have responses specific to that command.
+     *
+     * @param	cmd	the command
+     * @param	args	the arguments
+     * @exception	ProtocolException	for protocol failures
+     */
+    public void simpleCommand(String cmd, Argument args)
+			throws ProtocolException {
+	// Issue command
+	Response[] r = command(cmd, args);
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+
+	// Handle result of this command
+	handleResult(r[r.length-1]);
+    }
+
+    /**
+     * Start TLS on the current connection.
+     * <code>cmd</code> is the command to issue to start TLS negotiation.
+     * If the command succeeds, we begin TLS negotiation.
+     * If the socket is already an SSLSocket this is a nop and the command
+     * is not issued.
+     *
+     * @param	cmd	the command to issue
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     */
+    public synchronized void startTLS(String cmd)
+				throws IOException, ProtocolException {
+	if (socket instanceof SSLSocket)
+	    return;	// nothing to do
+	simpleCommand(cmd, null);
+	socket = SocketFetcher.startTLS(socket, host, props, prefix);
+	initStreams();
+    }
+
+    /**
+     * Start compression on the current connection.
+     * <code>cmd</code> is the command to issue to start compression.
+     * If the command succeeds, we begin compression.
+     *
+     * @param	cmd	the command to issue
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     */
+    public synchronized void startCompression(String cmd)
+				throws IOException, ProtocolException {
+	// XXX - check whether compression is already enabled?
+	simpleCommand(cmd, null);
+
+	// need to create our own Inflater and Deflater in order to set nowrap
+	Inflater inf = new Inflater(true);
+	traceInput = new TraceInputStream(new InflaterInputStream(
+			    socket.getInputStream(), inf), traceLogger);
+	traceInput.setQuote(quote);
+	input = new ResponseInputStream(traceInput);
+
+	// configure the Deflater
+	int level = PropUtil.getIntProperty(props, prefix + ".compress.level",
+						Deflater.DEFAULT_COMPRESSION);
+	int strategy = PropUtil.getIntProperty(props,
+						prefix + ".compress.strategy",
+						Deflater.DEFAULT_STRATEGY);
+	if (logger.isLoggable(Level.FINE))
+	    logger.log(Level.FINE,
+		"Creating Deflater with compression level {0} and strategy {1}",
+		new Object[] { level, strategy });
+	Deflater def = new Deflater(Deflater.DEFAULT_COMPRESSION, true);
+	try {
+	    def.setLevel(level);
+	} catch (IllegalArgumentException ex) {
+	    logger.log(Level.FINE, "Ignoring bad compression level", ex);
+	}
+	try {
+	    def.setStrategy(strategy);
+	} catch (IllegalArgumentException ex) {
+	    logger.log(Level.FINE, "Ignoring bad compression strategy", ex);
+	}
+	traceOutput = new TraceOutputStream(new DeflaterOutputStream(
+			    socket.getOutputStream(), def, true), traceLogger);
+	traceOutput.setQuote(quote);
+	output = new DataOutputStream(new BufferedOutputStream(traceOutput));
+    }
+
+    /**
+     * Is this connection using an SSL socket?
+     *
+     * @return	true if using SSL
+     * @since	JavaMail 1.4.6
+     */
+    public boolean isSSL() {
+	return socket instanceof SSLSocket;
+    }
+
+    /**
+     * Return the address the socket connected to.
+     *
+     * @return	the InetAddress the socket is connected to
+     * @since	JavaMail 1.5.2
+     */
+    public InetAddress getInetAddress() {
+	return socket.getInetAddress();
+    }
+
+    /**
+     * Return the SocketChannel associated with this connection, if any.
+     *
+     * @return	the SocketChannel
+     * @since	JavaMail 1.5.2
+     */
+    public SocketChannel getChannel() {
+	SocketChannel ret = socket.getChannel();
+	if (ret != null)
+	    return ret;
+
+	// XXX - Android is broken and SSL wrapped sockets don't delegate
+	// the getChannel method to the wrapped Socket
+	if (socket instanceof SSLSocket) {
+	    try {
+		Field f = socket.getClass().getDeclaredField("socket");
+		f.setAccessible(true);
+		Socket s = (Socket)f.get(socket);
+		ret = s.getChannel();
+	    } catch (Exception ex) {
+		// ignore anything that might go wrong
+	    }
+	}
+	return ret;
+    }
+
+    /**
+     * Does the server support UTF-8?
+     * This implementation returns false.
+     * Subclasses should override as appropriate.
+     *
+     * @return	true if the server supports UTF-8
+     * @since JavaMail 1.6.0
+     */
+    public boolean supportsUtf8() {
+	return false;
+    }
+
+    /**
+     * Disconnect.
+     */
+    protected synchronized void disconnect() {
+	if (socket != null) {
+	    try {
+		socket.close();
+	    } catch (IOException e) {
+		// ignore it
+	    }
+	    socket = null;
+	}
+    }
+
+    /**
+     * Get the name of the local host.
+     * The property &lt;prefix&gt;.localhost overrides
+     * &lt;prefix&gt;.localaddress,
+     * which overrides what InetAddress would tell us.
+     *
+     * @return	the name of the local host
+     */
+    protected synchronized String getLocalHost() {
+	// get our hostname and cache it for future use
+	if (localHostName == null || localHostName.length() <= 0)
+	    localHostName =
+		    props.getProperty(prefix + ".localhost");
+	if (localHostName == null || localHostName.length() <= 0)
+	    localHostName =
+		    props.getProperty(prefix + ".localaddress");
+	try {
+	    if (localHostName == null || localHostName.length() <= 0) {
+		InetAddress localHost = InetAddress.getLocalHost();
+		localHostName = localHost.getCanonicalHostName();
+		// if we can't get our name, use local address literal
+		if (localHostName == null)
+		    // XXX - not correct for IPv6
+		    localHostName = "[" + localHost.getHostAddress() + "]";
+	    }
+	} catch (UnknownHostException uhex) {
+	}
+
+	// last chance, try to get our address from our socket
+	if (localHostName == null || localHostName.length() <= 0) {
+	    if (socket != null && socket.isBound()) {
+		InetAddress localHost = socket.getLocalAddress();
+		localHostName = localHost.getCanonicalHostName();
+		// if we can't get our name, use local address literal
+		if (localHostName == null)
+		    // XXX - not correct for IPv6
+		    localHostName = "[" + localHost.getHostAddress() + "]";
+	    }
+	}
+	return localHostName;
+    }
+
+    /**
+     * Is protocol tracing enabled?
+     *
+     * @return	true if protocol tracing is enabled
+     */
+    protected boolean isTracing() {
+	return traceLogger.isLoggable(Level.FINEST);
+    }
+
+    /**
+     * Temporarily turn off protocol tracing, e.g., to prevent
+     * tracing the authentication sequence, including the password.
+     */
+    protected void suspendTracing() {
+	if (traceLogger.isLoggable(Level.FINEST)) {
+	    traceInput.setTrace(false);
+	    traceOutput.setTrace(false);
+	}
+    }
+
+    /**
+     * Resume protocol tracing, if it was enabled to begin with.
+     */
+    protected void resumeTracing() {
+	if (traceLogger.isLoggable(Level.FINEST)) {
+	    traceInput.setTrace(true);
+	    traceOutput.setTrace(true);
+	}
+    }
+
+    /**
+     * Finalizer.
+     */
+    @Override
+    protected void finalize() throws Throwable {
+	try {
+	    disconnect();
+	} finally {
+	    super.finalize();
+	}
+    }
+
+    /*
+     * Probe points for GlassFish monitoring.
+     */
+    private void commandStart(String command) { }
+    private void commandEnd() { }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/ProtocolException.java b/mail/src/main/java/com/sun/mail/iap/ProtocolException.java
new file mode 100644
index 0000000..f4d9e3a
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/ProtocolException.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * @author John Mani
+ */
+
+public class ProtocolException extends Exception {
+    protected transient Response response = null;
+
+    private static final long serialVersionUID = -4360500807971797439L;
+
+    /**
+     * Constructs a ProtocolException with no detail message.
+     */
+    public ProtocolException() {
+	super();
+    }
+
+    /**
+     * Constructs a ProtocolException with the specified detail message.
+     *
+     * @param message		the detail message
+     */
+    public ProtocolException(String message) {
+	super(message);
+    }
+
+    /**
+     * Constructs a ProtocolException with the specified detail message
+     * and cause.
+     *
+     * @param message		the detail message
+     * @param cause		the cause
+     */
+    public ProtocolException(String message, Throwable cause) {
+	super(message, cause);
+    }
+
+    /**
+     * Constructs a ProtocolException with the specified Response object.
+     *
+     * @param	r	the Response
+     */
+    public ProtocolException(Response r) {
+	super(r.toString());
+	response = r;
+    }
+
+    /**
+     * Return the offending Response object.
+     *
+     * @return	the Response object
+     */
+    public Response getResponse() {
+	return response;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/Response.java b/mail/src/main/java/com/sun/mail/iap/Response.java
new file mode 100644
index 0000000..cbec9e4
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/Response.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.*;
+import java.util.*;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * This class represents a response obtained from the input stream
+ * of an IMAP server.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class Response {
+    protected int index;  // internal index (updated during the parse)
+    protected int pindex; // index after parse, for reset
+    protected int size;   // number of valid bytes in our buffer
+    protected byte[] buffer = null;
+    protected int type = 0;
+    protected String tag = null;
+    /** @since JavaMail 1.5.4 */
+    protected Exception ex;
+    protected boolean utf8;
+
+    private static final int increment = 100;
+
+    // The first and second bits indicate whether this response
+    // is a Continuation, Tagged or Untagged
+    public final static int TAG_MASK 	 = 0x03;
+    public final static int CONTINUATION = 0x01;
+    public final static int TAGGED 	 = 0x02;
+    public final static int UNTAGGED 	 = 0x03;
+
+    // The third, fourth and fifth bits indicate whether this response
+    // is an OK, NO, BAD or BYE response
+    public final static int TYPE_MASK 	 = 0x1C;
+    public final static int OK 	 	 = 0x04;
+    public final static int NO 	 	 = 0x08;
+    public final static int BAD	 	 = 0x0C;
+    public final static int BYE	 	 = 0x10;
+
+    // The sixth bit indicates whether a BYE response is synthetic or real
+    public final static int SYNTHETIC 	 = 0x20;
+
+    /**
+     * An ATOM is any CHAR delimited by:
+     * SPACE | CTL | '(' | ')' | '{' | '%' | '*' | '"' | '\' | ']'
+     * (CTL is handled in readDelimString.)
+     */
+    private static String ATOM_CHAR_DELIM = " (){%*\"\\]";
+
+    /**
+     * An ASTRING_CHAR is any CHAR delimited by:
+     * SPACE | CTL | '(' | ')' | '{' | '%' | '*' | '"' | '\'
+     * (CTL is handled in readDelimString.)
+     */
+    private static String ASTRING_CHAR_DELIM = " (){%*\"\\";
+
+    public Response(String s) {
+	this(s, true);
+    }
+
+    /**
+     * Constructor for testing.
+     *
+     * @param	s	the response string
+     * @param	supportsUtf8	allow UTF-8 in response?
+     * @since	JavaMail 1.6.0
+     */
+    public Response(String s, boolean supportsUtf8) {
+	if (supportsUtf8)
+	    buffer = s.getBytes(StandardCharsets.UTF_8);
+	else
+	    buffer = s.getBytes(StandardCharsets.US_ASCII);
+	size = buffer.length;
+	utf8 = supportsUtf8;
+	parse();
+    }
+
+    /**
+     * Read a new Response from the given Protocol
+     *
+     * @param	p	the Protocol object
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     */
+    public Response(Protocol p) throws IOException, ProtocolException {
+	// read one response into 'buffer'
+	ByteArray ba = p.getResponseBuffer();
+	ByteArray response = p.getInputStream().readResponse(ba);
+	buffer = response.getBytes();
+	size = response.getCount() - 2; // Skip the terminating CRLF
+	utf8 = p.supportsUtf8();
+
+	parse();
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param	r	the Response to copy
+     */
+    public Response(Response r) {
+	index = r.index;
+	pindex = r.pindex;
+	size = r.size;
+	buffer = r.buffer;
+	type = r.type;
+	tag = r.tag;
+	ex = r.ex;
+	utf8 = r.utf8;
+    }
+
+    /**
+     * Return a Response object that looks like a BYE protocol response.
+     * Include the details of the exception in the response string.
+     *
+     * @param	ex	the exception
+     * @return		the synthetic Response object
+     */
+    public static Response byeResponse(Exception ex) {
+	String err = "* BYE JavaMail Exception: " + ex.toString();
+	err = err.replace('\r', ' ').replace('\n', ' ');
+	Response r = new Response(err);
+	r.type |= SYNTHETIC;
+	r.ex = ex;
+	return r;
+    }
+
+    /**
+     * Does the server support UTF-8?
+     *
+     * @return		true if the server supports UTF-8
+     * @since	JavaMail 1.6.0
+     */
+    public boolean supportsUtf8() {
+	return utf8;
+    }
+
+    private void parse() {
+	index = 0; // position internal index at start
+
+	if (size == 0)	// empty line
+	    return;
+	if (buffer[index] == '+') { // Continuation statement
+	    type |= CONTINUATION;
+	    index += 1; // Position beyond the '+'
+	    return;	// return
+	} else if (buffer[index] == '*') { // Untagged statement
+	    type |= UNTAGGED;
+	    index += 1; // Position beyond the '*'
+	} else {  // Tagged statement
+	    type |= TAGGED;
+	    tag = readAtom();	// read the TAG, index positioned beyond tag
+	    if (tag == null)
+		tag = "";	// avoid possible NPE
+	}
+
+	int mark = index; // mark
+	String s = readAtom();	// updates index
+	if (s == null)
+	    s = "";		// avoid possible NPE
+	if (s.equalsIgnoreCase("OK"))
+	    type |= OK;
+	else if (s.equalsIgnoreCase("NO"))
+	    type |= NO;
+	else if (s.equalsIgnoreCase("BAD"))
+	    type |= BAD;
+	else if (s.equalsIgnoreCase("BYE"))
+	    type |= BYE;
+	else
+	    index = mark; // reset
+
+	pindex = index;
+	return;
+    }
+
+    public void skipSpaces() {
+	while (index < size && buffer[index] == ' ')
+	    index++;
+    }
+
+    /**
+     * Skip past any spaces.  If the next non-space character is c,
+     * consume it and return true.  Otherwise stop at that point
+     * and return false.
+     *
+     * @param	c	the character to look for
+     * @return		true if the character is found
+     */
+    public boolean isNextNonSpace(char c) {
+	skipSpaces();
+	if (index < size && buffer[index] == (byte)c) {
+	    index++;
+	    return true;
+	}
+	return false;
+    }
+
+    /**
+     * Skip to the next space, for use in error recovery while parsing.
+     */
+    public void skipToken() {
+	while (index < size && buffer[index] != ' ')
+	    index++;
+    }
+
+    public void skip(int count) {
+	index += count;
+    }
+
+    public byte peekByte() {
+	if (index < size)
+	    return buffer[index];
+	else
+	    return 0;		// XXX - how else to signal error?
+    }
+
+    /**
+     * Return the next byte from this Statement.
+     *
+     * @return the next byte
+     */
+    public byte readByte() {
+	if (index < size)
+	    return buffer[index++];
+	else
+	    return 0;		// XXX - how else to signal error?
+    }
+
+    /**
+     * Extract an ATOM, starting at the current position. Updates
+     * the internal index to beyond the Atom.
+     *
+     * @return an Atom
+     */
+    public String readAtom() {
+	return readDelimString(ATOM_CHAR_DELIM);
+    }
+
+    /**
+     * Extract a string stopping at control characters or any
+     * character in delim.
+     */
+    private String readDelimString(String delim) {
+	skipSpaces();
+
+	if (index >= size) // already at end of response
+	    return null;
+
+	int b;
+	int start = index;
+	while (index < size && ((b = (((int)buffer[index])&0xff)) >= ' ') &&
+	       delim.indexOf((char)b) < 0 && b != 0x7f)
+	    index++;
+
+	return toString(buffer, start, index);
+    }
+
+    /**
+     * Read a string as an arbitrary sequence of characters,
+     * stopping at the delimiter  Used to read part of a
+     * response code inside [].
+     *
+     * @param	delim	the delimiter character
+     * @return		the string
+     */
+    public String readString(char delim) {
+	skipSpaces();
+
+	if (index >= size) // already at end of response
+	    return null;
+
+	int start = index;
+	while (index < size && buffer[index] != delim)
+	    index++;
+
+	return toString(buffer, start, index);
+    }
+
+    public String[] readStringList() {
+	return readStringList(false);
+    }
+
+    public String[] readAtomStringList() {
+	return readStringList(true);
+    }
+
+    private String[] readStringList(boolean atom) {
+	skipSpaces();
+
+	if (buffer[index] != '(') { // not what we expected
+	    return null;
+	}
+	index++; // skip '('
+
+	// to handle buggy IMAP servers, we tolerate multiple spaces as
+	// well as spaces after the left paren or before the right paren
+	List<String> result = new ArrayList<>();
+	while (!isNextNonSpace(')')) {
+	    String s = atom ? readAtomString() : readString();
+	    if (s == null)	// not the expected string or atom
+		break;
+	    result.add(s);
+	}
+
+	return result.toArray(new String[result.size()]);
+    }
+
+    /**
+     * Extract an integer, starting at the current position. Updates the
+     * internal index to beyond the number. Returns -1 if  a number was 
+     * not found.
+     *
+     * @return  a number
+     */
+    public int readNumber() {
+	// Skip leading spaces
+	skipSpaces();
+
+        int start = index;
+        while (index < size && Character.isDigit((char)buffer[index]))
+            index++;
+
+        if (index > start) {
+	    try {
+		return ASCIIUtility.parseInt(buffer, start, index);
+	    } catch (NumberFormatException nex) { }
+	}
+
+	return -1;
+    }
+
+    /**
+     * Extract a long number, starting at the current position. Updates the
+     * internal index to beyond the number. Returns -1 if a long number
+     * was not found.
+     *
+     * @return  a long
+     */
+    public long readLong() {
+	// Skip leading spaces
+	skipSpaces();
+
+        int start = index;
+        while (index < size && Character.isDigit((char)buffer[index]))
+            index++;
+
+        if (index > start) {
+	    try {
+		return ASCIIUtility.parseLong(buffer, start, index);
+	    } catch (NumberFormatException nex) { }
+	}
+
+	return -1;
+    }
+
+    /**
+     * Extract a NSTRING, starting at the current position. Return it as
+     * a String. The sequence 'NIL' is returned as null
+     *
+     * NSTRING := QuotedString | Literal | "NIL"
+     *
+     * @return  a String
+     */
+    public String readString() {
+	return (String)parseString(false, true);
+    }
+
+    /**
+     * Extract a NSTRING, starting at the current position. Return it as
+     * a ByteArrayInputStream. The sequence 'NIL' is returned as null
+     *
+     * NSTRING := QuotedString | Literal | "NIL"
+     *
+     * @return  a ByteArrayInputStream
+     */
+    public ByteArrayInputStream readBytes() {
+	ByteArray ba = readByteArray();
+	if (ba != null)
+	    return ba.toByteArrayInputStream();
+	else
+	    return null;
+    }
+
+    /**
+     * Extract a NSTRING, starting at the current position. Return it as
+     * a ByteArray. The sequence 'NIL' is returned as null
+     *
+     * NSTRING := QuotedString | Literal | "NIL"
+     *
+     * @return  a ByteArray
+     */
+    public ByteArray readByteArray() {
+	/*
+	 * Special case, return the data after the continuation uninterpreted.
+	 * It's usually a challenge for an AUTHENTICATE command.
+	 */
+	if (isContinuation()) {
+	    skipSpaces();
+	    return new ByteArray(buffer, index, size - index);
+	}
+	return (ByteArray)parseString(false, false);
+    }
+
+    /**
+     * Extract an ASTRING, starting at the current position
+     * and return as a String. An ASTRING can be a QuotedString, a
+     * Literal or an Atom (plus ']').
+     *
+     * Any errors in parsing returns null
+     *
+     * ASTRING := QuotedString | Literal | 1*ASTRING_CHAR
+     *
+     * @return a String
+     */ 
+    public String readAtomString() {
+	return (String)parseString(true, true);
+    }
+
+    /**
+     * Generic parsing routine that can parse out a Quoted-String,
+     * Literal or Atom and return the parsed token as a String
+     * or a ByteArray. Errors or NIL data will return null.
+     */
+    private Object parseString(boolean parseAtoms, boolean returnString) {
+	byte b;
+
+	// Skip leading spaces
+	skipSpaces();
+	
+	b = buffer[index];
+	if (b == '"') { // QuotedString
+	    index++; // skip the quote
+	    int start = index;
+	    int copyto = index;
+
+	    while (index < size && (b = buffer[index]) != '"') {
+		if (b == '\\') // skip escaped byte
+		    index++;
+		if (index != copyto) { // only copy if we need to
+		    // Beware: this is a destructive copy. I'm 
+		    // pretty sure this is OK, but ... ;>
+		    buffer[copyto] = buffer[index];
+		}
+		copyto++;
+		index++;
+	    }
+	    if (index >= size) {
+		// didn't find terminating quote, something is seriously wrong
+		//throw new ArrayIndexOutOfBoundsException(
+		//		    "index = " + index + ", size = " + size);
+		return null;
+	    } else
+		index++; // skip past the terminating quote
+
+	    if (returnString) 
+		return toString(buffer, start, copyto);
+	    else
+		return new ByteArray(buffer, start, copyto-start);
+	} else if (b == '{') { // Literal
+	    int start = ++index; // note the start position
+
+	    while (buffer[index] != '}')
+		index++;
+
+	    int count = 0;
+	    try {
+		count = ASCIIUtility.parseInt(buffer, start, index);
+	    } catch (NumberFormatException nex) { 
+	   	// throw new ParsingException();
+		return null;
+	    }
+
+	    start = index + 3; // skip "}\r\n"
+	    index = start + count; // position index to beyond the literal
+
+	    if (returnString) // return as String
+		return toString(buffer, start, start + count);
+	    else
+	    	return new ByteArray(buffer, start, count);
+	} else if (parseAtoms) { // parse as ASTRING-CHARs
+	    int start = index;	// track this, so that we can use to
+				// creating ByteArrayInputStream below.
+	    String s = readDelimString(ASTRING_CHAR_DELIM);
+	    if (returnString)
+		return s;
+	    else  // *very* unlikely
+		return new ByteArray(buffer, start, index);
+	} else if (b == 'N' || b == 'n') { // the only valid value is 'NIL'
+	    index += 3; // skip past NIL
+	    return null;
+	}
+	return null; // Error
+    }
+
+    private String toString(byte[] buffer, int start, int end) {
+	return utf8 ?
+		new String(buffer, start, end - start, StandardCharsets.UTF_8) :
+		ASCIIUtility.toString(buffer, start, end);
+    }
+
+    public int getType() {
+	return type;
+    }
+
+    public boolean isContinuation() {
+	return ((type & TAG_MASK) == CONTINUATION);
+    }
+
+    public boolean isTagged() {
+	return ((type & TAG_MASK) == TAGGED);
+    }
+
+    public boolean isUnTagged() {
+	return ((type & TAG_MASK) == UNTAGGED);
+    }
+
+    public boolean isOK() {
+	return ((type & TYPE_MASK) == OK);
+    }
+
+    public boolean isNO() {
+	return ((type & TYPE_MASK) == NO);
+    }
+
+    public boolean isBAD() {
+	return ((type & TYPE_MASK) == BAD);
+    }
+
+    public boolean isBYE() {
+	return ((type & TYPE_MASK) == BYE);
+    }
+
+    public boolean isSynthetic() {
+	return ((type & SYNTHETIC) == SYNTHETIC);
+    }
+
+    /**
+     * Return the tag, if this is a tagged statement.
+     *
+     * @return tag of this tagged statement
+     */
+    public String getTag() {
+	return tag;
+    }
+
+    /**
+     * Return the rest of the response as a string, usually used to
+     * return the arbitrary message text after a NO response.
+     *
+     * @return	the rest of the response
+     */
+    public String getRest() {
+	skipSpaces();
+	return toString(buffer, index, size);
+    }
+
+    /**
+     * Return the exception for a synthetic BYE response.
+     *
+     * @return	the exception
+     * @since	JavaMail 1.5.4
+     */
+    public Exception getException() {
+	return ex;
+    }
+
+    /**
+     * Reset pointer to beginning of response.
+     */
+    public void reset() {
+	index = pindex;
+    }
+
+    @Override
+    public String toString() {
+	return toString(buffer, 0, size);
+    }
+
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/ResponseHandler.java b/mail/src/main/java/com/sun/mail/iap/ResponseHandler.java
new file mode 100644
index 0000000..7d5a61c
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/ResponseHandler.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+/**
+ * This class 
+ *
+ * @author  John Mani
+ */
+
+public interface ResponseHandler { 
+    public void handleResponse(Response r);
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/ResponseInputStream.java b/mail/src/main/java/com/sun/mail/iap/ResponseInputStream.java
new file mode 100644
index 0000000..9e05b18
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/ResponseInputStream.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.*;
+import com.sun.mail.iap.ByteArray;
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ *
+ * Inputstream that is used to read a Response.
+ *
+ * @author  Arun Krishnan
+ * @author  Bill Shannon
+ */
+
+public class ResponseInputStream {
+
+    private static final int minIncrement = 256;
+    private static final int maxIncrement = 256 * 1024;
+    private static final int incrementSlop = 16;
+
+    // where we read from
+    private BufferedInputStream bin;
+
+    /**
+     * Constructor.
+     *
+     * @param	in	the InputStream to wrap
+     */
+    public ResponseInputStream(InputStream in) {
+	bin = new BufferedInputStream(in, 2 * 1024);
+    }
+
+    /**
+     * Read a Response from the InputStream.
+     *
+     * @return		ByteArray that contains the Response
+     * @exception	IOException	for I/O errors
+     */
+    public ByteArray readResponse() throws IOException {
+	return readResponse(null);
+    }
+
+    /**
+     * Read a Response from the InputStream.
+     *
+     * @param	ba	the ByteArray in which to store the response, or null
+     * @return		ByteArray that contains the Response
+     * @exception	IOException	for I/O errors
+     */
+    public ByteArray readResponse(ByteArray ba) throws IOException {
+	if (ba == null)
+	    ba = new ByteArray(new byte[128], 0, 128);
+
+	byte[] buffer = ba.getBytes();
+	int idx = 0;
+	for (;;) {	// read until CRLF with no preceeding literal
+	    // XXX - b needs to be an int, to handle bytes with value 0xff
+	    int b = 0;
+	    boolean gotCRLF=false;
+
+	    // Read a CRLF terminated line from the InputStream
+	    while (!gotCRLF &&
+		   ((b =  bin.read()) != -1)) {
+		if (b == '\n') {
+		    if ((idx > 0) && buffer[idx-1] == '\r')
+			gotCRLF = true;
+		}
+		if (idx >= buffer.length) {
+		    int incr = buffer.length;
+		    if (incr > maxIncrement)
+			incr = maxIncrement;
+		    ba.grow(incr);
+		    buffer = ba.getBytes();
+		}
+		buffer[idx++] = (byte)b;
+	    }
+
+	    if (b == -1)
+		throw new IOException("Connection dropped by server?");
+
+	    // Now lets check for literals : {<digits>}CRLF
+	    // Note: index needs to >= 5 for the above sequence to occur
+	    if (idx < 5 || buffer[idx-3] != '}')
+		break;
+
+	    int i;
+	    // look for left curly
+	    for (i = idx - 4; i >= 0; i--)
+		if (buffer[i] == '{')
+		    break;
+
+	    if (i < 0) // Nope, not a literal ?
+		break;
+
+	    int count = 0;
+	    // OK, handle the literal ..
+	    try {
+		count = ASCIIUtility.parseInt(buffer, i+1, idx-3);
+	    } catch (NumberFormatException e) {
+		break;
+	    }
+
+	    // Now read 'count' bytes. (Note: count could be 0)
+	    if (count > 0) {
+		int avail = buffer.length - idx; // available space in buffer
+		if (count + incrementSlop > avail) {
+		    // need count-avail more bytes
+		    ba.grow(minIncrement > count + incrementSlop - avail ? 
+			    minIncrement : count + incrementSlop - avail);
+		    buffer = ba.getBytes();
+		}
+
+		/*
+		 * read() might not return all the bytes in one shot,
+		 * so call repeatedly till we are done
+		 */
+		int actual;
+		while (count > 0) {
+		    actual = bin.read(buffer, idx, count);
+		    if (actual == -1)
+			throw new IOException("Connection dropped by server?");
+		    count -= actual;
+		    idx += actual;
+		}
+	    }
+	    // back to top of loop to read until CRLF
+	}
+	ba.setCount(idx);
+	return ba;
+    }
+
+    /**
+     * How much buffered data do we have?
+     *
+     * @return	number of bytes available
+     * @exception	IOException	if the stream has been closed
+     * @since	JavaMail 1.5.4
+     */
+    public int available() throws IOException {
+	return bin.available();
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/iap/package.html b/mail/src/main/java/com/sun/mail/iap/package.html
new file mode 100644
index 0000000..2607e33
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/iap/package.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.iap package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+This package includes internal IMAP support classes and
+<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/com/sun/mail/imap/ACL.java b/mail/src/main/java/com/sun/mail/imap/ACL.java
new file mode 100644
index 0000000..ee99c95
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/ACL.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.*;
+
+/**
+ * An access control list entry for a particular authentication identifier
+ * (user or group).  Associates a set of Rights with the identifier.
+ * See RFC 2086.
+ * <p>
+ *
+ * @author Bill Shannon
+ */
+
+public class ACL implements Cloneable {
+
+    private String name;
+    private Rights rights;
+
+    /**
+     * Construct an ACL entry for the given identifier and with no rights.
+     *
+     * @param	name	the identifier name
+     */
+    public ACL(String name) {
+	this.name = name;
+	this.rights = new Rights();
+    }
+
+    /**
+     * Construct an ACL entry for the given identifier with the given rights.
+     *
+     * @param	name	the identifier name
+     * @param	rights	the rights
+     */
+    public ACL(String name, Rights rights) {
+	this.name = name;
+	this.rights = rights;
+    }
+
+    /**
+     * Get the identifier name for this ACL entry.
+     *
+     * @return	the identifier name
+     */
+    public String getName() {
+	return name;
+    }
+
+    /**
+     * Set the rights associated with this ACL entry.
+     *
+     * @param	rights	the rights
+     */
+    public void setRights(Rights rights) {
+	this.rights = rights;
+    }
+
+    /**
+     * Get the rights associated with this ACL entry.
+     * Returns the actual Rights object referenced by this ACL;
+     * modifications to the Rights object will effect this ACL.
+     *
+     * @return	the rights
+     */
+    public Rights getRights() {
+	return rights;
+    }
+
+    /**
+     * Clone this ACL entry.
+     */
+    @Override
+    public Object clone() throws CloneNotSupportedException {
+	ACL acl = (ACL)super.clone();
+	acl.rights = (Rights)this.rights.clone();
+	return acl;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/AppendUID.java b/mail/src/main/java/com/sun/mail/imap/AppendUID.java
new file mode 100644
index 0000000..56c77fe
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/AppendUID.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import com.sun.mail.iap.*;
+
+/**
+ * Information from the APPENDUID response code
+ * defined by the UIDPLUS extension -
+ * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
+ *
+ * @author  Bill Shannon
+ */
+
+public class AppendUID { 
+    public long uidvalidity = -1;
+    public long uid = -1;
+
+    public AppendUID(long uidvalidity, long uid) {
+	this.uidvalidity = uidvalidity;
+	this.uid = uid;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/CopyUID.java b/mail/src/main/java/com/sun/mail/imap/CopyUID.java
new file mode 100644
index 0000000..3648fc5
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/CopyUID.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import com.sun.mail.imap.protocol.UIDSet;
+
+/**
+ * Information from the COPYUID response code
+ * defined by the UIDPLUS extension -
+ * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
+ *
+ * @author  Bill Shannon
+ */
+
+public class CopyUID { 
+    public long uidvalidity = -1;
+    public UIDSet[] src;
+    public UIDSet[] dst;
+
+    public CopyUID(long uidvalidity, UIDSet[] src, UIDSet[] dst) {
+	this.uidvalidity = uidvalidity;
+	this.src = src;
+	this.dst = dst;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/DefaultFolder.java b/mail/src/main/java/com/sun/mail/imap/DefaultFolder.java
new file mode 100644
index 0000000..9dab04f
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/DefaultFolder.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.MethodNotSupportedException;
+import com.sun.mail.iap.ProtocolException;
+import com.sun.mail.imap.protocol.IMAPProtocol;
+import com.sun.mail.imap.protocol.ListInfo;
+
+/**
+ * The default IMAP folder (root of the naming hierarchy).
+ *
+ * @author  John Mani
+ */
+
+public class DefaultFolder extends IMAPFolder {
+    
+    protected DefaultFolder(IMAPStore store) {
+	super("", UNKNOWN_SEPARATOR, store, null);
+	exists = true; // of course
+	type = HOLDS_FOLDERS; // obviously
+    }
+
+    @Override
+    public synchronized String getName() {
+	return fullName;
+    }
+
+    @Override
+    public Folder getParent() {
+	return null;
+    }
+
+    @Override
+    public synchronized Folder[] list(final String pattern)
+				throws MessagingException {
+	ListInfo[] li = null;
+
+	li = (ListInfo[])doCommand(new ProtocolCommand() {
+	    @Override
+	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
+		return p.list("", pattern);
+	    }
+	});
+
+	if (li == null)
+	    return new Folder[0];
+
+	IMAPFolder[] folders = new IMAPFolder[li.length];
+	for (int i = 0; i < folders.length; i++)
+	    folders[i] = ((IMAPStore)store).newIMAPFolder(li[i]);
+	return folders;
+    }
+
+    @Override
+    public synchronized Folder[] listSubscribed(final String pattern)
+				throws MessagingException {
+	ListInfo[] li = null;
+
+	li = (ListInfo[])doCommand(new ProtocolCommand() {
+	    @Override
+	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
+		return p.lsub("", pattern);
+	    }
+	});
+
+	if (li == null)
+	    return new Folder[0];
+
+	IMAPFolder[] folders = new IMAPFolder[li.length];
+	for (int i = 0; i < folders.length; i++)
+	    folders[i] = ((IMAPStore)store).newIMAPFolder(li[i]);
+	return folders;
+    }
+
+    @Override
+    public boolean hasNewMessages() throws MessagingException {
+	// Not applicable on DefaultFolder
+	return false;
+    }
+
+    @Override
+    public Folder getFolder(String name) throws MessagingException {
+	return ((IMAPStore)store).newIMAPFolder(name, UNKNOWN_SEPARATOR);
+    }
+
+    @Override
+    public boolean delete(boolean recurse) throws MessagingException {  
+	// Not applicable on DefaultFolder
+	throw new MethodNotSupportedException("Cannot delete Default Folder");
+    }
+
+    @Override
+    public boolean renameTo(Folder f) throws MessagingException {
+	// Not applicable on DefaultFolder
+	throw new MethodNotSupportedException("Cannot rename Default Folder");
+    }
+
+    @Override
+    public void appendMessages(Message[] msgs) throws MessagingException {
+	// Not applicable on DefaultFolder
+	throw new MethodNotSupportedException("Cannot append to Default Folder");
+    }
+
+    @Override
+    public Message[] expunge() throws MessagingException {
+	// Not applicable on DefaultFolder
+	throw new MethodNotSupportedException("Cannot expunge Default Folder");
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPBodyPart.java b/mail/src/main/java/com/sun/mail/imap/IMAPBodyPart.java
new file mode 100644
index 0000000..181bb94
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPBodyPart.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+
+import java.util.Enumeration;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.ReadableMime;
+import com.sun.mail.util.LineOutputStream;
+import com.sun.mail.util.SharedByteArrayOutputStream;
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+
+/**
+ * An IMAP body part.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class IMAPBodyPart extends MimeBodyPart implements ReadableMime {
+    private IMAPMessage message;
+    private BODYSTRUCTURE bs;
+    private String sectionId;
+
+    // processed values ..
+    private String type;
+    private String description;
+
+    private boolean headersLoaded = false;
+
+    private static final boolean decodeFileName =
+	PropUtil.getBooleanSystemProperty("mail.mime.decodefilename", false);
+
+    protected IMAPBodyPart(BODYSTRUCTURE bs, String sid, IMAPMessage message) {
+	super();
+	this.bs = bs;
+	this.sectionId = sid;
+	this.message = message;
+	// generate content-type
+	ContentType ct = new ContentType(bs.type, bs.subtype, bs.cParams);
+	type = ct.toString();
+    }
+
+    /* Override this method to make it a no-op, rather than throw
+     * an IllegalWriteException. This will permit IMAPBodyParts to
+     * be inserted in newly crafted MimeMessages, especially when
+     * forwarding or replying to messages.
+     */
+    @Override
+    protected void updateHeaders() {
+	return;
+    }
+
+    @Override
+    public int getSize() throws MessagingException {
+	return bs.size;
+    }
+
+    @Override
+    public int getLineCount() throws MessagingException {
+	return bs.lines;
+    }
+
+    @Override
+    public String getContentType() throws MessagingException {
+	return type;
+    }
+
+    @Override
+    public String getDisposition() throws MessagingException {
+	return bs.disposition;
+    }
+
+    @Override
+    public void setDisposition(String disposition) throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public String getEncoding() throws MessagingException {
+	return bs.encoding;
+    }
+
+    @Override
+    public String getContentID() throws MessagingException {
+	return bs.id;
+    }
+
+    @Override
+    public String getContentMD5() throws MessagingException {
+	return bs.md5;
+    }
+
+    @Override
+    public void setContentMD5(String md5) throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public String getDescription() throws MessagingException {
+	if (description != null) // cached value ?
+	    return description;
+
+	if (bs.description == null)
+	    return null;
+	
+	try {
+	    description = MimeUtility.decodeText(bs.description);
+	} catch (UnsupportedEncodingException ex) {
+	    description = bs.description;
+	}
+
+	return description;
+    }
+
+    @Override
+    public void setDescription(String description, String charset)
+			throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public String getFileName() throws MessagingException {
+	String filename = null;
+	if (bs.dParams != null)
+	    filename = bs.dParams.get("filename");
+	if ((filename == null || filename.isEmpty()) && bs.cParams != null)
+	    filename = bs.cParams.get("name");
+	if (decodeFileName && filename != null) {
+	    try {
+		filename = MimeUtility.decodeText(filename);
+	    } catch (UnsupportedEncodingException ex) {
+		throw new MessagingException("Can't decode filename", ex);
+	    }
+	}
+	return filename;
+    }
+
+    @Override
+    public void setFileName(String filename) throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    protected InputStream getContentStream() throws MessagingException {
+	InputStream is = null;
+	boolean pk = message.getPeek();	// acquire outside of message cache lock
+
+        // Acquire MessageCacheLock, to freeze seqnum.
+        synchronized(message.getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = message.getProtocol();
+
+		// Check whether this message is expunged
+		message.checkExpunged();
+
+		if (p.isREV1() && (message.getFetchBlockSize() != -1))
+		    return new IMAPInputStream(message, sectionId,
+			message.ignoreBodyStructureSize() ? -1 : bs.size, pk);
+
+		// Else, vanila IMAP4, no partial fetch 
+
+		int seqnum = message.getSequenceNumber();
+		BODY b;
+		if (pk)
+		    b = p.peekBody(seqnum, sectionId);
+		else
+		    b = p.fetchBody(seqnum, sectionId);
+		if (b != null)
+		    is = b.getByteArrayInputStream();
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(
+			message.getFolder(), cex.getMessage());
+	    } catch (ProtocolException pex) { 
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+
+	if (is == null) {
+	    message.forceCheckExpunged(); // may throw MessageRemovedException
+	    // nope, the server doesn't think it's expunged.
+	    // can't tell the difference between the server returning NIL
+	    // and some other error that caused null to be returned above,
+	    // so we'll just assume it was empty content.
+	    is = new ByteArrayInputStream(new byte[0]);
+	}
+	return is;
+    }
+
+    /**
+     * Return the MIME format stream of headers for this body part.
+     */
+    private InputStream getHeaderStream() throws MessagingException {
+	if (!message.isREV1())
+	    loadHeaders();	// will be needed below
+
+	// Acquire MessageCacheLock, to freeze seqnum.
+	synchronized(message.getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = message.getProtocol();
+
+		// Check whether this message got expunged
+		message.checkExpunged();
+
+		if (p.isREV1()) {
+		    int seqnum = message.getSequenceNumber();
+		    BODY b = p.peekBody(seqnum, sectionId + ".MIME");
+
+		    if (b == null)
+			throw new MessagingException("Failed to fetch headers");
+
+		    ByteArrayInputStream bis = b.getByteArrayInputStream();
+		    if (bis == null)
+			throw new MessagingException("Failed to fetch headers");
+		    return bis;
+
+		} else {
+		    // Can't read it from server, have to fake it
+		    SharedByteArrayOutputStream bos =
+			new SharedByteArrayOutputStream(0);
+		    LineOutputStream los = new LineOutputStream(bos);
+
+		    try {
+			// Write out the header
+			Enumeration<String> hdrLines
+				= super.getAllHeaderLines();
+			while (hdrLines.hasMoreElements())
+			    los.writeln(hdrLines.nextElement());
+
+			// The CRLF separator between header and content
+			los.writeln();
+		    } catch (IOException ioex) {
+			// should never happen
+		    } finally {
+			try {
+			    los.close();
+			} catch (IOException cex) { }
+		    }
+		    return bos.toStream();
+		}
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(
+			    message.getFolder(), cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+    }
+
+    /**
+     * Return the MIME format stream corresponding to this message part.
+     *
+     * @return	the MIME format stream
+     * @since	JavaMail 1.4.5
+     */
+    @Override
+    public InputStream getMimeStream() throws MessagingException {
+	/*
+	 * The IMAP protocol doesn't support returning the entire
+	 * part content in one operation so we have to fake it by
+	 * concatenating the header stream and the content stream.
+	 */
+	return new SequenceInputStream(getHeaderStream(), getContentStream());
+    }
+	    
+    @Override
+    public synchronized DataHandler getDataHandler() 
+		throws MessagingException {
+	if (dh == null) {
+	   if (bs.isMulti())
+		dh = new DataHandler(
+			new IMAPMultipartDataSource(
+				this, bs.bodies, sectionId, message)
+		     );
+	    else if (bs.isNested() && message.isREV1() && bs.envelope != null)
+		dh = new DataHandler(
+			new IMAPNestedMessage(message, 
+					      bs.bodies[0],
+					      bs.envelope,
+					      sectionId),
+			type
+		     );
+	}
+
+	return super.getDataHandler();
+    }
+
+    @Override
+    public void setDataHandler(DataHandler content) throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public void setContent(Object o, String type) throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public void setContent(Multipart mp) throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public String[] getHeader(String name) throws MessagingException {
+	loadHeaders();
+	return super.getHeader(name);
+    }
+
+    @Override
+    public void setHeader(String name, String value)
+		throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public void addHeader(String name, String value)
+		throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public void removeHeader(String name) throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public Enumeration<Header> getAllHeaders() throws MessagingException {
+	loadHeaders();
+	return super.getAllHeaders();
+    }
+
+    @Override
+    public Enumeration<Header> getMatchingHeaders(String[] names)
+		throws MessagingException {
+	loadHeaders();
+	return super.getMatchingHeaders(names);
+    }
+
+    @Override
+    public Enumeration<Header> getNonMatchingHeaders(String[] names)
+		throws MessagingException {
+	loadHeaders();
+	return super.getNonMatchingHeaders(names);
+    }
+
+    @Override
+    public void addHeaderLine(String line) throws MessagingException {
+	throw new IllegalWriteException("IMAPBodyPart is read-only");
+    }
+
+    @Override
+    public Enumeration<String> getAllHeaderLines() throws MessagingException {
+	loadHeaders();
+	return super.getAllHeaderLines();
+    }
+
+    @Override
+    public Enumeration<String> getMatchingHeaderLines(String[] names)
+		throws MessagingException {
+	loadHeaders();
+	return super.getMatchingHeaderLines(names);
+    }
+
+    @Override
+    public Enumeration<String> getNonMatchingHeaderLines(String[] names)
+		throws MessagingException {
+	loadHeaders();
+	return super.getNonMatchingHeaderLines(names);
+    }
+
+    private synchronized void loadHeaders() throws MessagingException {
+	if (headersLoaded)
+	    return;
+
+	// "headers" should never be null since it's set in the constructor.
+	// If something did go wrong this will fix it, but is an unsynchronized
+	// assignment of "headers".
+	if (headers == null)
+	    headers = new InternetHeaders();
+
+	// load headers
+
+	// Acquire MessageCacheLock, to freeze seqnum.
+	synchronized(message.getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = message.getProtocol();
+
+		// Check whether this message got expunged
+		message.checkExpunged();
+
+		if (p.isREV1()) {
+		    int seqnum = message.getSequenceNumber();
+		    BODY b = p.peekBody(seqnum, sectionId + ".MIME");
+
+		    if (b == null)
+			throw new MessagingException("Failed to fetch headers");
+
+		    ByteArrayInputStream bis = b.getByteArrayInputStream();
+		    if (bis == null)
+			throw new MessagingException("Failed to fetch headers");
+
+		    headers.load(bis);
+
+		} else {
+
+		    // RFC 1730 does not provide for fetching BodyPart headers
+		    // So, just dump the RFC1730 BODYSTRUCTURE into the
+		    // headerStore
+		    
+		    // Content-Type
+		    headers.addHeader("Content-Type", type);
+		    // Content-Transfer-Encoding
+		    headers.addHeader("Content-Transfer-Encoding", bs.encoding);
+		    // Content-Description
+		    if (bs.description != null)
+			headers.addHeader("Content-Description",
+							    bs.description);
+		    // Content-ID
+		    if (bs.id != null)
+			headers.addHeader("Content-ID", bs.id);
+		    // Content-MD5
+		    if (bs.md5 != null)
+			headers.addHeader("Content-MD5", bs.md5);
+		}
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(
+			    message.getFolder(), cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+	headersLoaded = true;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java b/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
new file mode 100644
index 0000000..74f2f56
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPFolder.java
@@ -0,0 +1,4153 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Date;
+import java.util.Vector;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.io.*;
+import java.net.SocketTimeoutException;
+import java.nio.channels.SocketChannel;
+
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+import javax.mail.search.*;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.CRLFOutputStream;
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+
+/**
+ * This class implements an IMAP folder. <p>
+ *
+ * A closed IMAPFolder object shares a protocol connection with its IMAPStore
+ * object. When the folder is opened, it gets its own protocol connection. <p>
+ *
+ * Applications that need to make use of IMAP-specific features may cast
+ * a <code>Folder</code> object to an <code>IMAPFolder</code> object and
+ * use the methods on this class. <p>
+ *
+ * The {@link #getQuota getQuota} and
+ * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
+ * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
+ * for more information. <p>
+ *
+ * The {@link #getACL getACL}, {@link #addACL addACL},
+ * {@link #removeACL removeACL}, {@link #addRights addRights},
+ * {@link #removeRights removeRights}, {@link #listRights listRights}, and
+ * {@link #myRights myRights} methods support the IMAP ACL extension.
+ * Refer to <A HREF="http://www.ietf.org/rfc/rfc2086.txt">RFC 2086</A>
+ * for more information. <p>
+ *
+ * The {@link #getSortedMessages getSortedMessages}
+ * methods support the IMAP SORT extension.
+ * Refer to <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>
+ * for more information. <p>
+ *
+ * The {@link #open(int,com.sun.mail.imap.ResyncData) open(int,ResyncData)}
+ * method and {@link com.sun.mail.imap.ResyncData ResyncData} class supports
+ * the IMAP CONDSTORE and QRESYNC extensions.
+ * Refer to <A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>
+ * and <A HREF="http://www.ietf.org/rfc/rfc5162.txt">RFC 5162</A>
+ * for more information. <p>
+ *
+ * The {@link #doCommand doCommand} method and
+ * {@link IMAPFolder.ProtocolCommand IMAPFolder.ProtocolCommand}
+ * interface support use of arbitrary IMAP protocol commands. <p>
+ *
+ * See the <a href="package-summary.html">com.sun.mail.imap</a> package
+ * documentation for further information on the IMAP protocol provider. <p>
+ *
+ * <strong>WARNING:</strong> The APIs unique to this class should be
+ * considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
+ * future in ways that are incompatible with applications using the
+ * current APIs.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ * @author  Jim Glennon
+ */
+
+/*
+ * The folder object itself serves as a lock for the folder's state
+ * EXCEPT for the message cache (see below), typically by using
+ * synchronized methods.  When checking that a folder is open or
+ * closed, the folder's lock must be held.  It's important that the
+ * folder's lock is acquired before the messageCacheLock (see below).
+ * Thus, the locking hierarchy is that the folder lock, while optional,
+ * must be acquired before the messageCacheLock, if it's acquired at
+ * all.  Be especially careful of callbacks that occur while holding
+ * the messageCacheLock into (e.g.) superclass Folder methods that are
+ * synchronized.  Note that methods in IMAPMessage will acquire the
+ * messageCacheLock without acquiring the folder lock. <p>
+ *
+ * When a folder is opened, it creates a messageCache (a Vector) of 
+ * empty IMAPMessage objects. Each Message has a messageNumber - which
+ * is its index into the messageCache, and a sequenceNumber - which is
+ * its IMAP sequence-number. All operations on a Message which involve
+ * communication with the server, use the message's sequenceNumber. <p>
+ *
+ * The most important thing to note here is that the server can send
+ * unsolicited EXPUNGE notifications as part of the responses for "most"
+ * commands. Refer RFC 3501, sections 5.3 & 5.5 for gory details. Also, 
+ * the server sends these  notifications AFTER the message has been 
+ * expunged. And once a message is expunged, the sequence-numbers of 
+ * those messages after the expunged one are renumbered. This essentially
+ * means that the mapping between *any* Message and its sequence-number 
+ * can change in the period when a IMAP command is issued and its responses
+ * are processed. Hence we impose a strict locking model as follows: <p>
+ *
+ * We define one mutex per folder - this is just a Java Object (named 
+ * messageCacheLock). Any time a command is to be issued to the IMAP
+ * server (i.e., anytime the corresponding IMAPProtocol method is
+ * invoked), follow the below style:
+ *		
+ *	synchronized (messageCacheLock) { // ACQUIRE LOCK
+ *	    issue command ()
+ *	    
+ *	    // The response processing is typically done within
+ *	    // the handleResponse() callback. A few commands (Fetch,
+ *	    // Expunge) return *all* responses and hence their
+ *	    // processing is done here itself. Now, as part of the
+ *	    // processing unsolicited EXPUNGE responses, we renumber
+ *	    // the necessary sequence-numbers. Thus the renumbering
+ *	    // happens within this critical-region, surrounded by
+ *	    // locks.
+ *	    process responses ()
+ *	} // RELEASE LOCK
+ *
+ * This technique is used both by methods in IMAPFolder and by methods
+ * in IMAPMessage and other classes that operate on data in the folder.
+ * Note that holding the messageCacheLock has the side effect of
+ * preventing the folder from being closed, and thus ensuring that the
+ * folder's protocol object is still valid.  The protocol object should
+ * only be accessed while holding the messageCacheLock (except for calls
+ * to IMAPProtocol.isREV1(), which don't need to be protected because it
+ * doesn't access the server).
+ *	    
+ * Note that interactions with the Store's protocol connection do
+ * not have to be protected as above, since the Store's protocol is
+ * never in a "meaningful" SELECT-ed state.
+ */
+
+public class IMAPFolder extends Folder implements UIDFolder, ResponseHandler {
+    
+    protected volatile String fullName;	// full name
+    protected String name;		// name
+    protected int type;			// folder type. 
+    protected char separator;		// separator
+    protected Flags availableFlags; 	// available flags
+    protected Flags permanentFlags; 	// permanent flags
+    protected volatile boolean exists;	// whether this folder really exists ?
+    protected boolean isNamespace = false; // folder is a namespace name
+    protected volatile String[] attributes;// name attributes from LIST response
+
+    protected volatile IMAPProtocol protocol; // this folder's protocol object
+    protected MessageCache messageCache;// message cache
+    // accessor lock for message cache
+    protected final Object messageCacheLock = new Object();
+
+    protected Hashtable<Long, IMAPMessage> uidTable; // UID->Message hashtable
+
+    /* An IMAP delimiter is a 7bit US-ASCII character. (except NUL).
+     * We use '\uffff' (a non 7bit character) to indicate that we havent
+     * yet determined what the separator character is.
+     * We use '\u0000' (NUL) to indicate that no separator character
+     * exists, i.e., a flat hierarchy
+     */
+    static final protected char UNKNOWN_SEPARATOR = '\uffff';
+
+    private volatile boolean opened = false; 	// is this folder opened ?
+
+    /* This field tracks the state of this folder. If the folder is closed
+     * due to external causes (i.e, not thru the close() method), then
+     * this field will remain false. If the folder is closed thru the
+     * close() method, then this field is set to true.
+     *
+     * If reallyClosed is false, then a FolderClosedException is
+     * generated when a method is invoked on any Messaging object
+     * owned by this folder. If reallyClosed is true, then the
+     * IllegalStateException runtime exception is thrown.
+     */
+    private boolean reallyClosed = true;
+
+    /*
+     * The idleState field supports the IDLE command.
+     * Normally when executing an IMAP command we hold the
+     * messageCacheLock and often the folder lock (see above).
+     * While executing the IDLE command we can't hold either
+     * of these locks or it would prevent other threads from
+     * entering Folder methods even far enough to check whether
+     * an IDLE command is in progress.  We need to check before
+     * issuing another command so that we can abort the IDLE
+     * command.
+     *
+     * The idleState field is protected by the messageCacheLock.
+     * The RUNNING state is the normal state and means no IDLE
+     * command is in progress.  The IDLE state means we've issued
+     * an IDLE command and are reading responses.  The ABORTING
+     * state means we've sent the DONE continuation command and
+     * are waiting for the thread running the IDLE command to
+     * break out of its read loop.
+     *
+     * When an IDLE command is in progress, the thread calling
+     * the idle method will be reading from the IMAP connection
+     * while holding neither the folder lock nor the messageCacheLock.
+     * It's obviously critical that no other thread try to send a
+     * command or read from the connection while in this state.
+     * However, other threads can send the DONE continuation
+     * command that will cause the server to break out of the IDLE
+     * loop and send the ending tag response to the IDLE command.
+     * The thread in the idle method that's reading the responses
+     * from the IDLE command will see this ending response and
+     * complete the idle method, setting the idleState field back
+     * to RUNNING, and notifying any threads waiting to use the
+     * connection.
+     *
+     * All uses of the IMAP connection (IMAPProtocol object) must
+     * be done while holding the messageCacheLock and must be
+     * preceeded by a check to make sure an IDLE command is not
+     * running, and abort the IDLE command if necessary.  While
+     * waiting for the IDLE command to complete, these other threads
+     * will give up the messageCacheLock, but might still be holding
+     * the folder lock.  This check is done by the getProtocol()
+     * method, resulting in a typical usage pattern of:
+     *
+     *	    synchronized (messageCacheLock) {
+     *		IMAPProtocol p = getProtocol();	// may block waiting for IDLE
+     *		// ... use protocol
+     *	    }
+     */
+    private static final int RUNNING = 0;	// not doing IDLE command
+    private static final int IDLE = 1;		// IDLE command in effect
+    private static final int ABORTING = 2;	// IDLE command aborting
+    private int idleState = RUNNING;
+    private IdleManager idleManager;
+
+    private volatile int total = -1;	// total number of messages in the
+					// message cache
+    private volatile int recent = -1;	// number of recent messages
+    private int realTotal = -1;		// total number of messages on
+    					// the server
+    private long uidvalidity = -1;	// UIDValidity
+    private long uidnext = -1;		// UIDNext
+    private boolean uidNotSticky = false;	// RFC 4315
+    private volatile long highestmodseq = -1;	// RFC 4551 - CONDSTORE
+    private boolean doExpungeNotification = true; // used in expunge handler
+
+    private Status cachedStatus = null;
+    private long cachedStatusTime = 0;
+
+    private boolean hasMessageCountListener = false;	// optimize notification
+
+    protected MailLogger logger;
+    private MailLogger connectionPoolLogger;
+
+    /**
+     * A fetch profile item for fetching headers.
+     * This inner class extends the <code>FetchProfile.Item</code>
+     * class to add new FetchProfile item types, specific to IMAPFolders.
+     *
+     * @see FetchProfile
+     */
+    public static class FetchProfileItem extends FetchProfile.Item {
+	protected FetchProfileItem(String name) {
+	    super(name);
+	}
+
+	/**
+	 * HEADERS is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the headers for messages in the specified 
+	 * range are desired to be prefetched. <p>
+	 * 
+	 * An example of how a client uses this is below:
+	 * <blockquote><pre>
+	 *
+	 * 	FetchProfile fp = new FetchProfile();
+	 *	fp.add(IMAPFolder.FetchProfileItem.HEADERS);
+	 *	folder.fetch(msgs, fp);
+	 *
+	 * </pre></blockquote>
+	 */ 
+	public static final FetchProfileItem HEADERS = 
+		new FetchProfileItem("HEADERS");
+
+	/**
+	 * SIZE is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the sizes of the messages in the specified 
+	 * range are desired to be prefetched. <p>
+	 *
+	 * SIZE was moved to FetchProfile.Item in JavaMail 1.5.
+	 *
+	 * @deprecated
+	 */
+	@Deprecated
+	public static final FetchProfileItem SIZE = 
+		new FetchProfileItem("SIZE");
+
+	/**
+	 * MESSAGE is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the entire messages (headers and body,
+	 * including all "attachments") in the specified 
+	 * range are desired to be prefetched.  Note that the entire message
+	 * content is cached in memory while the Folder is open.  The cached
+	 * message will be parsed locally to return header information and
+	 * message content. <p>
+	 * 
+	 * An example of how a client uses this is below:
+	 * <blockquote><pre>
+	 *
+	 * 	FetchProfile fp = new FetchProfile();
+	 *	fp.add(IMAPFolder.FetchProfileItem.MESSAGE);
+	 *	folder.fetch(msgs, fp);
+	 *
+	 * </pre></blockquote>
+	 *
+	 * @since	JavaMail 1.5.2
+	 */ 
+	public static final FetchProfileItem MESSAGE = 
+		new FetchProfileItem("MESSAGE");
+
+	/**
+	 * INTERNALDATE is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the IMAP INTERNALDATE values
+	 * (received date) of the messages in the specified 
+	 * range are desired to be prefetched.  <p>
+	 * 
+	 * An example of how a client uses this is below:
+	 * <blockquote><pre>
+	 *
+	 * 	FetchProfile fp = new FetchProfile();
+	 *	fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
+	 *	folder.fetch(msgs, fp);
+	 *
+	 * </pre></blockquote>
+	 *
+	 * @since	JavaMail 1.5.5
+	 */ 
+	public static final FetchProfileItem INTERNALDATE = 
+		new FetchProfileItem("INTERNALDATE");
+    }
+
+    /**
+     * Constructor used to create a possibly non-existent folder.
+     *
+     * @param fullName	fullname of this folder
+     * @param separator the default separator character for this 
+     *			folder's namespace
+     * @param store	the Store
+     * @param isNamespace if this folder represents a namespace
+     */
+    protected IMAPFolder(String fullName, char separator, IMAPStore store,
+				Boolean isNamespace) {
+	super(store);
+	if (fullName == null)
+	    throw new NullPointerException("Folder name is null");
+	this.fullName = fullName;
+	this.separator = separator;
+	logger = new MailLogger(this.getClass(), "DEBUG IMAP",
+	    store.getSession().getDebug(), store.getSession().getDebugOut());
+	connectionPoolLogger = store.getConnectionPoolLogger();
+
+	/*
+	 * Work around apparent bug in Exchange.  Exchange
+	 * will return a name of "Public Folders/" from
+	 * LIST "%".
+	 *
+	 * If name has one separator, and it's at the end,
+	 * assume this is a namespace name and treat it
+	 * accordingly.  Usually this will happen as a result
+	 * of the list method, but this also allows getFolder
+	 * to work with namespace names.
+	 */
+	this.isNamespace = false;
+	if (separator != UNKNOWN_SEPARATOR && separator != '\0') {
+	    int i = this.fullName.indexOf(separator);
+	    if (i > 0 && i == this.fullName.length() - 1) {
+		this.fullName = this.fullName.substring(0, i);
+		this.isNamespace = true;
+	    }
+	}
+
+	// if we were given a value, override default chosen above
+	if (isNamespace != null)
+	    this.isNamespace = isNamespace.booleanValue();
+    }
+
+    /**
+     * Constructor used to create an existing folder.
+     *
+     * @param	li	the ListInfo for this folder
+     * @param	store	the store containing this folder
+     */
+    protected IMAPFolder(ListInfo li, IMAPStore store) {
+	this(li.name, li.separator, store, null);
+
+	if (li.hasInferiors)
+	    type |= HOLDS_FOLDERS;
+	if (li.canOpen)
+	    type |= HOLDS_MESSAGES;
+	exists = true;
+	attributes = li.attrs;
+    }
+	
+    /*
+     * Ensure that this folder exists. If 'exists' has been set to true,
+     * we don't attempt to validate it with the server again. Note that
+     * this can result in a possible loss of sync with the server.
+     * ASSERT: Must be called with this folder's synchronization lock held.
+     */
+    protected void checkExists() throws MessagingException {
+	// If the boolean field 'exists' is false, check with the
+	// server by invoking exists() ..
+	if (!exists && !exists())
+	    throw new FolderNotFoundException(
+		this, fullName + " not found");
+    }
+
+    /*
+     * Ensure the folder is closed.
+     * ASSERT: Must be called with this folder's synchronization lock held.
+     */
+    protected void checkClosed() {
+	if (opened)
+	    throw new IllegalStateException(
+		"This operation is not allowed on an open folder"
+		);
+    }
+
+    /*
+     * Ensure the folder is open.
+     * ASSERT: Must be called with this folder's synchronization lock held.
+     */
+    protected void checkOpened() throws FolderClosedException {
+	assert Thread.holdsLock(this);
+	if (!opened) {
+	    if (reallyClosed)
+		throw new IllegalStateException(
+		    "This operation is not allowed on a closed folder"
+	    	);
+	    else // Folder was closed "implicitly"
+		throw new FolderClosedException(this, 
+		    "Lost folder connection to server"
+		);
+	}
+    }
+
+    /*
+     * Check that the given message number is within the range
+     * of messages present in this folder. If the message
+     * number is out of range, we ping the server to obtain any
+     * pending new message notifications from the server.
+     */
+    protected void checkRange(int msgno) throws MessagingException {
+	if (msgno < 1) // message-numbers start at 1
+	    throw new IndexOutOfBoundsException("message number < 1");
+
+	if (msgno <= total)
+	    return;
+
+	// Out of range, let's ping the server and see if
+	// the server has more messages for us.
+
+	synchronized(messageCacheLock) { // Acquire lock
+	    try {
+		keepConnectionAlive(false);
+	    } catch (ConnectionException cex) {
+		// Oops, lost connection
+		throw new FolderClosedException(this, cex.getMessage());
+	    } catch (ProtocolException pex) { 
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	} // Release lock
+
+	if (msgno > total) // Still out of range ? Throw up ...
+	    throw new IndexOutOfBoundsException(msgno + " > " + total);
+    }
+
+    /*
+     * Check whether the given flags are supported by this server,
+     * and also verify that the folder allows setting flags.
+     */
+    private void checkFlags(Flags flags) throws MessagingException {
+	assert Thread.holdsLock(this);
+	if (mode != READ_WRITE)
+	    throw new IllegalStateException(
+		"Cannot change flags on READ_ONLY folder: " + fullName
+		);
+	/*
+	if (!availableFlags.contains(flags))
+	    throw new MessagingException(
+		"These flags are not supported by this implementation"
+		);
+	*/
+    }
+
+    /**
+     * Get the name of this folder.
+     */
+    @Override
+    public synchronized String getName() {
+	/* Return the last component of this Folder's full name.
+	 * Folder components are delimited by the separator character.
+	 */
+	if (name == null) {
+	    try {
+		name = 	fullName.substring(
+			    fullName.lastIndexOf(getSeparator()) + 1
+			);
+	    } catch (MessagingException mex) { }
+	}
+	return name;
+    }
+
+    /**
+     * Get the fullname of this folder.
+     */
+    @Override
+    public String getFullName() {
+	return fullName;	
+    }
+
+    /**
+     * Get this folder's parent.
+     */
+    @Override
+    public synchronized Folder getParent() throws MessagingException {
+	char c = getSeparator();
+	int index;
+	if ((index = fullName.lastIndexOf(c)) != -1)
+	    return ((IMAPStore)store).newIMAPFolder(
+			    fullName.substring(0, index), c);
+	else
+	    return new DefaultFolder((IMAPStore)store);
+    }
+
+    /**
+     * Check whether this folder really exists on the server.
+     */
+    @Override
+    public synchronized boolean exists() throws MessagingException {
+	// Check whether this folder exists ..
+	ListInfo[] li = null;
+	final String lname;
+	if (isNamespace && separator != '\0')
+	    lname = fullName + separator;
+	else
+	    lname = fullName;
+
+	li = (ListInfo[])doCommand(new ProtocolCommand() {
+	    @Override
+	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
+		return p.list("", lname);
+	    }
+	});
+
+	if (li != null) {
+	    int i = findName(li, lname);
+	    fullName = li[i].name;
+	    separator = li[i].separator;
+	    int len = fullName.length();
+	    if (separator != '\0' && len > 0 &&
+		    fullName.charAt(len - 1) == separator) {
+		fullName = fullName.substring(0, len - 1);
+	    }
+	    type = 0;
+	    if (li[i].hasInferiors)
+		type |= HOLDS_FOLDERS;
+	    if (li[i].canOpen)
+		type |= HOLDS_MESSAGES;
+	    exists = true;
+	    attributes = li[i].attrs;
+	} else {
+	    exists = opened;
+	    attributes = null;
+	}
+
+	return exists;
+    }
+
+    /**
+     * Which entry in <code>li</code> matches <code>lname</code>?
+     * If the name contains wildcards, more than one entry may be
+     * returned.
+     */
+    private int findName(ListInfo[] li, String lname) {
+	int i;
+	// if the name contains a wildcard, there might be more than one
+	for (i = 0; i < li.length; i++) {
+	    if (li[i].name.equals(lname))
+		break;
+	}
+	if (i >= li.length) {	// nothing matched exactly
+	    // XXX - possibly should fail?  But what if server
+	    // is case insensitive and returns the preferred
+	    // case of the name here?
+	    i = 0;		// use first one
+	}
+	return i;
+    }
+
+    /**
+     * List all subfolders matching the specified pattern.
+     */
+    @Override
+    public Folder[] list(String pattern) throws MessagingException {
+	return doList(pattern, false);
+    }
+
+    /**
+     * List all subscribed subfolders matching the specified pattern.
+     */
+    @Override
+    public Folder[] listSubscribed(String pattern) throws MessagingException {
+	return doList(pattern, true);
+    }
+
+    private synchronized Folder[] doList(final String pattern,
+		final boolean subscribed) throws MessagingException {
+	checkExists(); // insure that this folder does exist.
+	
+	// Why waste a roundtrip to the server?
+	if (attributes != null && !isDirectory())
+	    return new Folder[0];
+
+	final char c = getSeparator();
+
+	ListInfo[] li = (ListInfo[])doCommandIgnoreFailure(
+	    new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    if (subscribed)
+			return p.lsub("", fullName + c + pattern);
+		    else 
+			return p.list("", fullName + c + pattern);
+		}
+	    });
+
+	if (li == null)
+	    return new Folder[0];
+
+	/*
+	 * The UW based IMAP4 servers (e.g. SIMS2.0) include
+	 * current folder (terminated with the separator), when
+	 * the LIST pattern is '%' or '*'. i.e, <LIST "" mail/%> 
+	 * returns "mail/" as the first LIST response.
+	 *
+	 * Doesn't make sense to include the current folder in this
+	 * case, so we filter it out. Note that I'm assuming that
+	 * the offending response is the *first* one, my experiments
+	 * with the UW & SIMS2.0 servers indicate that .. 
+	 */
+	int start = 0;
+	// Check the first LIST response.
+	if (li.length > 0 && li[0].name.equals(fullName + c)) 
+	    start = 1; // start from index = 1
+
+	IMAPFolder[] folders = new IMAPFolder[li.length - start];
+	IMAPStore st = (IMAPStore)store;
+	for (int i = start; i < li.length; i++)
+	    folders[i-start] = st.newIMAPFolder(li[i]);
+	return folders;
+    }
+
+    /**
+     * Get the separator character.
+     */
+    @Override
+    public synchronized char getSeparator() throws MessagingException {
+	if (separator == UNKNOWN_SEPARATOR) {
+	    ListInfo[] li = null;
+
+	    li = (ListInfo[])doCommand(new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    // REV1 allows the following LIST format to obtain
+		    // the hierarchy delimiter of non-existent folders
+		    if (p.isREV1()) // IMAP4rev1
+		        return p.list(fullName, "");
+		    else // IMAP4, note that this folder must exist for this
+		        // to work :(
+		        return p.list("", fullName);
+		}
+	    });
+
+	    if (li != null) 
+		separator = li[0].separator;
+	    else
+		separator = '/'; // punt !
+	}
+	return separator;
+    }
+
+    /**
+     * Get the type of this folder.
+     */
+    @Override
+    public synchronized int getType() throws MessagingException {
+	if (opened) {
+	    // never throw FolderNotFoundException if folder is open
+	    if (attributes == null)
+		exists();	// try to fetch attributes
+	} else {
+	    checkExists();
+	}
+	return type;
+    }
+    
+    /**
+     * Check whether this folder is subscribed.
+     */
+    @Override
+    public synchronized boolean isSubscribed() {
+	ListInfo[] li = null;
+	final String lname;
+	if (isNamespace && separator != '\0')
+	    lname = fullName + separator;
+	else
+	    lname = fullName;
+
+	try {
+	    li = (ListInfo[])doProtocolCommand(new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    return p.lsub("", lname);
+		}
+	    });
+	} catch (ProtocolException pex) {
+        }
+
+	if (li != null) {
+	    int i = findName(li, lname);
+	    return li[i].canOpen;
+	} else
+	    return false;
+    }
+
+    /**
+     * Subscribe/Unsubscribe this folder.
+     */
+    @Override
+    public synchronized void setSubscribed(final boolean subscribe) 
+			throws MessagingException {
+	doCommandIgnoreFailure(new ProtocolCommand() {
+	    @Override
+	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
+		if (subscribe)
+		    p.subscribe(fullName);
+		else
+		    p.unsubscribe(fullName);
+		return null;
+	    }
+	});
+    }
+	
+    /**
+     * Create this folder, with the specified type.
+     */
+    @Override
+    public synchronized boolean create(final int type)
+				throws MessagingException {
+
+	char c = 0;
+	if ((type & HOLDS_MESSAGES) == 0)	// only holds folders
+	    c = getSeparator();
+	final char sep = c;
+	Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
+	    @Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    if ((type & HOLDS_MESSAGES) == 0)	// only holds folders
+			p.create(fullName + sep);
+		    else {
+			p.create(fullName);
+
+			// Certain IMAP servers do not allow creation of folders
+			// that can contain messages *and* subfolders. So, if we
+			// were asked to create such a folder, we should verify
+			// that we could indeed do so.
+			if ((type & HOLDS_FOLDERS) != 0) {
+			    // we want to hold subfolders and messages. Check
+			    // whether we could create such a folder.
+			    ListInfo[] li = p.list("", fullName);
+			    if (li != null && !li[0].hasInferiors) {
+				// Hmm ..the new folder 
+				// doesn't support Inferiors ? Fail
+				p.delete(fullName);
+				throw new ProtocolException("Unsupported type");
+			    }
+			}
+		    }
+		    return Boolean.TRUE;
+		}
+	    });
+
+	if (ret == null)
+	    return false; // CREATE failure, maybe this 
+			  // folder already exists ?
+
+	// exists = true;
+	// this.type = type;
+	boolean retb = exists();	// set exists, type, and attributes
+	if (retb)		// Notify listeners on self and our Store
+	    notifyFolderListeners(FolderEvent.CREATED);
+	return retb;
+    }
+
+    /**
+     * Check whether this folder has new messages.
+     */
+    @Override
+    public synchronized boolean hasNewMessages() throws MessagingException {
+	synchronized (messageCacheLock) {
+	    if (opened) { // If we are open, we already have this information
+		// Folder is open, make sure information is up to date
+		// tickle the folder and store connections.
+		try {
+		    keepConnectionAlive(true);
+		} catch (ConnectionException cex) {
+		    throw new FolderClosedException(this, cex.getMessage());
+		} catch (ProtocolException pex) {
+		    throw new MessagingException(pex.getMessage(), pex);
+		}
+		return recent > 0 ? true : false;
+	    }
+	}
+
+	// First, the cheap way - use LIST and look for the \Marked
+	// or \Unmarked tag
+
+	ListInfo[] li = null;
+	final String lname;
+	if (isNamespace && separator != '\0')
+	    lname = fullName + separator;
+	else
+	    lname = fullName;
+	li = (ListInfo[])doCommandIgnoreFailure(new ProtocolCommand() {
+	    @Override
+	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
+		return p.list("", lname);
+	    }
+	});
+
+	// if folder doesn't exist, throw exception
+	if (li == null)
+	    throw new FolderNotFoundException(this, fullName + " not found");
+
+	int i = findName(li, lname);
+	if (li[i].changeState == ListInfo.CHANGED)
+	    return true;
+	else if (li[i].changeState == ListInfo.UNCHANGED)
+	    return false;
+
+	// LIST didn't work. Try the hard way, using STATUS
+	try {
+	    Status status = getStatus();
+	    if (status.recent > 0)
+		return true;
+	    else
+		return false;
+	} catch (BadCommandException bex) {
+	    // Probably doesn't support STATUS, tough luck.
+	    return false;
+	} catch (ConnectionException cex) {
+	    throw new StoreClosedException(store, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /**
+     * Get the named subfolder.
+     */
+    @Override
+    public synchronized Folder getFolder(String name)
+				throws MessagingException {
+	// If we know that this folder is *not* a directory, don't
+	// send the request to the server at all ...
+	if (attributes != null && !isDirectory())
+	    throw new MessagingException("Cannot contain subfolders");
+
+	char c = getSeparator();
+	return ((IMAPStore)store).newIMAPFolder(fullName + c + name, c);
+    }
+
+    /**
+     * Delete this folder.
+     */
+    @Override
+    public synchronized boolean delete(boolean recurse) 
+			throws MessagingException {  
+	checkClosed(); // insure that this folder is closed.
+
+	if (recurse) {
+	    // Delete all subfolders.
+	    Folder[] f = list();
+	    for (int i = 0; i < f.length; i++)
+		f[i].delete(recurse); // ignore intermediate failures
+	}
+
+	// Attempt to delete this folder
+
+	Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
+	    @Override
+	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
+		p.delete(fullName);
+		return Boolean.TRUE;
+	    }
+	});
+
+	if (ret == null)
+	    // Non-existent folder/No permission ??
+	    return false;
+
+	// DELETE succeeded.
+	exists = false;
+	attributes = null;
+
+	// Notify listeners on self and our Store
+	notifyFolderListeners(FolderEvent.DELETED);
+	return true;
+    }
+
+    /**
+     * Rename this folder.
+     */
+    @Override
+    public synchronized boolean renameTo(final Folder f)
+				throws MessagingException {
+	checkClosed(); // insure that we are closed.
+	checkExists();
+	if (f.getStore() != store)
+	    throw new MessagingException("Can't rename across Stores");
+
+
+	Object ret = doCommandIgnoreFailure(new ProtocolCommand() {
+	    @Override
+	    public Object doCommand(IMAPProtocol p) throws ProtocolException {
+		p.rename(fullName, f.getFullName());
+		return Boolean.TRUE;
+	    }
+	});
+
+	if (ret == null)
+	    return false;
+
+	exists = false;
+	attributes = null;
+	notifyFolderRenamedListeners(f);
+	return true;
+    }
+
+    /**
+     * Open this folder in the given mode.
+     */
+    @Override
+    public synchronized void open(int mode) throws MessagingException {
+	open(mode, null);
+    }
+
+    /**
+     * Open this folder in the given mode, with the given
+     * resynchronization data.
+     *
+     * @param	mode	the open mode (Folder.READ_WRITE or Folder.READ_ONLY)
+     * @param	rd	the ResyncData instance
+     * @return		a List of MailEvent instances, or null if none
+     * @exception MessagingException	if the open fails
+     * @since	JavaMail 1.5.1
+     */
+    public synchronized List<MailEvent> open(int mode, ResyncData rd)
+				throws MessagingException {
+	checkClosed(); // insure that we are not already open
+	
+	MailboxInfo mi = null;
+	// Request store for our own protocol connection.
+	protocol = ((IMAPStore)store).getProtocol(this);
+
+	List<MailEvent> openEvents = null;
+	synchronized(messageCacheLock) { // Acquire messageCacheLock
+
+	    /*
+	     * Add response handler right away so we get any alerts or
+	     * notifications that occur during the SELECT or EXAMINE.
+	     * Have to be sure to remove it if we fail to open the
+	     * folder.
+	     */
+	    protocol.addResponseHandler(this);
+
+	    try {
+		/*
+		 * Enable QRESYNC or CONDSTORE if needed and not enabled.
+		 * QRESYNC implies CONDSTORE, but servers that support
+		 * QRESYNC are not required to support just CONDSTORE
+		 * per RFC 5162.
+		 */
+		if (rd != null) {
+		    if (rd == ResyncData.CONDSTORE) {
+			if (!protocol.isEnabled("CONDSTORE") &&
+			    !protocol.isEnabled("QRESYNC")) {
+			    if (protocol.hasCapability("CONDSTORE"))
+				protocol.enable("CONDSTORE");
+			    else
+				protocol.enable("QRESYNC");
+			}
+		    } else {
+			if (!protocol.isEnabled("QRESYNC"))
+			    protocol.enable("QRESYNC");
+		    }
+		}
+
+		if (mode == READ_ONLY)
+		    mi = protocol.examine(fullName, rd);
+		else
+		    mi = protocol.select(fullName, rd);
+	    } catch (CommandFailedException cex) {
+		/*
+		 * Handle SELECT or EXAMINE failure.
+		 * Try to figure out why the operation failed so we can
+		 * report a more reasonable exception.
+		 *
+		 * Will use our existing protocol object.
+		 */
+		try {
+		    checkExists(); // throw exception if folder doesn't exist
+
+		    if ((type & HOLDS_MESSAGES) == 0)
+			throw new MessagingException(
+			    "folder cannot contain messages");
+		    throw new MessagingException(cex.getMessage(), cex);
+
+		} finally {
+		    // folder not open, don't keep this information
+		    exists = false;
+		    attributes = null;
+		    type = 0;
+		    // connection still good, return it
+		    releaseProtocol(true);
+		}
+		// NOTREACHED
+	    } catch (ProtocolException pex) {
+		// got a BAD or a BYE; connection may be bad, close it
+		try {
+		    throw logoutAndThrow(pex.getMessage(), pex);
+		} finally {
+		    releaseProtocol(false);
+		}
+	    }
+
+	    if (mi.mode != mode) {
+		if (mode == READ_WRITE && mi.mode == READ_ONLY &&
+			((IMAPStore)store).allowReadOnlySelect()) {
+		    ;		// all ok, allow it
+		} else {	// otherwise, it's an error
+		    ReadOnlyFolderException ife = new ReadOnlyFolderException(
+			    this, "Cannot open in desired mode");
+		    throw cleanupAndThrow(ife);
+		}
+            }
+	
+	    // Initialize stuff.
+	    opened = true;
+	    reallyClosed = false;
+	    this.mode = mi.mode;
+	    availableFlags = mi.availableFlags;
+	    permanentFlags = mi.permanentFlags;
+	    total = realTotal = mi.total;
+	    recent = mi.recent;
+	    uidvalidity = mi.uidvalidity;
+	    uidnext = mi.uidnext;
+	    uidNotSticky = mi.uidNotSticky;
+	    highestmodseq = mi.highestmodseq;
+
+	    // Create the message cache of appropriate size
+	    messageCache = new MessageCache(this, (IMAPStore)store, total);
+
+	    // process saved responses and return corresponding events
+	    if (mi.responses != null) {
+		openEvents = new ArrayList<>();
+		for (IMAPResponse ir : mi.responses) {
+		    if (ir.keyEquals("VANISHED")) {
+			// "VANISHED" SP ["(EARLIER)"] SP known-uids
+			String[] s = ir.readAtomStringList();
+			// check that it really is "EARLIER"
+			if (s == null || s.length != 1 ||
+					    !s[0].equalsIgnoreCase("EARLIER"))
+			    continue;	// it's not, what to do with it here?
+			String uids = ir.readAtom();
+			UIDSet[] uidset = UIDSet.parseUIDSets(uids);
+			long[] luid = UIDSet.toArray(uidset, uidnext);
+			if (luid != null && luid.length > 0)
+			    openEvents.add(
+				new MessageVanishedEvent(this, luid));
+		    } else if (ir.keyEquals("FETCH")) {
+			assert ir instanceof FetchResponse :
+				"!ir instanceof FetchResponse";
+			Message msg = processFetchResponse((FetchResponse)ir);
+			if (msg != null)
+			    openEvents.add(new MessageChangedEvent(this,
+				    MessageChangedEvent.FLAGS_CHANGED, msg));
+		    }
+		}
+	    }
+	} // Release lock
+
+	exists = true;		// if we opened it, it must exist
+	attributes = null;	// but we don't yet know its attributes
+	type = HOLDS_MESSAGES;	// lacking more info, we know at least this much
+
+	// notify listeners
+	notifyConnectionListeners(ConnectionEvent.OPENED);
+
+	return openEvents;
+    }
+
+    private MessagingException cleanupAndThrow(MessagingException ife) {
+	try {
+	    try {
+		// close mailbox and return connection
+		protocol.close();
+		releaseProtocol(true);
+	    } catch (ProtocolException pex) {
+		// something went wrong, close connection
+		try {
+		    addSuppressed(ife, logoutAndThrow(pex.getMessage(), pex));
+		} finally {
+		    releaseProtocol(false);
+		}
+	    }
+	} catch (Throwable thr) {
+	    addSuppressed(ife, thr);
+	}
+	return ife;
+    }
+
+    private MessagingException logoutAndThrow(String why, ProtocolException t) {
+	MessagingException ife = new MessagingException(why, t);
+	try {
+	    protocol.logout();
+	} catch (Throwable thr) {
+	    addSuppressed(ife, thr);
+	}
+	return ife;
+    }
+
+    private void addSuppressed(Throwable ife, Throwable thr) {
+	if (isRecoverable(thr)) {
+	    ife.addSuppressed(thr);
+	} else {
+	    thr.addSuppressed(ife);
+	    if (thr instanceof Error) {
+		throw (Error) thr;
+	    }
+	    if (thr instanceof RuntimeException) {
+		throw (RuntimeException) thr;
+	    }
+	    throw new RuntimeException("unexpected exception", thr);
+	}
+    }
+
+    private boolean isRecoverable(Throwable t) {
+	return (t instanceof Exception) || (t instanceof LinkageError);
+    }
+
+    /**
+     * Prefetch attributes, based on the given FetchProfile.
+     */
+    @Override
+    public synchronized void fetch(Message[] msgs, FetchProfile fp)
+			throws MessagingException {
+	// cache this information in case connection is closed and
+	// protocol is set to null
+	boolean isRev1;
+	FetchItem[] fitems;
+        synchronized (messageCacheLock) {
+	    checkOpened();
+	    isRev1 = protocol.isREV1();
+	    fitems = protocol.getFetchItems();
+	}
+
+	StringBuilder command = new StringBuilder();
+	boolean first = true;
+	boolean allHeaders = false;
+
+	if (fp.contains(FetchProfile.Item.ENVELOPE)) {
+	    command.append(getEnvelopeCommand());
+	    first = false;
+	}
+	if (fp.contains(FetchProfile.Item.FLAGS)) {
+	    command.append(first ? "FLAGS" : " FLAGS");
+	    first = false;
+	}
+	if (fp.contains(FetchProfile.Item.CONTENT_INFO)) {
+	    command.append(first ? "BODYSTRUCTURE" : " BODYSTRUCTURE");
+	    first = false;
+	}
+	if (fp.contains(UIDFolder.FetchProfileItem.UID)) {
+	    command.append(first ? "UID" : " UID");
+	    first = false;
+	}
+	if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS)) {
+	    allHeaders = true;
+	    if (isRev1)
+		command.append(first ?
+			    "BODY.PEEK[HEADER]" : " BODY.PEEK[HEADER]");
+	    else
+		command.append(first ? "RFC822.HEADER" : " RFC822.HEADER");
+	    first = false;
+	}
+	if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE)) {
+	    allHeaders = true;
+	    if (isRev1)
+		command.append(first ? "BODY.PEEK[]" : " BODY.PEEK[]");
+	    else
+		command.append(first ? "RFC822" : " RFC822");
+	    first = false;
+	}
+	if (fp.contains(FetchProfile.Item.SIZE) ||
+		fp.contains(IMAPFolder.FetchProfileItem.SIZE)) {
+	    command.append(first ? "RFC822.SIZE" : " RFC822.SIZE");
+	    first = false;
+	}
+	if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE)) {
+	    command.append(first ? "INTERNALDATE" : " INTERNALDATE");
+	    first = false;
+	}
+
+	// if we're not fetching all headers, fetch individual headers
+	String[] hdrs = null;
+	if (!allHeaders) {
+	    hdrs = fp.getHeaderNames();
+	    if (hdrs.length > 0) {
+		if (!first)
+		    command.append(" ");
+		command.append(createHeaderCommand(hdrs, isRev1));
+	    }
+	}
+
+	/*
+	 * Add any additional extension fetch items.
+	 */
+	for (int i = 0; i < fitems.length; i++) {
+	    if (fp.contains(fitems[i].getFetchProfileItem())) {
+		if (command.length() != 0)
+		    command.append(" ");
+		command.append(fitems[i].getName());
+	    }
+	}
+
+	Utility.Condition condition =
+	    new IMAPMessage.FetchProfileCondition(fp, fitems);
+
+        // Acquire the Folder's MessageCacheLock.
+        synchronized (messageCacheLock) {
+
+	    // check again to make sure folder is still open
+	    checkOpened();
+
+	    // Apply the test, and get the sequence-number set for
+	    // the messages that need to be prefetched.
+	    MessageSet[] msgsets = Utility.toMessageSetSorted(msgs, condition);
+
+	    if (msgsets == null)
+		// We already have what we need.
+		return;
+
+	    Response[] r = null;
+	    // to collect non-FETCH responses & unsolicited FETCH FLAG responses 
+	    List<Response> v = new ArrayList<>();
+	    try {
+		r = getProtocol().fetch(msgsets, command.toString());
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(this, cex.getMessage());
+	    } catch (CommandFailedException cfx) {
+		// Ignore these, as per RFC 2180
+	    } catch (ProtocolException pex) { 
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+
+	    if (r == null)
+		return;
+	   
+	    for (int i = 0; i < r.length; i++) {
+		if (r[i] == null)
+		    continue;
+		if (!(r[i] instanceof FetchResponse)) {
+		    v.add(r[i]); // Unsolicited Non-FETCH response
+		    continue;
+		}
+
+		// Got a FetchResponse.
+		FetchResponse f = (FetchResponse)r[i];
+		// Get the corresponding message.
+		IMAPMessage msg = getMessageBySeqNumber(f.getNumber());
+
+		int count = f.getItemCount();
+		boolean unsolicitedFlags = false;
+
+		for (int j = 0; j < count; j++) {
+		    Item item = f.getItem(j);
+		    // Check for the FLAGS item
+		    if (item instanceof Flags &&
+			    (!fp.contains(FetchProfile.Item.FLAGS) ||
+				msg == null)) {
+			// Ok, Unsolicited FLAGS update.
+			unsolicitedFlags = true;
+		    } else if (msg != null)
+			msg.handleFetchItem(item, hdrs, allHeaders);
+		}
+		if (msg != null)
+		    msg.handleExtensionFetchItems(f.getExtensionItems());
+
+		// If this response contains any unsolicited FLAGS
+		// add it to the unsolicited response vector
+		if (unsolicitedFlags)
+		    v.add(f);
+	    }
+
+	    // Dispatch any unsolicited responses
+	    if (!v.isEmpty()) {
+		Response[] responses = new Response[v.size()];
+		v.toArray(responses);
+		handleResponses(responses);
+	    }
+
+	} // Release messageCacheLock
+    }
+
+    /**
+     * Return the IMAP FETCH items to request in order to load
+     * all the "envelope" data.  Subclasses can override this
+     * method to fetch more data when FetchProfile.Item.ENVELOPE
+     * is requested.
+     *
+     * @return	the IMAP FETCH items to request
+     * @since JavaMail 1.4.6
+     */
+    protected String getEnvelopeCommand() {
+	return IMAPMessage.EnvelopeCmd;
+    }
+
+    /**
+     * Create a new IMAPMessage object to represent the given message number.
+     * Subclasses of IMAPFolder may override this method to create a
+     * subclass of IMAPMessage.
+     *
+     * @param	msgnum	the message sequence number
+     * @return	the new IMAPMessage object
+     * @since JavaMail 1.4.6
+     */
+    protected IMAPMessage newIMAPMessage(int msgnum) {
+	return new IMAPMessage(this, msgnum);
+    }
+
+    /**
+     * Create the appropriate IMAP FETCH command items to fetch the
+     * requested headers.
+     */
+    private String createHeaderCommand(String[] hdrs, boolean isRev1) {
+	StringBuilder sb;
+
+	if (isRev1)
+	    sb = new StringBuilder("BODY.PEEK[HEADER.FIELDS (");
+	else
+	    sb = new StringBuilder("RFC822.HEADER.LINES (");
+
+	for (int i = 0; i < hdrs.length; i++) {
+	    if (i > 0)
+		sb.append(" ");
+	    sb.append(hdrs[i]);
+	}
+
+	if (isRev1)
+	    sb.append(")]");
+	else
+	    sb.append(")");
+	
+	return sb.toString();
+    }
+
+    /**
+     * Set the specified flags for the given array of messages.
+     */
+    @Override
+    public synchronized void setFlags(Message[] msgs, Flags flag, boolean value)
+			throws MessagingException {
+	checkOpened();
+	checkFlags(flag); // validate flags
+
+	if (msgs.length == 0) // boundary condition
+	    return;
+
+	synchronized(messageCacheLock) {
+	    try {
+		IMAPProtocol p = getProtocol();
+		MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
+		if (ms == null)
+		    throw new MessageRemovedException(
+					"Messages have been removed");
+		p.storeFlags(ms, flag, value);
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(this, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+    }
+
+    /**
+     * Set the specified flags for the given range of message numbers.
+     */
+    @Override
+    public synchronized void setFlags(int start, int end,
+			Flags flag, boolean value) throws MessagingException {
+	checkOpened();
+	Message[] msgs = new Message[end - start + 1];
+	int i = 0;
+	for (int n = start; n <= end; n++)
+	    msgs[i++] = getMessage(n);
+	setFlags(msgs, flag, value);
+    }
+
+    /**
+     * Set the specified flags for the given array of message numbers.
+     */
+    @Override
+    public synchronized void setFlags(int[] msgnums, Flags flag, boolean value)
+			throws MessagingException {
+	checkOpened();
+	Message[] msgs = new Message[msgnums.length];
+	for (int i = 0; i < msgnums.length; i++)
+	    msgs[i] = getMessage(msgnums[i]);
+	setFlags(msgs, flag, value);
+    }
+
+    /**
+     * Close this folder.
+     */
+    @Override
+    public synchronized void close(boolean expunge) throws MessagingException {
+	close(expunge, false);
+    }
+
+    /**
+     * Close this folder without waiting for the server.
+     *
+     * @exception	MessagingException for failures
+     */
+    public synchronized void forceClose() throws MessagingException {
+	close(false, true);
+    }
+
+    /*
+     * Common close method.
+     */
+    private void close(boolean expunge, boolean force)
+				throws MessagingException {
+	assert Thread.holdsLock(this);
+	synchronized(messageCacheLock) {
+	    /*
+	     * If we already know we're closed, this is illegal.
+	     * Can't use checkOpened() because if we were forcibly
+	     * closed asynchronously we just want to complete the
+	     * closing here.
+	     */
+	    if (!opened && reallyClosed)
+		throw new IllegalStateException(
+		    "This operation is not allowed on a closed folder"
+		);
+
+	    reallyClosed = true; // Ok, lets reset
+
+	    // Maybe this folder is already closed, or maybe another
+	    // thread which had the messageCacheLock earlier, found
+	    // that our server connection is dead and cleaned up
+	    // everything ..
+	    if (!opened)
+		return;
+
+	    boolean reuseProtocol = true;
+	    try {
+		waitIfIdle();
+		if (force) {
+		    logger.log(Level.FINE, "forcing folder {0} to close",
+								    fullName);
+		    if (protocol != null)
+			protocol.disconnect();
+                } else if (((IMAPStore)store).isConnectionPoolFull()) {
+		    // If the connection pool is full, logout the connection
+		    logger.fine(
+			"pool is full, not adding an Authenticated connection");
+
+		    // If the expunge flag is set, close the folder first.
+		    if (expunge && protocol != null)
+			protocol.close();
+
+		    if (protocol != null)
+			protocol.logout();
+                } else {
+		    // If the expunge flag is set or we're open read-only we
+		    // can just close the folder, otherwise open it read-only
+		    // before closing, or unselect it if supported.
+                    if (!expunge && mode == READ_WRITE) {
+                        try {
+			    if (protocol != null &&
+				    protocol.hasCapability("UNSELECT"))
+				protocol.unselect();
+			    else {
+				// Unselect isn't supported so we need to
+				// select a folder to cause this one to be
+				// deselected without expunging messages.
+				// We try to do that by reopening the current
+				// folder read-only.  If the current folder
+				// was renamed out from under us, the EXAMINE
+				// might fail, but that's ok because it still
+				// leaves us with the folder deselected.
+				if (protocol != null) {
+				    boolean selected = true;
+				    try {
+					protocol.examine(fullName);
+					// success, folder still selected
+				    } catch (CommandFailedException ex) {
+					// EXAMINE failed, folder is no
+					// longer selected
+					selected = false;
+				    }
+				    if (selected && protocol != null)
+					protocol.close();
+				}
+			    }
+                        } catch (ProtocolException pex2) {
+			    reuseProtocol = false;	// something went wrong
+                        }
+                    } else {
+			if (protocol != null)
+			    protocol.close();
+		    }
+                }
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    } finally {
+		// cleanup if we haven't already
+		if (opened)
+		    cleanup(reuseProtocol);
+	    }
+	}
+    }
+
+    // NOTE: this method can currently be invoked from close() or
+    // from handleResponses(). Both invocations are conditional,
+    // based on the "opened" flag, so we are sure that multiple
+    // Connection.CLOSED events are not generated. Also both
+    // invocations are from within messageCacheLock-ed areas.
+    private void cleanup(boolean returnToPool) {
+	assert Thread.holdsLock(messageCacheLock);
+        releaseProtocol(returnToPool);
+	messageCache = null;
+	uidTable = null;
+	exists = false; // to force a recheck in exists().
+	attributes = null;
+        opened = false;
+	idleState = RUNNING;	// just in case
+	messageCacheLock.notifyAll();	// wake up anyone waiting
+	notifyConnectionListeners(ConnectionEvent.CLOSED);
+    }
+
+    /**
+     * Check whether this connection is really open.
+     */
+    @Override
+    public synchronized boolean isOpen() {
+	synchronized(messageCacheLock) {
+	    // Probe the connection to make sure its really open.
+	    if (opened) {
+		try {
+		    keepConnectionAlive(false);
+		} catch (ProtocolException pex) { }
+	    }
+	}
+
+	return opened;
+    }
+
+    /**
+     * Return the permanent flags supported by the server.
+     */
+    @Override
+    public synchronized Flags getPermanentFlags() {
+	if (permanentFlags == null)
+	    return null;
+	return (Flags)(permanentFlags.clone());
+    }
+
+    /**
+     * Get the total message count.
+     */
+    @Override
+    public synchronized int getMessageCount() throws MessagingException {
+	synchronized (messageCacheLock) {
+	    if (opened) {
+		// Folder is open, we know what the total message count is ..
+		// tickle the folder and store connections.
+		try {
+		    keepConnectionAlive(true);
+		    return total;
+		} catch (ConnectionException cex) {
+		    throw new FolderClosedException(this, cex.getMessage());
+		} catch (ProtocolException pex) {
+		    throw new MessagingException(pex.getMessage(), pex);
+		}
+	    }
+	}
+
+	// If this folder is not yet open, we use STATUS to
+	// get the total message count
+	checkExists();
+	try {
+	    Status status = getStatus();
+	    return status.total;
+	} catch (BadCommandException bex) {
+	    // doesn't support STATUS, probably vanilla IMAP4 ..
+	    // lets try EXAMINE
+	    IMAPProtocol p = null;
+
+	    try {
+		p = getStoreProtocol();	// XXX
+		MailboxInfo minfo = p.examine(fullName);
+		p.close();
+		return minfo.total;
+	    } catch (ProtocolException pex) {
+		// Give up.
+		throw new MessagingException(pex.getMessage(), pex);
+	    } finally {
+		releaseStoreProtocol(p);
+	    }
+	} catch (ConnectionException cex) {
+	    throw new StoreClosedException(store, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /**
+     * Get the new message count.
+     */
+    @Override
+    public synchronized int getNewMessageCount() throws MessagingException {
+	synchronized (messageCacheLock) {
+	    if (opened) {
+		// Folder is open, we know what the new message count is ..
+		// tickle the folder and store connections.
+		try {
+		    keepConnectionAlive(true);
+		    return recent;
+		} catch (ConnectionException cex) {
+		    throw new FolderClosedException(this, cex.getMessage());
+		} catch (ProtocolException pex) {
+		    throw new MessagingException(pex.getMessage(), pex);
+		}
+	    }
+	}
+
+	// If this folder is not yet open, we use STATUS to
+	// get the new message count
+	checkExists();
+	try {
+	    Status status = getStatus();
+	    return status.recent;
+	} catch (BadCommandException bex) {
+	    // doesn't support STATUS, probably vanilla IMAP4 ..
+	    // lets try EXAMINE
+	    IMAPProtocol p = null;
+
+	    try {
+		p = getStoreProtocol();	// XXX
+		MailboxInfo minfo = p.examine(fullName);
+		p.close();
+		return minfo.recent;
+	    } catch (ProtocolException pex) {
+		// Give up.
+		throw new MessagingException(pex.getMessage(), pex);
+	    } finally {
+		releaseStoreProtocol(p);
+	    }
+	} catch (ConnectionException cex) {
+	    throw new StoreClosedException(store, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /**
+     * Get the unread message count.
+     */
+    @Override
+    public synchronized int getUnreadMessageCount()
+			throws MessagingException {
+	if (!opened) {
+	    checkExists();
+	    // If this folder is not yet open, we use STATUS to
+	    // get the unseen message count
+	    try {
+		Status status = getStatus();
+		return status.unseen;
+	    } catch (BadCommandException bex) {
+		// doesn't support STATUS, probably vanilla IMAP4 ..
+		// Could EXAMINE, SEARCH for UNREAD messages and
+		// return the count .. bah, not worth it.
+		return -1;
+	    } catch (ConnectionException cex) {
+		throw new StoreClosedException(store, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+
+	// if opened, issue server-side search for messages that do
+	// *not* have the SEEN flag.
+	Flags f = new Flags();
+	f.add(Flags.Flag.SEEN);
+	try {
+	    synchronized(messageCacheLock) {
+		int[] matches = getProtocol().search(new FlagTerm(f, false));
+		return matches.length; // NOTE: 'matches' is never null
+	    }
+	} catch (ConnectionException cex) {
+	    throw new FolderClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    // Shouldn't happen
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /**
+     * Get the deleted message count.
+     */
+    @Override
+    public synchronized int getDeletedMessageCount()
+			throws MessagingException {
+	if (!opened) {
+	    checkExists();
+	    // no way to do this on closed folders
+	    return -1;
+	}
+
+	// if opened, issue server-side search for messages that do
+	// have the DELETED flag.
+	Flags f = new Flags();
+	f.add(Flags.Flag.DELETED);
+	try {
+	    synchronized(messageCacheLock) {
+		int[] matches = getProtocol().search(new FlagTerm(f, true));
+		return matches.length; // NOTE: 'matches' is never null
+	    }
+	} catch (ConnectionException cex) {
+	    throw new FolderClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    // Shouldn't happen
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /*
+     * Get results of STATUS command for this folder, checking cache first.
+     * ASSERT: Must be called with this folder's synchronization lock held.
+     * ASSERT: The folder must be closed.
+     */
+    private Status getStatus() throws ProtocolException {
+	int statusCacheTimeout = ((IMAPStore)store).getStatusCacheTimeout();
+
+	// if allowed to cache and our cache is still valid, return it
+	if (statusCacheTimeout > 0 && cachedStatus != null &&
+	    System.currentTimeMillis() - cachedStatusTime < statusCacheTimeout)
+	    return cachedStatus;
+
+        IMAPProtocol p = null;
+
+	try {
+	    p = getStoreProtocol();	// XXX
+	    Status s = p.status(fullName, null); 
+	    // if allowed to cache, do so
+	    if (statusCacheTimeout > 0) {
+		cachedStatus = s;
+		cachedStatusTime = System.currentTimeMillis();
+	    }
+	    return s;
+        } finally {
+            releaseStoreProtocol(p);
+        }
+    }
+
+    /**
+     * Get the specified message.
+     */
+    @Override
+    public synchronized Message getMessage(int msgnum) 
+		throws MessagingException {
+	checkOpened();
+	checkRange(msgnum);
+
+	return messageCache.getMessage(msgnum);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized Message[] getMessages() throws MessagingException {
+	/*
+	 * Need to override Folder method to throw FolderClosedException
+	 * instead of IllegalStateException if not really closed.
+	 */
+	checkOpened();
+	int total = getMessageCount();
+	Message[] msgs = new Message[total];
+	for (int i = 1; i <= total; i++)
+	    msgs[i - 1] = messageCache.getMessage(i);
+	return msgs;
+    }
+
+    /**
+     * Append the given messages into this folder.
+     */
+    @Override
+    public synchronized void appendMessages(Message[] msgs)
+				throws MessagingException {
+	checkExists(); // verify that self exists
+
+	// XXX - have to verify that messages are in a different
+	// store (if any) than target folder, otherwise could
+	// deadlock trying to fetch messages on the same connection
+	// we're using for the append.
+
+	int maxsize = ((IMAPStore)store).getAppendBufferSize();
+
+	for (int i = 0; i < msgs.length; i++) {
+	    final Message m = msgs[i];
+	    Date d = m.getReceivedDate(); // retain dates
+	    if (d == null)
+		d = m.getSentDate();
+	    final Date dd = d;
+	    final Flags f = m.getFlags();
+
+	    final MessageLiteral mos;
+	    try {
+		// if we know the message is too big, don't buffer any of it
+		mos = new MessageLiteral(m,
+				m.getSize() > maxsize ? 0 : maxsize);
+	    } catch (IOException ex) {
+		throw new MessagingException(
+				"IOException while appending messages", ex);
+	    } catch (MessageRemovedException mrex) {
+		continue; // just skip this expunged message
+	    }
+
+	    doCommand(new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    p.append(fullName, f, dd, mos);
+		    return null;
+		}
+	    });
+	}
+    }
+
+    /**
+     * Append the given messages into this folder.
+     * Return array of AppendUID objects containing
+     * UIDs of these messages in the destination folder.
+     * Each element of the returned array corresponds to
+     * an element of the <code>msgs</code> array.  A null
+     * element means the server didn't return UID information
+     * for the appended message.  <p>
+     *
+     * Depends on the APPENDUID response code defined by the
+     * UIDPLUS extension -
+     * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
+     *
+     * @param	msgs	the messages to append
+     * @return		array of AppendUID objects
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.4
+     */
+    public synchronized AppendUID[] appendUIDMessages(Message[] msgs)
+				throws MessagingException {
+	checkExists(); // verify that self exists
+
+	// XXX - have to verify that messages are in a different
+	// store (if any) than target folder, otherwise could
+	// deadlock trying to fetch messages on the same connection
+	// we're using for the append.
+
+	int maxsize = ((IMAPStore)store).getAppendBufferSize();
+
+	AppendUID[] uids = new AppendUID[msgs.length];
+	for (int i = 0; i < msgs.length; i++) {
+	    final Message m = msgs[i];
+	    final MessageLiteral mos;
+
+	    try {
+		// if we know the message is too big, don't buffer any of it
+		mos = new MessageLiteral(m,
+				m.getSize() > maxsize ? 0 : maxsize);
+	    } catch (IOException ex) {
+		throw new MessagingException(
+				"IOException while appending messages", ex);
+	    } catch (MessageRemovedException mrex) {
+		continue; // just skip this expunged message
+	    }
+
+	    Date d = m.getReceivedDate(); // retain dates
+	    if (d == null)
+		d = m.getSentDate();
+	    final Date dd = d;
+	    final Flags f = m.getFlags();
+	    AppendUID auid = (AppendUID)doCommand(new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    return p.appenduid(fullName, f, dd, mos);
+		}
+	    });
+	    uids[i] = auid;
+	}
+	return uids;
+    }
+
+    /**
+     * Append the given messages into this folder.
+     * Return array of Message objects representing
+     * the messages in the destination folder.  Note
+     * that the folder must be open.
+     * Each element of the returned array corresponds to
+     * an element of the <code>msgs</code> array.  A null
+     * element means the server didn't return UID information
+     * for the appended message. <p>
+     *
+     * Depends on the APPENDUID response code defined by the
+     * UIDPLUS extension -
+     * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
+     *
+     * @param	msgs	the messages to add
+     * @return		the messages in this folder
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.4
+     */
+    public synchronized Message[] addMessages(Message[] msgs)
+				throws MessagingException {
+	checkOpened();
+	Message[] rmsgs = new MimeMessage[msgs.length];
+	AppendUID[] uids = appendUIDMessages(msgs);
+	for (int i = 0; i < uids.length; i++) {
+	    AppendUID auid = uids[i];
+	    if (auid != null) {
+		if (auid.uidvalidity == uidvalidity) {
+		    try {
+			rmsgs[i] = getMessageByUID(auid.uid);
+		    } catch (MessagingException mex) {
+			// ignore errors at this stage
+		    }
+		}
+	    }
+	}
+	return rmsgs;
+    }
+
+    /**
+     * Copy the specified messages from this folder, to the
+     * specified destination.
+     */
+    @Override
+    public synchronized void copyMessages(Message[] msgs, Folder folder)
+			throws MessagingException {
+	copymoveMessages(msgs, folder, false);
+    }
+
+    /**
+     * Copy the specified messages from this folder, to the
+     * specified destination.
+     * Return array of AppendUID objects containing
+     * UIDs of these messages in the destination folder.
+     * Each element of the returned array corresponds to
+     * an element of the <code>msgs</code> array.  A null
+     * element means the server didn't return UID information
+     * for the copied message.  <p>
+     *
+     * Depends on the COPYUID response code defined by the
+     * UIDPLUS extension -
+     * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
+     *
+     * @param	msgs	the messages to copy
+     * @param	folder	the folder to copy the messages to
+     * @return		array of AppendUID objects
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.5.1
+     */
+    public synchronized AppendUID[] copyUIDMessages(Message[] msgs,
+			Folder folder) throws MessagingException {
+	return copymoveUIDMessages(msgs, folder, false);
+    }
+
+    /**
+     * Move the specified messages from this folder, to the
+     * specified destination.
+     *
+     * Depends on the MOVE extension
+     * (<A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>).
+     *
+     * @param	msgs	the messages to move
+     * @param	folder	the folder to move the messages to
+     * @exception	MessagingException for failures
+     *
+     * @since	JavaMail 1.5.4
+     */
+    public synchronized void moveMessages(Message[] msgs, Folder folder)
+			throws MessagingException {
+	copymoveMessages(msgs, folder, true);
+    }
+
+    /**
+     * Move the specified messages from this folder, to the
+     * specified destination.
+     * Return array of AppendUID objects containing
+     * UIDs of these messages in the destination folder.
+     * Each element of the returned array corresponds to
+     * an element of the <code>msgs</code> array.  A null
+     * element means the server didn't return UID information
+     * for the moved message.  <p>
+     *
+     * Depends on the MOVE extension
+     * (<A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>)
+     * and the COPYUID response code defined by the
+     * UIDPLUS extension
+     * (<A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>).
+     *
+     * @param	msgs	the messages to move
+     * @param	folder	the folder to move the messages to
+     * @return		array of AppendUID objects
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.5.4
+     */
+    public synchronized AppendUID[] moveUIDMessages(Message[] msgs,
+			Folder folder) throws MessagingException {
+	return copymoveUIDMessages(msgs, folder, true);
+    }
+
+    /**
+     * Copy or move the specified messages from this folder, to the
+     * specified destination.
+     *
+     * @since	JavaMail 1.5.4
+     */
+    private synchronized void copymoveMessages(Message[] msgs, Folder folder,
+			boolean move) throws MessagingException {
+	checkOpened();
+
+	if (msgs.length == 0) // boundary condition
+	    return;
+
+	// If the destination belongs to our same store, optimize
+	if (folder.getStore() == store) {
+	    synchronized(messageCacheLock) {
+		try {
+		    IMAPProtocol p = getProtocol();
+		    MessageSet[] ms = Utility.toMessageSet(msgs, null);
+		    if (ms == null)
+			throw new MessageRemovedException(
+					"Messages have been removed");
+		    if (move)
+			p.move(ms, folder.getFullName());
+		    else
+			p.copy(ms, folder.getFullName());
+		} catch (CommandFailedException cfx) {
+		    if (cfx.getMessage().indexOf("TRYCREATE") != -1)
+			throw new FolderNotFoundException(
+                            folder,
+			    folder.getFullName() + " does not exist"
+			   );
+		    else 
+			throw new MessagingException(cfx.getMessage(), cfx);
+		} catch (ConnectionException cex) {
+		    throw new FolderClosedException(this, cex.getMessage());
+		} catch (ProtocolException pex) {
+		    throw new MessagingException(pex.getMessage(), pex);
+	    	}
+	    }
+	} else // destination is a different store.
+	    if (move)
+		throw new MessagingException(
+					"Move between stores not supported");
+	    else
+		super.copyMessages(msgs, folder);
+    }
+
+    /**
+     * Copy or move the specified messages from this folder, to the
+     * specified destination.
+     * Return array of AppendUID objects containing
+     * UIDs of these messages in the destination folder.
+     * Each element of the returned array corresponds to
+     * an element of the <code>msgs</code> array.  A null
+     * element means the server didn't return UID information
+     * for the copied message.  <p>
+     *
+     * Depends on the COPYUID response code defined by the
+     * UIDPLUS extension -
+     * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
+     * Move depends on the MOVE extension -
+     * <A HREF="http://www.ietf.org/rfc/rfc6851.txt">RFC 6851</A>.
+     *
+     * @param	msgs	the messages to copy
+     * @param	folder	the folder to copy the messages to
+     * @param	move	move instead of copy?
+     * @return		array of AppendUID objects
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.5.4
+     */
+    private synchronized AppendUID[] copymoveUIDMessages(Message[] msgs,
+			Folder folder, boolean move) throws MessagingException {
+	checkOpened();
+
+	if (msgs.length == 0) // boundary condition
+	    return null;
+
+	// the destination must belong to our same store
+	if (folder.getStore() != store) // destination is a different store.
+	    throw new MessagingException(
+			move ?
+			"can't moveUIDMessages to a different store" :
+			"can't copyUIDMessages to a different store");
+
+	// call fetch to make sure we have all the UIDs
+	// necessary to interpret the COPYUID response
+	FetchProfile fp = new FetchProfile();
+	fp.add(UIDFolder.FetchProfileItem.UID);
+	fetch(msgs, fp);
+	// XXX - could pipeline the FETCH with the COPY/MOVE below
+
+	synchronized (messageCacheLock) {
+	    try {
+		IMAPProtocol p = getProtocol();
+		// XXX - messages have to be from this Folder, who checks?
+		MessageSet[] ms = Utility.toMessageSet(msgs, null);
+		if (ms == null)
+		    throw new MessageRemovedException(
+				    "Messages have been removed");
+		CopyUID cuid;
+		if (move)
+		    cuid = p.moveuid(ms, folder.getFullName());
+		else
+		    cuid = p.copyuid(ms, folder.getFullName());
+
+		/*
+		 * Correlate source UIDs with destination UIDs.
+		 * This won't be time or space efficient if there's
+		 * a lot of messages.
+		 *
+		 * In order to make sense of the returned UIDs, we need
+		 * the UIDs for every one of the original messages.
+		 * We fetch them above, to make sure we have them.
+		 * This is critical for MOVE since after the MOVE the
+		 * messages are gone/expunged.
+		 *
+		 * Assume the common case is that the messages are
+		 * in order by UID.  Map the returned source
+		 * UIDs to their corresponding Message objects.
+		 * Step through the msgs array looking for the
+		 * Message object in the returned source message
+		 * list.  Most commonly the source message (UID)
+		 * for the Nth original message will be in the Nth
+		 * position in the returned source message (UID)
+		 * list.  Thus, the destination UID is in the Nth
+		 * position in the returned destination UID list.
+		 * But if the source message isn't where expected,
+		 * we have to search the entire source message
+		 * list, starting from where we expect it and
+		 * wrapping around until we've searched it all.
+		 * (Gmail will often return the lists in an unexpected order.)
+		 *
+		 * A possible optimization:
+		 * If the number of UIDs returned is the same as the
+		 * number of messages being copied/moved, we could
+		 * sort the source messages by message number, sort
+		 * the source and destination parallel arrays by source
+		 * UID, and the resulting message and destination UID
+		 * arrays will correspond.
+		 *
+		 * If the returned UID array size is different, some
+		 * message was expunged while we were trying to copy/move it.
+		 * This should be rare but would mean falling back to the
+		 * general algorithm.
+		 */
+		long[] srcuids = UIDSet.toArray(cuid.src);
+		long[] dstuids = UIDSet.toArray(cuid.dst);
+		// map source UIDs to Message objects
+		// XXX - could inline/optimize this
+		Message[] srcmsgs = getMessagesByUID(srcuids);
+		AppendUID[] result = new AppendUID[msgs.length];
+		for (int i = 0; i < msgs.length; i++) {
+		    int j = i;
+		    do {
+			if (msgs[i] == srcmsgs[j]) {
+			    result[i] = new AppendUID(
+					    cuid.uidvalidity, dstuids[j]);
+			    break;
+			}
+			j++;
+			if (j >= srcmsgs.length)
+			    j = 0;
+		    } while (j != i);
+		}
+		return result;
+	    } catch (CommandFailedException cfx) {
+		if (cfx.getMessage().indexOf("TRYCREATE") != -1)
+		    throw new FolderNotFoundException(
+			folder,
+			folder.getFullName() + " does not exist"
+		       );
+		else 
+		    throw new MessagingException(cfx.getMessage(), cfx);
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(this, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+    }
+
+    /**
+     * Expunge all messages marked as DELETED.
+     */
+    @Override
+    public synchronized Message[] expunge() throws MessagingException {
+	return expunge(null);
+    }
+
+    /**
+     * Expunge the indicated messages, which must have been marked as DELETED.
+     *
+     * Depends on the UIDPLUS extension -
+     * <A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>.
+     *
+     * @param	msgs	the messages to expunge
+     * @return		the expunged messages
+     * @exception	MessagingException for failures
+     */
+    public synchronized Message[] expunge(Message[] msgs)
+				throws MessagingException {
+	checkOpened();
+
+	if (msgs != null) {
+	    // call fetch to make sure we have all the UIDs
+	    FetchProfile fp = new FetchProfile();
+	    fp.add(UIDFolder.FetchProfileItem.UID);
+	    fetch(msgs, fp);
+	}
+
+	IMAPMessage[] rmsgs;
+	synchronized(messageCacheLock) {
+	    doExpungeNotification = false; // We do this ourselves later
+	    try {
+		IMAPProtocol p = getProtocol();
+		if (msgs != null)
+		    p.uidexpunge(Utility.toUIDSet(msgs));
+		else
+		    p.expunge();
+	    } catch (CommandFailedException cfx) {
+		// expunge not allowed, perhaps due to a permission problem?
+		if (mode != READ_WRITE)
+		    throw new IllegalStateException(
+			"Cannot expunge READ_ONLY folder: " + fullName);
+		else
+		    throw new MessagingException(cfx.getMessage(), cfx);
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(this, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		// Bad bad server ..
+		throw new MessagingException(pex.getMessage(), pex);
+	    } finally {
+		doExpungeNotification = true;
+	    }
+
+	    // Cleanup expunged messages and sync messageCache with reality.
+	    if (msgs != null)
+		rmsgs = messageCache.removeExpungedMessages(msgs);
+	    else
+		rmsgs = messageCache.removeExpungedMessages();
+	    if (uidTable != null) {
+		for (int i = 0; i < rmsgs.length; i++) {
+		    IMAPMessage m = rmsgs[i];
+		    /* remove this message from the UIDTable */
+		    long uid = m.getUID();
+		    if (uid != -1)
+			uidTable.remove(Long.valueOf(uid));
+		}
+	    }
+
+	    // Update 'total'
+	    total = messageCache.size();
+	}
+
+	// Notify listeners. This time its for real, guys.
+	if (rmsgs.length > 0)
+	    notifyMessageRemovedListeners(true, rmsgs);
+	return rmsgs;
+    }
+
+    /**
+     * Search whole folder for messages matching the given term.
+     * If the property <code>mail.imap.throwsearchexception</code> is true,
+     * and the search term is too complex for the IMAP protocol,
+     * SearchException is thrown.  Otherwise, if the search term is too
+     * complex, <code>super.search</code> is called to do the search on
+     * the client.
+     *
+     * @param	term	the search term
+     * @return		the messages that match
+     * @exception	SearchException if mail.imap.throwsearchexception is
+     *			true and the search is too complex for the IMAP protocol
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public synchronized Message[] search(SearchTerm term)
+				throws MessagingException {
+	checkOpened();
+
+	try {
+	    Message[] matchMsgs = null;
+
+	    synchronized(messageCacheLock) {
+		int[] matches = getProtocol().search(term);
+		if (matches != null)
+		    matchMsgs = getMessagesBySeqNumbers(matches);
+	    }
+	    return matchMsgs;
+
+	} catch (CommandFailedException cfx) {
+	    // unsupported charset or search criterion
+	    return super.search(term);
+	} catch (SearchException sex) {
+	    // too complex for IMAP
+	    if (((IMAPStore)store).throwSearchException())
+		throw sex;
+	    return super.search(term);
+	} catch (ConnectionException cex) {
+	    throw new FolderClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    // bug in our IMAP layer ?
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /**
+     * Search the folder for messages matching the given term. Returns
+     * array of matching messages. Returns an empty array if no matching
+     * messages are found.
+     */
+    @Override
+    public synchronized Message[] search(SearchTerm term, Message[] msgs) 
+			throws MessagingException {
+	checkOpened();
+
+	if (msgs.length == 0)
+	    // need to return an empty array (not null!)
+	    return msgs;
+
+	try {
+	    Message[] matchMsgs = null;
+
+	    synchronized(messageCacheLock) {
+		IMAPProtocol p = getProtocol();
+		MessageSet[] ms = Utility.toMessageSetSorted(msgs, null);
+		if (ms == null)
+		    throw new MessageRemovedException(
+					"Messages have been removed");
+		int[] matches = p.search(ms, term);
+		if (matches != null)
+		    matchMsgs = getMessagesBySeqNumbers(matches);
+	    }
+	    return matchMsgs;
+
+	} catch (CommandFailedException cfx) {
+	    // unsupported charset or search criterion
+	    return super.search(term, msgs);
+	} catch (SearchException sex) {
+	    // too complex for IMAP
+	    return super.search(term, msgs);
+	} catch (ConnectionException cex) {
+	    throw new FolderClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    // bug in our IMAP layer ?
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /**
+     * Sort the messages in the folder according to the sort criteria.
+     * The messages are returned in the sorted order, but the order of
+     * the messages in the folder is not changed. <p>
+     *
+     * Depends on the SORT extension -
+     * <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>.
+     *
+     * @param	term	the SortTerms
+     * @return		the messages in sorted order
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.4
+     */
+    public synchronized Message[] getSortedMessages(SortTerm[] term)
+				throws MessagingException {
+	return getSortedMessages(term, null);
+    }
+
+    /**
+     * Sort the messages in the folder according to the sort criteria.
+     * The messages are returned in the sorted order, but the order of
+     * the messages in the folder is not changed.  Only messages matching
+     * the search criteria are considered. <p>
+     *
+     * Depends on the SORT extension -
+     * <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>.
+     *
+     * @param	term	the SortTerms
+     * @param	sterm	the SearchTerm
+     * @return		the messages in sorted order
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.4
+     */
+    public synchronized Message[] getSortedMessages(SortTerm[] term,
+				SearchTerm sterm) throws MessagingException {
+	checkOpened();
+
+	try {
+	    Message[] matchMsgs = null;
+
+	    synchronized(messageCacheLock) {
+		int[] matches = getProtocol().sort(term, sterm);
+		if (matches != null)
+		    matchMsgs = getMessagesBySeqNumbers(matches);
+	    }
+	    return matchMsgs;
+
+	} catch (CommandFailedException cfx) {
+	    // unsupported charset or search criterion
+	    throw new MessagingException(cfx.getMessage(), cfx);
+	} catch (SearchException sex) {
+	    // too complex for IMAP
+	    throw new MessagingException(sex.getMessage(), sex);
+	} catch (ConnectionException cex) {
+	    throw new FolderClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    // bug in our IMAP layer ?
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /*
+     * Override Folder method to keep track of whether we have any
+     * message count listeners.  Normally we won't have any, so we
+     * can avoid creating message objects to pass to the notify
+     * method.  It's too hard to keep track of when all listeners
+     * are removed, and that's a rare case, so we don't try.
+     */
+    @Override
+    public synchronized void addMessageCountListener(MessageCountListener l) { 
+	super.addMessageCountListener(l);
+	hasMessageCountListener = true;
+    }
+
+    /***********************************************************
+     *		UIDFolder interface methods
+     **********************************************************/
+
+    /**
+     * Returns the UIDValidity for this folder.
+     */
+    @Override
+    public synchronized long getUIDValidity() throws MessagingException {
+	if (opened) // we already have this information
+	    return uidvalidity;
+
+        IMAPProtocol p = null;
+        Status status = null;
+
+	try {
+	    p = getStoreProtocol();	// XXX
+	    String[] item = { "UIDVALIDITY" };
+	    status = p.status(fullName, item);
+	} catch (BadCommandException bex) {
+	    // Probably a RFC1730 server
+	    throw new MessagingException("Cannot obtain UIDValidity", bex);
+	} catch (ConnectionException cex) {
+            // Oops, the store or folder died on us.
+            throwClosedException(cex);
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	} finally {
+            releaseStoreProtocol(p);
+        }
+
+	if (status == null)
+	    throw new MessagingException("Cannot obtain UIDValidity");
+	return status.uidvalidity;
+    }
+
+    /**
+     * Returns the predicted UID that will be assigned to the
+     * next message that is appended to this folder.
+     * If the folder is closed, the STATUS command is used to
+     * retrieve this value.  If the folder is open, the value
+     * returned from the SELECT or EXAMINE command is returned.
+     * Note that messages may have been appended to the folder
+     * while it was open and thus this value may be out of
+     * date. <p>
+     *
+     * Servers implementing RFC2060 likely won't return this value
+     * when a folder is opened.  Servers implementing RFC3501
+     * should return this value when a folder is opened. <p>
+     *
+     * @return	the UIDNEXT value, or -1 if unknown
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.3.3
+     */
+    @Override
+    public synchronized long getUIDNext() throws MessagingException {
+	if (opened) // we already have this information
+	    return uidnext;
+
+        IMAPProtocol p = null;
+        Status status = null;
+
+	try {
+	    p = getStoreProtocol();	// XXX
+	    String[] item = { "UIDNEXT" };
+	    status = p.status(fullName, item);
+	} catch (BadCommandException bex) {
+	    // Probably a RFC1730 server
+	    throw new MessagingException("Cannot obtain UIDNext", bex);
+	} catch (ConnectionException cex) {
+            // Oops, the store or folder died on us.
+            throwClosedException(cex);
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	} finally {
+            releaseStoreProtocol(p);
+        }
+
+	if (status == null)
+	    throw new MessagingException("Cannot obtain UIDNext");
+	return status.uidnext;
+    }
+
+    /**
+     * Get the Message corresponding to the given UID.
+     * If no such message exists, <code> null </code> is returned.
+     */
+    @Override
+    public synchronized Message getMessageByUID(long uid) 
+			throws MessagingException {
+	checkOpened(); // insure folder is open
+
+	IMAPMessage m = null;
+
+	try {
+	    synchronized(messageCacheLock) {
+		Long l = Long.valueOf(uid);
+
+		if (uidTable != null) {
+		    // Check in uidTable
+		    m = uidTable.get(l);
+		    if (m != null) // found it
+			return m;
+		} else
+		    uidTable = new Hashtable<>();
+
+		// Check with the server
+		// Issue UID FETCH command
+		getProtocol().fetchSequenceNumber(uid);
+
+		if (uidTable != null) {
+		    // Check in uidTable
+		    m = uidTable.get(l);
+		    if (m != null) // found it
+			return m;
+		}
+	    }
+	} catch(ConnectionException cex) {
+	    throw new FolderClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+
+	return m;
+    }
+
+    /**
+     * Get the Messages specified by the given range. <p>
+     * Returns Message objects for all valid messages in this range.
+     * Returns an empty array if no messages are found.
+     */
+    @Override
+    public synchronized Message[] getMessagesByUID(long start, long end) 
+			throws MessagingException {
+	checkOpened(); // insure that folder is open
+
+	Message[] msgs; // array of messages to be returned
+
+	try {
+	    synchronized(messageCacheLock) {
+		if (uidTable == null)
+		    uidTable = new Hashtable<>();
+
+		// Issue UID FETCH for given range
+		long[] ua = getProtocol().fetchSequenceNumbers(start, end);
+
+		List<Message> ma = new ArrayList<>();
+		// NOTE: Below must be within messageCacheLock region
+		for (int i = 0; i < ua.length; i++) {
+		    Message m = uidTable.get(Long.valueOf(ua[i]));
+		    if (m != null) // found it
+			ma.add(m);
+		}
+		msgs = ma.toArray(new Message[ma.size()]);
+	    }
+	} catch(ConnectionException cex) {
+	    throw new FolderClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+
+	return msgs;
+    }
+
+    /**
+     * Get the Messages specified by the given array. <p>
+     *
+     * <code>uids.length()</code> elements are returned.
+     * If any UID in the array is invalid, a <code>null</code> entry
+     * is returned for that element.
+     */
+    @Override
+    public synchronized Message[] getMessagesByUID(long[] uids)
+			throws MessagingException {
+	checkOpened(); // insure that folder is open
+
+	try {
+	    synchronized(messageCacheLock) {
+		long[] unavailUids = uids;
+		if (uidTable != null) {
+		    // to collect unavailable UIDs
+		    List<Long> v = new ArrayList<>();
+		    for (long uid : uids) {
+			if (!uidTable.containsKey(uid)) {
+			    // This UID has not been loaded yet.
+			    v.add(uid);
+			}
+		    }
+
+		    int vsize = v.size();
+		    unavailUids = new long[vsize];
+		    for (int i = 0; i < vsize; i++) {
+			unavailUids[i] = v.get(i);
+		    }
+		} else
+		    uidTable = new Hashtable<>();
+
+		if (unavailUids.length > 0) {
+		    // Issue UID FETCH request for given uids
+		    getProtocol().fetchSequenceNumbers(unavailUids);
+		}
+
+		// Return array of size = uids.length
+		Message[] msgs = new Message[uids.length];
+		for (int i = 0; i < uids.length; i++)
+		    msgs[i] = (Message)uidTable.get(Long.valueOf(uids[i]));
+		return msgs;
+	    }
+	} catch(ConnectionException cex) {
+	    throw new FolderClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /**
+     * Get the UID for the specified message.
+     */
+    @Override
+    public synchronized long getUID(Message message) 
+			throws MessagingException {
+	if (message.getFolder() != this)
+	    throw new NoSuchElementException(
+		"Message does not belong to this folder");
+
+	checkOpened(); // insure that folder is open
+
+	if (!(message instanceof IMAPMessage))
+	    throw new MessagingException("message is not an IMAPMessage");
+	IMAPMessage m = (IMAPMessage)message;
+	// If the message already knows its UID, great ..
+	long uid;
+	if ((uid = m.getUID()) != -1)
+	    return uid;
+
+	synchronized(messageCacheLock) { // Acquire Lock
+	    try {
+		IMAPProtocol p = getProtocol();
+		m.checkExpunged(); // insure that message is not expunged
+		UID u = p.fetchUID(m.getSequenceNumber());
+
+		if (u != null) {
+		    uid = u.uid;
+		    m.setUID(uid); // set message's UID
+
+		    // insert this message into uidTable
+		    if (uidTable == null)
+			uidTable = new Hashtable<>();
+		    uidTable.put(Long.valueOf(uid), m);
+		}
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(this, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+
+	return uid;
+    }
+
+    /**
+     * Servers that support the UIDPLUS extension
+     * (<A HREF="http://www.ietf.org/rfc/rfc4315.txt">RFC 4315</A>)
+     * may indicate that this folder does not support persistent UIDs;
+     * that is, UIDVALIDITY will be different each time the folder is
+     * opened.  Only valid when the folder is open.
+     *
+     * @return	true if UIDs are not sticky
+     * @exception	MessagingException for failures
+     * @exception	IllegalStateException	if the folder isn't open
+     * @see "RFC 4315"
+     * @since	JavaMail 1.6.0
+     */
+    public synchronized boolean getUIDNotSticky() throws MessagingException {
+	checkOpened();
+	return uidNotSticky;
+    }
+
+    /**
+     * Get or create Message objects for the UIDs.
+     */
+    private Message[] createMessagesForUIDs(long[] uids) {
+	IMAPMessage[] msgs = new IMAPMessage[uids.length];
+	for (int i = 0; i < uids.length; i++) {
+	    IMAPMessage m = null;
+	    if (uidTable != null)
+		m = uidTable.get(Long.valueOf(uids[i]));
+	    if (m == null) {
+		// fake it, we don't know what message this really is
+		m = newIMAPMessage(-1);	// no sequence number
+		m.setUID(uids[i]);
+		m.setExpunged(true);
+	    }
+	    msgs[i++] = m;
+	}
+	return msgs;
+    }
+
+    /**
+     * Returns the HIGHESTMODSEQ for this folder.
+     *
+     * @return	the HIGHESTMODSEQ value
+     * @exception	MessagingException for failures
+     * @see "RFC 4551"
+     * @since	JavaMail 1.5.1
+     */
+    public synchronized long getHighestModSeq() throws MessagingException {
+	if (opened) // we already have this information
+	    return highestmodseq;
+
+        IMAPProtocol p = null;
+        Status status = null;
+
+	try {
+	    p = getStoreProtocol();	// XXX
+	    if (!p.hasCapability("CONDSTORE"))
+		throw new BadCommandException("CONDSTORE not supported");
+	    String[] item = { "HIGHESTMODSEQ" };
+	    status = p.status(fullName, item);
+	} catch (BadCommandException bex) {
+	    // Probably a RFC1730 server
+	    throw new MessagingException("Cannot obtain HIGHESTMODSEQ", bex);
+	} catch (ConnectionException cex) {
+            // Oops, the store or folder died on us.
+            throwClosedException(cex);
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	} finally {
+            releaseStoreProtocol(p);
+        }
+
+	if (status == null)
+	    throw new MessagingException("Cannot obtain HIGHESTMODSEQ");
+	return status.highestmodseq;
+    }
+
+    /**
+     * Get the messages that have been changed since the given MODSEQ value.
+     * Also, prefetch the flags for the messages. <p>
+     *
+     * The server must support the CONDSTORE extension.
+     *
+     * @param	start	the first message number
+     * @param	end	the last message number
+     * @param	modseq	the MODSEQ value
+     * @return	the changed messages
+     * @exception	MessagingException for failures
+     * @see "RFC 4551"
+     * @since	JavaMail 1.5.1
+     */
+    public synchronized Message[] getMessagesByUIDChangedSince(
+				long start, long end, long modseq)
+				throws MessagingException {
+	checkOpened(); // insure that folder is open
+
+	try {
+	    synchronized (messageCacheLock) {
+		IMAPProtocol p = getProtocol();
+		if (!p.hasCapability("CONDSTORE"))
+		    throw new BadCommandException("CONDSTORE not supported");
+
+		// Issue FETCH for given range
+		int[] nums = p.uidfetchChangedSince(start, end, modseq);
+		return getMessagesBySeqNumbers(nums);
+	    }
+	} catch(ConnectionException cex) {
+	    throw new FolderClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+    }
+
+    /**
+     * Get the quotas for the quotaroot associated with this
+     * folder.  Note that many folders may have the same quotaroot.
+     * Quotas are controlled on the basis of a quotaroot, not
+     * (necessarily) a folder.  The relationship between folders
+     * and quotaroots depends on the IMAP server.  Some servers
+     * might implement a single quotaroot for all folders owned by
+     * a user.  Other servers might implement a separate quotaroot
+     * for each folder.  A single folder can even have multiple
+     * quotaroots, perhaps controlling quotas for different
+     * resources.
+     *
+     * @return	array of Quota objects for the quotaroots associated with
+     *		this folder
+     * @exception MessagingException	if the server doesn't support the
+     *					QUOTA extension
+     */
+    public Quota[] getQuota() throws MessagingException {
+	return (Quota[])doOptionalCommand("QUOTA not supported",
+	    new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    return p.getQuotaRoot(fullName);
+		}
+	    });
+    }
+
+    /**
+     * Set the quotas for the quotaroot specified in the quota argument.
+     * Typically this will be one of the quotaroots associated with this
+     * folder, as obtained from the <code>getQuota</code> method, but it
+     * need not be.
+     *
+     * @param	quota	the quota to set
+     * @exception MessagingException	if the server doesn't support the
+     *					QUOTA extension
+     */
+    public void setQuota(final Quota quota) throws MessagingException {
+	doOptionalCommand("QUOTA not supported",
+	    new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    p.setQuota(quota);
+		    return null;
+		}
+	    });
+    }
+
+    /**
+     * Get the access control list entries for this folder.
+     *
+     * @return	array of access control list entries
+     * @exception MessagingException	if the server doesn't support the
+     *					ACL extension
+     */
+    public ACL[] getACL() throws MessagingException {
+	return (ACL[])doOptionalCommand("ACL not supported",
+	    new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    return p.getACL(fullName);
+		}
+	    });
+    }
+
+    /**
+     * Add an access control list entry to the access control list
+     * for this folder.
+     *
+     * @param	acl	the access control list entry to add
+     * @exception MessagingException	if the server doesn't support the
+     *					ACL extension
+     */
+    public void addACL(ACL acl) throws MessagingException {
+	setACL(acl, '\0');
+    }
+
+    /**
+     * Remove any access control list entry for the given identifier
+     * from the access control list for this folder.
+     *
+     * @param	name	the identifier for which to remove all ACL entries
+     * @exception MessagingException	if the server doesn't support the
+     *					ACL extension
+     */
+    public void removeACL(final String name) throws MessagingException {
+	doOptionalCommand("ACL not supported",
+	    new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    p.deleteACL(fullName, name);
+		    return null;
+		}
+	    });
+    }
+
+    /**
+     * Add the rights specified in the ACL to the entry for the
+     * identifier specified in the ACL.  If an entry for the identifier
+     * doesn't already exist, add one.
+     *
+     * @param	acl	the identifer and rights to add
+     * @exception MessagingException	if the server doesn't support the
+     *					ACL extension
+     */
+    public void addRights(ACL acl) throws MessagingException {
+	setACL(acl, '+');
+    }
+
+    /**
+     * Remove the rights specified in the ACL from the entry for the
+     * identifier specified in the ACL.
+     *
+     * @param	acl	the identifer and rights to remove
+     * @exception MessagingException	if the server doesn't support the
+     *					ACL extension
+     */
+    public void removeRights(ACL acl) throws MessagingException {
+	setACL(acl, '-');
+    }
+
+    /**
+     * Get all the rights that may be allowed to the given identifier.
+     * Rights are grouped per RFC 2086 and each group is returned as an
+     * element of the array.  The first element of the array is the set
+     * of rights that are always granted to the identifier.  Later
+     * elements are rights that may be optionally granted to the
+     * identifier. <p>
+     *
+     * Note that this method lists the rights that it is possible to
+     * assign to the given identifier, <em>not</em> the rights that are
+     * actually granted to the given identifier.  For the latter, see
+     * the <code>getACL</code> method.
+     *
+     * @param	name	the identifier to list rights for
+     * @return		array of Rights objects representing possible
+     *			rights for the identifier
+     * @exception MessagingException	if the server doesn't support the
+     *					ACL extension
+     */
+    public Rights[] listRights(final String name) throws MessagingException {
+	return (Rights[])doOptionalCommand("ACL not supported",
+	    new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    return p.listRights(fullName, name);
+		}
+	    });
+    }
+
+    /**
+     * Get the rights allowed to the currently authenticated user.
+     *
+     * @return	the rights granted to the current user
+     * @exception MessagingException	if the server doesn't support the
+     *					ACL extension
+     */
+    public Rights myRights() throws MessagingException {
+	return (Rights)doOptionalCommand("ACL not supported",
+	    new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    return p.myRights(fullName);
+		}
+	    });
+    }
+
+    private void setACL(final ACL acl, final char mod)
+				throws MessagingException {
+	doOptionalCommand("ACL not supported",
+	    new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    p.setACL(fullName, mod, acl);
+		    return null;
+		}
+	    });
+    }
+
+    /**
+     * Get the attributes that the IMAP server returns with the
+     * LIST response.
+     *
+     * @return	array of attributes for this folder
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.3.3
+     */
+    public synchronized String[] getAttributes() throws MessagingException {
+	checkExists();
+	if (attributes == null)
+	    exists();		// do a LIST to set the attributes
+	return attributes == null ? new String[0] : attributes.clone();
+    }
+
+    /**
+     * Use the IMAP IDLE command (see
+     * <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>),
+     * if supported by the server, to enter idle mode so that the server
+     * can send unsolicited notifications of new messages arriving, etc.
+     * without the need for the client to constantly poll the server.
+     * Use an appropriate listener to be notified of new messages or
+     * other events.  When another thread (e.g., the listener thread)
+     * needs to issue an IMAP comand for this folder, the idle mode will
+     * be terminated and this method will return.  Typically the caller
+     * will invoke this method in a loop. <p>
+     *
+     * The mail.imap.minidletime property enforces a minimum delay
+     * before returning from this method, to ensure that other threads
+     * have a chance to issue commands before the caller invokes this
+     * method again.  The default delay is 10 milliseconds.
+     *
+     * @exception MessagingException	if the server doesn't support the
+     *					IDLE extension
+     * @exception IllegalStateException	if the folder isn't open
+     *
+     * @since	JavaMail 1.4.1
+     */
+    public void idle() throws MessagingException {
+	idle(false);
+    }
+
+    /**
+     * Like {@link #idle}, but if <code>once</code> is true, abort the
+     * IDLE command after the first notification, to allow the caller
+     * to process any notification synchronously.
+     *
+     * @param	once	only do one notification?
+     * @exception MessagingException	if the server doesn't support the
+     *					IDLE extension
+     * @exception IllegalStateException	if the folder isn't open
+     *
+     * @since	JavaMail 1.4.3
+     */
+    public void idle(boolean once) throws MessagingException {
+	synchronized (this) {
+	    /*
+	     * We can't support the idle method if we're using SocketChannels
+	     * because SocketChannels don't allow simultaneous read and write.
+	     * If we're blocked in a read waiting for IDLE responses, we can't
+	     * send the DONE message to abort the IDLE.  Sigh.
+	     * XXX - We could do select here too, like IdleManager, instead
+	     * of blocking in read, but that's more complicated.
+	     */
+	    if (protocol != null && protocol.getChannel() != null)
+		throw new MessagingException(
+			    "idle method not supported with SocketChannels");
+	}
+	if (!startIdle(null))
+	    return;
+
+	/*
+	 * We gave up the folder lock so that other threads
+	 * can get into the folder far enough to see that we're
+	 * in IDLE and abort the IDLE.
+	 *
+	 * Now we read responses from the IDLE command, especially
+	 * including unsolicited notifications from the server.
+	 * We don't hold the messageCacheLock while reading because
+	 * it protects the idleState and other threads need to be
+	 * able to examine the state.
+	 *
+	 * The messageCacheLock is held in handleIdle while processing
+	 * the responses so that we can update the number of messages
+	 * in the folder (for example).
+	 */
+	for (;;) {
+	    if (!handleIdle(once))
+		break;
+	}
+
+	/*
+	 * Enforce a minimum delay to give time to threads
+	 * processing the responses that came in while we
+	 * were idle.
+	 */
+	int minidle = ((IMAPStore)store).getMinIdleTime();
+	if (minidle > 0) {
+	    try {
+		Thread.sleep(minidle);
+	    } catch (InterruptedException ex) {
+		// restore the interrupted state, which callers might depend on
+		Thread.currentThread().interrupt();
+	    }
+	}
+    }
+
+    /**
+     * Start the IDLE command and put this folder into the IDLE state.
+     * IDLE processing is done later in handleIdle(), e.g., called from
+     * the IdleManager.
+     *
+     * @return	true if IDLE started, false otherwise
+     * @exception MessagingException	if the server doesn't support the
+     *					IDLE extension
+     * @exception IllegalStateException	if the folder isn't open
+     * @since	JavaMail 1.5.2
+     */
+    boolean startIdle(final IdleManager im) throws MessagingException {
+	// ASSERT: Must NOT be called with this folder's
+	// synchronization lock held.
+	assert !Thread.holdsLock(this);
+	synchronized(this) {
+	    checkOpened();
+	    if (im != null && idleManager != null && im != idleManager)
+		throw new MessagingException(
+		    "Folder already being watched by another IdleManager");
+	    Boolean started = (Boolean)doOptionalCommand("IDLE not supported",
+		new ProtocolCommand() {
+		    @Override
+		    public Object doCommand(IMAPProtocol p)
+			    throws ProtocolException {
+			// if the IdleManager is already watching this folder,
+			// there's nothing to do here
+			if (idleState == IDLE &&
+				im != null && im == idleManager)
+			    return Boolean.TRUE;	// already watching it
+			if (idleState == RUNNING) {
+			    p.idleStart();
+			    logger.finest("startIdle: set to IDLE");
+			    idleState = IDLE;
+			    idleManager = im;
+			    return Boolean.TRUE;
+			} else {
+			    // some other thread must be running the IDLE
+			    // command, we'll just wait for it to finish
+			    // without aborting it ourselves
+			    try {
+				// give up lock and wait to be not idle
+				messageCacheLock.wait();
+			    } catch (InterruptedException ex) {
+				// restore the interrupted state, which callers
+				// might depend on
+				Thread.currentThread().interrupt();
+			    }
+			    return Boolean.FALSE;
+			}
+		    }
+		});
+	    logger.log(Level.FINEST, "startIdle: return {0}", started);
+	    return started.booleanValue();
+	}
+    }
+
+    /**
+     * Read a response from the server while we're in the IDLE state.
+     * We hold the messageCacheLock while processing the
+     * responses so that we can update the number of messages
+     * in the folder (for example).
+     *
+     * @param	once	only do one notification?
+     * @return	true if we should look for more IDLE responses,
+     *		false if IDLE is done
+     * @exception MessagingException	for errors
+     * @since	JavaMail 1.5.2
+     */
+    boolean handleIdle(boolean once) throws MessagingException {
+	Response r = null;
+	do {
+	    r = protocol.readIdleResponse();
+	    try {
+		synchronized (messageCacheLock) {
+		    if (r.isBYE() && r.isSynthetic() && idleState == IDLE) {
+			/*
+			 * If it was a timeout and no bytes were transferred
+			 * we ignore it and go back and read again.
+			 * If the I/O was otherwise interrupted, and no
+			 * bytes were transferred, we take it as a request
+			 * to abort the IDLE.
+			 */
+			Exception ex = r.getException();
+			if (ex instanceof InterruptedIOException &&
+			    ((InterruptedIOException)ex).
+				    bytesTransferred == 0) {
+			    if (ex instanceof SocketTimeoutException) {
+				logger.finest(
+				    "handleIdle: ignoring socket timeout");
+				r = null;	// repeat do/while loop
+			    } else {
+				logger.finest("handleIdle: interrupting IDLE");
+				IdleManager im = idleManager;
+				if (im != null) {
+				    logger.finest(
+				    "handleIdle: request IdleManager to abort");
+				    im.requestAbort(this);
+				} else {
+				    logger.finest("handleIdle: abort IDLE");
+				    protocol.idleAbort();
+				    idleState = ABORTING;
+				}
+				// normally will exit the do/while loop
+			    }
+			    continue;
+			}
+		    }
+		    boolean done = true;
+		    try {
+			if (protocol == null ||
+				!protocol.processIdleResponse(r))
+			    return false;	// done
+			done = false;
+		    } finally {
+			if (done) {
+			    logger.finest("handleIdle: set to RUNNING");
+			    idleState = RUNNING;
+			    idleManager = null;
+			    messageCacheLock.notifyAll();
+			}
+		    }
+		    if (once) {
+			if (idleState == IDLE) {
+			    try {
+				protocol.idleAbort();
+			    } catch (Exception ex) {
+				// ignore any failures, still have to abort.
+				// connection failures will be detected above
+				// in the call to readIdleResponse.
+			    }
+			    idleState = ABORTING;
+			}
+		    }
+		}
+	    } catch (ConnectionException cex) {
+		// Oops, the folder died on us.
+		throw new FolderClosedException(this, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	// keep processing responses already in our buffer
+	} while (r == null || protocol.hasResponse());
+	return true;
+    }
+
+    /*
+     * If an IDLE command is in progress, abort it if necessary,
+     * and wait until it completes.
+     * ASSERT: Must be called with the message cache lock held.
+     */
+    void waitIfIdle() throws ProtocolException {
+	assert Thread.holdsLock(messageCacheLock);
+	while (idleState != RUNNING) {
+	    if (idleState == IDLE) {
+		IdleManager im = idleManager;
+		if (im != null) {
+		    logger.finest("waitIfIdle: request IdleManager to abort");
+		    im.requestAbort(this);
+		} else {
+		    logger.finest("waitIfIdle: abort IDLE");
+		    protocol.idleAbort();
+		    idleState = ABORTING;
+		}
+	    } else
+		logger.log(Level.FINEST, "waitIfIdle: idleState {0}", idleState);
+	    try {
+		// give up lock and wait to be not idle
+		if (logger.isLoggable(Level.FINEST))
+		    logger.finest("waitIfIdle: wait to be not idle: " +
+				    Thread.currentThread());
+		messageCacheLock.wait();
+		if (logger.isLoggable(Level.FINEST))
+		    logger.finest("waitIfIdle: wait done, idleState " +
+				    idleState + ": " + Thread.currentThread());
+	    } catch (InterruptedException ex) {
+		// restore the interrupted state, which callers might depend on
+		Thread.currentThread().interrupt();
+		// If someone is trying to interrupt us we can't keep going
+		// around the loop waiting for IDLE to complete, but we can't
+		// just return because callers expect the idleState to be
+		// RUNNING when we return.  Throwing this exception seems
+		// like the best choice.
+		throw new ProtocolException("Interrupted waitIfIdle", ex);
+	    }
+	}
+    }
+
+    /*
+     * Send the DONE command that aborts the IDLE; used by IdleManager.
+     */
+    void idleAbort() {
+	synchronized (messageCacheLock) {
+	    if (idleState == IDLE && protocol != null) {
+		protocol.idleAbort();
+		idleState = ABORTING;
+	    }
+	}
+    }
+
+    /*
+     * Send the DONE command that aborts the IDLE and wait for the response;
+     * used by IdleManager.
+     */
+    void idleAbortWait() {
+	synchronized (messageCacheLock) {
+	    if (idleState == IDLE && protocol != null) {
+		protocol.idleAbort();
+		idleState = ABORTING;
+
+		// read responses until OK or connection failure
+		try {
+		    for (;;) {
+			if (!handleIdle(false))
+			    break;
+		    }
+		} catch (Exception ex) {
+		    // assume it's a connection failure; nothing more to do
+		    logger.log(Level.FINEST, "Exception in idleAbortWait", ex);
+		}
+		logger.finest("IDLE aborted");
+	    }
+	}
+    }
+
+    /**
+     * Return the SocketChannel for this connection, if any, for use
+     * in IdleManager.
+     */
+    SocketChannel getChannel() {
+	return protocol != null ? protocol.getChannel() : null;
+    }
+
+    /**
+     * Send the IMAP ID command (if supported by the server) and return
+     * the result from the server.  The ID command identfies the client
+     * to the server and returns information about the server to the client.
+     * See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
+     * The returned Map is unmodifiable.
+     *
+     * @param	clientParams	a Map of keys and values identifying the client
+     * @return			a Map of keys and values identifying the server
+     * @exception MessagingException	if the server doesn't support the
+     *					ID extension
+     * @since	JavaMail 1.5.1
+     */
+    @SuppressWarnings("unchecked")
+    public Map<String, String> id(final Map<String, String> clientParams)
+				throws MessagingException {
+	checkOpened();
+	return (Map<String,String>)doOptionalCommand("ID not supported",
+	    new ProtocolCommand() {
+		@Override
+		public Object doCommand(IMAPProtocol p)
+			throws ProtocolException {
+		    return p.id(clientParams);
+		}
+	    });
+    }
+
+    /**
+     * Use the IMAP STATUS command to get the indicated item.
+     * The STATUS item may be a standard item such as "RECENT" or "UNSEEN",
+     * or may be a server-specific item.
+     * The folder must be closed.  If the item is not found, or the
+     * folder is open, -1 is returned.
+     *
+     * @param	item	the STATUS item to fetch
+     * @return		the value of the STATUS item, or -1
+     * @exception MessagingException	for errors
+     * @since	JavaMail 1.5.2
+     */
+    public synchronized long getStatusItem(String item)
+				throws MessagingException {
+	if (!opened) {
+	    checkExists();
+
+	    IMAPProtocol p = null;
+	    Status status = null;
+	    try {
+		p = getStoreProtocol();	// XXX
+		String[] items = { item };
+		status = p.status(fullName, items);
+		return status != null ? status.getItem(item) : -1;
+	    } catch (BadCommandException bex) {
+		// doesn't support STATUS, probably vanilla IMAP4 ..
+		// Could EXAMINE, SEARCH for UNREAD messages and
+		// return the count .. bah, not worth it.
+		return -1;
+	    } catch (ConnectionException cex) {
+		throw new StoreClosedException(store, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    } finally {
+		releaseStoreProtocol(p);
+	    }
+	}
+	return -1;
+    }
+
+    /**
+     * The response handler. This is the callback routine that is 
+     * invoked by the protocol layer.
+     */
+    /*
+     * ASSERT: This method must be called only when holding the
+     * messageCacheLock.
+     * ASSERT: This method must *not* invoke any other method that
+     * might grab the 'folder' lock or 'message' lock (i.e., any 
+     * synchronized methods on IMAPFolder or IMAPMessage)
+     * since that will result in violating the locking hierarchy.
+     */
+    @Override
+    public void handleResponse(Response r) {
+	assert Thread.holdsLock(messageCacheLock);
+
+	/*
+	 * First, delegate possible ALERT or notification to the Store.
+	 */
+	if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
+	    ((IMAPStore)store).handleResponseCode(r);
+
+	/*
+	 * Now check whether this is a BYE or OK response and
+	 * handle appropriately.
+	 */
+	if (r.isBYE()) {
+	    if (opened)		// XXX - accessed without holding folder lock
+		cleanup(false); 
+	    return;
+	} else if (r.isOK()) {
+	    // HIGHESTMODSEQ can be updated on any OK response
+	    r.skipSpaces();
+	    if (r.readByte() == '[') {
+		String s = r.readAtom();
+		if (s.equalsIgnoreCase("HIGHESTMODSEQ"))
+		    highestmodseq = r.readLong();
+	    }
+	    r.reset();
+	    return;
+	} else if (!r.isUnTagged()) {
+	    return;	// might be a continuation for IDLE
+	}
+
+	/* Now check whether this is an IMAP specific response */
+	if (!(r instanceof IMAPResponse)) {
+	    // Probably a bug in our code !
+	    // XXX - should be an assert
+	    logger.fine("UNEXPECTED RESPONSE : " + r.toString());
+	    return;
+	}
+
+	IMAPResponse ir = (IMAPResponse)r;
+
+	if (ir.keyEquals("EXISTS")) { // EXISTS
+	    int exists = ir.getNumber();
+	    if (exists <= realTotal) 
+		// Could be the EXISTS following EXPUNGE, ignore 'em
+		return;
+	
+	    int count = exists - realTotal; // number of new messages
+	    Message[] msgs = new Message[count];
+
+	    // Add 'count' new IMAPMessage objects into the messageCache
+	    messageCache.addMessages(count, realTotal + 1);
+	    int oldtotal = total;	// used in loop below
+	    realTotal += count;
+	    total += count;
+
+	    // avoid instantiating Message objects if no listeners.
+	    if (hasMessageCountListener) {
+		for (int i = 0; i < count; i++)
+		    msgs[i] = messageCache.getMessage(++oldtotal);
+
+		// Notify listeners.
+		notifyMessageAddedListeners(msgs);
+	    }
+
+	} else if (ir.keyEquals("EXPUNGE")) {
+	    // EXPUNGE response.
+
+	    int seqnum = ir.getNumber();
+	    if (seqnum > realTotal) {
+		// A message was expunged that we never knew about.
+		// Exchange will do this.  Just ignore the notification.
+		// (Alternatively, we could simulate an EXISTS for the
+		// expunged message before expunging it.)
+		return;
+	    }
+	    Message[] msgs = null;
+	    if (doExpungeNotification && hasMessageCountListener) {
+		// save the Message object first; can't look it
+		// up after it's expunged
+		msgs = new Message[] { getMessageBySeqNumber(seqnum) };
+		if (msgs[0] == null)	// XXX - should never happen
+		    msgs = null;
+	    }
+
+	    messageCache.expungeMessage(seqnum);
+
+	    // decrement 'realTotal'; but leave 'total' unchanged
+	    realTotal--;
+
+	    if (msgs != null)	// Do the notification here.
+		notifyMessageRemovedListeners(false, msgs);
+
+	} else if (ir.keyEquals("VANISHED")) {
+	    // after the folder is opened with QRESYNC, a VANISHED response
+	    // without the (EARLIER) tag is used instead of the EXPUNGE
+	    // response
+
+	    // "VANISHED" SP ["(EARLIER)"] SP known-uids
+	    String[] s = ir.readAtomStringList();
+	    if (s == null) {	// no (EARLIER)
+		String uids = ir.readAtom();
+		UIDSet[] uidset = UIDSet.parseUIDSets(uids);
+		// assume no duplicates and no UIDs out of range
+		realTotal -= UIDSet.size(uidset);
+		long[] luid = UIDSet.toArray(uidset);
+		Message[] msgs = createMessagesForUIDs(luid);
+		for (Message m : msgs) {
+		    if (m.getMessageNumber() > 0)
+			messageCache.expungeMessage(m.getMessageNumber());
+		}
+		if (doExpungeNotification && hasMessageCountListener) {
+		    notifyMessageRemovedListeners(true, msgs);
+		}
+	    } // else if (EARLIER), ignore
+
+	} else if (ir.keyEquals("FETCH")) {
+	    assert ir instanceof FetchResponse : "!ir instanceof FetchResponse";
+	    Message msg = processFetchResponse((FetchResponse)ir);
+	    if (msg != null)
+		notifyMessageChangedListeners(
+			MessageChangedEvent.FLAGS_CHANGED, msg);
+
+	} else if (ir.keyEquals("RECENT")) {
+	    // update 'recent'
+	    recent = ir.getNumber();
+	}
+    }
+
+    /**
+     * Process a FETCH response.
+     * The only unsolicited FETCH response that makes sense
+     * to me (for now) is FLAGS updates, which might include
+     * UID and MODSEQ information.  Ignore any other junk.
+     */
+    private Message processFetchResponse(FetchResponse fr) {
+	IMAPMessage msg = getMessageBySeqNumber(fr.getNumber());
+	if (msg != null) {	// should always be true
+	    boolean notify = false;
+
+	    UID uid = fr.getItem(UID.class);
+	    if (uid != null && msg.getUID() != uid.uid) {
+		msg.setUID(uid.uid);
+		if (uidTable == null)
+		    uidTable = new Hashtable<>();
+		uidTable.put(Long.valueOf(uid.uid), msg);
+		notify = true;
+	    }
+
+	    MODSEQ modseq = fr.getItem(MODSEQ.class);
+	    if (modseq != null && msg._getModSeq() != modseq.modseq) {
+		msg.setModSeq(modseq.modseq);
+		/*
+		 * XXX - should we update the folder's HIGHESTMODSEQ or not?
+		 *
+		if (modseq.modseq > highestmodseq)
+		    highestmodseq = modseq.modseq;
+		 */
+		notify = true;
+	    }
+
+	    // Get FLAGS response, if present
+	    FLAGS flags = fr.getItem(FLAGS.class);
+	    if (flags != null) {
+		msg._setFlags(flags);	// assume flags changed
+		notify = true;
+	    }
+
+	    // handle any extension items that might've changed
+	    // XXX - no notifications associated with extension items
+	    msg.handleExtensionFetchItems(fr.getExtensionItems());
+
+	    if (!notify)
+		msg = null;
+	}
+	return msg;
+    }
+
+    /**
+     * Handle the given array of Responses.
+     *
+     * ASSERT: This method must be called only when holding the
+     * 	messageCacheLock
+     */
+    void handleResponses(Response[] r) {
+	for (int i = 0; i < r.length; i++) {
+	    if (r[i] != null)
+		handleResponse(r[i]);
+	}
+    }
+
+    /**
+     * Get this folder's Store's protocol connection.
+     *
+     * When acquiring a store protocol object, it is important to
+     * use the following steps:
+     *
+     * <blockquote><pre>
+     *     IMAPProtocol p = null;
+     *     try {
+     *         p = getStoreProtocol();
+     *         // perform the command
+     *     } catch (WhateverException ex) {
+     *         // handle it
+     *     } finally {
+     *         releaseStoreProtocol(p);
+     *     }
+     * </pre></blockquote>
+     *
+     * ASSERT: Must be called with this folder's synchronization lock held.
+     *
+     * @return	the IMAPProtocol for the Store's connection
+     * @exception	ProtocolException for protocol errors
+     */
+    protected synchronized IMAPProtocol getStoreProtocol() 
+            throws ProtocolException {
+	connectionPoolLogger.fine("getStoreProtocol() borrowing a connection");
+	return ((IMAPStore)store).getFolderStoreProtocol();
+    }
+
+    /**
+     * Throw the appropriate 'closed' exception.
+     *
+     * @param	cex	the ConnectionException
+     * @exception	FolderClosedException if the folder is closed
+     * @exception	StoreClosedException if the store is closed
+     */
+    protected synchronized void throwClosedException(ConnectionException cex) 
+            throws FolderClosedException, StoreClosedException {
+	// If it's the folder's protocol object, throw a FolderClosedException;
+	// otherwise, throw a StoreClosedException.
+	// If a command has failed because the connection is closed,
+	// the folder will have already been forced closed by the
+	// time we get here and our protocol object will have been
+	// released, so if we no longer have a protocol object we base
+	// this decision on whether we *think* the folder is open.
+	if ((protocol != null && cex.getProtocol() == protocol) ||
+		(protocol == null && !reallyClosed))
+            throw new FolderClosedException(this, cex.getMessage());
+        else
+            throw new StoreClosedException(store, cex.getMessage());
+    }
+
+    /**
+     * Return the IMAPProtocol object for this folder. <p>
+     *
+     * This method will block if necessary to wait for an IDLE
+     * command to finish.
+     *
+     * @return	the IMAPProtocol object used when the folder is open
+     * @exception	ProtocolException for protocol errors
+     */
+    protected IMAPProtocol getProtocol() throws ProtocolException {
+	assert Thread.holdsLock(messageCacheLock);
+	waitIfIdle();
+	// if we no longer have a protocol object after waiting, it probably
+	// means the connection has been closed due to a communnication error,
+	// or possibly because the folder has been closed
+	if (protocol == null)
+	    throw new ConnectionException("Connection closed");
+        return protocol;
+    }
+
+    /**
+     * A simple interface for user-defined IMAP protocol commands.
+     */
+    public static interface ProtocolCommand {
+	/**
+	 * Execute the user-defined command using the supplied IMAPProtocol
+	 * object.
+	 *
+	 * @param	protocol	the IMAPProtocol for the connection
+	 * @return			the results of the command
+	 * @exception	ProtocolException for protocol errors
+	 */
+	public Object doCommand(IMAPProtocol protocol) throws ProtocolException;
+    }
+
+    /**
+     * Execute a user-supplied IMAP command.  The command is executed
+     * in the appropriate context with the necessary locks held and
+     * using the appropriate <code>IMAPProtocol</code> object. <p>
+     *
+     * This method returns whatever the <code>ProtocolCommand</code>
+     * object's <code>doCommand</code> method returns.  If the
+     * <code>doCommand</code> method throws a <code>ConnectionException</code>
+     * it is translated into a <code>StoreClosedException</code> or
+     * <code>FolderClosedException</code> as appropriate.  If the
+     * <code>doCommand</code> method throws a <code>ProtocolException</code>
+     * it is translated into a <code>MessagingException</code>. <p>
+     *
+     * The following example shows how to execute the IMAP NOOP command.
+     * Executing more complex IMAP commands requires intimate knowledge
+     * of the <code>com.sun.mail.iap</code> and
+     * <code>com.sun.mail.imap.protocol</code> packages, best acquired by
+     * reading the source code.
+     *
+     * <blockquote><pre>
+     * import com.sun.mail.iap.*;
+     * import com.sun.mail.imap.*;
+     * import com.sun.mail.imap.protocol.*;
+     *
+     * ...
+     *
+     * IMAPFolder f = (IMAPFolder)folder;
+     * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
+     *	public Object doCommand(IMAPProtocol p)
+     *			throws ProtocolException {
+     *	    p.simpleCommand("NOOP", null);
+     *	    return null;
+     *	}
+     * });
+     * </pre></blockquote>
+     * <p>
+     *
+     * Here's a more complex example showing how to use the proposed
+     * IMAP SORT extension:
+     *
+     * <blockquote><pre>
+     * import com.sun.mail.iap.*;
+     * import com.sun.mail.imap.*;
+     * import com.sun.mail.imap.protocol.*;
+     *
+     * ...
+     *
+     * IMAPFolder f = (IMAPFolder)folder;
+     * Object val = f.doCommand(new IMAPFolder.ProtocolCommand() {
+     *	public Object doCommand(IMAPProtocol p)
+     *			throws ProtocolException {
+     *	    // Issue command
+     *	    Argument args = new Argument();
+     *	    Argument list = new Argument();
+     *	    list.writeString("SUBJECT");
+     *	    args.writeArgument(list);
+     *	    args.writeString("UTF-8");
+     *	    args.writeString("ALL");
+     *	    Response[] r = p.command("SORT", args);
+     *	    Response response = r[r.length-1];
+     *
+     *	    // Grab response
+     *	    Vector v = new Vector();
+     *	    if (response.isOK()) { // command succesful 
+     *		for (int i = 0, len = r.length; i &lt; len; i++) {
+     *		    if (!(r[i] instanceof IMAPResponse))
+     *			continue;
+     *
+     *		    IMAPResponse ir = (IMAPResponse)r[i];
+     *		    if (ir.keyEquals("SORT")) {
+     *			String num;
+     *			while ((num = ir.readAtomString()) != null)
+     *			    System.out.println(num);
+     *			r[i] = null;
+     *		    }
+     *		}
+     *	    }
+     *
+     *	    // dispatch remaining untagged responses
+     *	    p.notifyResponseHandlers(r);
+     *	    p.handleResult(response);
+     *
+     *	    return null;
+     *	}
+     * });
+     * </pre></blockquote>
+     *
+     * @param	cmd	the protocol command
+     * @return		the result of the command
+     * @exception	MessagingException for failures
+     */
+    public Object doCommand(ProtocolCommand cmd) throws MessagingException {
+	try {
+	    return doProtocolCommand(cmd);
+	} catch (ConnectionException cex) {
+            // Oops, the store or folder died on us.
+            throwClosedException(cex);
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+	return null;
+    }
+
+    public Object doOptionalCommand(String err, ProtocolCommand cmd)
+				throws MessagingException {
+	try {
+	    return doProtocolCommand(cmd);
+	} catch (BadCommandException bex) {
+	    throw new MessagingException(err, bex);
+	} catch (ConnectionException cex) {
+            // Oops, the store or folder died on us.
+            throwClosedException(cex);
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+	return null;
+    }
+
+    public Object doCommandIgnoreFailure(ProtocolCommand cmd)
+				throws MessagingException {
+	try {
+	    return doProtocolCommand(cmd);
+	} catch (CommandFailedException cfx) {
+	    return null;
+	} catch (ConnectionException cex) {
+            // Oops, the store or folder died on us.
+            throwClosedException(cex);
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	}
+	return null;
+    }
+
+    protected synchronized Object doProtocolCommand(ProtocolCommand cmd)
+				throws ProtocolException {
+	/*
+	 * Check whether we have a protocol object, not whether we're
+	 * opened, to allow use of the exsting protocol object in the
+	 * open method before the state is changed to "opened".
+	 */
+	if (protocol != null) {
+	    synchronized (messageCacheLock) {
+		return cmd.doCommand(getProtocol());
+	    }
+	}
+
+	// only get here if using store's connection
+	IMAPProtocol p = null;
+
+	try {
+            p = getStoreProtocol();
+	    return cmd.doCommand(p);
+	} finally {
+	    releaseStoreProtocol(p);
+	}
+    }
+
+    /**
+     * Release the store protocol object.  If we borrowed a protocol
+     * object from the connection pool, give it back.  If we used our
+     * own protocol object, nothing to do.
+     *
+     * ASSERT: Must be called with this folder's synchronization lock held.
+     *
+     * @param	p	the IMAPProtocol object
+     */
+    protected synchronized void releaseStoreProtocol(IMAPProtocol p) {
+        if (p != protocol)
+            ((IMAPStore)store).releaseFolderStoreProtocol(p);
+	else {
+	    // XXX - should never happen
+	    logger.fine("releasing our protocol as store protocol?");
+	}
+    }
+
+    /**
+     * Release the protocol object.
+     *
+     * ASSERT: This method must be called only when holding the
+     *  messageCacheLock
+     *
+     * @param	returnToPool	return the protocol object to the pool?
+     */
+    protected void releaseProtocol(boolean returnToPool) {
+        if (protocol != null) {
+            protocol.removeResponseHandler(this);
+
+            if (returnToPool)
+                ((IMAPStore)store).releaseProtocol(this, protocol);
+            else {
+		protocol.disconnect();	// make sure it's disconnected
+                ((IMAPStore)store).releaseProtocol(this, null);
+	    }
+	    protocol = null;
+        }
+    }
+    
+    /**
+     * Issue a noop command for the connection if the connection has not been
+     * used in more than a second. If <code>keepStoreAlive</code> is true,
+     * also issue a noop over the store's connection.
+     *
+     * ASSERT: This method must be called only when holding the
+     *  messageCacheLock
+     *
+     * @param	keepStoreAlive	keep the Store alive too?
+     * @exception	ProtocolException for protocol errors
+     */
+    protected void keepConnectionAlive(boolean keepStoreAlive) 
+                    throws ProtocolException {
+
+	assert Thread.holdsLock(messageCacheLock);
+	if (protocol == null)	// in case connection was closed
+	    return;
+        if (System.currentTimeMillis() - protocol.getTimestamp() > 1000) {
+	    waitIfIdle();
+	    if (protocol != null)
+		protocol.noop(); 
+	}
+
+        if (keepStoreAlive && ((IMAPStore)store).hasSeparateStoreConnection()) {
+            IMAPProtocol p = null;
+	    try {
+		p = ((IMAPStore)store).getFolderStoreProtocol();
+		if (System.currentTimeMillis() - p.getTimestamp() > 1000)
+		    p.noop();
+	    } finally {
+		((IMAPStore)store).releaseFolderStoreProtocol(p);
+	    }
+        }
+    }
+
+    /**
+     * Get the message object for the given sequence number. If
+     * none found, null is returned.
+     *
+     * ASSERT: This method must be called only when holding the
+     *  messageCacheLock
+     *
+     * @param	seqnum	the message sequence number
+     * @return	the IMAPMessage object
+     */
+    protected IMAPMessage getMessageBySeqNumber(int seqnum) {
+	if (seqnum > messageCache.size()) {
+	    // Microsoft Exchange will sometimes return message
+	    // numbers that it has not yet notified the client
+	    // about via EXISTS; ignore those messages here.
+	    // GoDaddy IMAP does this too.
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("ignoring message number " +
+		    seqnum + " outside range " + messageCache.size());
+	    return null;
+	}
+	return messageCache.getMessageBySeqnum(seqnum);
+    }
+
+    /**
+     * Get the message objects for the given sequence numbers.
+     *
+     * ASSERT: This method must be called only when holding the
+     *  messageCacheLock
+     *
+     * @param	seqnums	the array of message sequence numbers
+     * @return	the IMAPMessage objects
+     * @since	JavaMail 1.5.3
+     */
+    protected IMAPMessage[] getMessagesBySeqNumbers(int[] seqnums) {
+	IMAPMessage[] msgs = new IMAPMessage[seqnums.length];
+	int nulls = 0;
+	// Map seq-numbers into actual Messages.
+	for (int i = 0; i < seqnums.length; i++) {
+	    msgs[i] = getMessageBySeqNumber(seqnums[i]);
+	    if (msgs[i] == null)
+		nulls++;
+	}
+	if (nulls > 0) {	// compress the array to remove the nulls
+	    IMAPMessage[] nmsgs = new IMAPMessage[seqnums.length - nulls];
+	    for (int i = 0, j = 0; i < msgs.length; i++) {
+		if (msgs[i] != null)
+		    nmsgs[j++] = msgs[i];
+	    }
+	    msgs = nmsgs;
+	}
+	return msgs;
+    }
+
+    private boolean isDirectory() {
+	return ((type & HOLDS_FOLDERS) != 0);
+    }
+}
+
+/**
+ * An object that holds a Message object
+ * and reports its size and writes it to another OutputStream
+ * on demand.  Used by appendMessages to avoid the need to
+ * buffer the entire message in memory in a single byte array
+ * before sending it to the server.
+ */
+class MessageLiteral implements Literal {
+    private Message msg;
+    private int msgSize = -1;
+    private byte[] buf;		// the buffered message, if not null
+
+    public MessageLiteral(Message msg, int maxsize)
+				throws MessagingException, IOException {
+	this.msg = msg;
+	// compute the size here so exceptions can be returned immediately
+	LengthCounter lc = new LengthCounter(maxsize);
+	OutputStream os = new CRLFOutputStream(lc);
+	msg.writeTo(os);
+	os.flush();
+	msgSize = lc.getSize();
+	buf = lc.getBytes();
+    }
+
+    @Override
+    public int size() {
+	return msgSize;
+    }
+
+    @Override
+    public void writeTo(OutputStream os) throws IOException {
+	// the message should not change between the constructor and this call
+	try {
+	    if (buf != null)
+		os.write(buf, 0, msgSize);
+	    else {
+		os = new CRLFOutputStream(os);
+		msg.writeTo(os);
+	    }
+	} catch (MessagingException mex) {
+	    // exceptions here are bad, "should" never happen
+	    throw new IOException("MessagingException while appending message: "
+				    + mex);
+	}
+    }
+}
+
+/**
+ * Count the number of bytes written to the stream.
+ * Also, save a copy of small messages to avoid having to process
+ * the data again.
+ */
+class LengthCounter extends OutputStream {
+    private int size = 0;
+    private byte[] buf;
+    private int maxsize;
+
+    public LengthCounter(int maxsize) {
+	buf = new byte[8192];
+	this.maxsize = maxsize;
+    }
+
+    @Override
+    public void write(int b) {
+	int newsize = size + 1;
+	if (buf != null) {
+	    if (newsize > maxsize && maxsize >= 0) {
+		buf = null;
+	    } else if (newsize > buf.length) {
+		byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
+		System.arraycopy(buf, 0, newbuf, 0, size);
+		buf = newbuf;
+		buf[size] = (byte)b;
+	    } else {
+		buf[size] = (byte)b;
+	    }
+	}
+	size = newsize;
+    }
+
+    @Override
+    public void write(byte b[], int off, int len) {
+	if ((off < 0) || (off > b.length) || (len < 0) ||
+            ((off + len) > b.length) || ((off + len) < 0)) {
+	    throw new IndexOutOfBoundsException();
+	} else if (len == 0) {
+	    return;
+	}
+        int newsize = size + len;
+	if (buf != null) {
+	    if (newsize > maxsize && maxsize >= 0) {
+		buf = null;
+	    } else if (newsize > buf.length) {
+		byte newbuf[] = new byte[Math.max(buf.length << 1, newsize)];
+		System.arraycopy(buf, 0, newbuf, 0, size);
+		buf = newbuf;
+		System.arraycopy(b, off, buf, size, len);
+	    } else {
+		System.arraycopy(b, off, buf, size, len);
+	    }
+	}
+        size = newsize;
+    }
+
+    @Override
+    public void write(byte[] b) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    public int getSize() {
+	return size;
+    }
+
+    public byte[] getBytes() {
+	return buf;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPInputStream.java b/mail/src/main/java/com/sun/mail/imap/IMAPInputStream.java
new file mode 100644
index 0000000..c1c993d
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPInputStream.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+import javax.mail.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.iap.*;
+import com.sun.mail.util.FolderClosedIOException;
+import com.sun.mail.util.MessageRemovedIOException;
+
+/**
+ * This class implements an IMAP data stream.
+ *
+ * @author  John Mani
+ */
+
+public class IMAPInputStream extends InputStream {
+    private IMAPMessage msg; // this message
+    private String section;  // section-id
+    private int pos;	  // track the position within the IMAP datastream
+    private int blksize;  // number of bytes to read in each FETCH request
+    private int max;	  // the total number of bytes in this section.
+			  //  -1 indicates unknown
+    private byte[] buf;   // the buffer obtained from fetchBODY()
+    private int bufcount; // The index one greater than the index of the
+			  // last valid byte in 'buf'
+    private int bufpos;   // The current position within 'buf'
+    private boolean lastBuffer; // is this the last buffer of data?
+    private boolean peek; // peek instead of fetch?
+    private ByteArray readbuf; // reuse for each read
+
+    // Allocate this much extra space in the read buffer to allow
+    // space for the FETCH response overhead
+    private static final int slop = 64;
+
+
+    /**
+     * Create an IMAPInputStream.
+     *
+     * @param	msg	the IMAPMessage the data will come from
+     * @param	section	the IMAP section/part identifier for the data
+     * @param	max	the number of bytes in this section
+     * @param	peek	peek instead of fetch?
+     */
+    public IMAPInputStream(IMAPMessage msg, String section, int max,
+				boolean peek) {
+	this.msg = msg;
+	this.section = section;
+	this.max = max;
+	this.peek = peek;
+	pos = 0;
+	blksize = msg.getFetchBlockSize();
+    }
+
+    /**
+     * Do a NOOP to force any untagged EXPUNGE responses
+     * and then check if this message is expunged.
+     */
+    private void forceCheckExpunged()
+		    throws MessageRemovedIOException, FolderClosedIOException {
+	synchronized (msg.getMessageCacheLock()) {
+	    try {
+		msg.getProtocol().noop();
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedIOException(msg.getFolder(),
+						cex.getMessage());
+	    } catch (FolderClosedException fex) {
+		throw new FolderClosedIOException(fex.getFolder(),
+						fex.getMessage());
+	    } catch (ProtocolException pex) {
+		// ignore it
+	    }
+	}
+	if (msg.isExpunged())
+	    throw new MessageRemovedIOException();
+    }
+
+    /**
+     * Fetch more data from the server. This method assumes that all
+     * data has already been read in, hence bufpos > bufcount.
+     */
+    private void fill() throws IOException {
+	/*
+	 * If we've read the last buffer, there's no more to read.
+	 * If we know the total number of bytes available from this
+	 * section, let's check if we have consumed that many bytes.
+	 */
+	if (lastBuffer || max != -1 && pos >= max) {
+	    if (pos == 0)
+		checkSeen();
+	    readbuf = null;	// XXX - return to pool?
+	    return; // the caller of fill() will return -1.
+	}
+
+	BODY b = null;
+	if (readbuf == null)
+	    readbuf = new ByteArray(blksize + slop);
+
+	ByteArray ba;
+	int cnt;
+	// Acquire MessageCacheLock, to freeze seqnum.
+	synchronized (msg.getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = msg.getProtocol();
+
+		// Check whether this message is expunged
+		if (msg.isExpunged())
+		    throw new MessageRemovedIOException(
+				"No content for expunged message");
+
+		int seqnum = msg.getSequenceNumber();
+		cnt = blksize;
+		if (max != -1 && pos + blksize > max)
+		    cnt = max - pos;
+		if (peek)
+		    b = p.peekBody(seqnum, section, pos, cnt, readbuf);
+		else
+		    b = p.fetchBody(seqnum, section, pos, cnt, readbuf);
+	    } catch (ProtocolException pex) {
+		forceCheckExpunged();
+		throw new IOException(pex.getMessage());
+	    } catch (FolderClosedException fex) {
+		throw new FolderClosedIOException(fex.getFolder(),
+						fex.getMessage());
+	    }
+
+	    if (b == null || ((ba = b.getByteArray()) == null)) {
+		forceCheckExpunged();
+		// nope, the server doesn't think it's expunged.
+		// can't tell the difference between the server returning NIL
+		// and some other error that caused null to be returned above,
+		// so we'll just assume it was empty content.
+		ba = new ByteArray(0);
+	    }
+	}
+
+	// make sure the SEEN flag is set after reading the first chunk
+	if (pos == 0)
+	    checkSeen();
+
+	// setup new values ..
+	buf = ba.getBytes();
+	bufpos = ba.getStart();
+	int n = ba.getCount();    // will be zero, if all data has been
+				  // consumed from the server.
+	// if we got less than we asked for, this is the last buffer of data
+	lastBuffer = n < cnt;
+	bufcount = bufpos + n;
+	pos += n;
+    }
+
+    /**
+     * Reads the next byte of data from this buffered input stream.
+     * If no byte is available, the value <code>-1</code> is returned.
+     */
+    @Override
+    public synchronized int read() throws IOException {
+	if (bufpos >= bufcount) {
+	    fill();
+	    if (bufpos >= bufcount)
+		return -1;	// EOF
+	}
+	return buf[bufpos++] & 0xff;
+    }
+
+    /**
+     * Reads up to <code>len</code> bytes of data from this
+     * input stream into the given buffer. <p>
+     *
+     * Returns the total number of bytes read into the buffer,
+     * or <code>-1</code> if there is no more data. <p>
+     *
+     * Note that this method mimics the "weird !" semantics of
+     * BufferedInputStream in that the number of bytes actually
+     * returned may be less that the requested value. So callers
+     * of this routine should be aware of this and must check
+     * the return value to insure that they have obtained the
+     * requisite number of bytes.
+     */
+    @Override
+    public synchronized int read(byte b[], int off, int len) 
+		throws IOException {
+
+	int avail = bufcount - bufpos;
+	if (avail <= 0) {
+	    fill();
+	    avail = bufcount - bufpos;
+	    if (avail <= 0)
+		return -1; // EOF
+	}
+	int cnt = (avail < len) ? avail : len;
+	System.arraycopy(buf, bufpos, b, off, cnt);
+	bufpos += cnt;
+	return cnt;
+    }
+
+    /**
+     * Reads up to <code>b.length</code> bytes of data from this input
+     * stream into an array of bytes. <p>
+     *
+     * Returns the total number of bytes read into the buffer, or
+     * <code>-1</code> is there is no more data. <p>
+     *
+     * Note that this method mimics the "weird !" semantics of
+     * BufferedInputStream in that the number of bytes actually
+     * returned may be less that the requested value. So callers
+     * of this routine should be aware of this and must check
+     * the return value to insure that they have obtained the
+     * requisite number of bytes.
+     */
+    @Override
+    public int read(byte b[]) throws IOException {
+	return read(b, 0, b.length);
+    }
+
+    /**
+     * Returns the number of bytes that can be read from this input
+     * stream without blocking.
+     */
+    @Override
+    public synchronized int available() throws IOException {
+	return (bufcount - bufpos);
+    }
+
+    /**
+     * Normally the SEEN flag will have been set by now, but if not,
+     * force it to be set (as long as the folder isn't open read-only
+     * and we're not peeking).
+     * And of course, if there's no folder (e.g., a nested message)
+     * don't do anything.
+     */
+    private void checkSeen() {
+	if (peek)	// if we're peeking, don't set the SEEN flag
+	    return;
+	try {
+	    Folder f = msg.getFolder();
+	    if (f != null && f.getMode() != Folder.READ_ONLY &&
+		    !msg.isSet(Flags.Flag.SEEN))
+		msg.setFlag(Flags.Flag.SEEN, true);
+	} catch (MessagingException ex) {
+	    // ignore it
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java b/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
new file mode 100644
index 0000000..453479d
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPMessage.java
@@ -0,0 +1,1700 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Date;
+import java.io.*;
+import java.util.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+import com.sun.mail.util.ReadableMime;
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+
+/**
+ * This class implements an IMAPMessage object. <p>
+ *
+ * An IMAPMessage object starts out as a light-weight object. It gets
+ * filled-in incrementally when a request is made for some item. Or
+ * when a prefetch is done using the FetchProfile. <p>
+ *
+ * An IMAPMessage has a messageNumber and a sequenceNumber. The 
+ * messageNumber is its index into its containing folder's messageCache.
+ * The sequenceNumber is its IMAP sequence-number.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+/*
+ * The lock hierarchy is that the lock on the IMAPMessage object, if
+ * it's acquired at all, must be acquired before the message cache lock.
+ * The IMAPMessage lock protects the message flags, sort of.
+ *
+ * XXX - I'm not convinced that all fields of IMAPMessage are properly
+ * protected by locks.
+ */
+
+public class IMAPMessage extends MimeMessage implements ReadableMime {
+    protected BODYSTRUCTURE bs;		// BODYSTRUCTURE
+    protected ENVELOPE envelope;	// ENVELOPE
+
+    /**
+     * A map of the extension FETCH items.  In addition to saving the
+     * data in this map, an entry in this map indicates that we *have*
+     * the data, and so it doesn't need to be fetched again.  The map
+     * is created only when needed, to avoid significantly increasing
+     * the effective size of an IMAPMessage object.
+     *
+     * @since JavaMail 1.4.6
+     */
+    protected Map<String, Object> items;		// Map<String,Object>
+
+    private Date receivedDate;		// INTERNALDATE
+    private long size = -1;		// RFC822.SIZE
+
+    private Boolean peek;		// use BODY.PEEK when fetching content?
+
+    // this message's IMAP UID
+    private volatile long uid = -1;
+
+    // this message's IMAP MODSEQ - RFC 4551 CONDSTORE
+    private volatile long modseq = -1;
+
+    // this message's IMAP sectionId (null for toplevel message, 
+    // 	non-null for a nested message)
+    protected String sectionId;
+
+    // processed values
+    private String type;		// Content-Type (with params)
+    private String subject;		// decoded (Unicode) subject
+    private String description;		// decoded (Unicode) desc
+
+    // Indicates that we've loaded *all* headers for this message
+    private volatile boolean headersLoaded = false;
+
+    // Indicates that we've cached the body of this message
+    private volatile boolean bodyLoaded = false;
+
+    /* Hashtable of names of headers we've loaded from the server.
+     * Used in isHeaderLoaded() and getHeaderLoaded() to keep track
+     * of those headers we've attempted to load from the server. We
+     * need this table of names to avoid multiple attempts at loading
+     * headers that don't exist for a particular message.
+     *
+     * Could this somehow be included in the InternetHeaders object ??
+     */
+    private Hashtable<String, String> loadedHeaders
+	    = new Hashtable<>(1);
+
+    // This is our Envelope
+    static final String EnvelopeCmd = "ENVELOPE INTERNALDATE RFC822.SIZE";
+
+    /**
+     * Constructor.
+     *
+     * @param	folder	the folder containing this message
+     * @param	msgnum	the message sequence number
+     */
+    protected IMAPMessage(IMAPFolder folder, int msgnum) {
+	super(folder, msgnum);
+	flags = null;
+    }
+
+    /**
+     * Constructor, for use by IMAPNestedMessage.
+     *
+     * @param	session	the Session
+     */
+    protected IMAPMessage(Session session) {
+	super(session);
+    }
+
+    /**
+     * Get this message's folder's protocol connection.
+     * Throws FolderClosedException, if the protocol connection
+     * is not available.
+     *
+     * ASSERT: Must hold the messageCacheLock.
+     *
+     * @return	the IMAPProtocol object for the containing folder
+     * @exception	ProtocolException for protocol errors
+     * @exception	FolderClosedException if the folder is closed
+     */
+    protected IMAPProtocol getProtocol()
+			    throws ProtocolException, FolderClosedException {
+	((IMAPFolder)folder).waitIfIdle();
+	IMAPProtocol p = ((IMAPFolder)folder).protocol;
+	if (p == null)
+	    throw new FolderClosedException(folder);
+	else
+	    return p;
+    }
+
+    /*
+     * Is this an IMAP4 REV1 server?
+     */
+    protected boolean isREV1() throws FolderClosedException {
+	// access the folder's protocol object without waiting
+	// for IDLE to complete
+	IMAPProtocol p = ((IMAPFolder)folder).protocol;
+	if (p == null)
+	    throw new FolderClosedException(folder);
+	else
+	    return p.isREV1();
+    }
+
+    /**
+     * Get the messageCacheLock, associated with this Message's
+     * Folder.
+     *
+     * @return	the message cache lock object
+     */
+    protected Object getMessageCacheLock() {
+	return ((IMAPFolder)folder).messageCacheLock;
+    }
+
+    /**
+     * Get this message's IMAP sequence number.
+     *
+     * ASSERT: This method must be called only when holding the
+     * 	messageCacheLock.
+     *
+     * @return	the message sequence number
+     */
+    protected int getSequenceNumber() {
+	return ((IMAPFolder)folder).messageCache.seqnumOf(getMessageNumber());
+    }
+
+    /**
+     * Wrapper around the protected method Message.setMessageNumber() to 
+     * make that method accessible to IMAPFolder.
+     */
+    @Override
+    protected void setMessageNumber(int msgnum) {
+	super.setMessageNumber(msgnum);
+    }
+
+    /**
+     * Return the UID for this message.
+     * Returns -1 if not known; use UIDFolder.getUID() in this case.
+     *
+     * @return	the UID
+     * @see	javax.mail.UIDFolder#getUID
+     */
+    protected long getUID() {
+	return uid;
+    }
+
+    protected void setUID(long uid) {
+	this.uid = uid;
+    }
+
+    /**
+     * Return the modification sequence number (MODSEQ) for this message.
+     * Returns -1 if not known.
+     *
+     * @return	the modification sequence number
+     * @exception	MessagingException for failures
+     * @see	"RFC 4551"
+     * @since	JavaMail 1.5.1
+     */
+    public synchronized long getModSeq() throws MessagingException {
+	if (modseq != -1)
+	    return modseq;
+
+	synchronized (getMessageCacheLock()) { // Acquire Lock
+	    try {
+		IMAPProtocol p = getProtocol();
+		checkExpunged(); // insure that message is not expunged
+		MODSEQ ms = p.fetchMODSEQ(getSequenceNumber());
+
+		if (ms != null)
+		    modseq = ms.modseq;
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+	return modseq;
+    }
+
+    long _getModSeq() {
+	return modseq;
+    }
+
+    void setModSeq(long modseq) {
+	this.modseq = modseq;
+    }
+
+    // expose to MessageCache
+    @Override
+    protected void setExpunged(boolean set) {
+	super.setExpunged(set);
+    }
+
+    // Convenience routine
+    protected void checkExpunged() throws MessageRemovedException {
+	if (expunged)
+	    throw new MessageRemovedException();
+    }
+
+    /**
+     * Do a NOOP to force any untagged EXPUNGE responses
+     * and then check if this message is expunged.
+     *
+     * @exception	MessageRemovedException if the message has been removed
+     * @exception	FolderClosedException if the folder has been closed
+     */
+    protected void forceCheckExpunged()
+			throws MessageRemovedException, FolderClosedException {
+	synchronized (getMessageCacheLock()) {
+	    try {
+		getProtocol().noop();
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		// ignore it
+	    }
+	}
+	if (expunged)
+	    throw new MessageRemovedException();
+    }
+
+    // Return the block size for FETCH requests
+    // MUST be overridden by IMAPNestedMessage
+    protected int getFetchBlockSize() {
+	return ((IMAPStore)folder.getStore()).getFetchBlockSize();
+    }
+
+    // Should we ignore the size in the BODYSTRUCTURE?
+    // MUST be overridden by IMAPNestedMessage
+    protected boolean ignoreBodyStructureSize() {
+	return ((IMAPStore)folder.getStore()).ignoreBodyStructureSize();
+    }
+
+    /**
+     * Get the "From" attribute.
+     */
+    @Override
+    public Address[] getFrom() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getFrom();
+	loadEnvelope();
+	InternetAddress[] a = envelope.from;
+	/*
+	 * Per RFC 2822, the From header is required, and thus the IMAP
+	 * spec also requires that it be present, but we know that in
+	 * practice it is often missing.  Some servers fill in the
+	 * From field with the Sender field in this case, but at least
+	 * Exchange 2007 does not.  Use the same fallback strategy used
+	 * by MimeMessage.
+	 */
+	if (a == null || a.length == 0)
+	    a = envelope.sender;
+	return aaclone(a);
+    }
+
+    @Override
+    public void setFrom(Address address) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    @Override
+    public void addFrom(Address[] addresses) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+    
+    /**
+     * Get the "Sender" attribute.
+     */
+    @Override
+    public Address getSender() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getSender();
+	loadEnvelope();
+	if (envelope.sender != null && envelope.sender.length > 0)
+		return (envelope.sender)[0];	// there can be only one sender
+	else 
+		return null;
+    }
+	
+
+    @Override
+    public void setSender(Address address) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }    
+
+    /**
+     * Get the desired Recipient type.
+     */
+    @Override
+    public Address[] getRecipients(Message.RecipientType type)
+				throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getRecipients(type);
+	loadEnvelope();
+
+	if (type == Message.RecipientType.TO)
+	    return aaclone(envelope.to);
+	else if (type == Message.RecipientType.CC)
+	    return aaclone(envelope.cc);
+	else if (type == Message.RecipientType.BCC)
+	    return aaclone(envelope.bcc);
+	else
+	    return super.getRecipients(type);
+    }
+
+    @Override
+    public void setRecipients(Message.RecipientType type, Address[] addresses)
+			throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    @Override
+    public void addRecipients(Message.RecipientType type, Address[] addresses)
+			throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get the ReplyTo addresses.
+     */
+    @Override
+    public Address[] getReplyTo() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getReplyTo();
+	loadEnvelope();
+	/*
+	 * The IMAP spec requires that the Reply-To field never be
+	 * null, but at least Exchange 2007 fails to fill it in in
+	 * some cases.  Use the same fallback strategy used by
+	 * MimeMessage.
+	 */
+	if (envelope.replyTo == null || envelope.replyTo.length == 0)
+	    return getFrom();
+	return aaclone(envelope.replyTo);
+    }
+
+    @Override
+    public void setReplyTo(Address[] addresses) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get the decoded subject.
+     */
+    @Override
+    public String getSubject() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getSubject();
+
+	if (subject != null) // already cached ?
+	    return subject;
+
+	loadEnvelope();
+	if (envelope.subject == null) // no subject
+	    return null;
+
+	// Cache and return the decoded value.
+	try {
+	    // The server *should* unfold the value, but just in case it
+	    // doesn't we unfold it here.
+	    subject =
+		MimeUtility.decodeText(MimeUtility.unfold(envelope.subject));
+	} catch (UnsupportedEncodingException ex) {
+	    subject = envelope.subject;
+	}
+
+	return subject;
+    }
+
+    @Override
+    public void setSubject(String subject, String charset) 
+		throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get the SentDate.
+     */
+    @Override
+    public Date getSentDate() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getSentDate();
+	loadEnvelope();
+	if (envelope.date == null)
+	    return null;
+	else
+	    return new Date(envelope.date.getTime());
+    }
+
+    @Override
+    public void setSentDate(Date d) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get the received date (INTERNALDATE).
+     */
+    @Override
+    public Date getReceivedDate() throws MessagingException {
+	checkExpunged();
+	if (receivedDate == null)
+	    loadEnvelope(); // have to go to the server for this
+	if (receivedDate == null)
+	    return null;
+	else
+	    return new Date(receivedDate.getTime());
+    }
+
+    /**
+     * Get the message size. <p>
+     *
+     * Note that this returns RFC822.SIZE.  That is, it's the
+     * size of the whole message, header and body included.
+     * Note also that if the size of the message is greater than
+     * Integer.MAX_VALUE (2GB), this method returns Integer.MAX_VALUE.
+     */
+    @Override
+    public int getSize() throws MessagingException {
+	checkExpunged();
+	// if bodyLoaded, size is already set
+	if (size == -1)
+	    loadEnvelope();	// XXX - could just fetch the size
+	if (size > Integer.MAX_VALUE)
+	    return Integer.MAX_VALUE;	// the best we can do...
+	else
+	    return (int)size;
+    }
+
+    /**
+     * Get the message size as a long. <p>
+     *
+     * Suitable for messages that might be larger than 2GB.
+     * @return	the message size as a long integer
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.6
+     */
+    public long getSizeLong() throws MessagingException {
+	checkExpunged();
+	// if bodyLoaded, size is already set
+	if (size == -1)
+	    loadEnvelope();	// XXX - could just fetch the size
+	return size;
+    }
+
+    /**
+     * Get the total number of lines. <p>
+     *
+     * Returns the "body_fld_lines" field from the
+     * BODYSTRUCTURE. Note that this field is available
+     * only for text/plain and message/rfc822 types
+     */
+    @Override
+    public int getLineCount() throws MessagingException {
+	checkExpunged();
+	// XXX - superclass doesn't implement this
+	loadBODYSTRUCTURE();
+	return bs.lines;
+    }
+
+    /** 
+     * Get the content language.
+     */
+    @Override
+    public String[] getContentLanguage() throws MessagingException {
+    	checkExpunged();
+	if (bodyLoaded)
+	    return super.getContentLanguage();
+    	loadBODYSTRUCTURE();
+    	if (bs.language != null)
+	    return bs.language.clone();
+    	else
+	    return null;
+    }
+ 
+    @Override
+    public void setContentLanguage(String[] languages)
+				throws MessagingException {
+    	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+ 
+    /**
+     * Get the In-Reply-To header.
+     *
+     * @return	the In-Reply-To header
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.3.3
+     */
+    public String getInReplyTo() throws MessagingException {
+    	checkExpunged();
+	if (bodyLoaded)
+	    return super.getHeader("In-Reply-To", " ");
+    	loadEnvelope();
+    	return envelope.inReplyTo;
+    }
+ 
+    /**
+     * Get the Content-Type.
+     *
+     * Generate this header from the BODYSTRUCTURE. Append parameters
+     * as well.
+     */
+    @Override
+    public synchronized String getContentType() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getContentType();
+
+	// If we haven't cached the type yet ..
+	if (type == null) {
+	    loadBODYSTRUCTURE();
+	    // generate content-type from BODYSTRUCTURE
+	    ContentType ct = new ContentType(bs.type, bs.subtype, bs.cParams);
+	    type = ct.toString();
+	}
+	return type;
+    }
+
+    /**
+     * Get the Content-Disposition.
+     */
+    @Override
+    public String getDisposition() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getDisposition();
+	loadBODYSTRUCTURE();
+	return bs.disposition;
+    }
+
+    @Override
+    public void setDisposition(String disposition) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get the Content-Transfer-Encoding.
+     */
+    @Override
+    public String getEncoding() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getEncoding();
+	loadBODYSTRUCTURE();
+	return bs.encoding;
+    }
+
+    /**
+     * Get the Content-ID.
+     */
+    @Override
+    public String getContentID() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getContentID();
+	loadBODYSTRUCTURE();
+	return bs.id;
+    }
+
+    @Override
+    public void setContentID(String cid) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get the Content-MD5.
+     */
+    @Override
+    public String getContentMD5() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getContentMD5();
+	loadBODYSTRUCTURE();
+	return bs.md5;
+    }
+
+    @Override
+    public void setContentMD5(String md5) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get the decoded Content-Description.
+     */
+    @Override
+    public String getDescription() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getDescription();
+
+	if (description != null) // cached value ?
+	    return description;
+	
+	loadBODYSTRUCTURE();
+	if (bs.description == null)
+	    return null;
+	
+	try {
+	    description = MimeUtility.decodeText(bs.description);
+	} catch (UnsupportedEncodingException ex) {
+	    description = bs.description;
+	}
+
+	return description;
+    }
+
+    @Override
+    public void setDescription(String description, String charset) 
+			throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get the Message-ID.
+     */
+    @Override
+    public String getMessageID() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getMessageID();
+	loadEnvelope();
+	return envelope.messageId;
+    }
+
+    /**
+     * Get the "filename" Disposition parameter. (Only available in
+     * IMAP4rev1). If thats not available, get the "name" ContentType
+     * parameter.
+     */
+    @Override
+    public String getFileName() throws MessagingException {
+	checkExpunged();
+	if (bodyLoaded)
+	    return super.getFileName();
+
+	String filename = null;
+	loadBODYSTRUCTURE();
+
+	if (bs.dParams != null)
+	    filename = bs.dParams.get("filename");
+	if (filename == null && bs.cParams != null)
+	    filename = bs.cParams.get("name");
+	return filename;
+    }
+
+    @Override
+    public void setFileName(String filename) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get all the bytes for this message. Overrides getContentStream()
+     * in MimeMessage. This method is ultimately used by the DataHandler
+     * to obtain the input stream for this message.
+     *
+     * @see javax.mail.internet.MimeMessage#getContentStream
+     */
+    @Override
+    protected InputStream getContentStream() throws MessagingException {
+	if (bodyLoaded)
+	    return super.getContentStream();
+	InputStream is = null;
+	boolean pk = getPeek();	// get before acquiring message cache lock
+
+        // Acquire MessageCacheLock, to freeze seqnum.
+        synchronized(getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = getProtocol();
+
+		// This message could be expunged when we were waiting
+		// to acquire the lock ...
+		checkExpunged();
+
+		if (p.isREV1() && (getFetchBlockSize() != -1)) // IMAP4rev1
+		    return new IMAPInputStream(this, toSection("TEXT"),
+				    bs != null && !ignoreBodyStructureSize() ?
+					bs.size : -1, pk);
+
+		if (p.isREV1()) {
+		    BODY b;
+		    if (pk)
+			b = p.peekBody(getSequenceNumber(), toSection("TEXT"));
+		    else
+			b = p.fetchBody(getSequenceNumber(), toSection("TEXT"));
+		    if (b != null)
+			is = b.getByteArrayInputStream();
+		} else {
+		    RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), "TEXT");
+		    if (rd != null)
+			is = rd.getByteArrayInputStream();
+		}
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		forceCheckExpunged();
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+
+	if (is == null) {
+	    forceCheckExpunged();	// may throw MessageRemovedException
+	    // nope, the server doesn't think it's expunged.
+	    // can't tell the difference between the server returning NIL
+	    // and some other error that caused null to be returned above,
+	    // so we'll just assume it was empty content.
+	    is = new ByteArrayInputStream(new byte[0]);
+	}
+	return is;
+    }
+
+    /**
+     * Get the DataHandler object for this message.
+     */
+    @Override
+    public synchronized DataHandler getDataHandler()
+		throws MessagingException {
+	checkExpunged();
+
+	if (dh == null && !bodyLoaded) {
+	    loadBODYSTRUCTURE();
+	    if (type == null) { // type not yet computed
+		// generate content-type from BODYSTRUCTURE
+		ContentType ct = new ContentType(bs.type, bs.subtype,
+						 bs.cParams);
+		type = ct.toString();
+	    }
+
+	    /* Special-case Multipart and Nested content. All other
+	     * cases are handled by the superclass.
+	     */
+	    if (bs.isMulti())
+		dh = new DataHandler(
+			new IMAPMultipartDataSource(this, bs.bodies, 
+						    sectionId, this)
+		     );
+	    else if (bs.isNested() && isREV1() && bs.envelope != null)
+		/* Nested messages are handled specially only for
+		 * IMAP4rev1. IMAP4 doesn't provide enough support to 
+		 * FETCH the components of nested messages
+		 */
+		dh = new DataHandler(
+			    new IMAPNestedMessage(this, 
+				bs.bodies[0], 
+				bs.envelope,
+				sectionId == null ? "1" : sectionId + ".1"),
+			    type
+		     );
+	}
+
+	return super.getDataHandler();
+    }
+
+    @Override
+    public void setDataHandler(DataHandler content) 
+			throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Return the MIME format stream corresponding to this message.
+     *
+     * @return	the MIME format stream
+     * @since	JavaMail 1.4.5
+     */
+    @Override
+    public InputStream getMimeStream() throws MessagingException {
+	// XXX - need an "if (bodyLoaded)" version
+	InputStream is = null;
+	boolean pk = getPeek();	// get before acquiring message cache lock
+
+        // Acquire MessageCacheLock, to freeze seqnum.
+        synchronized(getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = getProtocol();
+
+		checkExpunged(); // insure this message is not expunged
+
+		if (p.isREV1() && (getFetchBlockSize() != -1)) // IMAP4rev1
+		    return new IMAPInputStream(this, sectionId, -1, pk);
+
+		if (p.isREV1()) {
+		    BODY b;
+		    if (pk)
+			b = p.peekBody(getSequenceNumber(), sectionId);
+		    else
+			b = p.fetchBody(getSequenceNumber(), sectionId);
+		    if (b != null)
+			is = b.getByteArrayInputStream();
+		} else {
+		    RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), null);
+		    if (rd != null)
+			is = rd.getByteArrayInputStream();
+		}
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		forceCheckExpunged();
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+
+	if (is == null) {
+	    forceCheckExpunged();	// may throw MessageRemovedException
+	    // nope, the server doesn't think it's expunged.
+	    // can't tell the difference between the server returning NIL
+	    // and some other error that caused null to be returned above,
+	    // so we'll just assume it was empty content.
+	    is = new ByteArrayInputStream(new byte[0]);
+	}
+	return is;
+    }
+
+    /**
+     * Write out the bytes into the given OutputStream.
+     */
+    @Override
+    public void writeTo(OutputStream os)
+				throws IOException, MessagingException {
+	if (bodyLoaded) {
+	    super.writeTo(os);
+	    return;
+	}
+	InputStream is = getMimeStream();
+	try {
+	    // write out the bytes
+	    byte[] bytes = new byte[16*1024];
+	    int count;
+	    while ((count = is.read(bytes)) != -1)
+		os.write(bytes, 0, count);
+	} finally {
+	    is.close();
+	}
+    }
+
+    /**
+     * Get the named header.
+     */
+    @Override
+    public String[] getHeader(String name) throws MessagingException {
+	checkExpunged();
+
+	if (isHeaderLoaded(name)) // already loaded ?
+	    return headers.getHeader(name);
+
+	// Load this particular header
+	InputStream is = null;
+
+        // Acquire MessageCacheLock, to freeze seqnum.
+        synchronized(getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = getProtocol();
+
+		// This message could be expunged when we were waiting
+		// to acquire the lock ...
+		checkExpunged();
+
+		if (p.isREV1()) {
+		    BODY b = p.peekBody(getSequenceNumber(), 
+				toSection("HEADER.FIELDS (" + name + ")")
+			     );
+		    if (b != null)
+			is = b.getByteArrayInputStream();
+		} else {
+		    RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), 
+					"HEADER.LINES (" + name + ")");
+		    if (rd != null)
+			is = rd.getByteArrayInputStream();
+		}
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		forceCheckExpunged();
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+
+	// if we get this far without "is" being set, something has gone
+	// wrong; prevent a later NullPointerException and return null here
+	if (is == null)
+	    return null;
+
+	if (headers == null)
+	    headers = new InternetHeaders();
+	headers.load(is); // load this header into the Headers object.
+	setHeaderLoaded(name); // Mark this header as loaded
+
+	return headers.getHeader(name);
+    }
+
+    /**
+     * Get the named header.
+     */
+    @Override
+    public String getHeader(String name, String delimiter)
+			throws MessagingException {
+	checkExpunged();
+
+	// force the header to be loaded by invoking getHeader(name)
+	if (getHeader(name) == null)
+	    return null;
+	return headers.getHeader(name, delimiter);
+    }
+
+    @Override
+    public void setHeader(String name, String value)
+			throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    @Override
+    public void addHeader(String name, String value)
+			throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+	    
+    @Override
+    public void removeHeader(String name)
+			throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get all headers.
+     */
+    @Override
+    public Enumeration<Header> getAllHeaders() throws MessagingException {
+	checkExpunged();
+	loadHeaders();
+	return super.getAllHeaders();
+    }
+
+    /**
+     * Get matching headers.
+     */
+    @Override
+    public Enumeration<Header> getMatchingHeaders(String[] names)
+			throws MessagingException {
+	checkExpunged();
+	loadHeaders();
+	return super.getMatchingHeaders(names);
+    }
+
+    /**
+     * Get non-matching headers.
+     */
+    @Override
+    public Enumeration<Header> getNonMatchingHeaders(String[] names)
+			throws MessagingException {
+	checkExpunged();
+	loadHeaders();
+	return super.getNonMatchingHeaders(names);
+    }
+
+    @Override
+    public void addHeaderLine(String line) throws MessagingException {
+	throw new IllegalWriteException("IMAPMessage is read-only");
+    }
+
+    /**
+     * Get all header-lines.
+     */
+    @Override
+    public Enumeration<String> getAllHeaderLines() throws MessagingException {
+	checkExpunged();
+	loadHeaders();
+	return super.getAllHeaderLines();
+    }
+
+    /**
+     * Get all matching header-lines.
+     */
+    @Override
+    public Enumeration<String> getMatchingHeaderLines(String[] names)
+			throws MessagingException {
+	checkExpunged();
+	loadHeaders();
+	return super.getMatchingHeaderLines(names);
+    }
+
+    /**
+     * Get all non-matching headerlines.
+     */
+    @Override
+    public Enumeration<String> getNonMatchingHeaderLines(String[] names)
+			throws MessagingException {
+	checkExpunged();
+	loadHeaders();
+	return super.getNonMatchingHeaderLines(names);
+    }
+
+    /**
+     * Get the Flags for this message.
+     */
+    @Override
+    public synchronized Flags getFlags() throws MessagingException {
+	checkExpunged();
+	loadFlags();
+	return super.getFlags();
+    }
+
+    /**
+     * Test if the given Flags are set in this message.
+     */
+    @Override
+    public synchronized boolean isSet(Flags.Flag flag)
+				throws MessagingException {
+	checkExpunged();
+	loadFlags();
+	return super.isSet(flag);
+    }
+
+    /**
+     * Set/Unset the given flags in this message.
+     */
+    @Override
+    public synchronized void setFlags(Flags flag, boolean set)
+			throws MessagingException {
+        // Acquire MessageCacheLock, to freeze seqnum.
+        synchronized(getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = getProtocol();
+		checkExpunged(); // Insure that this message is not expunged
+		p.storeFlags(getSequenceNumber(), flag, set);
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	}
+    }
+
+    /**
+     * Set whether or not to use the PEEK variant of FETCH when
+     * fetching message content.  This overrides the default
+     * value from the "mail.imap.peek" property.
+     *
+     * @param	peek	the peek flag
+     * @since	JavaMail 1.3.3
+     */
+    public synchronized void setPeek(boolean peek) {
+	this.peek = Boolean.valueOf(peek);
+    }
+
+    /**
+     * Get whether or not to use the PEEK variant of FETCH when
+     * fetching message content.
+     *
+     * @return	the peek flag
+     * @since	JavaMail 1.3.3
+     */
+    public synchronized boolean getPeek() {
+	if (peek == null)
+	    return ((IMAPStore)folder.getStore()).getPeek();
+	else
+	    return peek.booleanValue();
+    }
+
+    /**
+     * Invalidate cached header and envelope information for this
+     * message.  Subsequent accesses of this information will
+     * cause it to be fetched from the server.
+     *
+     * @since	JavaMail 1.3.3
+     */
+    public synchronized void invalidateHeaders() {
+	headersLoaded = false;
+	loadedHeaders.clear();
+	headers = null;
+	envelope = null;
+	bs = null;
+	receivedDate = null;
+	size = -1;
+	type = null;
+	subject = null;
+	description = null;
+	flags = null;
+	content = null;
+	contentStream = null;
+	bodyLoaded = false;
+    }
+
+    /**
+     * This class implements the test to be done on each
+     * message in the folder. The test is to check whether the
+     * message has already cached all the items requested in the
+     * FetchProfile. If any item is missing, the test succeeds and
+     * breaks out.
+     */
+    public static class FetchProfileCondition implements Utility.Condition {
+	private boolean needEnvelope = false;
+	private boolean needFlags = false;
+	private boolean needBodyStructure = false;
+	private boolean needUID = false;
+	private boolean needHeaders = false;
+	private boolean needSize = false;
+	private boolean needMessage = false;
+	private boolean needRDate = false;
+	private String[] hdrs = null;
+	private Set<FetchItem> need = new HashSet<>();
+
+	/**
+	 * Create a FetchProfileCondition to determine if we need to fetch
+	 * any of the information specified in the FetchProfile.
+	 *
+	 * @param	fp	the FetchProfile
+	 * @param	fitems	the FETCH items
+	 */
+	@SuppressWarnings("deprecation")	// for FetchProfile.Item.SIZE
+	public FetchProfileCondition(FetchProfile fp, FetchItem[] fitems) {
+	    if (fp.contains(FetchProfile.Item.ENVELOPE))
+		needEnvelope = true;
+	    if (fp.contains(FetchProfile.Item.FLAGS))
+		needFlags = true;
+	    if (fp.contains(FetchProfile.Item.CONTENT_INFO))
+		needBodyStructure = true;
+	    if (fp.contains(FetchProfile.Item.SIZE))
+		needSize = true;
+	    if (fp.contains(UIDFolder.FetchProfileItem.UID))
+		needUID = true;
+	    if (fp.contains(IMAPFolder.FetchProfileItem.HEADERS))
+		needHeaders = true;
+	    if (fp.contains(IMAPFolder.FetchProfileItem.SIZE))
+		needSize = true;
+	    if (fp.contains(IMAPFolder.FetchProfileItem.MESSAGE))
+		needMessage = true;
+	    if (fp.contains(IMAPFolder.FetchProfileItem.INTERNALDATE))
+		needRDate = true;
+	    hdrs = fp.getHeaderNames();
+	    for (int i = 0; i < fitems.length; i++) {
+		if (fp.contains(fitems[i].getFetchProfileItem()))
+		    need.add(fitems[i]);
+	    }
+	}
+
+	/**
+	 * Return true if we NEED to fetch the requested information
+	 * for the specified message.
+	 */
+	@Override
+	public boolean test(IMAPMessage m) {
+	    if (needEnvelope && m._getEnvelope() == null && !m.bodyLoaded)
+		return true; // no envelope
+	    if (needFlags && m._getFlags() == null)
+		return true; // no flags
+	    if (needBodyStructure && m._getBodyStructure() == null &&
+								!m.bodyLoaded)
+		return true; // no BODYSTRUCTURE
+	    if (needUID && m.getUID() == -1)	// no UID
+		return true;
+	    if (needHeaders && !m.areHeadersLoaded()) // no headers
+		return true;
+	    if (needSize && m.size == -1 && !m.bodyLoaded) // no size
+		return true;
+	    if (needMessage && !m.bodyLoaded)		// no message body
+		return true;
+	    if (needRDate && m.receivedDate == null)	// no received date
+		return true;
+
+	    // Is the desired header present ?
+	    for (int i = 0; i < hdrs.length; i++) {
+		if (!m.isHeaderLoaded(hdrs[i]))
+		    return true; // Nope, return
+	    }
+	    Iterator<FetchItem> it = need.iterator();
+	    while (it.hasNext()) {
+		FetchItem fitem = it.next();
+		if (m.items == null || m.items.get(fitem.getName()) == null)
+		    return true;
+	    }
+
+	    return false;
+	}
+    }
+
+    /**
+     * Apply the data in the FETCH item to this message.
+     *
+     * ASSERT: Must hold the messageCacheLock.
+     *
+     * @param	item	the fetch item
+     * @param	hdrs	the headers we're asking for
+     * @param	allHeaders load all headers?
+     * @return		did we handle this fetch item?
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.6
+     */
+    protected boolean handleFetchItem(Item item,
+				String[] hdrs, boolean allHeaders)
+				throws MessagingException {
+	// Check for the FLAGS item
+	if (item instanceof Flags)
+	    flags = (Flags)item;
+	// Check for ENVELOPE items
+	else if (item instanceof ENVELOPE)
+	    envelope = (ENVELOPE)item;
+	else if (item instanceof INTERNALDATE)
+	    receivedDate = ((INTERNALDATE)item).getDate();
+	else if (item instanceof RFC822SIZE)
+	    size = ((RFC822SIZE)item).size;
+	else if (item instanceof MODSEQ)
+	    modseq = ((MODSEQ)item).modseq;
+
+	// Check for the BODYSTRUCTURE item
+	else if (item instanceof BODYSTRUCTURE)
+	    bs = (BODYSTRUCTURE)item;
+	// Check for the UID item
+	else if (item instanceof UID) {
+	    UID u = (UID)item;
+	    uid = u.uid; // set uid
+	    // add entry into uid table
+	    if (((IMAPFolder)folder).uidTable == null)
+		((IMAPFolder) folder).uidTable
+			= new Hashtable<>();
+	    ((IMAPFolder)folder).uidTable.put(Long.valueOf(u.uid), this);
+	}
+
+	// Check for header items
+	else if (item instanceof RFC822DATA ||
+		 item instanceof BODY) {
+	    InputStream headerStream;
+	    boolean isHeader;
+	    if (item instanceof RFC822DATA) { // IMAP4
+		headerStream = 
+		    ((RFC822DATA)item).getByteArrayInputStream();
+		isHeader = ((RFC822DATA)item).isHeader();
+	    } else {	// IMAP4rev1
+		headerStream = 
+		    ((BODY)item).getByteArrayInputStream();
+		isHeader = ((BODY)item).isHeader();
+	    }
+
+	    if (!isHeader) {
+		// load the entire message by using the superclass
+		// MimeMessage.parse method
+		// first, save the size of the message
+		try {
+		    size = headerStream.available();
+		} catch (IOException ex) {
+		    // should never occur
+		}
+		parse(headerStream);
+		bodyLoaded = true;
+		setHeadersLoaded(true);
+	    } else {
+		// Load the obtained headers.
+		InternetHeaders h = new InternetHeaders();
+		// Some IMAP servers (e.g., gmx.net) return NIL 
+		// instead of a string just containing a CR/LF
+		// when the header list is empty.
+		if (headerStream != null)
+		    h.load(headerStream);
+		if (headers == null || allHeaders)
+		    headers = h;
+		else {
+		    /*
+		     * This is really painful.  A second fetch
+		     * of the same headers (which might occur because
+		     * a new header was added to the set requested)
+		     * will return headers we already know about.
+		     * In this case, only load the headers we haven't
+		     * seen before to avoid adding duplicates of
+		     * headers we already have.
+		     *
+		     * XXX - There's a race condition here if another
+		     * thread is reading headers in the same message
+		     * object, because InternetHeaders is not thread
+		     * safe.
+		     */
+		    Enumeration<Header> e = h.getAllHeaders();
+		    while (e.hasMoreElements()) {
+			Header he = e.nextElement();
+			if (!isHeaderLoaded(he.getName()))
+			    headers.addHeader(
+					he.getName(), he.getValue());
+		    }
+		}
+
+		// if we asked for all headers, assume we got them
+		if (allHeaders)
+		    setHeadersLoaded(true);
+		else {
+		    // Mark all headers we asked for as 'loaded'
+		    for (int k = 0; k < hdrs.length; k++)
+			setHeaderLoaded(hdrs[k]);
+		}
+	    }
+	} else
+	    return false;	// not handled
+	return true;		// something above handled it
+    }
+
+    /**
+     * Apply the data in the extension FETCH items to this message.
+     * This method adds all the items to the items map.
+     * Subclasses may override this method to call super and then
+     * also copy the data to a more convenient form.
+     *
+     * ASSERT: Must hold the messageCacheLock.
+     *
+     * @param	extensionItems	the Map to add fetch items to
+     * @since JavaMail 1.4.6
+     */
+    protected void handleExtensionFetchItems(
+	    Map<String, Object> extensionItems) {
+	if (extensionItems == null || extensionItems.isEmpty())
+	    return;
+	if (items == null)
+	    items = new HashMap<>();
+	items.putAll(extensionItems);
+    }
+
+    /**
+     * Fetch an individual item for the current message.
+     * Note that handleExtensionFetchItems will have been called
+     * to store this item in the message before this method
+     * returns.
+     *
+     * @param	fitem	the FetchItem
+     * @return		the data associated with the FetchItem
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.6
+     */
+    protected Object fetchItem(FetchItem fitem)
+				throws MessagingException {
+
+	// Acquire MessageCacheLock, to freeze seqnum.
+	synchronized(getMessageCacheLock()) {
+	    Object robj = null;
+
+	    try {
+		IMAPProtocol p = getProtocol();
+
+		checkExpunged(); // Insure that this message is not expunged
+
+		int seqnum = getSequenceNumber();
+		Response[] r = p.fetch(seqnum, fitem.getName());
+
+		for (int i = 0; i < r.length; i++) {
+		    // If this response is NOT a FetchResponse or if it does
+		    // not match our seqnum, skip.
+		    if (r[i] == null ||
+			!(r[i] instanceof FetchResponse) ||
+			((FetchResponse)r[i]).getNumber() != seqnum)
+			continue;
+
+		    FetchResponse f = (FetchResponse)r[i];
+		    handleExtensionFetchItems(f.getExtensionItems());
+		    if (items != null) {
+			Object o = items.get(fitem.getName());
+			if (o != null)
+			    robj = o;
+		    }
+		}
+
+		// ((IMAPFolder)folder).handleResponses(r);
+		p.notifyResponseHandlers(r);
+		p.handleResult(r[r.length - 1]);
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		forceCheckExpunged();
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	    return robj;
+
+	} // Release MessageCacheLock
+    }
+
+    /**
+     * Return the data associated with the FetchItem.
+     * If the data hasn't been fetched, call the fetchItem
+     * method to fetch it.  Returns null if there is no
+     * data for the FetchItem.
+     *
+     * @param	fitem	the FetchItem
+     * @return		the data associated with the FetchItem
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.6
+     */
+    public synchronized Object getItem(FetchItem fitem)
+				throws MessagingException {
+	Object item = items == null ? null : items.get(fitem.getName());
+	if (item == null)
+	    item = fetchItem(fitem);
+	return item;
+    }
+
+    /*
+     * Load the Envelope for this message.
+     */
+    private synchronized void loadEnvelope() throws MessagingException {
+	if (envelope != null) // already loaded
+	    return;
+
+	Response[] r = null;
+
+	// Acquire MessageCacheLock, to freeze seqnum.
+	synchronized(getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = getProtocol();
+
+		checkExpunged(); // Insure that this message is not expunged
+
+		int seqnum = getSequenceNumber();
+		r = p.fetch(seqnum, EnvelopeCmd);
+
+		for (int i = 0; i < r.length; i++) {
+		    // If this response is NOT a FetchResponse or if it does
+		    // not match our seqnum, skip.
+		    if (r[i] == null ||
+			!(r[i] instanceof FetchResponse) ||
+			((FetchResponse)r[i]).getNumber() != seqnum)
+			continue;
+
+		    FetchResponse f = (FetchResponse)r[i];
+		    
+		    // Look for the Envelope items.
+		    int count = f.getItemCount();
+		    for (int j = 0; j < count; j++) {
+			Item item = f.getItem(j);
+			
+			if (item instanceof ENVELOPE)
+			    envelope = (ENVELOPE)item;
+			else if (item instanceof INTERNALDATE)
+			    receivedDate = ((INTERNALDATE)item).getDate();
+			else if (item instanceof RFC822SIZE)
+			    size = ((RFC822SIZE)item).size;
+		    }
+		}
+
+		// ((IMAPFolder)folder).handleResponses(r);
+		p.notifyResponseHandlers(r);
+		p.handleResult(r[r.length - 1]);
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		forceCheckExpunged();
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+
+	} // Release MessageCacheLock
+
+	if (envelope == null)
+	    throw new MessagingException("Failed to load IMAP envelope");
+    }
+
+    /*
+     * Load the BODYSTRUCTURE
+     */
+    private synchronized void loadBODYSTRUCTURE() 
+		throws MessagingException {
+	if (bs != null) // already loaded
+	    return;
+
+	// Acquire MessageCacheLock, to freeze seqnum.
+	synchronized(getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = getProtocol();
+
+		// This message could be expunged when we were waiting 
+		// to acquire the lock ...
+		checkExpunged();
+
+		bs = p.fetchBodyStructure(getSequenceNumber());
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		forceCheckExpunged();
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	    if (bs == null) {
+		// if the FETCH is successful, we should always get a
+		// BODYSTRUCTURE, but some servers fail to return it
+		// if the message has been expunged
+		forceCheckExpunged();
+		throw new MessagingException("Unable to load BODYSTRUCTURE");
+	    }
+	}
+    }
+
+    /*
+     * Load all headers.
+     */
+    private synchronized void loadHeaders() throws MessagingException {
+	if (headersLoaded)
+	    return;
+
+	InputStream is = null;
+
+	// Acquire MessageCacheLock, to freeze seqnum.
+	synchronized (getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = getProtocol();
+
+		// This message could be expunged when we were waiting 
+		// to acquire the lock ...
+		checkExpunged();
+
+		if (p.isREV1()) {
+		    BODY b = p.peekBody(getSequenceNumber(), 
+					 toSection("HEADER"));
+		    if (b != null)
+			is = b.getByteArrayInputStream();
+		} else {
+		    RFC822DATA rd = p.fetchRFC822(getSequenceNumber(), 
+						  "HEADER");
+		    if (rd != null)
+			is = rd.getByteArrayInputStream();
+		}
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		forceCheckExpunged();
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	} // Release MessageCacheLock
+
+	if (is == null)
+	    throw new MessagingException("Cannot load header");
+	headers = new InternetHeaders(is);
+	headersLoaded = true;
+    }
+
+    /*
+     * Load this message's Flags
+     */
+    private synchronized void loadFlags() throws MessagingException {
+	if (flags != null)
+	    return;
+	
+	// Acquire MessageCacheLock, to freeze seqnum.
+	synchronized(getMessageCacheLock()) {
+	    try {
+		IMAPProtocol p = getProtocol();
+
+		// This message could be expunged when we were waiting 
+		// to acquire the lock ...
+		checkExpunged();
+
+		flags = p.fetchFlags(getSequenceNumber());
+		// make sure flags is always set, even if server is broken
+		if (flags == null)
+		    flags = new Flags();
+	    } catch (ConnectionException cex) {
+		throw new FolderClosedException(folder, cex.getMessage());
+	    } catch (ProtocolException pex) {
+		forceCheckExpunged();
+		throw new MessagingException(pex.getMessage(), pex);
+	    }
+	} // Release MessageCacheLock
+    }
+
+    /*
+     * Are all headers loaded?
+     */
+    private boolean areHeadersLoaded() {
+	return headersLoaded;
+    }
+
+    /*
+     * Set whether all headers are loaded.
+     */
+    private void setHeadersLoaded(boolean loaded) {
+	headersLoaded = loaded;
+    }
+
+    /* 
+     * Check if the given header was ever loaded from the server
+     */
+    private boolean isHeaderLoaded(String name) {
+	if (headersLoaded) // All headers for this message have been loaded
+	    return true;
+	
+	return loadedHeaders.containsKey(name.toUpperCase(Locale.ENGLISH));
+    }
+
+    /*
+     * Mark that the given headers have been loaded from the server.
+     */
+    private void setHeaderLoaded(String name) {
+	loadedHeaders.put(name.toUpperCase(Locale.ENGLISH), name);
+    }
+
+    /*
+     * Convert the given FETCH item identifier to the approriate 
+     * section-string for this message.
+     */
+    private String toSection(String what) {
+	if (sectionId == null)
+	    return what;
+	else
+	    return sectionId + "." + what;
+    }
+
+    /*
+     * Clone an array of InternetAddresses.
+     */
+    private InternetAddress[] aaclone(InternetAddress[] aa) {
+	if (aa == null)
+	    return null;
+	else
+	    return aa.clone();
+    }
+
+    private Flags _getFlags() {
+	return flags;
+    }
+
+    private ENVELOPE _getEnvelope() {
+	return envelope;
+    }
+
+    private BODYSTRUCTURE _getBodyStructure() {
+	return bs;
+    }
+
+    /***********************************************************
+     * accessor routines to make available certain private/protected
+     * fields to other classes in this package.
+     ***********************************************************/
+
+    /*
+     * Called by IMAPFolder.
+     * Must not be synchronized.
+     */
+    void _setFlags(Flags flags) {
+	this.flags = flags;
+    }
+
+    /*
+     * Called by IMAPNestedMessage.
+     */
+    Session _getSession() {
+	return session;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPMultipartDataSource.java b/mail/src/main/java/com/sun/mail/imap/IMAPMultipartDataSource.java
new file mode 100644
index 0000000..c55ef02
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPMultipartDataSource.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.imap.protocol.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class 
+ *
+ * @author  John Mani
+ */
+
+public class IMAPMultipartDataSource extends MimePartDataSource
+				     implements MultipartDataSource {
+    private List<IMAPBodyPart> parts;
+
+    protected IMAPMultipartDataSource(MimePart part, BODYSTRUCTURE[] bs, 
+				      String sectionId, IMAPMessage msg) {
+	super(part);
+
+	parts = new ArrayList<>(bs.length);
+	for (int i = 0; i < bs.length; i++)
+	    parts.add(
+		new IMAPBodyPart(bs[i], 
+				 sectionId == null ? 
+				   Integer.toString(i+1) : 
+				   sectionId + "." + Integer.toString(i+1),
+				 msg)
+	    );
+    }
+
+    @Override
+    public int getCount() {
+	return parts.size();
+    }
+
+    @Override
+    public BodyPart getBodyPart(int index) throws MessagingException {
+	return parts.get(index);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPNestedMessage.java b/mail/src/main/java/com/sun/mail/imap/IMAPNestedMessage.java
new file mode 100644
index 0000000..12cd4e4
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPNestedMessage.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+import javax.mail.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.iap.ProtocolException;
+
+/**
+ * This class implements a nested IMAP message
+ *
+ * @author  John Mani
+ */
+
+public class IMAPNestedMessage extends IMAPMessage {
+    private IMAPMessage msg; // the enclosure of this nested message
+
+    /**
+     * Package private constructor. <p>
+     *
+     * Note that nested messages have no containing folder, nor 
+     * a message number.
+     */
+    IMAPNestedMessage(IMAPMessage m, BODYSTRUCTURE b, ENVELOPE e, String sid) {
+	super(m._getSession());
+	msg = m;
+	bs = b;
+	envelope = e;
+	sectionId = sid;
+	setPeek(m.getPeek());
+    }
+
+    /*
+     * Get the enclosing message's Protocol object. Overrides
+     * IMAPMessage.getProtocol().
+     */
+    @Override
+    protected IMAPProtocol getProtocol()
+			throws ProtocolException, FolderClosedException {
+	return msg.getProtocol();
+    }
+
+    /*
+     * Is this an IMAP4 REV1 server?
+     */
+    @Override
+    protected boolean isREV1() throws FolderClosedException {
+	return msg.isREV1();
+    }
+
+    /*
+     * Get the enclosing message's messageCacheLock. Overrides
+     * IMAPMessage.getMessageCacheLock().
+     */
+    @Override
+    protected Object getMessageCacheLock() {
+	return msg.getMessageCacheLock();
+    }
+
+    /*
+     * Get the enclosing message's sequence number. Overrides
+     * IMAPMessage.getSequenceNumber().
+     */
+    @Override
+    protected int getSequenceNumber() {
+	return msg.getSequenceNumber();
+    }
+
+    /*
+     * Check whether the enclosing message is expunged. Overrides 
+     * IMAPMessage.checkExpunged().
+     */
+    @Override
+    protected void checkExpunged() throws MessageRemovedException {
+	msg.checkExpunged();
+    }
+
+    /*
+     * Check whether the enclosing message is expunged. Overrides
+     * Message.isExpunged().
+     */
+    @Override
+    public boolean isExpunged() {
+	return msg.isExpunged();
+    }
+
+    /*
+     * Get the enclosing message's fetchBlockSize. 
+     */
+    @Override
+    protected int getFetchBlockSize() {
+	return msg.getFetchBlockSize();
+    }
+
+    /*
+     * Get the enclosing message's ignoreBodyStructureSize. 
+     */
+    @Override
+    protected boolean ignoreBodyStructureSize() {
+	return msg.ignoreBodyStructureSize();
+    }
+
+    /*
+     * IMAPMessage uses RFC822.SIZE. We use the "size" field from
+     * our BODYSTRUCTURE.
+     */
+    @Override
+    public int getSize() throws MessagingException {
+	return bs.size;
+    }
+
+    /*
+     * Disallow setting flags on nested messages
+     */
+    @Override
+    public synchronized void setFlags(Flags flag, boolean set) 
+			throws MessagingException {
+	// Cannot set FLAGS on a nested IMAP message	
+	throw new MethodNotSupportedException(
+		"Cannot set flags on this nested message");
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPProvider.java b/mail/src/main/java/com/sun/mail/imap/IMAPProvider.java
new file mode 100644
index 0000000..1604501
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Provider;
+
+/**
+ * The IMAP protocol provider.
+ */
+public class IMAPProvider extends Provider {
+    public IMAPProvider() {
+	super(Provider.Type.STORE, "imap", IMAPStore.class.getName(),
+	    "Oracle", null);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPSSLProvider.java b/mail/src/main/java/com/sun/mail/imap/IMAPSSLProvider.java
new file mode 100644
index 0000000..71fe686
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPSSLProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Provider;
+
+/**
+ * The IMAP SSL protocol provider.
+ */
+public class IMAPSSLProvider extends Provider {
+    public IMAPSSLProvider() {
+	super(Provider.Type.STORE, "imaps", IMAPSSLStore.class.getName(),
+	    "Oracle", null);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPSSLStore.java b/mail/src/main/java/com/sun/mail/imap/IMAPSSLStore.java
new file mode 100644
index 0000000..488b4ec
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPSSLStore.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.*;
+
+/**
+ * This class provides access to an IMAP message store over SSL.
+ */
+
+public class IMAPSSLStore extends IMAPStore {
+    
+    /**
+     * Constructor that takes a Session object and a URLName that
+     * represents a specific IMAP server.
+     *
+     * @param	session	the Session
+     * @param	url	the URLName of this store
+     */
+    public IMAPSSLStore(Session session, URLName url) {
+	super(session, url, "imaps", true); // call super constructor
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IMAPStore.java b/mail/src/main/java/com/sun/mail/imap/IMAPStore.java
new file mode 100644
index 0000000..16b5182
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IMAPStore.java
@@ -0,0 +1,2210 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.lang.reflect.*;
+import java.util.Vector;
+import java.util.StringTokenizer;
+import java.util.Locale;
+import java.util.Properties;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.logging.Level;
+
+import javax.mail.*;
+import javax.mail.event.*;
+
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.protocol.*;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.SocketConnectException;
+import com.sun.mail.util.MailConnectException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides access to an IMAP message store. <p>
+ *
+ * Applications that need to make use of IMAP-specific features may cast
+ * a <code>Store</code> object to an <code>IMAPStore</code> object and
+ * use the methods on this class. The {@link #getQuota getQuota} and
+ * {@link #setQuota setQuota} methods support the IMAP QUOTA extension.
+ * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
+ * for more information. <p>
+ *
+ * The {@link #id id} method supports the IMAP ID extension;
+ * see <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
+ * The fields ID_NAME, ID_VERSION, etc. represent the suggested field names
+ * in RFC 2971 section 3.3 and may be used as keys in the Map containing
+ * client values or server values. <p>
+ *
+ * See the <a href="package-summary.html">com.sun.mail.imap</a> package
+ * documentation for further information on the IMAP protocol provider. <p>
+ *
+ * <strong>WARNING:</strong> The APIs unique to this class should be
+ * considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
+ * future in ways that are incompatible with applications using the
+ * current APIs.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ * @author  Jim Glennon
+ */
+/*
+ * This package is implemented over the "imap.protocol" package, which
+ * implements the protocol-level commands. <p>
+ *
+ * A connected IMAPStore maintains a pool of IMAP protocol objects for
+ * use in communicating with the IMAP server. The IMAPStore will create
+ * the initial AUTHENTICATED connection and seed the pool with this
+ * connection. As folders are opened and new IMAP protocol objects are
+ * needed, the IMAPStore will provide them from the connection pool,
+ * or create them if none are available. When a folder is closed,
+ * its IMAP protocol object is returned to the connection pool if the
+ * pool is not over capacity. The pool size can be configured by setting
+ * the mail.imap.connectionpoolsize property. <p>
+ *
+ * Note that all connections in the connection pool have their response
+ * handler set to be the Store.  When the connection is removed from the
+ * pool for use by a folder, the response handler is removed and then set
+ * to either the Folder or to the special nonStoreResponseHandler, depending
+ * on how the connection is being used.  This is probably excessive.
+ * Better would be for the Protocol object to support only a single
+ * response handler, which would be set before the connection is used
+ * and cleared when the connection is in the pool and can't be used. <p>
+ *
+ * A mechanism is provided for timing out idle connection pool IMAP
+ * protocol objects. Timed out connections are closed and removed (pruned)
+ * from the connection pool. The time out interval can be configured via
+ * the mail.imap.connectionpooltimeout property. <p>
+ *
+ * The connected IMAPStore object may or may not maintain a separate IMAP
+ * protocol object that provides the store a dedicated connection to the
+ * IMAP server. This is provided mainly for compatibility with previous
+ * implementations of JavaMail and is determined by the value of the 
+ * mail.imap.separatestoreconnection property. <p>
+ *
+ * An IMAPStore object provides closed IMAPFolder objects thru its list()
+ * and listSubscribed() methods. A closed IMAPFolder object acquires an
+ * IMAP protocol object from the store to communicate with the server. When
+ * the folder is opened, it gets its own protocol object and thus its own,
+ * separate connection to the server. The store maintains references to
+ * all 'open' folders. When a folder is/gets closed, the store removes
+ * it from its list. When the store is/gets closed, it closes all open 
+ * folders in its list, thus cleaning up all open connections to the
+ * server. <p>
+ *
+ * A mutex is used to control access to the connection pool resources.
+ * Any time any of these resources need to be accessed, the following
+ * convention should be followed:
+ *
+ *     synchronized (pool) { // ACQUIRE LOCK
+ *         // access connection pool resources
+ *     } // RELEASE LOCK <p>
+ *
+ * The locking relationship between the store and folders is that the
+ * store lock must be acquired before a folder lock. This is currently only
+ * applicable in the store's cleanup method. It's important that the
+ * connection pool lock is not held when calling into folder objects.
+ * The locking hierarchy is that a folder lock must be acquired before
+ * any connection pool operations are performed.  You never need to hold
+ * all three locks, but if you hold more than one this is the order you
+ * have to acquire them in. <p>
+ *
+ * That is: Store > Folder, Folder > pool, Store > pool <p>
+ *
+ * The IMAPStore implements the ResponseHandler interface and listens to
+ * BYE or untagged OK-notification events from the server as a result of
+ * Store operations.  IMAPFolder forwards notifications that result from
+ * Folder operations using the store connection; the IMAPStore ResponseHandler
+ * is not used directly in this case. <p>
+ */
+
+public class IMAPStore extends Store 
+	     implements QuotaAwareStore, ResponseHandler {
+    
+    /**
+     * A special event type for a StoreEvent to indicate an IMAP
+     * response, if the mail.imap.enableimapevents property is set.
+     */
+    public static final int RESPONSE = 1000;
+
+    public static final String ID_NAME = "name";
+    public static final String ID_VERSION = "version";
+    public static final String ID_OS = "os";
+    public static final String ID_OS_VERSION = "os-version";
+    public static final String ID_VENDOR = "vendor";
+    public static final String ID_SUPPORT_URL = "support-url";
+    public static final String ID_ADDRESS = "address";
+    public static final String ID_DATE = "date";
+    public static final String ID_COMMAND = "command";
+    public static final String ID_ARGUMENTS = "arguments";
+    public static final String ID_ENVIRONMENT = "environment";
+
+    protected final String name;	// name of this protocol
+    protected final int defaultPort;	// default IMAP port
+    protected final boolean isSSL;	// use SSL?
+
+    private final int blksize;		// Block size for data requested
+					// in FETCH requests. Defaults to
+					// 16K
+
+    private boolean ignoreSize;		// ignore the size in BODYSTRUCTURE?
+
+    private final int statusCacheTimeout;	// cache Status for 1 second
+
+    private final int appendBufferSize;	// max size of msg buffered for append
+
+    private final int minIdleTime;	// minimum idle time
+
+    private volatile int port = -1;	// port to use
+
+    // Auth info
+    protected String host;
+    protected String user;
+    protected String password;
+    protected String proxyAuthUser;
+    protected String authorizationID;
+    protected String saslRealm;
+
+    private Namespaces namespaces;
+
+    private boolean enableStartTLS = false;	// enable STARTTLS
+    private boolean requireStartTLS = false;	// require STARTTLS
+    private boolean usingSSL = false;		// using SSL?
+    private boolean enableSASL = false;		// enable SASL authentication
+    private String[] saslMechanisms;
+    private boolean forcePasswordRefresh = false;
+    // enable notification of IMAP responses
+    private boolean enableResponseEvents = false;
+    // enable notification of IMAP responses during IDLE
+    private boolean enableImapEvents = false;
+    private String guid;			// for Yahoo! Mail IMAP
+    private boolean throwSearchException = false;
+    private boolean peek = false;
+    private boolean closeFoldersOnStoreFailure = true;
+    private boolean enableCompress = false;	// enable COMPRESS=DEFLATE
+    private boolean finalizeCleanClose = false;
+
+    /*
+     * This field is set in the Store's response handler if we see
+     * a BYE response.  The releaseStore method checks this field
+     * and if set it cleans up the Store.  Field is volatile because
+     * there's no lock we consistently hold while manipulating it.
+     *
+     * Because volatile doesn't really work before JDK 1.5,
+     * use a lock to protect these two fields.
+     */
+    private volatile boolean connectionFailed = false;
+    private volatile boolean forceClose = false;
+    private final Object connectionFailedLock = new Object();
+
+    private boolean debugusername;	// include username in debug output?
+    private boolean debugpassword;	// include password in debug output?
+    protected MailLogger logger;	// for debug output
+
+    private boolean messageCacheDebug;
+
+    // constructors for IMAPFolder class provided by user
+    private volatile Constructor<?> folderConstructor = null;
+    private volatile Constructor<?> folderConstructorLI = null;
+
+    // Connection pool info
+
+    static class ConnectionPool {
+
+        // container for the pool's IMAP protocol objects
+        private Vector<IMAPProtocol> authenticatedConnections
+		= new Vector<>();
+
+        // vectore of open folders
+        private Vector<IMAPFolder> folders;
+
+        // is the store connection being used?
+        private boolean storeConnectionInUse = false; 
+
+        // the last time (in millis) the pool was checked for timed out
+        // connections
+        private long lastTimePruned;
+
+        // flag to indicate whether there is a dedicated connection for
+        // store commands
+        private final boolean separateStoreConnection;
+
+        // client timeout interval
+        private final long clientTimeoutInterval;
+
+        // server timeout interval
+        private final long serverTimeoutInterval;
+
+        // size of the connection pool
+        private final int poolSize;
+
+        // interval for checking for timed out connections
+        private final long pruningInterval;
+    
+        // connection pool logger
+        private final MailLogger logger;
+
+	/*
+	 * The idleState field supports the IDLE command.
+	 * Normally when executing an IMAP command we hold the
+	 * store's lock.
+	 * While executing the IDLE command we can't hold the
+	 * lock or it would prevent other threads from
+	 * entering Store methods even far enough to check whether
+	 * an IDLE command is in progress.  We need to check before
+	 * issuing another command so that we can abort the IDLE
+	 * command.
+	 *
+	 * The idleState field is protected by the store's lock.
+	 * The RUNNING state is the normal state and means no IDLE
+	 * command is in progress.  The IDLE state means we've issued
+	 * an IDLE command and are reading responses.  The ABORTING
+	 * state means we've sent the DONE continuation command and
+	 * are waiting for the thread running the IDLE command to
+	 * break out of its read loop.
+	 *
+	 * When an IDLE command is in progress, the thread calling
+	 * the idle method will be reading from the IMAP connection
+	 * while not holding the store's lock.
+	 * It's obviously critical that no other thread try to send a
+	 * command or read from the connection while in this state.
+	 * However, other threads can send the DONE continuation
+	 * command that will cause the server to break out of the IDLE
+	 * loop and send the ending tag response to the IDLE command.
+	 * The thread in the idle method that's reading the responses
+	 * from the IDLE command will see this ending response and
+	 * complete the idle method, setting the idleState field back
+	 * to RUNNING, and notifying any threads waiting to use the
+	 * connection.
+	 *
+	 * All uses of the IMAP connection (IMAPProtocol object) must
+	 * be preceeded by a check to make sure an IDLE command is not
+	 * running, and abort the IDLE command if necessary.  This check
+	 * is made while holding the connection pool lock.  While
+	 * waiting for the IDLE command to complete, these other threads
+	 * will give up the connection pool lock.  This check is done by
+	 * the getStoreProtocol() method.
+	 */
+	private static final int RUNNING = 0;	// not doing IDLE command
+	private static final int IDLE = 1;	// IDLE command in effect
+	private static final int ABORTING = 2;	// IDLE command aborting
+	private int idleState = RUNNING;
+	private IMAPProtocol idleProtocol;	// protocol object when IDLE
+
+	ConnectionPool(String name, MailLogger plogger, Session session) {
+	    lastTimePruned = System.currentTimeMillis();
+	    Properties props = session.getProperties();
+
+	    boolean debug = PropUtil.getBooleanProperty(props,
+		"mail." + name + ".connectionpool.debug", false);
+	    logger = plogger.getSubLogger("connectionpool",
+					    "DEBUG IMAP CP", debug);
+
+	    // check if the default connection pool size is overridden
+	    int size = PropUtil.getIntProperty(props,
+		"mail." + name + ".connectionpoolsize", -1);
+	    if (size > 0) {
+		poolSize = size;
+		if (logger.isLoggable(Level.CONFIG))
+		    logger.config("mail.imap.connectionpoolsize: " + poolSize);
+	    } else
+		poolSize = 1;
+
+	    // check if the default client-side timeout value is overridden
+	    int connectionPoolTimeout = PropUtil.getIntProperty(props,
+		"mail." + name + ".connectionpooltimeout", -1);
+	    if (connectionPoolTimeout > 0) {
+		clientTimeoutInterval = connectionPoolTimeout;
+		if (logger.isLoggable(Level.CONFIG))
+		    logger.config("mail.imap.connectionpooltimeout: " +
+			clientTimeoutInterval);
+	    } else 
+		clientTimeoutInterval = 45 * 1000;	// 45 seconds
+
+	    // check if the default server-side timeout value is overridden
+	    int serverTimeout = PropUtil.getIntProperty(props,
+		"mail." + name + ".servertimeout", -1);
+	    if (serverTimeout > 0) {
+		serverTimeoutInterval = serverTimeout;
+		if (logger.isLoggable(Level.CONFIG))
+		    logger.config("mail.imap.servertimeout: " +
+			serverTimeoutInterval);
+	    }  else
+		serverTimeoutInterval = 30 * 60 * 1000;	// 30 minutes
+
+	    // check if the default server-side timeout value is overridden
+	    int pruning = PropUtil.getIntProperty(props,
+		"mail." + name + ".pruninginterval", -1);
+	    if (pruning > 0) {
+		pruningInterval = pruning;
+		if (logger.isLoggable(Level.CONFIG))
+		    logger.config("mail.imap.pruninginterval: " +
+			pruningInterval);
+	    }  else
+		pruningInterval = 60 * 1000;		// 1 minute
+     
+	    // check to see if we should use a separate (i.e. dedicated)
+	    // store connection
+	    separateStoreConnection =
+		PropUtil.getBooleanProperty(props,
+		    "mail." + name + ".separatestoreconnection", false);
+	    if (separateStoreConnection)
+		logger.config("dedicate a store connection");
+
+	}
+    }
+ 
+    private final ConnectionPool pool;
+
+    /**
+     * A special response handler for connections that are being used
+     * to perform operations on behalf of an object other than the Store.
+     * It DOESN'T cause the Store to be cleaned up if a BYE is seen.
+     * The BYE may be real or synthetic and in either case just indicates
+     * that the connection is dead.
+     */
+    private ResponseHandler nonStoreResponseHandler = new ResponseHandler() {
+	@Override
+	public void handleResponse(Response r) {
+	    // Any of these responses may have a response code.
+	    if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
+		handleResponseCode(r);
+	    if (r.isBYE())
+		logger.fine("IMAPStore non-store connection dead");
+	}
+    };
+ 
+    /**
+     * Constructor that takes a Session object and a URLName that
+     * represents a specific IMAP server.
+     *
+     * @param	session	the Session
+     * @param	url	the URLName of this store
+     */
+    public IMAPStore(Session session, URLName url) {
+	this(session, url, "imap", false);
+    }
+
+    /**
+     * Constructor used by this class and by IMAPSSLStore subclass.
+     *
+     * @param	session	the Session
+     * @param	url	the URLName of this store
+     * @param	name	the protocol name for this store
+     * @param	isSSL	use SSL?
+     */
+    protected IMAPStore(Session session, URLName url,
+				String name, boolean isSSL) {
+	super(session, url); // call super constructor
+	Properties props = session.getProperties();
+
+	if (url != null)
+	    name = url.getProtocol();
+	this.name = name;
+	if (!isSSL)
+	    isSSL = PropUtil.getBooleanProperty(props,
+				"mail." + name + ".ssl.enable", false);
+	if (isSSL)
+	    this.defaultPort = 993;
+	else
+	    this.defaultPort = 143;
+	this.isSSL = isSSL;
+
+        debug = session.getDebug();
+	debugusername = PropUtil.getBooleanProperty(props,
+			"mail.debug.auth.username", true);
+	debugpassword = PropUtil.getBooleanProperty(props,
+			"mail.debug.auth.password", false);
+	logger = new MailLogger(this.getClass(),
+			"DEBUG " + name.toUpperCase(Locale.ENGLISH),
+			session.getDebug(), session.getDebugOut());
+
+	boolean partialFetch = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".partialfetch", true);
+	if (!partialFetch) {
+	    blksize = -1;
+	    logger.config("mail.imap.partialfetch: false");
+	} else {
+	    blksize = PropUtil.getIntProperty(props,
+		"mail." + name +".fetchsize", 1024 * 16);
+	    if (logger.isLoggable(Level.CONFIG))
+		logger.config("mail.imap.fetchsize: " + blksize);
+	}
+
+	ignoreSize = PropUtil.getBooleanProperty(props,
+	    "mail." + name +".ignorebodystructuresize", false);
+	if (logger.isLoggable(Level.CONFIG))
+	    logger.config("mail.imap.ignorebodystructuresize: " + ignoreSize);
+
+	statusCacheTimeout = PropUtil.getIntProperty(props,
+	    "mail." + name + ".statuscachetimeout", 1000);
+	if (logger.isLoggable(Level.CONFIG))
+	    logger.config("mail.imap.statuscachetimeout: " +
+						statusCacheTimeout);
+
+	appendBufferSize = PropUtil.getIntProperty(props,
+	    "mail." + name + ".appendbuffersize", -1);
+	if (logger.isLoggable(Level.CONFIG))
+	    logger.config("mail.imap.appendbuffersize: " + appendBufferSize);
+
+	minIdleTime = PropUtil.getIntProperty(props,
+	    "mail." + name + ".minidletime", 10);
+	if (logger.isLoggable(Level.CONFIG))
+	    logger.config("mail.imap.minidletime: " + minIdleTime);
+
+	// check if we should do a PROXYAUTH login
+	String s = session.getProperty("mail." + name + ".proxyauth.user");
+	if (s != null) {
+	    proxyAuthUser = s;
+	    if (logger.isLoggable(Level.CONFIG))
+		logger.config("mail.imap.proxyauth.user: " + proxyAuthUser);
+	}
+
+	// check if STARTTLS is enabled
+	enableStartTLS = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".starttls.enable", false);
+	if (enableStartTLS)
+	    logger.config("enable STARTTLS");
+
+	// check if STARTTLS is required
+	requireStartTLS = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".starttls.required", false);
+	if (requireStartTLS)
+	    logger.config("require STARTTLS");
+
+	// check if SASL is enabled
+	enableSASL = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".sasl.enable", false);
+	if (enableSASL)
+	    logger.config("enable SASL");
+
+	// check if SASL mechanisms are specified
+	if (enableSASL) {
+	    s = session.getProperty("mail." + name + ".sasl.mechanisms");
+	    if (s != null && s.length() > 0) {
+		if (logger.isLoggable(Level.CONFIG))
+		    logger.config("SASL mechanisms allowed: " + s);
+		List<String> v = new ArrayList<>(5);
+		StringTokenizer st = new StringTokenizer(s, " ,");
+		while (st.hasMoreTokens()) {
+		    String m = st.nextToken();
+		    if (m.length() > 0)
+			v.add(m);
+		}
+		saslMechanisms = new String[v.size()];
+		v.toArray(saslMechanisms);
+	    }
+	}
+
+	// check if an authorization ID has been specified
+	s = session.getProperty("mail." + name + ".sasl.authorizationid");
+	if (s != null) {
+	    authorizationID = s;
+	    logger.log(Level.CONFIG, "mail.imap.sasl.authorizationid: {0}",
+						authorizationID);
+	}
+
+	// check if a SASL realm has been specified
+	s = session.getProperty("mail." + name + ".sasl.realm");
+	if (s != null) {
+	    saslRealm = s;
+	    logger.log(Level.CONFIG, "mail.imap.sasl.realm: {0}", saslRealm);
+	}
+
+	// check if forcePasswordRefresh is enabled
+	forcePasswordRefresh = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".forcepasswordrefresh", false);
+	if (forcePasswordRefresh)
+	    logger.config("enable forcePasswordRefresh");
+
+	// check if enableimapevents is enabled
+	enableResponseEvents = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".enableresponseevents", false);
+	if (enableResponseEvents)
+	    logger.config("enable IMAP response events");
+
+	// check if enableresponseevents is enabled
+	enableImapEvents = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".enableimapevents", false);
+	if (enableImapEvents)
+	    logger.config("enable IMAP IDLE events");
+
+	// check if message cache debugging set
+	messageCacheDebug = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".messagecache.debug", false);
+
+	guid = session.getProperty("mail." + name + ".yahoo.guid");
+	if (guid != null)
+	    logger.log(Level.CONFIG, "mail.imap.yahoo.guid: {0}", guid);
+
+	// check if throwsearchexception is enabled
+	throwSearchException = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".throwsearchexception", false);
+	if (throwSearchException)
+	    logger.config("throw SearchException");
+
+	// check if peek is set
+	peek = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".peek", false);
+	if (peek)
+	    logger.config("peek");
+
+	// check if closeFoldersOnStoreFailure is set
+	closeFoldersOnStoreFailure = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".closefoldersonstorefailure", true);
+	if (closeFoldersOnStoreFailure)
+	    logger.config("closeFoldersOnStoreFailure");
+
+	// check if COMPRESS is enabled
+	enableCompress = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".compress.enable", false);
+	if (enableCompress)
+	    logger.config("enable COMPRESS");
+
+	// check if finalizeCleanClose is enabled
+	finalizeCleanClose = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".finalizecleanclose", false);
+	if (finalizeCleanClose)
+	    logger.config("close connection cleanly in finalize");
+
+	s = session.getProperty("mail." + name + ".folder.class");
+	if (s != null) {
+	    logger.log(Level.CONFIG, "IMAP: folder class: {0}", s);
+	    try {
+		ClassLoader cl = this.getClass().getClassLoader();
+
+		// now load the class
+		Class<?> folderClass = null;
+		try {
+		    // First try the "application's" class loader.
+		    // This should eventually be replaced by
+		    // Thread.currentThread().getContextClassLoader().
+		    folderClass = Class.forName(s, false, cl);
+		} catch (ClassNotFoundException ex1) {
+		    // That didn't work, now try the "system" class loader.
+		    // (Need both of these because JDK 1.1 class loaders
+		    // may not delegate to their parent class loader.)
+		    folderClass = Class.forName(s);
+		}
+
+		Class<?>[] c = { String.class, char.class, IMAPStore.class,
+				Boolean.class };
+		folderConstructor = folderClass.getConstructor(c);
+		Class<?>[] c2 = { ListInfo.class, IMAPStore.class };
+		folderConstructorLI = folderClass.getConstructor(c2);
+	    } catch (Exception ex) {
+		logger.log(Level.CONFIG,
+			"IMAP: failed to load folder class", ex);
+	    }
+	}
+
+	pool = new ConnectionPool(name, logger, session);
+    }
+
+    /**
+     * Implementation of protocolConnect().  Will create a connection
+     * to the server and authenticate the user using the mechanisms
+     * specified by various properties. <p>
+     *
+     * The <code>host</code>, <code>user</code>, and <code>password</code>
+     * parameters must all be non-null.  If the authentication mechanism
+     * being used does not require a password, an empty string or other
+     * suitable dummy password should be used.
+     */
+    @Override
+    protected synchronized boolean 
+    protocolConnect(String host, int pport, String user, String password)
+		throws MessagingException {
+        
+        IMAPProtocol protocol = null;
+
+	// check for non-null values of host, password, user
+	if (host == null || password == null || user == null) {
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("protocolConnect returning false" +
+				", host=" + host +
+				", user=" + traceUser(user) +
+				", password=" + tracePassword(password));
+	    return false;
+	}
+
+	// set the port correctly
+	if (pport != -1) {
+	    port = pport;
+	} else {
+	    port = PropUtil.getIntProperty(session.getProperties(),
+					"mail." + name + ".port", port);
+	} 
+	
+	// use the default if needed
+	if (port == -1) {
+	    port = defaultPort;
+	}
+	
+	try {
+            boolean poolEmpty;
+            synchronized (pool) {
+                poolEmpty = pool.authenticatedConnections.isEmpty();
+            }
+
+            if (poolEmpty) {
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("trying to connect to host \"" + host +
+				"\", port " + port + ", isSSL " + isSSL);
+                protocol = newIMAPProtocol(host, port);
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("protocolConnect login" +
+				", host=" + host +
+				", user=" + traceUser(user) +
+				", password=" + tracePassword(password));
+		protocol.addResponseHandler(nonStoreResponseHandler);
+	        login(protocol, user, password);
+		protocol.removeResponseHandler(nonStoreResponseHandler);
+	        protocol.addResponseHandler(this);
+
+		usingSSL = protocol.isSSL();	// in case anyone asks
+
+	        this.host = host;
+	        this.user = user;
+	        this.password = password;
+
+                synchronized (pool) {
+                    pool.authenticatedConnections.addElement(protocol);
+                }
+            }
+	} catch (IMAPReferralException ex) {
+	    // login failure due to IMAP REFERRAL, close connection to server
+	    if (protocol != null)
+		protocol.disconnect();
+	    protocol = null;
+	    throw new ReferralException(ex.getUrl(), ex.getMessage());
+	} catch (CommandFailedException cex) {
+	    // login failure, close connection to server
+	    if (protocol != null)
+		protocol.disconnect();
+	    protocol = null;
+	    Response r = cex.getResponse();
+	    throw new AuthenticationFailedException(
+				    r != null ? r.getRest() : cex.getMessage());
+	} catch (ProtocolException pex) { // any other exception
+	    // failure in login command, close connection to server
+	    if (protocol != null)
+		protocol.disconnect();
+	    protocol = null;
+	    throw new MessagingException(pex.getMessage(), pex);
+	} catch (SocketConnectException scex) {
+	    throw new MailConnectException(scex);
+	} catch (IOException ioex) {
+	    throw new MessagingException(ioex.getMessage(), ioex);
+	} 
+
+        return true;
+    }
+
+    /**
+     * Create an IMAPProtocol object connected to the host and port.
+     * Subclasses of IMAPStore may override this method to return a
+     * subclass of IMAPProtocol that supports product-specific extensions.
+     *
+     * @param	host	the host name
+     * @param	port	the port number
+     * @return		the new IMAPProtocol object
+     * @exception	IOException for I/O errors
+     * @exception	ProtocolException for protocol errors
+     * @since JavaMail 1.4.6
+     */
+    protected IMAPProtocol newIMAPProtocol(String host, int port)
+				throws IOException, ProtocolException {
+	return new IMAPProtocol(name, host, port, 
+					    session.getProperties(),
+					    isSSL,
+					    logger
+					   );
+    }
+
+    private void login(IMAPProtocol p, String u, String pw) 
+		throws ProtocolException {
+	// turn on TLS if it's been enabled or required and is supported
+	// and we're not already using SSL
+	if ((enableStartTLS || requireStartTLS) && !p.isSSL()) {
+	    if (p.hasCapability("STARTTLS")) {
+		p.startTLS();
+		// if startTLS succeeds, refresh capabilities
+		p.capability();
+	    } else if (requireStartTLS) {
+		logger.fine("STARTTLS required but not supported by server");
+		throw new ProtocolException(
+		    "STARTTLS required but not supported by server");
+	    }
+	}
+	if (p.isAuthenticated())
+	    return;		// no need to login
+
+	// allow subclasses to issue commands before login
+	preLogin(p);
+
+	// issue special ID command to Yahoo! Mail IMAP server
+	// http://en.wikipedia.org/wiki/Yahoo%21_Mail#Free_IMAP_and_SMTPs_access
+	if (guid != null) {
+	    Map<String,String> gmap = new HashMap<>();
+	    gmap.put("GUID", guid);
+	    p.id(gmap);
+	}
+
+	/*
+	 * Put a special "marker" in the capabilities list so we can
+	 * detect if the server refreshed the capabilities in the OK
+	 * response.
+	 */
+	p.getCapabilities().put("__PRELOGIN__", "");
+	String authzid;
+	if (authorizationID != null)
+	    authzid = authorizationID;
+	else if (proxyAuthUser != null)
+	    authzid = proxyAuthUser;
+	else
+	    authzid = null;
+
+	if (enableSASL) {
+	    try {
+		p.sasllogin(saslMechanisms, saslRealm, authzid, u, pw);
+		if (!p.isAuthenticated())
+		    throw new CommandFailedException(
+						"SASL authentication failed");
+	    } catch (UnsupportedOperationException ex) {
+		// continue to try other authentication methods below
+	    }
+	}
+
+	if (!p.isAuthenticated())
+	    authenticate(p, authzid, u, pw);
+
+	if (proxyAuthUser != null)
+	    p.proxyauth(proxyAuthUser);
+
+	/*
+	 * If marker is still there, capabilities haven't been refreshed,
+	 * refresh them now.
+	 */
+	if (p.hasCapability("__PRELOGIN__")) {
+	    try {
+		p.capability();
+	    } catch (ConnectionException cex) {
+		throw cex;	// rethrow connection failures
+		// XXX - assume connection has been closed
+	    } catch (ProtocolException pex) {
+		// ignore other exceptions that "should never happen"
+	    }
+	}
+
+	if (enableCompress) {
+	    if (p.hasCapability("COMPRESS=DEFLATE")) {
+		p.compress();
+	    }
+	}
+
+	// if server supports UTF-8, enable it for client use
+	// note that this is safe to enable even if mail.mime.allowutf8=false
+	if (p.hasCapability("UTF8=ACCEPT") || p.hasCapability("UTF8=ONLY"))
+	    p.enable("UTF8=ACCEPT");
+    }
+
+    /**
+     * Authenticate using one of the non-SASL mechanisms.
+     *
+     * @param	p	the IMAPProtocol object
+     * @param	authzid	the authorization ID
+     * @param	user	the user name
+     * @param	password the password
+     * @exception	ProtocolException	on failures
+     */
+    private void authenticate(IMAPProtocol p, String authzid,
+				String user, String password)
+				throws ProtocolException {
+	// this list must match the "if" statements below
+	String defaultAuthenticationMechanisms = "PLAIN LOGIN NTLM XOAUTH2";
+
+	// setting mail.imap.auth.mechanisms controls which mechanisms will
+	// be used, and in what order they'll be considered.  only the first
+	// match is used.
+	String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
+
+	if (mechs == null)
+	    mechs = defaultAuthenticationMechanisms;
+
+	/*
+	 * Loop through the list of mechanisms supplied by the user
+	 * (or defaulted) and try each in turn.  If the server supports
+	 * the mechanism and we have an authenticator for the mechanism,
+	 * and it hasn't been disabled, use it.
+	 */
+	StringTokenizer st = new StringTokenizer(mechs);
+	while (st.hasMoreTokens()) {
+	    String m = st.nextToken();
+	    m = m.toUpperCase(Locale.ENGLISH);
+
+	    /*
+	     * If using the default mechanisms, check if this one is disabled.
+	     */
+	    if (mechs == defaultAuthenticationMechanisms) {
+		String dprop = "mail." + name + ".auth." +
+				    m.toLowerCase(Locale.ENGLISH) + ".disable";
+		boolean disabled = PropUtil.getBooleanProperty(
+					session.getProperties(),
+					dprop, m.equals("XOAUTH2"));
+		if (disabled) {
+		    if (logger.isLoggable(Level.FINE))
+			logger.fine("mechanism " + m +
+					" disabled by property: " + dprop);
+		    continue;
+		}
+	    }
+
+	    if (!(p.hasCapability("AUTH=" + m) ||
+		    (m.equals("LOGIN") && p.hasCapability("AUTH-LOGIN")))) {
+		logger.log(Level.FINE, "mechanism {0} not supported by server",
+					m);
+		continue;
+	    }
+
+	    if (m.equals("PLAIN"))
+		p.authplain(authzid, user, password);
+	    else if (m.equals("LOGIN"))
+		p.authlogin(user, password);
+	    else if (m.equals("NTLM"))
+		p.authntlm(authzid, user, password);
+	    else if (m.equals("XOAUTH2"))
+		p.authoauth2(user, password);
+	    else {
+		logger.log(Level.FINE, "no authenticator for mechanism {0}", m);
+		continue;
+	    }
+	    return;
+	}
+
+	if (!p.hasCapability("LOGINDISABLED")) {
+	    p.login(user, password);
+	    return;
+	}
+
+	throw new ProtocolException("No login methods supported!");
+    }
+
+    /**
+     * This method is called after the connection is made and
+     * TLS is started (if needed), but before any authentication
+     * is attempted.  Subclasses can override this method to
+     * issue commands that are needed in the "not authenticated"
+     * state.  Note that if the connection is pre-authenticated,
+     * this method won't be called. <p>
+     *
+     * The implementation of this method in this class does nothing.
+     *
+     * @param	p	the IMAPProtocol connection
+     * @exception	ProtocolException for protocol errors
+     * @since JavaMail 1.4.4
+     */
+    protected void preLogin(IMAPProtocol p) throws ProtocolException {
+    }
+
+    /**
+     * Does this IMAPStore use SSL when connecting to the server?
+     *
+     * @return	true if using SSL
+     * @since	JavaMail 1.4.6
+     */
+    public synchronized boolean isSSL() {
+        return usingSSL;
+    }
+
+    /**
+     * Set the user name that will be used for subsequent connections
+     * after this Store is first connected (for example, when creating
+     * a connection to open a Folder).  This value is overridden
+     * by any call to the Store's connect method. <p>
+     *
+     * Some IMAP servers may provide an authentication ID that can
+     * be used for more efficient authentication for future connections.
+     * This authentication ID is provided in a server-specific manner
+     * not described here. <p>
+     *
+     * Most applications will never need to use this method.
+     *
+     * @param	user	the user name for the store
+     * @since	JavaMail 1.3.3
+     */
+    public synchronized void setUsername(String user) {
+	this.user = user;
+    }
+
+    /**
+     * Set the password that will be used for subsequent connections
+     * after this Store is first connected (for example, when creating
+     * a connection to open a Folder).  This value is overridden
+     * by any call to the Store's connect method. <p>
+     *
+     * Most applications will never need to use this method.
+     *
+     * @param	password	the password for the store
+     * @since	JavaMail 1.3.3
+     */
+    public synchronized void setPassword(String password) {
+	this.password = password;
+    }
+
+    /*
+     * Get a new authenticated protocol object for this Folder.
+     * Also store a reference to this folder in our list of
+     * open folders.
+     */
+    IMAPProtocol getProtocol(IMAPFolder folder) 
+		throws MessagingException {
+	IMAPProtocol p = null;
+
+	// keep looking for a connection until we get a good one
+	while (p == null) {
+ 
+        // New authenticated protocol objects are either acquired
+        // from the connection pool, or created when the pool is
+        // empty or no connections are available. None are available
+        // if the current pool size is one and the separate store
+        // property is set or the connection is in use.
+
+        synchronized (pool) {
+
+            // If there's none available in the pool,
+            // create a new one.
+            if (pool.authenticatedConnections.isEmpty() ||
+                (pool.authenticatedConnections.size() == 1 &&
+                (pool.separateStoreConnection || pool.storeConnectionInUse))) {
+
+		logger.fine("no connections in the pool, creating a new one");
+                try {
+		    if (forcePasswordRefresh)
+			refreshPassword();
+                    // Use cached host, port and timeout values.
+                    p = newIMAPProtocol(host, port);
+		    p.addResponseHandler(nonStoreResponseHandler);
+                    // Use cached auth info
+                    login(p, user, password);
+		    p.removeResponseHandler(nonStoreResponseHandler);
+                } catch(Exception ex1) {
+                    if (p != null)
+                        try {
+                            p.disconnect();
+                        } catch (Exception ex2) { }
+                    p = null;
+                }
+                 
+                if (p == null)
+                    throw new MessagingException("connection failure");
+            } else {
+		if (logger.isLoggable(Level.FINE))
+                    logger.fine("connection available -- size: " +
+                        pool.authenticatedConnections.size());
+
+                // remove the available connection from the Authenticated queue
+                p = pool.authenticatedConnections.lastElement();
+                pool.authenticatedConnections.removeElement(p);
+
+		// check if the connection is still live
+		long lastUsed = System.currentTimeMillis() - p.getTimestamp();
+		if (lastUsed > pool.serverTimeoutInterval) {
+		    try {
+			/*
+			 * Swap in a special response handler that will handle
+			 * alerts, but won't cause the store to be closed and
+			 * cleaned up if the connection is dead.
+			 */
+			p.removeResponseHandler(this);
+			p.addResponseHandler(nonStoreResponseHandler);
+			p.noop();
+			p.removeResponseHandler(nonStoreResponseHandler);
+			p.addResponseHandler(this);
+		    } catch (ProtocolException pex) {
+			try {
+			    p.removeResponseHandler(nonStoreResponseHandler);
+			    p.disconnect();
+			} catch (RuntimeException ignored) {
+			    // don't let any exception stop us
+			}
+			p = null;
+			continue;   // try again, from the top
+		    }
+		}
+
+		// if proxyAuthUser has changed, switch to new user
+		if (proxyAuthUser != null &&
+			!proxyAuthUser.equals(p.getProxyAuthUser()) &&
+			p.hasCapability("X-UNAUTHENTICATE")) {
+		    try {
+			/*
+			 * Swap in a special response handler that will handle
+			 * alerts, but won't cause the store to be closed and
+			 * cleaned up if the connection is dead.
+			 */
+			p.removeResponseHandler(this);
+			p.addResponseHandler(nonStoreResponseHandler);
+			p.unauthenticate();
+			login(p, user, password);
+			p.removeResponseHandler(nonStoreResponseHandler);
+			p.addResponseHandler(this);
+		    } catch (ProtocolException pex) {
+			try {
+			    p.removeResponseHandler(nonStoreResponseHandler);
+			    p.disconnect();
+			} catch (RuntimeException ignored) {
+			    // don't let any exception stop us
+			}
+			p = null;
+			continue;   // try again, from the top
+		    }
+		}
+
+                // remove the store as a response handler.
+                p.removeResponseHandler(this);
+	    }
+
+            // check if we need to look for client-side timeouts
+            timeoutConnections();
+
+	    // Add folder to folder-list
+	    if (folder != null) {
+                if (pool.folders == null)
+                    pool.folders = new Vector<>();
+		pool.folders.addElement(folder);
+	    }
+        }
+
+	}
+	
+	return p;
+    }
+
+    /**
+     * Get this Store's protocol connection.
+     *
+     * When acquiring a store protocol object, it is important to
+     * use the following steps:
+     *
+     *     IMAPProtocol p = null;
+     *     try {
+     *         p = getStoreProtocol();
+     *         // perform the command
+     *     } catch (ConnectionException cex) {
+     *         throw new StoreClosedException(this, cex.getMessage());
+     *     } catch (WhateverException ex) {
+     *         // handle it
+     *     } finally {
+     *         releaseStoreProtocol(p);
+     *     }
+     */
+    private IMAPProtocol getStoreProtocol() throws ProtocolException {
+        IMAPProtocol p = null;
+
+	while (p == null) {
+        synchronized (pool) {
+	    waitIfIdle();
+
+            // If there's no authenticated connections available create a 
+            // new one and place it in the authenticated queue.
+            if (pool.authenticatedConnections.isEmpty()) {
+		pool.logger.fine("getStoreProtocol() - no connections " +
+                        "in the pool, creating a new one");
+                try {
+		    if (forcePasswordRefresh)
+			refreshPassword();
+                    // Use cached host, port and timeout values.
+                    p = newIMAPProtocol(host, port);
+                    // Use cached auth info
+                    login(p, user, password);
+                } catch(Exception ex1) {
+                    if (p != null)
+                        try {
+                            p.logout();
+                        } catch (Exception ex2) { }
+                    p = null;
+                }
+ 
+                if (p == null)
+                    throw new ConnectionException(
+				"failed to create new store connection");
+             
+	        p.addResponseHandler(this);
+                pool.authenticatedConnections.addElement(p);
+ 
+            } else {
+                // Always use the first element in the Authenticated queue.
+		if (pool.logger.isLoggable(Level.FINE))
+                    pool.logger.fine("getStoreProtocol() - " +
+                        "connection available -- size: " +
+                        pool.authenticatedConnections.size());
+                p = pool.authenticatedConnections.firstElement();
+
+		// if proxyAuthUser has changed, switch to new user
+		if (proxyAuthUser != null &&
+			!proxyAuthUser.equals(p.getProxyAuthUser()) &&
+			p.hasCapability("X-UNAUTHENTICATE")) {
+		    p.unauthenticate();
+		    login(p, user, password);
+		}
+            }
+ 
+	    if (pool.storeConnectionInUse) {
+		try {
+		    // someone else is using the connection, give up
+		    // and wait until they're done
+		    p = null;
+		    pool.wait();
+		} catch (InterruptedException ex) {
+		    // restore the interrupted state, which callers might
+		    // depend on
+		    Thread.currentThread().interrupt();
+		    // don't keep looking for a connection if we've been
+		    // interrupted
+		    throw new ProtocolException(
+				    "Interrupted getStoreProtocol", ex);
+		}
+	    } else {
+		pool.storeConnectionInUse = true;
+
+		pool.logger.fine("getStoreProtocol() -- storeConnectionInUse");
+	    }
+ 
+            timeoutConnections();
+        }
+	}
+	return p;
+    }
+
+    /**
+     * Get a store protocol object for use by a folder.
+     */
+    IMAPProtocol getFolderStoreProtocol() throws ProtocolException {
+	IMAPProtocol p = getStoreProtocol();
+	p.removeResponseHandler(this);
+	p.addResponseHandler(nonStoreResponseHandler);
+	return p;
+    }
+
+    /*
+     * Some authentication systems use one time passwords
+     * or tokens, so each authentication request requires
+     * a new password.  This "kludge" allows a callback
+     * to application code to get a new password.
+     *
+     * XXX - remove this when SASL support is added
+     */
+    private void refreshPassword() {
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("refresh password, user: " + traceUser(user));
+	InetAddress addr;
+	try {
+	    addr = InetAddress.getByName(host);
+	} catch (UnknownHostException e) {
+	    addr = null;
+	}
+	PasswordAuthentication pa =
+	    session.requestPasswordAuthentication(addr, port,
+					name, null, user);
+	if (pa != null) {
+	    user = pa.getUserName();
+	    password = pa.getPassword();
+	}
+    }
+
+    /**
+     * If a SELECT succeeds, but indicates that the folder is
+     * READ-ONLY, and the user asked to open the folder READ_WRITE,
+     * do we allow the open to succeed?
+     */
+    boolean allowReadOnlySelect() {
+	return PropUtil.getBooleanProperty(session.getProperties(),
+	    "mail." + name + ".allowreadonlyselect", false);
+    }
+
+    /**
+     * Report whether the separateStoreConnection is set.
+     */
+    boolean hasSeparateStoreConnection() {
+        return pool.separateStoreConnection;
+    }
+
+    /** 
+     * Return the connection pool logger.
+     */ 
+    MailLogger getConnectionPoolLogger() {
+        return pool.logger; 
+    } 
+ 
+    /** 
+     * Report whether message cache debugging is enabled. 
+     */ 
+    boolean getMessageCacheDebug() {
+        return messageCacheDebug; 
+    } 
+ 
+    /**
+     * Report whether the connection pool is full.
+     */
+    boolean isConnectionPoolFull() {
+
+        synchronized (pool) {
+	    if (pool.logger.isLoggable(Level.FINE))
+                pool.logger.fine("connection pool current size: " +
+                    pool.authenticatedConnections.size() + 
+                    "   pool size: " + pool.poolSize);
+
+            return (pool.authenticatedConnections.size() >= pool.poolSize);
+
+        }
+    }
+
+    /**
+     * Release the protocol object back to the connection pool.
+     */
+    void releaseProtocol(IMAPFolder folder, IMAPProtocol protocol) {
+
+        synchronized (pool) {
+            if (protocol != null) {
+                // If the pool is not full, add the store as a response handler
+                // and return the protocol object to the connection pool.
+                if (!isConnectionPoolFull()) {
+                    protocol.addResponseHandler(this);
+                    pool.authenticatedConnections.addElement(protocol);
+
+		    if (logger.isLoggable(Level.FINE))
+                        logger.fine(
+			    "added an Authenticated connection -- size: " +
+                            pool.authenticatedConnections.size());
+                } else {
+		    logger.fine(
+			"pool is full, not adding an Authenticated connection");
+                    try {
+                        protocol.logout();
+                    } catch (ProtocolException pex) {};
+                }
+            }
+
+            if (pool.folders != null)
+                pool.folders.removeElement(folder);
+
+            timeoutConnections();
+        }
+    }
+
+    /**
+     * Release the store connection.
+     */
+    private void releaseStoreProtocol(IMAPProtocol protocol) {
+
+	// will be called from idle() without the Store lock held,
+	// but cleanup is synchronized and will acquire the Store lock
+
+	if (protocol == null) {
+	    cleanup();		// failed to ever get the connection
+	    return;		// nothing to release
+	}
+
+	/*
+	 * Read out the flag that says whether this connection failed
+	 * before releasing the protocol object for others to use.
+	 */
+	boolean failed;
+	synchronized (connectionFailedLock) {
+	    failed = connectionFailed;
+	    connectionFailed = false;	// reset for next use
+	}
+
+	// now free the store connection
+        synchronized (pool) {
+	    pool.storeConnectionInUse = false;
+	    pool.notifyAll();	// in case anyone waiting
+
+	    pool.logger.fine("releaseStoreProtocol()");
+
+            timeoutConnections();
+        }
+
+	/*
+	 * If the connection died while we were using it, clean up.
+	 * It's critical that the store connection be freed and the
+	 * connection pool not be locked while we do this.
+	 */
+	assert !Thread.holdsLock(pool);
+	if (failed)
+	    cleanup();
+    }
+
+    /**
+     * Release a store protocol object that was being used by a folder.
+     */
+    void releaseFolderStoreProtocol(IMAPProtocol protocol) {
+	if (protocol == null)
+	    return;		// should never happen
+	protocol.removeResponseHandler(nonStoreResponseHandler);
+	protocol.addResponseHandler(this);
+        synchronized (pool) {
+	    pool.storeConnectionInUse = false;
+	    pool.notifyAll();	// in case anyone waiting
+
+	    pool.logger.fine("releaseFolderStoreProtocol()");
+
+            timeoutConnections();
+        }
+    }
+
+    /**
+     * Empty the connection pool.
+     */ 
+    private void emptyConnectionPool(boolean force) {
+
+        synchronized (pool) {
+            for (int index = pool.authenticatedConnections.size() - 1;
+		    index >= 0; --index) {
+                try {
+		    IMAPProtocol p =
+			pool.authenticatedConnections.elementAt(index);
+		    p.removeResponseHandler(this);
+		    if (force)
+			p.disconnect();
+		    else
+			p.logout();
+                } catch (ProtocolException pex) {};
+            }
+
+            pool.authenticatedConnections.removeAllElements();
+        }
+        
+	pool.logger.fine("removed all authenticated connections from pool");
+    }
+
+    /**  
+     * Check to see if it's time to shrink the connection pool.
+     */  
+    private void timeoutConnections() {
+
+        synchronized (pool) {
+
+            // If we've exceeded the pruning interval, look for stale
+            // connections to logout.
+            if (System.currentTimeMillis() - pool.lastTimePruned > 
+                pool.pruningInterval && 
+                pool.authenticatedConnections.size() > 1) {
+
+		if (pool.logger.isLoggable(Level.FINE)) {
+                    pool.logger.fine("checking for connections to prune: " +
+                        (System.currentTimeMillis() - pool.lastTimePruned));
+                    pool.logger.fine("clientTimeoutInterval: " +
+                        pool.clientTimeoutInterval);
+                }   
+ 
+                IMAPProtocol p;
+ 
+                // Check the timestamp of the protocol objects in the pool and
+                // logout if the interval exceeds the client timeout value
+                // (leave the first connection).
+                for (int index = pool.authenticatedConnections.size() - 1; 
+                     index > 0; index--) {
+                    p = pool.authenticatedConnections.
+                        elementAt(index);
+		    if (pool.logger.isLoggable(Level.FINE))
+                        pool.logger.fine("protocol last used: " +
+                            (System.currentTimeMillis() - p.getTimestamp()));
+                    if (System.currentTimeMillis() - p.getTimestamp() >
+                        pool.clientTimeoutInterval) {
+ 
+			pool.logger.fine(
+			    "authenticated connection timed out, " +
+			    "logging out the connection");
+ 
+                        p.removeResponseHandler(this);
+                        pool.authenticatedConnections.removeElementAt(index);
+
+                        try {
+                            p.logout();
+                        } catch (ProtocolException pex) {}
+                    }
+                }
+                pool.lastTimePruned = System.currentTimeMillis();
+            }
+        }
+    }
+
+    /**
+     * Get the block size to use for fetch requests on this Store.
+     */
+    int getFetchBlockSize() {
+	return blksize;
+    }
+
+    /**
+     * Ignore the size reported in the BODYSTRUCTURE when fetching data?
+     */
+    boolean ignoreBodyStructureSize() {
+	return ignoreSize;
+    }
+
+    /**
+     * Get a reference to the session.
+     */
+    Session getSession() {
+        return session;
+    }
+
+    /**
+     * Get the number of milliseconds to cache STATUS response.
+     */
+    int getStatusCacheTimeout() {
+	return statusCacheTimeout;
+    }
+
+    /**
+     * Get the maximum size of a message to buffer for append.
+     */
+    int getAppendBufferSize() {
+	return appendBufferSize;
+    }
+
+    /**
+     * Get the minimum amount of time to delay when returning from idle.
+     */
+    int getMinIdleTime() {
+	return minIdleTime;
+    }
+
+    /**
+     * Throw a SearchException if the search expression is too complex?
+     */
+    boolean throwSearchException() {
+	return throwSearchException;
+    }
+
+    /**
+     * Get the default "peek" value.
+     */
+    boolean getPeek() {
+	return peek;
+    }
+
+    /**
+     * Return true if the specified capability string is in the list
+     * of capabilities the server announced.
+     *
+     * @param	capability	the capability string
+     * @return			true if the server supports this capability
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.3.3
+     */
+    public synchronized boolean hasCapability(String capability)
+				throws MessagingException {
+        IMAPProtocol p = null;
+	try {
+	    p = getStoreProtocol();
+            return p.hasCapability(capability);
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+        } finally {
+            releaseStoreProtocol(p);
+        }
+    }
+
+    /**
+     * Set the user name to be used with the PROXYAUTH command.
+     * The PROXYAUTH user name can also be set using the
+     * <code>mail.imap.proxyauth.user</code> property when this
+     * Store is created.
+     *
+     * @param	user	the user name to set
+     * @since	JavaMail 1.5.1
+     */
+    public void setProxyAuthUser(String user) {
+	proxyAuthUser = user;
+    }
+
+    /**
+     * Get the user name to be used with the PROXYAUTH command.
+     *
+     * @return	the user name
+     * @since	JavaMail 1.5.1
+     */
+    public String getProxyAuthUser() {
+	return proxyAuthUser;
+    }
+
+    /**
+     * Check whether this store is connected. Override superclass
+     * method, to actually ping our server connection.
+     */
+    @Override
+    public synchronized boolean isConnected() {
+	if (!super.isConnected()) {
+	    // if we haven't been connected at all, don't bother with
+	    // the NOOP.
+	    return false;
+	}
+
+	/*
+	 * The below noop() request can:
+	 * (1) succeed - in which case all is fine.
+	 *
+	 * (2) fail because the server returns NO or BAD, in which
+	 * 	case we ignore it since we can't really do anything.
+	 * (2) fail because a BYE response is obtained from the 
+	 *	server
+	 * (3) fail because the socket.write() to the server fails,
+	 *	in which case the iap.protocol() code converts the
+	 *	IOException into a BYE response.
+	 *
+	 * Thus, our BYE handler will take care of closing the Store
+	 * in case our connection is really gone.
+	 */
+   
+        IMAPProtocol p = null;
+	try {
+	    p = getStoreProtocol();
+            p.noop();
+	} catch (ProtocolException pex) {
+	    // will return false below
+        } finally {
+            releaseStoreProtocol(p);
+        }
+
+
+	return super.isConnected();
+    }
+
+    /**
+     * Close this Store.
+     */
+    @Override
+    public synchronized void close() throws MessagingException {
+	cleanup();
+	// do these again in case cleanup returned early
+	// because we were already closed due to a failure
+	closeAllFolders(false);
+	emptyConnectionPool(false);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+	if (!finalizeCleanClose) {
+	    // when finalizing, close connections abruptly
+	    synchronized (connectionFailedLock) {
+		connectionFailed = true;
+		forceClose = true;
+	    }
+	    closeFoldersOnStoreFailure = true;	// make sure folders get closed
+	}
+	try {
+	    close();
+	} finally {
+	    super.finalize();
+	}
+    }
+
+    /**
+     * Cleanup before dying.
+     */
+    private synchronized void cleanup() {
+	// if we're not connected, someone beat us to it
+	if (!super.isConnected()) {
+	    logger.fine("IMAPStore cleanup, not connected");
+	    return;
+	}
+
+	/*
+	 * If forceClose is true, some thread ran into an error that suggests
+	 * the server might be dead, so we force the folders to close
+	 * abruptly without waiting for the server.  Used when
+	 * the store connection times out, for example.
+	 */
+	boolean force;
+	synchronized (connectionFailedLock) {
+	    force = forceClose;
+	    forceClose = false;
+	    connectionFailed = false;
+	}
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("IMAPStore cleanup, force " + force);
+
+	if (!force || closeFoldersOnStoreFailure) {
+	    closeAllFolders(force);
+	}
+
+	emptyConnectionPool(force);
+
+	// to set the state and send the closed connection event
+	try {
+	    super.close();
+	} catch (MessagingException mex) {
+	    // ignore it
+	}
+	logger.fine("IMAPStore cleanup done");
+    }
+
+    /**
+     * Close all open Folders.  If force is true, close them forcibly.
+     */
+    private void closeAllFolders(boolean force) {
+        List<IMAPFolder> foldersCopy = null;
+        boolean done = true;
+
+	// To avoid violating the locking hierarchy, there's no lock we
+	// can hold that prevents another thread from trying to open a
+	// folder at the same time we're trying to close all the folders.
+	// Thus, there's an inherent race condition here.  We close all
+	// the folders we know about and then check whether any new folders
+	// have been opened in the mean time.  We keep trying until we're
+	// successful in closing all the folders.
+	for (;;) {
+	    // Make a copy of the folders list so we do not violate the
+	    // folder-connection pool locking hierarchy.
+	    synchronized (pool) {
+		if (pool.folders != null) {
+		    done = false;
+		    foldersCopy = pool.folders;
+		    pool.folders = null;
+		} else {
+                    done = true;
+                }
+	    }
+	    if (done)
+		break;
+
+	    // Close and remove any open folders under this Store.
+	    for (int i = 0, fsize = foldersCopy.size(); i < fsize; i++) {
+		IMAPFolder f = foldersCopy.get(i);
+
+		try {
+		    if (force) {
+			logger.fine("force folder to close");
+			// Don't want to wait for folder connection to timeout
+			// (if, for example, the server is down) so we close
+			// folders abruptly.
+			f.forceClose();
+		    } else {
+			logger.fine("close folder");
+			f.close(false);
+		    }
+		} catch (MessagingException mex) {
+		    // Who cares ?! Ignore 'em.
+		} catch (IllegalStateException ex) {
+		    // Ditto
+		}
+	    }
+
+	}
+    }
+
+    /**
+     * Get the default folder, representing the root of this user's 
+     * namespace. Returns a closed DefaultFolder object.
+     */
+    @Override
+    public synchronized Folder getDefaultFolder() throws MessagingException {
+	checkConnected();
+	return new DefaultFolder(this);
+    }
+
+    /**
+     * Get named folder. Returns a new, closed IMAPFolder.
+     */
+    @Override
+    public synchronized Folder getFolder(String name)
+				throws MessagingException {
+	checkConnected();
+	return newIMAPFolder(name, IMAPFolder.UNKNOWN_SEPARATOR);
+    }
+
+    /**
+     * Get named folder. Returns a new, closed IMAPFolder.
+     */
+    @Override
+    public synchronized Folder getFolder(URLName url)
+				throws MessagingException {
+	checkConnected();
+	return newIMAPFolder(url.getFile(), IMAPFolder.UNKNOWN_SEPARATOR);
+    }
+
+    /**
+     * Create an IMAPFolder object.  If user supplied their own class,
+     * use it.  Otherwise, call the constructor.
+     *
+     * @param	fullName the full name of the folder
+     * @param	separator the separator character for the folder hierarchy
+     * @param	isNamespace does this name represent a namespace?
+     * @return		the new IMAPFolder object
+     */
+    protected IMAPFolder newIMAPFolder(String fullName, char separator,
+				Boolean isNamespace) {
+	IMAPFolder f = null;
+	if (folderConstructor != null) {
+	    try {
+		Object[] o =
+		  { fullName, Character.valueOf(separator), this, isNamespace };
+		f = (IMAPFolder)folderConstructor.newInstance(o);
+	    } catch (Exception ex) {
+		logger.log(Level.FINE,
+			    "exception creating IMAPFolder class", ex);
+	    }
+	}
+	if (f == null)
+	    f = new IMAPFolder(fullName, separator, this, isNamespace);
+	return f;
+    }
+
+    /**
+     * Create an IMAPFolder object.  Call the newIMAPFolder method
+     * above with a null isNamespace.
+     *
+     * @param	fullName the full name of the folder
+     * @param	separator the separator character for the folder hierarchy
+     * @return		the new IMAPFolder object
+     */
+    protected IMAPFolder newIMAPFolder(String fullName, char separator) {
+	return newIMAPFolder(fullName, separator, null);
+    }
+
+    /**
+     * Create an IMAPFolder object.  If user supplied their own class,
+     * use it.  Otherwise, call the constructor.
+     *
+     * @param	li	the ListInfo for the folder
+     * @return		the new IMAPFolder object
+     */
+    protected IMAPFolder newIMAPFolder(ListInfo li) {
+	IMAPFolder f = null;
+	if (folderConstructorLI != null) {
+	    try {
+		Object[] o = { li, this };
+		f = (IMAPFolder)folderConstructorLI.newInstance(o);
+	    } catch (Exception ex) {
+		logger.log(Level.FINE,
+			"exception creating IMAPFolder class LI", ex);
+	    }
+	}
+	if (f == null)
+	    f = new IMAPFolder(li, this);
+	return f;
+    }
+
+    /**
+     * Using the IMAP NAMESPACE command (RFC 2342), return a set
+     * of folders representing the Personal namespaces.
+     */
+    @Override
+    public Folder[] getPersonalNamespaces() throws MessagingException {
+	Namespaces ns = getNamespaces();
+	if (ns == null || ns.personal == null)
+	    return super.getPersonalNamespaces();
+	return namespaceToFolders(ns.personal, null);
+    }
+
+    /**
+     * Using the IMAP NAMESPACE command (RFC 2342), return a set
+     * of folders representing the User's namespaces.
+     */
+    @Override
+    public Folder[] getUserNamespaces(String user)
+				throws MessagingException {
+	Namespaces ns = getNamespaces();
+	if (ns == null || ns.otherUsers == null)
+	    return super.getUserNamespaces(user);
+	return namespaceToFolders(ns.otherUsers, user);
+    }
+
+    /**
+     * Using the IMAP NAMESPACE command (RFC 2342), return a set
+     * of folders representing the Shared namespaces.
+     */
+    @Override
+    public Folder[] getSharedNamespaces() throws MessagingException {
+	Namespaces ns = getNamespaces();
+	if (ns == null || ns.shared == null)
+	    return super.getSharedNamespaces();
+	return namespaceToFolders(ns.shared, null);
+    }
+
+    private synchronized Namespaces getNamespaces() throws MessagingException {
+	checkConnected();
+
+        IMAPProtocol p = null;
+
+	if (namespaces == null) {
+	    try {
+                p = getStoreProtocol();
+		namespaces = p.namespace();
+	    } catch (BadCommandException bex) { 
+		// NAMESPACE not supported, ignore it
+	    } catch (ConnectionException cex) {
+		throw new StoreClosedException(this, cex.getMessage());
+	    } catch (ProtocolException pex) { 
+		throw new MessagingException(pex.getMessage(), pex);
+	    } finally {
+		releaseStoreProtocol(p);
+	    }
+	}
+	return namespaces;
+    }
+
+    private Folder[] namespaceToFolders(Namespaces.Namespace[] ns,
+					String user) {
+	Folder[] fa = new Folder[ns.length];
+	for (int i = 0; i < fa.length; i++) {
+	    String name = ns[i].prefix;
+	    if (user == null) {
+		// strip trailing delimiter
+		int len = name.length();
+		if ( len > 0 && name.charAt(len - 1) == ns[i].delimiter)
+		    name = name.substring(0, len - 1);
+	    } else {
+		// add user
+		name += user;
+	    }
+	    fa[i] = newIMAPFolder(name, ns[i].delimiter,
+					Boolean.valueOf(user == null));
+	}
+	return fa;
+    }
+
+    /**
+     * Get the quotas for the named quota root.
+     * Quotas are controlled on the basis of a quota root, not
+     * (necessarily) a folder.  The relationship between folders
+     * and quota roots depends on the IMAP server.  Some servers
+     * might implement a single quota root for all folders owned by
+     * a user.  Other servers might implement a separate quota root
+     * for each folder.  A single folder can even have multiple
+     * quota roots, perhaps controlling quotas for different
+     * resources.
+     *
+     * @param	root	the name of the quota root
+     * @return		array of Quota objects
+     * @exception MessagingException	if the server doesn't support the
+     *					QUOTA extension
+     */
+    @Override
+    public synchronized Quota[] getQuota(String root)
+				throws MessagingException {
+	checkConnected();
+	Quota[] qa = null;
+
+        IMAPProtocol p = null;
+	try {
+	    p = getStoreProtocol();
+	    qa = p.getQuotaRoot(root);
+	} catch (BadCommandException bex) {
+	    throw new MessagingException("QUOTA not supported", bex);
+	} catch (ConnectionException cex) {
+	    throw new StoreClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	} finally {
+	    releaseStoreProtocol(p);
+	}
+	return qa;
+    }
+
+    /**
+     * Set the quotas for the quota root specified in the quota argument.
+     * Typically this will be one of the quota roots obtained from the
+     * <code>getQuota</code> method, but it need not be.
+     *
+     * @param	quota	the quota to set
+     * @exception MessagingException	if the server doesn't support the
+     *					QUOTA extension
+     */
+    @Override
+    public synchronized void setQuota(Quota quota) throws MessagingException {
+	checkConnected();
+        IMAPProtocol p = null;
+	try {
+	    p = getStoreProtocol();
+	    p.setQuota(quota);
+	} catch (BadCommandException bex) {
+	    throw new MessagingException("QUOTA not supported", bex);
+	} catch (ConnectionException cex) {
+	    throw new StoreClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	} finally {
+	    releaseStoreProtocol(p);
+	}
+    }
+
+    private void checkConnected() {
+	assert Thread.holdsLock(this);
+	if (!super.isConnected())
+	    throw new IllegalStateException("Not connected");
+    }
+
+    /**
+     * Response handler method.
+     */
+    @Override
+    public void handleResponse(Response r) {
+	// Any of these responses may have a response code.
+	if (r.isOK() || r.isNO() || r.isBAD() || r.isBYE())
+	    handleResponseCode(r);
+	if (r.isBYE()) {
+	    logger.fine("IMAPStore connection dead");
+	    // Store's IMAP connection is dead, save the response so that
+	    // releaseStoreProtocol will cleanup later.
+	    synchronized (connectionFailedLock) {
+		connectionFailed = true;
+		if (r.isSynthetic())
+		    forceClose = true;
+	    }
+	    return;
+	}
+    }
+
+    /**
+     * Use the IMAP IDLE command (see
+     * <A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>),
+     * if supported by the server, to enter idle mode so that the server
+     * can send unsolicited notifications
+     * without the need for the client to constantly poll the server.
+     * Use a <code>ConnectionListener</code> to be notified of
+     * events.  When another thread (e.g., the listener thread)
+     * needs to issue an IMAP comand for this Store, the idle mode will
+     * be terminated and this method will return.  Typically the caller
+     * will invoke this method in a loop. <p>
+     *
+     * If the mail.imap.enableimapevents property is set, notifications
+     * received while the IDLE command is active will be delivered to
+     * <code>ConnectionListener</code>s as events with a type of
+     * <code>IMAPStore.RESPONSE</code>.  The event's message will be
+     * the raw IMAP response string.
+     * Note that most IMAP servers will not deliver any events when
+     * using the IDLE command on a connection with no mailbox selected
+     * (i.e., this method).  In most cases you'll want to use the
+     * <code>idle</code> method on <code>IMAPFolder</code>. <p>
+     *
+     * NOTE: This capability is highly experimental and likely will change
+     * in future releases. <p>
+     *
+     * The mail.imap.minidletime property enforces a minimum delay
+     * before returning from this method, to ensure that other threads
+     * have a chance to issue commands before the caller invokes this
+     * method again.  The default delay is 10 milliseconds.
+     *
+     * @exception MessagingException	if the server doesn't support the
+     *					IDLE extension
+     * @exception IllegalStateException	if the store isn't connected
+     *
+     * @since	JavaMail 1.4.1
+     */
+    public void idle() throws MessagingException {
+	IMAPProtocol p = null;
+	// ASSERT: Must NOT be called with the connection pool
+	// synchronization lock held.
+	assert !Thread.holdsLock(pool);
+	synchronized (this) {
+	    checkConnected();
+	}
+	boolean needNotification = false;
+	try {
+	    synchronized (pool) {
+		p = getStoreProtocol();
+		if (pool.idleState != ConnectionPool.RUNNING) {
+		    // some other thread must be running the IDLE
+		    // command, we'll just wait for it to finish
+		    // without aborting it ourselves
+		    try {
+			// give up lock and wait to be not idle
+			pool.wait();
+		    } catch (InterruptedException ex) {
+			// restore the interrupted state, which callers might
+			// depend on
+			Thread.currentThread().interrupt();
+			// stop waiting and return to caller
+			throw new MessagingException("idle interrupted", ex);
+		    }
+		    return;
+		}
+		p.idleStart();
+		needNotification = true;
+		pool.idleState = ConnectionPool.IDLE;
+		pool.idleProtocol = p;
+	    }
+
+	    /*
+	     * We gave up the pool lock so that other threads
+	     * can get into the pool far enough to see that we're
+	     * in IDLE and abort the IDLE.
+	     *
+	     * Now we read responses from the IDLE command, especially
+	     * including unsolicited notifications from the server.
+	     * We don't hold the pool lock while reading because
+	     * it protects the idleState and other threads need to be
+	     * able to examine the state.
+	     *
+	     * We hold the pool lock while processing the responses.
+	     */
+	    for (;;) {
+		Response r = p.readIdleResponse();
+		synchronized (pool) {
+		    if (r == null || !p.processIdleResponse(r)) {
+			pool.idleState = ConnectionPool.RUNNING;
+			pool.idleProtocol = null;
+			pool.notifyAll();
+			needNotification = false;
+			break;
+		    }
+		}
+		if (enableImapEvents && r.isUnTagged()) {
+		    notifyStoreListeners(IMAPStore.RESPONSE, r.toString());
+		}
+	    }
+
+	    /*
+	     * Enforce a minimum delay to give time to threads
+	     * processing the responses that came in while we
+	     * were idle.
+	     */
+	    int minidle = getMinIdleTime();
+	    if (minidle > 0) {
+		try {
+		    Thread.sleep(minidle);
+		} catch (InterruptedException ex) {
+		    // restore the interrupted state, which callers might
+		    // depend on
+		    Thread.currentThread().interrupt();
+		}
+	    }
+
+	} catch (BadCommandException bex) {
+	    throw new MessagingException("IDLE not supported", bex);
+	} catch (ConnectionException cex) {
+	    throw new StoreClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	} finally {
+	    if (needNotification) {
+		synchronized (pool) {
+		    pool.idleState = ConnectionPool.RUNNING;
+		    pool.idleProtocol = null;
+		    pool.notifyAll();
+		}
+	    }
+	    releaseStoreProtocol(p);
+	}
+    }
+
+    /*
+     * If an IDLE command is in progress, abort it if necessary,
+     * and wait until it completes.
+     * ASSERT: Must be called with the pool's lock held.
+     */
+    private void waitIfIdle() throws ProtocolException {
+	assert Thread.holdsLock(pool);
+	while (pool.idleState != ConnectionPool.RUNNING) {
+	    if (pool.idleState == ConnectionPool.IDLE) {
+		pool.idleProtocol.idleAbort();
+		pool.idleState = ConnectionPool.ABORTING;
+	    }
+	    try {
+		// give up lock and wait to be not idle
+		pool.wait();
+	    } catch (InterruptedException ex) {
+		// If someone is trying to interrupt us we can't keep going
+		// around the loop waiting for IDLE to complete, but we can't
+		// just return because callers expect the idleState to be
+		// RUNNING when we return.  Throwing this exception seems
+		// like the best choice.
+		throw new ProtocolException("Interrupted waitIfIdle", ex);
+	    }
+	}
+    }
+
+    /**
+     * Send the IMAP ID command (if supported by the server) and return
+     * the result from the server.  The ID command identfies the client
+     * to the server and returns information about the server to the client.
+     * See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
+     * The returned Map is unmodifiable.
+     *
+     * @param	clientParams	a Map of keys and values identifying the client
+     * @return			a Map of keys and values identifying the server
+     * @exception MessagingException	if the server doesn't support the
+     *					ID extension
+     * @since	JavaMail 1.5.1
+     */
+    public synchronized Map<String, String> id(Map<String, String> clientParams)
+				throws MessagingException {
+	checkConnected();
+	Map<String, String> serverParams = null;
+
+        IMAPProtocol p = null;
+	try {
+	    p = getStoreProtocol();
+	    serverParams = p.id(clientParams);
+	} catch (BadCommandException bex) {
+	    throw new MessagingException("ID not supported", bex);
+	} catch (ConnectionException cex) {
+	    throw new StoreClosedException(this, cex.getMessage());
+	} catch (ProtocolException pex) {
+	    throw new MessagingException(pex.getMessage(), pex);
+	} finally {
+	    releaseStoreProtocol(p);
+	}
+	return serverParams;
+    }
+
+    /**
+     * Handle notifications and alerts.
+     * Response must be an OK, NO, BAD, or BYE response.
+     */
+    void handleResponseCode(Response r) {
+	if (enableResponseEvents)
+	    notifyStoreListeners(IMAPStore.RESPONSE, r.toString());
+	String s = r.getRest();	// get the text after the response
+	boolean isAlert = false;
+	if (s.startsWith("[")) {	// a response code
+	    int i = s.indexOf(']');
+	    // remember if it's an alert
+	    if (i > 0 && s.substring(0, i + 1).equalsIgnoreCase("[ALERT]"))
+		isAlert = true;
+	    // strip off the response code in any event
+	    s = s.substring(i + 1).trim();
+	}
+	if (isAlert)
+	    notifyStoreListeners(StoreEvent.ALERT, s);
+	else if (r.isUnTagged() && s.length() > 0)
+	    // Only send notifications that come with untagged
+	    // responses, and only if there is actually some
+	    // text there.
+	    notifyStoreListeners(StoreEvent.NOTICE, s);
+    }
+
+    private String traceUser(String user) {
+	return debugusername ? user : "<user name suppressed>";
+    }
+
+    private String tracePassword(String password) {
+	return debugpassword ? password :
+				(password == null ? "<null>" : "<non-null>");
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/IdleManager.java b/mail/src/main/java/com/sun/mail/imap/IdleManager.java
new file mode 100644
index 0000000..781f58d
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/IdleManager.java
@@ -0,0 +1,491 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.Socket;
+import java.nio.*;
+import java.nio.channels.*;
+import java.util.*;
+import java.util.logging.*;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.Executor;
+
+import javax.mail.*;
+
+import com.sun.mail.imap.protocol.IMAPProtocol;
+import com.sun.mail.util.MailLogger;
+
+/**
+ * IdleManager uses the optional IMAP IDLE command
+ * (<A HREF="http://www.ietf.org/rfc/rfc2177.txt">RFC 2177</A>)
+ * to watch multiple folders for new messages.
+ * IdleManager uses an Executor to execute tasks in separate threads.
+ * An Executor is typically provided by an ExecutorService.
+ * For example, for a Java SE application:
+ * <blockquote><pre>
+ *	ExecutorService es = Executors.newCachedThreadPool();
+ *	final IdleManager idleManager = new IdleManager(session, es);
+ * </pre></blockquote>
+ * For a Java EE 7 application:
+ * <blockquote><pre>
+ *	{@literal @}Resource
+ *	ManagedExecutorService es;
+ *	final IdleManager idleManager = new IdleManager(session, es);
+ * </pre></blockquote>
+ * To watch for new messages in a folder, open the folder, register a listener,
+ * and ask the IdleManager to watch the folder:
+ * <blockquote><pre>
+ *	Folder folder = store.getFolder("INBOX");
+ *	folder.open(Folder.READ_WRITE);
+ *	folder.addMessageCountListener(new MessageCountAdapter() {
+ *	    public void messagesAdded(MessageCountEvent ev) {
+ *		Folder folder = (Folder)ev.getSource();
+ *		Message[] msgs = ev.getMessages();
+ *		System.out.println("Folder: " + folder +
+ *		    " got " + msgs.length + " new messages");
+ *		try {
+ *		    // process new messages
+ *		    idleManager.watch(folder); // keep watching for new messages
+ *		} catch (MessagingException mex) {
+ *		    // handle exception related to the Folder
+ *		}
+ *	    }
+ *	});
+ *	idleManager.watch(folder);
+ * </pre></blockquote>
+ * This delivers the events for each folder in a separate thread, <b>NOT</b>
+ * using the Executor.  To deliver all events in a single thread
+ * using the Executor, set the following properties for the Session
+ * (once), and then add listeners and watch the folder as above.
+ * <blockquote><pre>
+ *	// the following should be done once...
+ *	Properties props = session.getProperties();
+ *	props.put("mail.event.scope", "session"); // or "application"
+ *	props.put("mail.event.executor", es);
+ * </pre></blockquote>
+ * Note that, after processing new messages in your listener, or doing any
+ * other operations on the folder in any other thread, you need to tell
+ * the IdleManager to watch for more new messages.  Unless, of course, you
+ * close the folder.
+ * <p>
+ * The IdleManager is created with a Session, which it uses only to control
+ * debug output.  A single IdleManager instance can watch multiple Folders
+ * from multiple Stores and multiple Sessions.
+ * <p>
+ * Due to limitations in the Java SE nio support, a
+ * {@link java.nio.channels.SocketChannel SocketChannel} must be used instead
+ * of a {@link java.net.Socket Socket} to connect to the server.  However,
+ * SocketChannels don't support all the features of Sockets, such as connecting
+ * through a SOCKS proxy server.  SocketChannels also don't support
+ * simultaneous read and write, which means that the
+ * {@link com.sun.mail.imap.IMAPFolder#idle idle} method can't be used if
+ * SocketChannels are being used; use this IdleManager instead.
+ * To enable support for SocketChannels instead of Sockets, set the
+ * <code>mail.imap.usesocketchannels</code> property in the Session used to
+ * access the IMAP Folder.  (Or <code>mail.imaps.usesocketchannels</code> if
+ * you're using the "imaps" protocol.)  This will effect all connections in
+ * that Session, but you can create another Session without this property set
+ * if you need to use the features that are incompatible with SocketChannels.
+ * <p>
+ * NOTE: The IdleManager, and all APIs and properties related to it, should
+ * be considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
+ * future in ways that are incompatible with applications using the
+ * current APIs.
+ *
+ * @since JavaMail 1.5.2
+ */
+public class IdleManager {
+    private Executor es;
+    private Selector selector;
+    private MailLogger logger;
+    private volatile boolean die = false;
+    private volatile boolean running;
+    private Queue<IMAPFolder> toWatch = new ConcurrentLinkedQueue<>();
+    private Queue<IMAPFolder> toAbort = new ConcurrentLinkedQueue<>();
+
+    /**
+     * Create an IdleManager.  The Session is used only to configure
+     * debugging output.  The Executor is used to create the
+     * "select" thread.
+     *
+     * @param	session	the Session containing configuration information
+     * @param	es	the Executor used to create threads
+     * @exception	IOException	for Selector failures
+     */
+    public IdleManager(Session session, Executor es) throws IOException {
+	this.es = es;
+	logger = new MailLogger(this.getClass(), "DEBUG IMAP",
+				session.getDebug(), session.getDebugOut());
+	selector = Selector.open();
+	es.execute(new Runnable() {
+	    @Override
+	    public void run() {
+		logger.fine("IdleManager select starting");
+		try {
+		    running = true;
+		    select();
+		} finally {
+		    running = false;
+		    logger.fine("IdleManager select terminating");
+		}
+	    }
+	});
+    }
+
+    /**
+     * Is the IdleManager currently running?  The IdleManager starts
+     * running when the Executor schedules its task.  The IdleManager
+     * stops running after its task detects the stop request from the
+     * {@link #stop stop} method, or if it terminates abnormally due
+     * to an unexpected error.
+     *
+     * @return	true if the IdleMaanger is running
+     * @since JavaMail 1.5.5
+     */
+    public boolean isRunning() {
+	return running;
+    }
+
+    /**
+     * Watch the Folder for new messages and other events using the IMAP IDLE
+     * command.
+     *
+     * @param	folder	the folder to watch
+     * @exception	MessagingException	for errors related to the folder
+     */
+    public void watch(Folder folder)
+				throws MessagingException {
+	if (die)	// XXX - should be IllegalStateException?
+	    throw new MessagingException("IdleManager is not running");
+	if (!(folder instanceof IMAPFolder))
+	    throw new MessagingException("Can only watch IMAP folders");
+	IMAPFolder ifolder = (IMAPFolder)folder;
+	SocketChannel sc = ifolder.getChannel();
+	if (sc == null) {
+	    if (folder.isOpen())
+		throw new MessagingException(
+					"Folder is not using SocketChannels");
+	    else
+		throw new MessagingException("Folder is not open");
+	}
+	if (logger.isLoggable(Level.FINEST))
+	    logger.log(Level.FINEST, "IdleManager watching {0}",
+							folderName(ifolder));
+	// keep trying to start the IDLE command until we're successful.
+	// may block if we're in the middle of aborting an IDLE command.
+	int tries = 0;
+	while (!ifolder.startIdle(this)) {
+	    if (logger.isLoggable(Level.FINEST))
+		logger.log(Level.FINEST,
+			    "IdleManager.watch startIdle failed for {0}",
+			    folderName(ifolder));
+	    tries++;
+	}
+	if (logger.isLoggable(Level.FINEST)) {
+	    if (tries > 0)
+		logger.log(Level.FINEST,
+			"IdleManager.watch startIdle succeeded for {0}" +
+			" after " + tries + " tries",
+			folderName(ifolder));
+	    else
+		logger.log(Level.FINEST,
+			"IdleManager.watch startIdle succeeded for {0}",
+			folderName(ifolder));
+	}
+	synchronized (this) {
+	    toWatch.add(ifolder);
+	    selector.wakeup();
+	}
+    }
+
+    /**
+     * Request that the specified folder abort an IDLE command.
+     * We can't do the abort directly because the DONE message needs
+     * to be sent through the (potentially) SSL socket, which means
+     * we need to be in blocking I/O mode.  We can only switch to
+     * blocking I/O mode when not selecting, so wake up the selector,
+     * which will process this request when it wakes up.
+     */
+    void requestAbort(IMAPFolder folder) {
+	toAbort.add(folder);
+	selector.wakeup();
+    }
+
+    /**
+     * Run the {@link java.nio.channels.Selector#select select} loop
+     * to poll each watched folder for events sent from the server.
+     */
+    private void select() {
+	die = false;
+	try {
+	    while (!die) {
+		watchAll();
+		logger.finest("IdleManager waiting...");
+		int ns = selector.select();
+		if (logger.isLoggable(Level.FINEST))
+		    logger.log(Level.FINEST,
+			"IdleManager selected {0} channels", ns);
+		if (die || Thread.currentThread().isInterrupted())
+		    break;
+
+		/*
+		 * Process any selected folders.  We cancel the
+		 * selection key for any selected folder, so if we
+		 * need to continue watching that folder it's added
+		 * to the toWatch list again.  We can't actually
+		 * register that folder again until the previous
+		 * selection key is cancelled, so we call selectNow()
+		 * just for the side effect of cancelling the selection
+		 * keys.  But if selectNow() selects something, we
+		 * process it before adding folders from the toWatch
+		 * queue.  And so on until there is nothing to do, at
+		 * which point it's safe to register folders from the
+		 * toWatch queue.  This should be "fair" since each
+		 * selection key is used only once before being added
+		 * to the toWatch list.
+		 */
+		do {
+		    processKeys();
+		} while (selector.selectNow() > 0 || !toAbort.isEmpty());
+	    }
+	} catch (InterruptedIOException ex) {
+	    logger.log(Level.FINEST, "IdleManager interrupted", ex);
+	} catch (IOException ex) {
+	    logger.log(Level.FINEST, "IdleManager got I/O exception", ex);
+	} catch (Exception ex) {
+	    logger.log(Level.FINEST, "IdleManager got exception", ex);
+	} finally {
+	    die = true;	// prevent new watches in case of exception
+	    logger.finest("IdleManager unwatchAll");
+	    try {
+		unwatchAll();
+		selector.close();
+	    } catch (IOException ex2) {
+		// nothing to do...
+		logger.log(Level.FINEST, "IdleManager unwatch exception", ex2);
+	    }
+	    logger.fine("IdleManager exiting");
+	}
+    }
+
+    /**
+     * Register all of the folders in the queue with the selector,
+     * switching them to nonblocking I/O mode first.
+     */
+    private void watchAll() {
+	/*
+	 * Pull each of the folders from the toWatch queue
+	 * and register it.
+	 */
+	IMAPFolder folder;
+	while ((folder = toWatch.poll()) != null) {
+	    if (logger.isLoggable(Level.FINEST))
+		logger.log(Level.FINEST,
+		    "IdleManager adding {0} to selector", folderName(folder));
+	    try {
+		SocketChannel sc = folder.getChannel();
+		if (sc == null)
+		    continue;
+		// has to be non-blocking to select
+		sc.configureBlocking(false);
+		sc.register(selector, SelectionKey.OP_READ, folder);
+	    } catch (IOException ex) {
+		// oh well, nothing to do
+		logger.log(Level.FINEST,
+		    "IdleManager can't register folder", ex);
+	    } catch (CancelledKeyException ex) {
+		// this should never happen
+		logger.log(Level.FINEST,
+		    "IdleManager can't register folder", ex);
+	    }
+	}
+    }
+
+    /**
+     * Process the selected keys.
+     */
+    private void processKeys() throws IOException {
+	IMAPFolder folder;
+
+	/*
+	 * First, process any channels with data to read.
+	 */
+	Set<SelectionKey> selectedKeys = selector.selectedKeys();
+	/*
+	 * XXX - this is simpler, but it can fail with
+	 *	 ConcurrentModificationException
+	 *
+	for (SelectionKey sk : selectedKeys) {
+	    selectedKeys.remove(sk);	// only process each key once
+	    ...
+	}
+	*/
+	Iterator<SelectionKey> it = selectedKeys.iterator();
+	while (it.hasNext()) {
+	    SelectionKey sk = it.next();
+	    it.remove();	// only process each key once
+	    // have to cancel so we can switch back to blocking I/O mode
+	    sk.cancel();
+	    folder = (IMAPFolder)sk.attachment();
+	    if (logger.isLoggable(Level.FINEST))
+		logger.log(Level.FINEST,
+		    "IdleManager selected folder: {0}", folderName(folder));
+	    SelectableChannel sc = sk.channel();
+	    // switch back to blocking to allow normal I/O
+	    sc.configureBlocking(true);
+	    try {
+		if (folder.handleIdle(false)) {
+		    if (logger.isLoggable(Level.FINEST))
+			logger.log(Level.FINEST,
+			    "IdleManager continue watching folder {0}",
+							folderName(folder));
+		    // more to do with this folder, select on it again
+		    toWatch.add(folder);
+		} else {
+		    // done watching this folder,
+		    if (logger.isLoggable(Level.FINEST))
+			logger.log(Level.FINEST,
+			    "IdleManager done watching folder {0}",
+							folderName(folder));
+		}
+	    } catch (MessagingException ex) {
+		// something went wrong, stop watching this folder
+		logger.log(Level.FINEST,
+		    "IdleManager got exception for folder: " +
+						    folderName(folder), ex);
+	    }
+	}
+
+	/*
+	 * Now, process any folders that we need to abort.
+	 */
+	while ((folder = toAbort.poll()) != null) {
+	    if (logger.isLoggable(Level.FINEST))
+		logger.log(Level.FINEST,
+		    "IdleManager aborting IDLE for folder: {0}",
+							folderName(folder));
+	    SocketChannel sc = folder.getChannel();
+	    if (sc == null)
+		continue;
+	    SelectionKey sk = sc.keyFor(selector);
+	    // have to cancel so we can switch back to blocking I/O mode
+	    if (sk != null)
+		sk.cancel();
+	    // switch back to blocking to allow normal I/O
+	    sc.configureBlocking(true);
+
+	    // if there's a read timeout, have to do the abort in a new thread
+	    Socket sock = sc.socket();
+	    if (sock != null && sock.getSoTimeout() > 0) {
+		logger.finest("IdleManager requesting DONE with timeout");
+		toWatch.remove(folder);
+		final IMAPFolder folder0 = folder;
+		es.execute(new Runnable() {
+		    @Override
+		    public void run() {
+			// send the DONE and wait for the response
+			folder0.idleAbortWait();
+		    }
+		});
+	    } else {
+		folder.idleAbort();	// send the DONE message
+		// watch for OK response to DONE
+		// XXX - what if we also added it above?  should be a nop
+		toWatch.add(folder);
+	    }
+	}
+    }
+
+    /**
+     * Stop watching all folders.  Cancel any selection keys and,
+     * most importantly, switch the channel back to blocking mode.
+     * If there's any folders waiting to be watched, need to abort
+     * them too.
+     */
+    private void unwatchAll() {
+	IMAPFolder folder;
+	Set<SelectionKey> keys = selector.keys();
+	for (SelectionKey sk : keys) {
+	    // have to cancel so we can switch back to blocking I/O mode
+	    sk.cancel();
+	    folder = (IMAPFolder)sk.attachment();
+	    if (logger.isLoggable(Level.FINEST))
+		logger.log(Level.FINEST,
+		    "IdleManager no longer watching folder: {0}",
+							folderName(folder));
+	    SelectableChannel sc = sk.channel();
+	    // switch back to blocking to allow normal I/O
+	    try {
+		sc.configureBlocking(true);
+		folder.idleAbortWait();	// send the DONE message and wait
+	    } catch (IOException ex) {
+		// ignore it, channel might be closed
+		logger.log(Level.FINEST,
+		    "IdleManager exception while aborting idle for folder: " +
+						    folderName(folder), ex);
+	    }
+	}
+
+	/*
+	 * Finally, process any folders waiting to be watched.
+	 */
+	while ((folder = toWatch.poll()) != null) {
+	    if (logger.isLoggable(Level.FINEST))
+		logger.log(Level.FINEST,
+		    "IdleManager aborting IDLE for unwatched folder: {0}",
+							folderName(folder));
+	    SocketChannel sc = folder.getChannel();
+	    if (sc == null)
+		continue;
+	    try {
+		// channel should still be in blocking mode, but make sure
+		sc.configureBlocking(true);
+		folder.idleAbortWait();	// send the DONE message and wait
+	    } catch (IOException ex) {
+		// ignore it, channel might be closed
+		logger.log(Level.FINEST,
+		    "IdleManager exception while aborting idle for folder: " +
+						    folderName(folder), ex);
+	    }
+	}
+    }
+
+    /**
+     * Stop the IdleManager.  The IdleManager can not be restarted.
+     */
+    public synchronized void stop() {
+	die = true;
+	logger.fine("IdleManager stopping");
+	selector.wakeup();
+    }
+
+    /**
+     * Return the fully qualified name of the folder, for use in log messages.
+     * Essentially just the getURLName method, but ignoring the
+     * MessagingException that can never happen.
+     */
+    private static String folderName(Folder folder) {
+	try {
+	    return folder.getURLName().toString();
+	} catch (MessagingException mex) {
+	    // can't happen
+	    return folder.getStore().toString() + "/" + folder.toString();
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/MessageCache.java b/mail/src/main/java/com/sun/mail/imap/MessageCache.java
new file mode 100644
index 0000000..86c7899
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/MessageCache.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.PrintStream;
+import java.util.*;
+import java.util.logging.Level;
+
+import javax.mail.*;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+
+/**
+ * A cache of IMAPMessage objects along with the
+ * mapping from message number to IMAP sequence number.
+ *
+ * All operations on this object are protected by the messageCacheLock
+ * in IMAPFolder.
+ */
+public class MessageCache {
+    /*
+     * The array of IMAPMessage objects.  Elements of the array might
+     * be null if no one has asked for the message.  The array expands
+     * as needed and might be larger than the number of messages in the
+     * folder.  The "size" field indicates the number of entries that
+     * are valid.
+     */
+    private IMAPMessage[] messages;
+
+    /*
+     * A parallel array of sequence numbers for each message.  If the
+     * array pointer is null, the sequence number of a message is just
+     * its message number.  This is the common case, until a message is
+     * expunged.
+     */
+    private int[] seqnums;
+
+    /*
+     * The amount of the messages (and seqnum) array that is valid.
+     * Might be less than the actual size of the array.
+     */
+    private int size;
+
+    /**
+     * The folder these messages belong to.
+     */
+    private IMAPFolder folder;
+
+    // debugging logger
+    private MailLogger logger;
+
+    /**
+     * Grow the array by at least this much, to avoid constantly
+     * reallocating the array.
+     */
+    private static final int SLOP = 64;
+
+    /**
+     * Construct a new message cache of the indicated size.
+     */
+    MessageCache(IMAPFolder folder, IMAPStore store, int size) {
+	this.folder = folder;
+	logger = folder.logger.getSubLogger("messagecache", "DEBUG IMAP MC",
+						store.getMessageCacheDebug());
+	if (logger.isLoggable(Level.CONFIG))
+	    logger.config("create cache of size " + size);
+	ensureCapacity(size, 1);
+    }
+
+    /**
+     * Constructor for debugging and testing.
+     */
+    MessageCache(int size, boolean debug) {
+	this.folder = null;
+	logger = new MailLogger(
+		    this.getClass(), "messagecache",
+		    "DEBUG IMAP MC", debug, System.out);
+	if (logger.isLoggable(Level.CONFIG))
+	    logger.config("create DEBUG cache of size " + size);
+	ensureCapacity(size, 1);
+    }
+
+    /**
+     * Size of cache.
+     *
+     * @return	the size of the cache
+     */
+    public int size() {
+	return size;
+    }
+
+    /**
+     * Get the message object for the indicated message number.
+     * If the message object hasn't been created, create it.
+     *
+     * @param	msgnum	the message number
+     * @return		the message
+     */
+    public IMAPMessage getMessage(int msgnum) {
+	// check range
+	if (msgnum < 1 || msgnum > size)
+	    throw new ArrayIndexOutOfBoundsException(
+		"message number (" + msgnum + ") out of bounds (" + size + ")");
+	IMAPMessage msg = messages[msgnum-1];
+	if (msg == null) {
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("create message number " + msgnum);
+	    msg = folder.newIMAPMessage(msgnum);
+	    messages[msgnum-1] = msg;
+	    // mark message expunged if no seqnum
+	    if (seqnumOf(msgnum) <= 0) {
+		logger.fine("it's expunged!");
+		msg.setExpunged(true);
+	    }
+	}
+	return msg;
+    }
+
+    /**
+     * Get the message object for the indicated sequence number.
+     * If the message object hasn't been created, create it.
+     * Return null if there's no message with that sequence number.
+     *
+     * @param	seqnum	the sequence number of the message
+     * @return		the message
+     */
+    public IMAPMessage getMessageBySeqnum(int seqnum) {
+	int msgnum = msgnumOf(seqnum);
+	if (msgnum < 0) {		// XXX - < 1 ?
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("no message seqnum " + seqnum);
+	    return null;
+	} else
+	    return getMessage(msgnum);
+    }
+
+    /**
+     * Expunge the message with the given sequence number.
+     *
+     * @param	seqnum	the sequence number of the message to expunge
+     */
+    public void expungeMessage(int seqnum) {
+	int msgnum = msgnumOf(seqnum);
+	if (msgnum < 0) {
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("expunge no seqnum " + seqnum);
+	    return;		// XXX - should never happen
+	}
+	IMAPMessage msg = messages[msgnum-1];
+	if (msg != null) {
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("expunge existing " + msgnum);
+	    msg.setExpunged(true);
+	}
+	if (seqnums == null) {		// time to fill it in
+	    logger.fine("create seqnums array");
+	    seqnums = new int[messages.length];
+	    for (int i = 1; i < msgnum; i++)
+		seqnums[i-1] = i;
+	    seqnums[msgnum - 1] = 0;
+	    for (int i = msgnum + 1; i <= seqnums.length; i++)
+		seqnums[i-1] = i - 1;
+	} else {
+	    seqnums[msgnum - 1] = 0;
+	    for (int i = msgnum + 1; i <= seqnums.length; i++) {
+		assert seqnums[i-1] != 1;
+		if (seqnums[i-1] > 0)
+		    seqnums[i-1]--;
+	    }
+	}
+    }
+
+    /**
+     * Remove all the expunged messages from the array,
+     * returning a list of removed message objects.
+     *
+     * @return	the removed messages
+     */
+    public IMAPMessage[] removeExpungedMessages() {
+	logger.fine("remove expunged messages");
+	// list of expunged messages
+	List<IMAPMessage> mlist = new ArrayList<>();
+
+	/*
+	 * Walk through the array compressing it by copying
+	 * higher numbered messages further down in the array,
+	 * effectively removing expunged messages from the array.
+	 * oldnum is the index we use to walk through the array.
+	 * newnum is the index where we copy the next valid message.
+	 * oldnum == newnum until we encounter an expunged message.
+	 */
+	int oldnum = 1;
+	int newnum = 1;
+	while (oldnum <= size) {
+	    // is message expunged?
+	    if (seqnumOf(oldnum) <= 0) {
+		IMAPMessage m = getMessage(oldnum);
+		mlist.add(m);
+	    } else {
+		// keep this message
+		if (newnum != oldnum) {
+		    // move message down in the array (compact array)
+		    messages[newnum-1] = messages[oldnum-1];
+		    if (messages[newnum-1] != null)
+			messages[newnum-1].setMessageNumber(newnum);
+		}
+		newnum++;
+	    }
+	    oldnum++;
+	}
+	seqnums = null;
+	shrink(newnum, oldnum);
+
+	IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("return " + rmsgs.length);
+	mlist.toArray(rmsgs);
+	return rmsgs;
+    }
+
+    /**
+     * Remove expunged messages in msgs from the array,
+     * returning a list of removed message objects.
+     * All messages in msgs must be IMAPMessage objects
+     * from this folder.
+     *
+     * @param	msgs	the messages
+     * @return		the removed messages
+     */
+    public IMAPMessage[] removeExpungedMessages(Message[] msgs) {
+	logger.fine("remove expunged messages");
+	// list of expunged messages
+	List<IMAPMessage> mlist = new ArrayList<>();
+
+	/*
+	 * Copy the message numbers of the expunged messages into
+	 * a separate array and sort the array to make it easier to
+	 * process later.
+	 */
+	int[] mnum = new int[msgs.length];
+	for (int i = 0; i < msgs.length; i++)
+	    mnum[i] = msgs[i].getMessageNumber();
+	Arrays.sort(mnum);
+
+	/*
+	 * Walk through the array compressing it by copying
+	 * higher numbered messages further down in the array,
+	 * effectively removing expunged messages from the array.
+	 * oldnum is the index we use to walk through the array.
+	 * newnum is the index where we copy the next valid message.
+	 * oldnum == newnum until we encounter an expunged message.
+	 *
+	 * Even though we know the message number of the first possibly
+	 * expunged message, we still start scanning at message number 1
+	 * so that we can check whether there's any message whose
+	 * sequence number is different than its message number.  If there
+	 * is, we can't throw away the seqnums array when we're done.
+	 */
+	int oldnum = 1;
+	int newnum = 1;
+	int mnumi = 0;		// index into mnum
+	boolean keepSeqnums = false;
+	while (oldnum <= size) {
+	    /*
+	     * Are there still expunged messsages in msgs to consider,
+	     * and is the message we're considering the next one in the
+	     * list, and is it expunged?
+	     */
+	    if (mnumi < mnum.length &&
+		    oldnum == mnum[mnumi] &&
+		    seqnumOf(oldnum) <= 0) {
+		IMAPMessage m = getMessage(oldnum);
+		mlist.add(m);
+		/*
+		 * Just in case there are duplicate entries in the msgs array,
+		 * we keep advancing mnumi past any duplicates, but of course
+		 * stop when we get to the end of the array.
+		 */
+		while (mnumi < mnum.length && mnum[mnumi] <= oldnum)
+		    mnumi++;	// consider next message in array
+	    } else {
+		// keep this message
+		if (newnum != oldnum) {
+		    // move message down in the array (compact array)
+		    messages[newnum-1] = messages[oldnum-1];
+		    if (messages[newnum-1] != null)
+			messages[newnum-1].setMessageNumber(newnum);
+		    if (seqnums != null)
+			seqnums[newnum-1] = seqnums[oldnum-1];
+		}
+		if (seqnums != null && seqnums[newnum-1] != newnum)
+		    keepSeqnums = true;
+		newnum++;
+	    }
+	    oldnum++;
+	}
+
+	if (!keepSeqnums)
+	    seqnums = null;
+	shrink(newnum, oldnum);
+
+	IMAPMessage[] rmsgs = new IMAPMessage[mlist.size()];
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("return " + rmsgs.length);
+	mlist.toArray(rmsgs);
+	return rmsgs;
+    }
+
+    /**
+     * Shrink the messages and seqnums arrays.  newend is one past last
+     * valid element.  oldend is one past the previous last valid element.
+     */
+    private void shrink(int newend, int oldend) {
+	size = newend - 1;
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("size now " + size);
+	if (size == 0) {	// no messages left
+	    messages = null;
+	    seqnums = null;
+	} else if (size > SLOP && size < messages.length / 2) {
+	    // if array shrinks by too much, reallocate it
+	    logger.fine("reallocate array");
+	    IMAPMessage[] newm = new IMAPMessage[size + SLOP];
+	    System.arraycopy(messages, 0, newm, 0, size);
+	    messages = newm;
+	    if (seqnums != null) {
+		int[] news = new int[size + SLOP];
+		System.arraycopy(seqnums, 0, news, 0, size);
+		seqnums = news;
+	    }
+	} else {
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("clean " + newend + " to " + oldend);
+	    // clear out unused entries in array
+	    for (int msgnum = newend; msgnum < oldend; msgnum++) {
+		messages[msgnum-1] = null;
+		if (seqnums != null)
+		    seqnums[msgnum-1] = 0;
+	    }
+	}
+    }
+
+    /**
+     * Add count messages to the cache.
+     * newSeqNum is the sequence number of the first message added.
+     *
+     * @param	count	the number of messges
+     * @param	newSeqNum	sequence number of first message
+     */
+    public void addMessages(int count, int newSeqNum) {
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("add " + count + " messages");
+	// don't have to do anything other than making sure there's space
+	ensureCapacity(size + count, newSeqNum);
+    }
+
+    /*
+     * Make sure the arrays are at least big enough to hold
+     * "newsize" messages.
+     */
+    private void ensureCapacity(int newsize, int newSeqNum) {
+	if (messages == null)
+	    messages = new IMAPMessage[newsize + SLOP];
+	else if (messages.length < newsize) {
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("expand capacity to " + newsize);
+	    IMAPMessage[] newm = new IMAPMessage[newsize + SLOP];
+	    System.arraycopy(messages, 0, newm, 0, messages.length);
+	    messages = newm;
+	    if (seqnums != null) {
+		int[] news = new int[newsize + SLOP];
+		System.arraycopy(seqnums, 0, news, 0, seqnums.length);
+		for (int i = size; i < news.length; i++)
+		    news[i] = newSeqNum++;
+		seqnums = news;
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("message " + newsize +
+			" has sequence number " + seqnums[newsize-1]);
+	    }
+	} else if (newsize < size) {		// shrinking?
+	    // this should never happen
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("shrink capacity to " + newsize);
+	    for (int msgnum = newsize + 1; msgnum <= size; msgnum++) {
+		messages[msgnum-1] = null;
+		if (seqnums != null)
+		    seqnums[msgnum-1] = -1;
+	    }
+	}
+	size = newsize;
+    }
+
+    /**
+     * Return the sequence number for the given message number.
+     *
+     * @param	msgnum	the message number
+     * @return		the sequence number
+     */
+    public int seqnumOf(int msgnum) {
+	if (seqnums == null)
+	    return msgnum;
+	else {
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("msgnum " + msgnum + " is seqnum " +
+			    seqnums[msgnum-1]);
+	    return seqnums[msgnum-1];
+	}
+    }
+
+    /**
+     * Return the message number for the given sequence number.
+     */
+    private int msgnumOf(int seqnum) {
+	if (seqnums == null)
+	    return seqnum;
+	if (seqnum < 1) {		// should never happen
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("bad seqnum " + seqnum);
+	    return -1;
+	}
+	for (int msgnum = seqnum; msgnum <= size; msgnum++) {
+	    if (seqnums[msgnum-1] == seqnum)
+		return msgnum;
+	    if (seqnums[msgnum-1] > seqnum)
+		break;		// message doesn't exist
+	}
+	return -1;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/MessageVanishedEvent.java b/mail/src/main/java/com/sun/mail/imap/MessageVanishedEvent.java
new file mode 100644
index 0000000..056cabc
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/MessageVanishedEvent.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.event.MessageCountEvent;
+
+/**
+ * This class provides notification of messages that have been removed
+ * since the folder was last synchronized.
+ *
+ * @since	JavaMail 1.5.1
+ * @author	Bill Shannon
+ */
+
+public class MessageVanishedEvent extends MessageCountEvent {
+
+    /**
+     * The message UIDs.
+     */
+    private long[] uids;
+
+    // a reusable empty array
+    private static final Message[] noMessages = { };
+
+    private static final long serialVersionUID = 2142028010250024922L;
+
+    /**
+     * Constructor.
+     *
+     * @param folder  	the containing folder
+     * @param uids	the UIDs for the vanished messages
+     */
+    public MessageVanishedEvent(Folder folder, long[] uids) {
+	super(folder, REMOVED, true, noMessages);
+	this.uids = uids;
+    }
+
+    /**
+     * Return the UIDs for this event.
+     *
+     * @return  the UIDs
+     */
+    public long[] getUIDs() {
+	return uids;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/ModifiedSinceTerm.java b/mail/src/main/java/com/sun/mail/imap/ModifiedSinceTerm.java
new file mode 100644
index 0000000..7332266
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/ModifiedSinceTerm.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.Message;
+import javax.mail.search.SearchTerm;
+
+/**
+ * Find messages that have been modified since a given MODSEQ value.
+ * Relies on the server implementing the CONDSTORE extension
+ * (<A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>).
+ *
+ * @since	JavaMail 1.5.1
+ * @author	Bill Shannon
+ */
+public final class ModifiedSinceTerm extends SearchTerm {
+
+    private long modseq;
+
+    private static final long serialVersionUID = 5151457469634727992L;
+
+    /**
+     * Constructor.
+     *
+     * @param modseq	modification sequence number
+     */
+    public ModifiedSinceTerm(long modseq) {
+	this.modseq = modseq;
+    }
+
+    /**
+     * Return the modseq.
+     *
+     * @return	the modseq
+     */
+    public long getModSeq() {
+	return modseq;
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the date comparator is applied to this Message's
+     *			MODSEQ
+     * @return		true if the comparison succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	long m;
+
+	try {
+	    if (msg instanceof IMAPMessage)
+		m = ((IMAPMessage)msg).getModSeq();
+	    else
+		return false;
+	} catch (Exception e) {
+	    return false;
+	}
+
+	return m >= modseq;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof ModifiedSinceTerm))
+	    return false;
+	return modseq == ((ModifiedSinceTerm)obj).modseq;
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return (int)modseq;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/OlderTerm.java b/mail/src/main/java/com/sun/mail/imap/OlderTerm.java
new file mode 100644
index 0000000..19857c4
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/OlderTerm.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Date;
+import javax.mail.Message;
+import javax.mail.search.SearchTerm;
+
+/**
+ * Find messages that are older than a given interval (in seconds).
+ * Relies on the server implementing the WITHIN search extension
+ * (<A HREF="http://www.ietf.org/rfc/rfc5032.txt">RFC 5032</A>).
+ *
+ * @since	JavaMail 1.5.1
+ * @author	Bill Shannon
+ */
+public final class OlderTerm extends SearchTerm {
+
+    private int interval;
+
+    private static final long serialVersionUID = 3951078948727995682L;
+
+    /**
+     * Constructor.
+     *
+     * @param interval	number of seconds older
+     */
+    public OlderTerm(int interval) {
+	this.interval = interval;
+    }
+
+    /**
+     * Return the interval.
+     *
+     * @return	the interval
+     */
+    public int getInterval() {
+	return interval;
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the date comparator is applied to this Message's
+     *			received date
+     * @return		true if the comparison succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	Date d;
+
+	try {
+	    d = msg.getReceivedDate();
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (d == null)
+	    return false;
+
+	return d.getTime() <=
+		    System.currentTimeMillis() - ((long)interval * 1000);
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof OlderTerm))
+	    return false;
+	return interval == ((OlderTerm)obj).interval;
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return interval;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/ReferralException.java b/mail/src/main/java/com/sun/mail/imap/ReferralException.java
new file mode 100644
index 0000000..f4a335c
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/ReferralException.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import javax.mail.AuthenticationFailedException;
+
+/**
+ * A special kind of AuthenticationFailedException that indicates that
+ * the reason for the failure was an IMAP REFERRAL in the response code.
+ * See <a href="http://www.ietf.org/rfc/rfc2221.txt">RFC 2221</a> for details.
+ *
+ * @since JavaMail 1.5.5
+ */
+
+public class ReferralException extends AuthenticationFailedException {
+
+    private String url;
+    private String text;
+
+    private static final long serialVersionUID = -3414063558596287683L;
+
+    /**
+     * Constructs an ReferralException with the specified URL and text.
+     *
+     * @param text	the detail message
+     * @param url	the URL
+     */
+    public ReferralException(String url, String text) {
+	super("[REFERRAL " + url + "] " + text);
+	this.url = url;
+	this.text = text;
+    }
+
+    /**
+     * Return the IMAP URL in the referral.
+     *
+     * @return	the IMAP URL
+     */
+    public String getUrl() {
+	return url;
+    }
+
+    /**
+     * Return the text sent by the server along with the referral.
+     *
+     * @return	the text
+     */
+    public String getText() {
+	return text;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/ResyncData.java b/mail/src/main/java/com/sun/mail/imap/ResyncData.java
new file mode 100644
index 0000000..f5c295e
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/ResyncData.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import com.sun.mail.imap.protocol.UIDSet;
+
+/**
+ * Resynchronization data as defined by the QRESYNC extension
+ * (<A HREF="http://www.ietf.org/rfc/rfc5162.txt">RFC 5162</A>).
+ * An instance of <CODE>ResyncData</CODE> is supplied to the
+ * {@link com.sun.mail.imap.IMAPFolder#open(int,com.sun.mail.imap.ResyncData)
+ * IMAPFolder open} method.
+ * The CONDSTORE <CODE>ResyncData</CODE> instance is used to enable the
+ * CONDSTORE extension
+ * (<A HREF="http://www.ietf.org/rfc/rfc4551.txt">RFC 4551</A>).
+ * A <CODE>ResyncData</CODE> instance with uidvalidity and modseq values
+ * is used to enable the QRESYNC extension.
+ *
+ * @since	JavaMail 1.5.1
+ * @author	Bill Shannon
+ */
+
+public class ResyncData { 
+    private long uidvalidity = -1;
+    private long modseq = -1;
+    private UIDSet[] uids = null;
+
+    /**
+     * Used to enable only the CONDSTORE extension.
+     */
+    public static final ResyncData CONDSTORE = new ResyncData(-1, -1);
+
+    /**
+     * Used to report on changes since the specified modseq.
+     * If the UIDVALIDITY of the folder has changed, no message
+     * changes will be reported.  The application must check the
+     * UIDVALIDITY of the folder after open to make sure it's
+     * the expected folder.
+     *
+     * @param	uidvalidity	the UIDVALIDITY
+     * @param	modseq		the MODSEQ
+     */
+    public ResyncData(long uidvalidity, long modseq) {
+	this.uidvalidity = uidvalidity;
+	this.modseq = modseq;
+	this.uids = null;
+    }
+
+    /**
+     * Used to limit the reported message changes to those with UIDs
+     * in the specified range.
+     *
+     * @param	uidvalidity	the UIDVALIDITY
+     * @param	modseq		the MODSEQ
+     * @param	uidFirst	the first UID
+     * @param	uidLast		the last UID
+     */
+    public ResyncData(long uidvalidity, long modseq,
+				long uidFirst, long uidLast) {
+	this.uidvalidity = uidvalidity;
+	this.modseq = modseq;
+	this.uids = new UIDSet[] { new UIDSet(uidFirst, uidLast) };
+    }
+
+    /**
+     * Used to limit the reported message changes to those with the
+     * specified UIDs.
+     *
+     * @param	uidvalidity	the UIDVALIDITY
+     * @param	modseq		the MODSEQ
+     * @param	uids		the UID values
+     */
+    public ResyncData(long uidvalidity, long modseq, long[] uids) {
+	this.uidvalidity = uidvalidity;
+	this.modseq = modseq;
+	this.uids = UIDSet.createUIDSets(uids);
+    }
+
+    /**
+     * Get the UIDVALIDITY value specified when this instance was created.
+     *
+     * @return	the UIDVALIDITY value
+     */
+    public long getUIDValidity() {
+	return uidvalidity;
+    }
+
+    /**
+     * Get the MODSEQ value specified when this instance was created.
+     *
+     * @return	the MODSEQ value
+     */
+    public long getModSeq() {
+	return modseq;
+    }
+
+    /*
+     * Package private.  IMAPProtocol gets this data indirectly
+     * using Utility.getResyncUIDSet().
+     */
+    UIDSet[] getUIDSet() {
+	return uids;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/Rights.java b/mail/src/main/java/com/sun/mail/imap/Rights.java
new file mode 100644
index 0000000..d9c4ce0
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/Rights.java
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.*;
+
+/**
+ * The Rights class represents the set of rights for an authentication
+ * identifier (for instance, a user or a group). <p>
+ *
+ * A right is represented by the <code>Rights.Right</code> 
+ * inner class. <p>
+ *
+ * A set of standard rights are predefined (see RFC 2086).  Most folder
+ * implementations are expected to support these rights.  Some
+ * implementations may also support site-defined rights. <p>
+ *
+ * The following code sample illustrates how to examine your
+ * rights for a folder.
+ * <pre>
+ *
+ * Rights rights = folder.myRights();
+ *
+ * // Check if I can write this folder
+ * if (rights.contains(Rights.Right.WRITE))
+ *	System.out.println("Can write folder");
+ *
+ * // Now give Joe all my rights, except the ability to write the folder
+ * rights.remove(Rights.Right.WRITE);
+ * ACL acl = new ACL("joe", rights);
+ * folder.setACL(acl);
+ * </pre>
+ * <p>
+ *
+ * @author Bill Shannon
+ */
+
+public class Rights implements Cloneable {
+
+    private boolean[] rights = new boolean[128];	// XXX
+
+    /**
+     * This inner class represents an individual right. A set
+     * of standard rights objects are predefined here.
+     */
+    public static final class Right {
+	private static Right[] cache = new Right[128];
+
+	// XXX - initialization order?
+	/**
+	 * Lookup - mailbox is visible to LIST/LSUB commands.
+	 */
+	public static final Right LOOKUP = getInstance('l');
+
+	/**
+	 * Read - SELECT the mailbox, perform CHECK, FETCH, PARTIAL,
+	 * SEARCH, COPY from mailbox
+	 */
+	public static final Right READ = getInstance('r');
+
+	/**
+	 * Keep seen/unseen information across sessions - STORE \SEEN flag.
+	 */
+	public static final Right KEEP_SEEN = getInstance('s');
+
+	/**
+	 * Write - STORE flags other than \SEEN and \DELETED.
+	 */
+	public static final Right WRITE = getInstance('w');
+
+	/**
+	 * Insert - perform APPEND, COPY into mailbox.
+	 */
+	public static final Right INSERT = getInstance('i');
+
+	/**
+	 * Post - send mail to submission address for mailbox,
+	 * not enforced by IMAP4 itself.
+	 */
+	public static final Right POST = getInstance('p');
+
+	/**
+	 * Create - CREATE new sub-mailboxes in any implementation-defined
+	 * hierarchy, RENAME or DELETE mailbox.
+	 */
+	public static final Right CREATE = getInstance('c');
+
+	/**
+	 * Delete - STORE \DELETED flag, perform EXPUNGE.
+	 */
+	public static final Right DELETE = getInstance('d');
+
+	/**
+	 * Administer - perform SETACL.
+	 */
+	public static final Right ADMINISTER = getInstance('a');
+
+	char right;	// the right represented by this Right object
+
+	/**
+	 * Private constructor used only by getInstance.
+	 */
+	private Right(char right) {
+	    if ((int)right >= 128)
+		throw new IllegalArgumentException("Right must be ASCII");
+	    this.right = right;
+	}
+
+	/**
+	 * Get a Right object representing the specified character.
+	 * Characters are assigned per RFC 2086.
+	 *
+	 * @param	right	the character representing the right
+	 * @return		the Right object
+	 */
+	public static synchronized Right getInstance(char right) {
+	    if ((int)right >= 128)
+		throw new IllegalArgumentException("Right must be ASCII");
+	    if (cache[(int)right] == null)
+		cache[(int)right] = new Right(right);
+	    return cache[(int)right];
+	}
+
+	@Override
+	public String toString() {
+	    return String.valueOf(right);
+	}
+    }
+
+
+    /**
+     * Construct an empty Rights object.
+     */
+    public Rights() { }
+
+    /**
+     * Construct a Rights object initialized with the given rights.
+     *
+     * @param rights	the rights for initialization
+     */
+    public Rights(Rights rights) {
+	System.arraycopy(rights.rights, 0, this.rights, 0, this.rights.length);
+    }
+
+    /**
+     * Construct a Rights object initialized with the given rights.
+     *
+     * @param rights	the rights for initialization
+     */
+    public Rights(String rights) {
+	for (int i = 0; i < rights.length(); i++)
+	    add(Right.getInstance(rights.charAt(i)));
+    }
+
+    /**
+     * Construct a Rights object initialized with the given right.
+     *
+     * @param right	the right for initialization
+     */
+    public Rights(Right right) {
+	this.rights[(int)right.right] = true;
+    }
+
+    /**
+     * Add the specified right to this Rights object.
+     *
+     * @param right	the right to add
+     */
+    public void add(Right right) {
+	this.rights[(int)right.right] = true;
+    }
+
+    /**
+     * Add all the rights in the given Rights object to this
+     * Rights object.
+     *
+     * @param rights	Rights object
+     */
+    public void add(Rights rights) {
+	for (int i = 0; i < rights.rights.length; i++)
+	    if (rights.rights[i])
+		this.rights[i] = true;
+    }
+
+    /**
+     * Remove the specified right from this Rights object.
+     *
+     * @param	right 	the right to be removed
+     */
+    public void remove(Right right) {
+	this.rights[(int)right.right] = false;
+    }
+
+    /**
+     * Remove all rights in the given Rights object from this 
+     * Rights object.
+     *
+     * @param	rights 	the rights to be removed
+     */
+    public void remove(Rights rights) {
+	for (int i = 0; i < rights.rights.length; i++)
+	    if (rights.rights[i])
+		this.rights[i] = false;
+    }
+
+    /**
+     * Check whether the specified right is present in this Rights object.
+     *
+     * @param	right	the Right to check
+     * @return 		true of the given right is present, otherwise false.
+     */
+    public boolean contains(Right right) {
+	return this.rights[(int)right.right];
+    }
+
+    /**
+     * Check whether all the rights in the specified Rights object are
+     * present in this Rights object.
+     *
+     * @param	rights	the Rights to check
+     * @return	true if all rights in the given Rights object are present, 
+     *		otherwise false.
+     */
+    public boolean contains(Rights rights) {
+	for (int i = 0; i < rights.rights.length; i++)
+	    if (rights.rights[i] && !this.rights[i])
+		return false;
+
+	// If we've made it till here, return true
+	return true;
+    }
+
+    /**
+     * Check whether the two Rights objects are equal.
+     *
+     * @return	true if they're equal
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof Rights))
+	    return false;
+
+	Rights rights = (Rights)obj;
+
+	for (int i = 0; i < rights.rights.length; i++)
+	    if (rights.rights[i] != this.rights[i])
+		return false;
+
+	return true;
+    }
+
+    /**
+     * Compute a hash code for this Rights object.
+     *
+     * @return	the hash code
+     */
+    @Override
+    public int hashCode() {
+	int hash = 0;
+	for (int i = 0; i < this.rights.length; i++)
+	    if (this.rights[i])
+		hash++;
+	return hash;
+    }
+
+    /**
+     * Return all the rights in this Rights object.  Returns
+     * an array of size zero if no rights are set.
+     *
+     * @return	array of Rights.Right objects representing rights
+     */
+    public Right[] getRights() {
+	List<Right> v = new ArrayList<>();
+	for (int i = 0; i < this.rights.length; i++)
+	    if (this.rights[i])
+		v.add(Right.getInstance((char)i));
+	return v.toArray(new Right[v.size()]);
+    }
+
+    /**
+     * Returns a clone of this Rights object.
+     */
+    @Override
+    public Object clone() {
+	Rights r = null;
+	try {
+	    r = (Rights)super.clone();
+	    r.rights = new boolean[128];
+	    System.arraycopy(this.rights, 0, r.rights, 0, this.rights.length);
+	} catch (CloneNotSupportedException cex) {
+	    // ignore, can't happen
+	}
+	return r;
+    }
+
+    @Override
+    public String toString() {
+	StringBuilder sb = new StringBuilder();
+	for (int i = 0; i < this.rights.length; i++)
+	    if (this.rights[i])
+		sb.append((char)i);
+	return sb.toString();
+    }
+
+    /*****
+    public static void main(String argv[]) throws Exception {
+	// a new rights object
+	Rights f1 = new Rights();
+	f1.add(Rights.Right.READ);
+	f1.add(Rights.Right.WRITE);
+	f1.add(Rights.Right.CREATE);
+	f1.add(Rights.Right.DELETE);
+
+	// check copy constructor
+	Rights fc = new Rights(f1);
+	if (f1.equals(fc) && fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// check clone
+	fc = (Rights)f1.clone();
+	if (f1.equals(fc) && fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// add a right and make sure it still works right
+	f1.add(Rights.Right.ADMINISTER);
+
+	// shouldn't be equal here
+	if (!f1.equals(fc) && !fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// check clone
+	fc = (Rights)f1.clone();
+	if (f1.equals(fc) && fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	fc.add(Rights.Right.INSERT);
+	if (!f1.equals(fc) && !fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// check copy constructor
+	fc = new Rights(f1);
+	if (f1.equals(fc) && fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// another new rights object
+	Rights f2 = new Rights(Rights.Right.READ);
+	f2.add(Rights.Right.WRITE);
+
+	if (f1.contains(Rights.Right.READ))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+		
+	if (f1.contains(Rights.Right.WRITE))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	if (f1.contains(Rights.Right.CREATE))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	if (f1.contains(Rights.Right.DELETE))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	if (f2.contains(Rights.Right.WRITE))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+
+	System.out.println("----------------");
+
+	Right[] r = f1.getRights();
+	for (int i = 0; i < r.length; i++)
+	    System.out.println(r[i]);
+	System.out.println("----------------");
+
+	if (f1.contains(f2)) // this should be true
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	if (!f2.contains(f1)) // this should be false
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	Rights f3 = new Rights();
+	f3.add(Rights.Right.READ);
+	f3.add(Rights.Right.WRITE);
+	f3.add(Rights.Right.CREATE);
+	f3.add(Rights.Right.DELETE);
+	f3.add(Rights.Right.ADMINISTER);
+	f3.add(Rights.Right.LOOKUP);
+
+	f1.add(Rights.Right.LOOKUP);
+
+	if (f1.equals(f3))
+	    System.out.println("equals success");
+	else
+	    System.out.println("fail");
+	if (f3.equals(f1))
+	    System.out.println("equals success");
+	else
+	    System.out.println("fail");
+	System.out.println("f1 hash code " + f1.hashCode());
+	System.out.println("f3 hash code " + f3.hashCode());
+	if (f1.hashCode() == f3.hashCode())
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+    }
+    ****/
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/SortTerm.java b/mail/src/main/java/com/sun/mail/imap/SortTerm.java
new file mode 100644
index 0000000..6f624e8
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/SortTerm.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+/**
+ * A particular sort criteria, as defined by
+ * <A HREF="http://www.ietf.org/rfc/rfc5256.txt">RFC 5256</A>.
+ * Sort criteria are used with the
+ * {@link IMAPFolder#getSortedMessages getSortedMessages} method.
+ * Multiple sort criteria are specified in an array with the order in
+ * the array specifying the order in which the sort criteria are applied.
+ *
+ * @since JavaMail 1.4.4
+ */
+public final class SortTerm {
+    /**
+     * Sort by message arrival date and time.
+     */
+    public static final SortTerm ARRIVAL = new SortTerm("ARRIVAL");
+
+    /**
+     * Sort by email address of first Cc recipient.
+     */
+    public static final SortTerm CC = new SortTerm("CC");
+
+    /**
+     * Sort by sent date and time.
+     */
+    public static final SortTerm DATE = new SortTerm("DATE");
+
+    /**
+     * Sort by first From email address.
+     */
+    public static final SortTerm FROM = new SortTerm("FROM");
+
+    /**
+     * Reverse the sort order of the following item.
+     */
+    public static final SortTerm REVERSE = new SortTerm("REVERSE");
+
+    /**
+     * Sort by the message size.
+     */
+    public static final SortTerm SIZE = new SortTerm("SIZE");
+
+    /**
+     * Sort by the base subject text.  Note that the "base subject"
+     * is defined by RFC 5256 and doesn't include items such as "Re:"
+     * in the subject header.
+     */
+    public static final SortTerm SUBJECT = new SortTerm("SUBJECT");
+
+    /**
+     * Sort by email address of first To recipient.
+     */
+    public static final SortTerm TO = new SortTerm("TO");
+
+    private String term;
+    private SortTerm(String term) {
+	this.term = term;
+    }
+
+    @Override
+    public String toString() {
+	return term;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/Utility.java b/mail/src/main/java/com/sun/mail/imap/Utility.java
new file mode 100644
index 0000000..43f1998
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/Utility.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import javax.mail.*;
+
+import com.sun.mail.imap.protocol.MessageSet;
+import com.sun.mail.imap.protocol.UIDSet;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Holder for some static utility methods.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public final class Utility {
+
+    // Cannot be initialized
+    private Utility() { }
+
+    /**
+     * Run thru the given array of messages, apply the given
+     * Condition on each message and generate sets of contiguous 
+     * sequence-numbers for the successful messages. If a message 
+     * in the given array is found to be expunged, it is ignored.
+     *
+     * ASSERT: Since this method uses and returns message sequence
+     * numbers, you should use this method only when holding the
+     * messageCacheLock.
+     *
+     * @param	msgs	the messages
+     * @param	cond	the condition to check
+     * @return		the MessageSet array
+     */
+    public static MessageSet[] toMessageSet(Message[] msgs, Condition cond) {
+	List<MessageSet> v = new ArrayList<>(1);
+	int current, next;
+
+	IMAPMessage msg;
+	for (int i = 0; i < msgs.length; i++) {
+	    msg = (IMAPMessage)msgs[i];
+	    if (msg.isExpunged()) // expunged message, skip it
+		continue;
+
+	    current = msg.getSequenceNumber();
+	    // Apply the condition. If it fails, skip it.
+	    if ((cond != null) && !cond.test(msg))
+		continue;
+	    
+	    MessageSet set = new MessageSet();
+	    set.start = current;
+
+	    // Look for contiguous sequence numbers
+	    for (++i; i < msgs.length; i++) {
+		// get next message
+		msg = (IMAPMessage)msgs[i];
+
+		if (msg.isExpunged()) // expunged message, skip it
+		    continue;
+		next = msg.getSequenceNumber();
+
+		// Does this message match our condition ?
+		if ((cond != null) && !cond.test(msg))
+		    continue;
+		
+		if (next == current+1)
+		    current = next;
+		else { // break in sequence
+		    // We need to reexamine this message at the top of
+		    // the outer loop, so decrement 'i' to cancel the
+		    // outer loop's autoincrement 
+		    i--;
+		    break;
+		}
+	    }
+	    set.end = current;
+	    v.add(set);
+	}
+	
+	if (v.isEmpty()) // No valid messages
+	    return null;
+	else {
+	    return v.toArray(new MessageSet[v.size()]);
+	}
+    }
+    /**
+     * Sort (a copy of) the given array of messages and then
+     * run thru the sorted array of messages, apply the given
+     * Condition on each message and generate sets of contiguous 
+     * sequence-numbers for the successful messages. If a message 
+     * in the given array is found to be expunged, it is ignored.
+     *
+     * ASSERT: Since this method uses and returns message sequence
+     * numbers, you should use this method only when holding the
+     * messageCacheLock.
+     *
+     * @param	msgs	the messages
+     * @param	cond	the condition to check
+     * @return		the MessageSet array
+     * @since JavaMail 1.5.4
+     */
+    public static MessageSet[] toMessageSetSorted(Message[] msgs,
+							    Condition cond) {
+	/*
+	 * XXX - This is quick and dirty.  A more efficient strategy would be
+	 * to generate an array of message numbers by applying the condition
+	 * (with zero indicating the message doesn't satisfy the condition),
+	 * sort it, and then convert it to a MessageSet skipping all the zeroes.
+	 */
+	msgs = msgs.clone();
+	Arrays.sort(msgs,
+	    new Comparator<Message>() {
+		@Override
+		public int compare(Message msg1, Message msg2) {
+		    return msg1.getMessageNumber() - msg2.getMessageNumber();
+		}
+	    });
+	return toMessageSet(msgs, cond);
+    }
+
+    /**
+     * Return UIDSets for the messages.  Note that the UIDs
+     * must have already been fetched for the messages.
+     *
+     * @param	msgs	the messages
+     * @return		the UIDSet array
+     */
+    public static UIDSet[] toUIDSet(Message[] msgs) {
+	List<UIDSet> v = new ArrayList<>(1);
+	long current, next;
+
+	IMAPMessage msg;
+	for (int i = 0; i < msgs.length; i++) {
+	    msg = (IMAPMessage)msgs[i];
+	    if (msg.isExpunged()) // expunged message, skip it
+		continue;
+
+	    current = msg.getUID();
+ 
+	    UIDSet set = new UIDSet();
+	    set.start = current;
+
+	    // Look for contiguous UIDs
+	    for (++i; i < msgs.length; i++) {
+		// get next message
+		msg = (IMAPMessage)msgs[i];
+
+		if (msg.isExpunged()) // expunged message, skip it
+		    continue;
+		next = msg.getUID();
+
+		if (next == current+1)
+		    current = next;
+		else { // break in sequence
+		    // We need to reexamine this message at the top of
+		    // the outer loop, so decrement 'i' to cancel the
+		    // outer loop's autoincrement 
+		    i--;
+		    break;
+		}
+	    }
+	    set.end = current;
+	    v.add(set);
+	}
+
+	if (v.isEmpty()) // No valid messages
+	    return null;
+	else {
+	    return v.toArray(new UIDSet[v.size()]);
+	}
+    }
+
+    /**
+     * Make the ResyncData UIDSet available to IMAPProtocol,
+     * which is in a different package.  Note that this class
+     * is not included in the public javadocs, thus "hiding"
+     * this method.
+     *
+     * @param	rd	the ResyncData
+     * @return		the UIDSet array
+     * @since	JavaMail 1.5.1
+     */
+    public static UIDSet[] getResyncUIDSet(ResyncData rd) {
+	return rd.getUIDSet();
+    }
+
+    /**
+     * This interface defines the test to be executed in 
+     * <code>toMessageSet()</code>. 
+     */
+    public static interface Condition {
+	public boolean test(IMAPMessage message);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/YoungerTerm.java b/mail/src/main/java/com/sun/mail/imap/YoungerTerm.java
new file mode 100644
index 0000000..e8264ae
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/YoungerTerm.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.util.Date;
+import javax.mail.Message;
+import javax.mail.search.SearchTerm;
+
+/**
+ * Find messages that are younger than a given interval (in seconds).
+ * Relies on the server implementing the WITHIN search extension
+ * (<A HREF="http://www.ietf.org/rfc/rfc5032.txt">RFC 5032</A>).
+ *
+ * @since	JavaMail 1.5.1
+ * @author	Bill Shannon
+ */
+public final class YoungerTerm extends SearchTerm {
+
+    private int interval;
+
+    private static final long serialVersionUID = 1592714210688163496L;
+
+    /**
+     * Constructor.
+     *
+     * @param interval	number of seconds younger
+     */
+    public YoungerTerm(int interval) {
+	this.interval = interval;
+    }
+
+    /**
+     * Return the interval.
+     *
+     * @return	the interval
+     */
+    public int getInterval() {
+	return interval;
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the date comparator is applied to this Message's
+     *			received date
+     * @return		true if the comparison succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	Date d;
+
+	try {
+	    d = msg.getReceivedDate();
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (d == null)
+	    return false;
+
+	return d.getTime() >=
+		    System.currentTimeMillis() - ((long)interval * 1000);
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof YoungerTerm))
+	    return false;
+	return interval == ((YoungerTerm)obj).interval;
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return interval;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/package.html b/mail/src/main/java/com/sun/mail/imap/package.html
new file mode 100644
index 0000000..fa5ccdd
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/package.html
@@ -0,0 +1,1000 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.imap package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+An IMAP protocol provider for the JavaMail API
+that provides access to an IMAP message store.
+Both the IMAP4 and IMAP4rev1 protocols are supported.
+Refer to <A HREF="http://www.ietf.org/rfc/rfc3501.txt" TARGET="_top">
+RFC 3501</A>
+for more information.
+The IMAP protocol provider also supports many IMAP extensions (described below).
+Note that the server needs to support these extensions (and not all servers do)
+in order to use the support in the IMAP provider.
+You can query the server for support of these extensions using the
+{@link com.sun.mail.imap.IMAPStore#hasCapability IMAPStore hasCapability}
+method using the capability name defined by the extension
+(see the appropriate RFC) after connecting to the server.
+</P>
+<STRONG>UIDPLUS Support</STRONG>
+<P>
+The IMAP UIDPLUS extension
+(<A HREF="http://www.ietf.org/rfc/rfc4315.txt" TARGET="_top">RFC 4315</A>)
+is supported via the IMAPFolder methods
+{@link com.sun.mail.imap.IMAPFolder#addMessages addMessages},
+{@link com.sun.mail.imap.IMAPFolder#appendUIDMessages appendUIDMessages}, and
+{@link com.sun.mail.imap.IMAPFolder#copyUIDMessages copyUIDMessages}.
+</P>
+<STRONG>MOVE Support</STRONG>
+<P>
+The IMAP MOVE extension
+(<A HREF="http://www.ietf.org/rfc/rfc6851.txt" TARGET="_top">RFC 6851</A>)
+is supported via the IMAPFolder methods
+{@link com.sun.mail.imap.IMAPFolder#moveMessages moveMessages} and
+{@link com.sun.mail.imap.IMAPFolder#moveUIDMessages moveUIDMessages}.
+</P>
+<STRONG>SASL Support</STRONG>
+<P>
+The IMAP protocol provider can use SASL
+(<A HREF="http://www.ietf.org/rfc/rfc4422.txt" TARGET="_top">RFC 4422</A>)
+authentication mechanisms on systems that support the
+<CODE>javax.security.sasl</CODE> APIs.
+The SASL-IR
+(<A HREF="http://www.ietf.org/rfc/rfc4959.txt" TARGET="_top">RFC 4959</A>)
+capability is also supported.
+In addition to the SASL mechanisms that are built into 
+the SASL implementation, users can also provide additional
+SASL mechanisms of their own design to support custom authentication
+schemes.  See the
+<A HREF="http://download.oracle.com/javase/6/docs/technotes/guides/security/sasl/sasl-refguide.html" TARGET="_top">
+Java SASL API Programming and Deployment Guide</A> for details.
+Note that the current implementation doesn't support SASL mechanisms
+that provide their own integrity or confidentiality layer.
+</P>
+<STRONG>OAuth 2.0 Support</STRONG>
+<P>
+Support for OAuth 2.0 authentication via the
+<A HREF="https://developers.google.com/gmail/xoauth2_protocol" TARGET="_top">
+XOAUTH2 authentication mechanism</A> is provided either through the SASL
+support described above or as a built-in authentication mechanism in the
+IMAP provider.
+The OAuth 2.0 Access Token should be passed as the password for this mechanism.
+See <A HREF="https://java.net/projects/javamail/pages/OAuth2" TARGET="_top">
+OAuth2 Support</A> for details.
+</P>
+<STRONG>Connection Pool</STRONG>
+<P>
+A connected IMAPStore maintains a pool of IMAP protocol objects for
+use in communicating with the IMAP server. The IMAPStore will create
+the initial AUTHENTICATED connection and seed the pool with this
+connection. As folders are opened and new IMAP protocol objects are
+needed, the IMAPStore will provide them from the connection pool,
+or create them if none are available. When a folder is closed,
+its IMAP protocol object is returned to the connection pool if the
+pool is not over capacity.
+</P>
+<P>
+A mechanism is provided for timing out idle connection pool IMAP
+protocol objects. Timed out connections are closed and removed (pruned)
+from the connection pool.
+</P>
+<P>
+The connected IMAPStore object may or may not maintain a separate IMAP
+protocol object that provides the store a dedicated connection to the
+IMAP server. This is provided mainly for compatibility with previous
+implementations of the IMAP protocol provider.
+</P>
+<STRONG>QUOTA Support</STRONG>
+<P>
+The IMAP QUOTA extension
+(<A HREF="http://www.ietf.org/rfc/rfc2087.txt" TARGET="_top">RFC 2087</A>)
+is supported via the
+{@link javax.mail.QuotaAwareStore QuotaAwareStore} interface implemented by
+{@link com.sun.mail.imap.IMAPStore IMAPStore}, and the
+{@link com.sun.mail.imap.IMAPFolder#getQuota IMAPFolder getQuota} and
+{@link com.sun.mail.imap.IMAPFolder#setQuota IMAPFolder setQuota} methods.
+<STRONG>ACL Support</STRONG>
+<P>
+The IMAP ACL extension
+(<A HREF="http://www.ietf.org/rfc/rfc2086.txt" TARGET="_top">RFC 2086</A>)
+is supported via the
+{@link com.sun.mail.imap.Rights Rights} class and the IMAPFolder methods
+{@link com.sun.mail.imap.IMAPFolder#getACL getACL},
+{@link com.sun.mail.imap.IMAPFolder#addACL addACL},
+{@link com.sun.mail.imap.IMAPFolder#removeACL removeACL},
+{@link com.sun.mail.imap.IMAPFolder#addRights addRights},
+{@link com.sun.mail.imap.IMAPFolder#removeRights removeRights},
+{@link com.sun.mail.imap.IMAPFolder#listRights listRights}, and
+{@link com.sun.mail.imap.IMAPFolder#myRights myRights}.
+</P>
+<STRONG>SORT Support</STRONG>
+<P>
+The IMAP SORT extension
+(<A HREF="http://www.ietf.org/rfc/rfc5256.txt" TARGET="_top">RFC 5256</A>)
+is supported via the
+{@link com.sun.mail.imap.SortTerm SortTerm} class and the IMAPFolder
+{@link com.sun.mail.imap.IMAPFolder#getSortedMessages getSortedMessages}
+methods.
+</P>
+<STRONG>CONDSTORE and QRESYNC Support</STRONG>
+<P>
+Basic support is provided for the IMAP CONDSTORE
+(<A HREF="http://www.ietf.org/rfc/rfc4551.txt" TARGET="_top">RFC 4551</A>)
+and QRESYNC
+(<A HREF="http://www.ietf.org/rfc/rfc5162.txt" TARGET="_top">RFC 5162</A>)
+extensions for the purpose of resynchronizing a folder after offline operation.
+Of course, the server must support these extensions.
+Use of these extensions is enabled by using the new
+{@link com.sun.mail.imap.IMAPFolder#open(int,com.sun.mail.imap.ResyncData)
+IMAPFolder open} method and supplying an appropriate
+{@link com.sun.mail.imap.ResyncData ResyncData} instance.
+Using
+{@link com.sun.mail.imap.ResyncData#CONDSTORE ResyncData.CONDSTORE}
+enables the CONDSTORE extension, which allows you to discover the
+modification sequence number (modseq) of messages using the
+{@link com.sun.mail.imap.IMAPMessage#getModSeq IMAPMessage getModSeq}
+method and the
+{@link com.sun.mail.imap.IMAPFolder#getHighestModSeq
+IMAPFolder getHighestModSeq} method.
+Using a
+{@link com.sun.mail.imap.ResyncData ResyncData} instance with appropriate
+values also allows the server to report any changes in messages since the last
+resynchronization.
+The changes are reported as a list of
+{@link javax.mail.event.MailEvent MailEvent} instances.
+The special
+{@link com.sun.mail.imap.MessageVanishedEvent MessageVanishedEvent} reports on
+UIDs of messages that have been removed since the last resynchronization.
+A
+{@link javax.mail.event.MessageChangedEvent MessageChangedEvent} reports on
+changes to flags of messages.
+For example:
+</P>
+<PRE>
+	Folder folder = store.getFolder("whatever");
+	IMAPFolder ifolder = (IMAPFolder)folder;
+	List&lt;MailEvent&gt; events = ifolder.open(Folder.READ_WRITE,
+		    new ResyncData(prevUidValidity, prevModSeq));
+	for (MailEvent ev : events) {
+	    if (ev instanceOf MessageChangedEvent) {
+		// process flag changes
+	    } else if (ev instanceof MessageVanishedEvent) {
+		// process messages that were removed
+	    }
+	}
+</PRE>
+<P>
+See the referenced RFCs for more details on these IMAP extensions.
+</P>
+<STRONG>WITHIN Search Support</STRONG>
+<P>
+The IMAP WITHIN search extension
+(<A HREF="http://www.ietf.org/rfc/rfc5032.txt" TARGET="_top">RFC 5032</A>)
+is supported via the
+{@link com.sun.mail.imap.YoungerTerm YoungerTerm} and
+{@link com.sun.mail.imap.OlderTerm OlderTerm} 
+{@link javax.mail.search.SearchTerm SearchTerms}, which can be used as follows:
+</P>
+<PRE>
+	// search for messages delivered in the last day
+	Message[] msgs = folder.search(new YoungerTerm(24 * 60 * 60));
+</PRE>
+<STRONG>LOGIN-REFERRAL Support</STRONG>
+<P>
+The IMAP LOGIN-REFERRAL extension
+(<A HREF="http://www.ietf.org/rfc/rfc2221.txt" TARGET="_top">RFC 2221</A>)
+is supported.
+If a login referral is received when connecting or when authentication fails, a
+{@link com.sun.mail.imap.ReferralException ReferralException} is thrown.
+A referral can also occur when login succeeds.  By default, no exception is
+thrown in this case.  To force an exception to be thrown and the authentication
+to fail, set the <code>mail.imap.referralexception</code> property to "true".
+</P>
+<STRONG>COMPRESS Support</STRONG>
+<P>
+The IMAP COMPRESS extension
+(<A HREF="http://www.ietf.org/rfc/rfc4978.txt" TARGET="_top">RFC 4978</A>)
+is supported.
+If the server supports the extension and the
+<code>mail.imap.compress.enable</code> property is set to "true",
+compression will be enabled.
+</P>
+<STRONG>UTF-8 Support</STRONG>
+<P>
+The IMAP UTF8 extension
+(<A HREF="http://www.ietf.org/rfc/rfc6855.txt" TARGET="_top">RFC 6855</A>)
+is supported.
+If the server supports the extension, the client will enable use of UTF-8,
+allowing use of UTF-8 in IMAP protocol strings such as folder names.
+</P>
+<A NAME="properties"><STRONG>Properties</STRONG></A>
+<P>
+The IMAP protocol provider supports the following properties,
+which may be set in the JavaMail <code>Session</code> object.
+The properties are always set as strings; the Type column describes
+how the string is interpreted.  For example, use
+</P>
+<PRE>
+	props.put("mail.imap.port", "888");
+</PRE>
+<P>
+to set the <CODE>mail.imap.port</CODE> property, which is of type int.
+</P>
+<P>
+Note that if you're using the "imaps" protocol to access IMAP over SSL,
+all the properties would be named "mail.imaps.*".
+</P>
+<TABLE BORDER SUMMARY="IMAP properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.user">mail.imap.user</A></TD>
+<TD>String</TD>
+<TD>Default user name for IMAP.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.host">mail.imap.host</A></TD>
+<TD>String</TD>
+<TD>The IMAP server to connect to.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.port">mail.imap.port</A></TD>
+<TD>int</TD>
+<TD>The IMAP server port to connect to, if the connect() method doesn't
+explicitly specify one. Defaults to 143.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.partialfetch">mail.imap.partialfetch</A></TD>
+<TD>boolean</TD>
+<TD>Controls whether the IMAP partial-fetch capability should be used.
+Defaults to true.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.fetchsize">mail.imap.fetchsize</A></TD>
+<TD>int</TD>
+<TD>Partial fetch size in bytes. Defaults to 16K.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.peek">mail.imap.peek</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, use the IMAP PEEK option when fetching body parts,
+to avoid setting the SEEN flag on messages.
+Defaults to false.
+Can be overridden on a per-message basis by the
+{@link com.sun.mail.imap.IMAPMessage#setPeek setPeek}
+method on IMAPMessage.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.ignorebodystructuresize">mail.imap.ignorebodystructuresize</A></TD>
+<TD>boolean</TD>
+<TD>The IMAP BODYSTRUCTURE response includes the exact size of each body part.
+Normally, this size is used to determine how much data to fetch for each
+body part.
+Some servers report this size incorrectly in some cases; this property can
+be set to work around such server bugs.
+If this property is set to true, this size is ignored and data is fetched
+until the server reports the end of data.
+This will result in an extra fetch if the data size is a multiple of the
+block size.
+Defaults to false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.connectiontimeout">mail.imap.connectiontimeout</A></TD>
+<TD>int</TD>
+<TD>Socket connection timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.timeout">mail.imap.timeout</A></TD>
+<TD>int</TD>
+<TD>Socket read timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.writetimeout">mail.imap.writetimeout</A></TD>
+<TD>int</TD>
+<TD>Socket write timeout value in milliseconds.
+This timeout is implemented by using a
+java.util.concurrent.ScheduledExecutorService per connection
+that schedules a thread to close the socket if the timeout expires.
+Thus, the overhead of using this timeout is one thread per connection.
+Default is infinite timeout.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.statuscachetimeout">mail.imap.statuscachetimeout</A></TD>
+<TD>int</TD>
+<TD>Timeout value in milliseconds for cache of STATUS command response.
+Default is 1000 (1 second).  Zero disables cache.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.appendbuffersize">mail.imap.appendbuffersize</A></TD>
+<TD>int</TD>
+<TD>
+Maximum size of a message to buffer in memory when appending to an IMAP
+folder.  If not set, or set to -1, there is no maximum and all messages
+are buffered.  If set to 0, no messages are buffered.  If set to (e.g.)
+8192, messages of 8K bytes or less are buffered, larger messages are
+not buffered.  Buffering saves cpu time at the expense of short term
+memory usage.  If you commonly append very large messages to IMAP
+mailboxes you might want to set this to a moderate value (1M or less).
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.connectionpoolsize">mail.imap.connectionpoolsize</A></TD>
+<TD>int</TD>
+<TD>Maximum number of available connections in the connection pool.
+Default is 1.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.connectionpooltimeout">mail.imap.connectionpooltimeout</A></TD>
+<TD>int</TD>
+<TD>Timeout value in milliseconds for connection pool connections.  Default
+is 45000 (45 seconds).</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.separatestoreconnection">mail.imap.separatestoreconnection</A></TD>
+<TD>boolean</TD>
+<TD>Flag to indicate whether to use a dedicated store connection for store
+commands.  Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.allowreadonlyselect">mail.imap.allowreadonlyselect</A></TD>
+<TD>boolean</TD>
+<TD>If false, attempts to open a folder read/write will fail
+if the SELECT command succeeds but indicates that the folder is READ-ONLY.
+This sometimes indicates that the folder contents can'tbe changed, but
+the flags are per-user and can be changed, such as might be the case for
+public shared folders.  If true, such open attempts will succeed, allowing
+the flags to be changed.  The <code>getMode</code> method on the
+<code>Folder</code> object will return <code>Folder.READ_ONLY</code>
+in this case even though the <code>open</code> method specified
+<code>Folder.READ_WRITE</code>.  Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.auth.mechanisms">mail.imap.auth.mechanisms</A></TD>
+<TD>String</TD>
+<TD>
+If set, lists the authentication mechanisms to consider, and the order
+in which to consider them.  Only mechanisms supported by the server and
+supported by the current implementation will be used.
+The default is <code>"PLAIN LOGIN NTLM"</code>, which includes all
+the authentication mechanisms supported by the current implementation
+except XOAUTH2.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.auth.login.disable">mail.imap.auth.login.disable</A></TD>
+<TD>boolean</TD>
+<TD>If true, prevents use of the non-standard <code>AUTHENTICATE LOGIN</code>
+command, instead using the plain <code>LOGIN</code> command.
+Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.auth.plain.disable">mail.imap.auth.plain.disable</A></TD>
+<TD>boolean</TD>
+<TD>If true, prevents use of the <code>AUTHENTICATE PLAIN</code> command.
+Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.auth.ntlm.disable">mail.imap.auth.ntlm.disable</A></TD>
+<TD>boolean</TD>
+<TD>If true, prevents use of the <code>AUTHENTICATE NTLM</code> command.
+Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.auth.ntlm.domain">mail.imap.auth.ntlm.domain</A></TD>
+<TD>String</TD>
+<TD>
+The NTLM authentication domain.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.auth.ntlm.flags">mail.imap.auth.ntlm.flags</A></TD>
+<TD>int</TD>
+<TD>
+NTLM protocol-specific flags.
+See <A HREF="http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags" TARGET="_top">
+http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags</A> for details.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.auth.xoauth2.disable">mail.imap.auth.xoauth2.disable</A></TD>
+<TD>boolean</TD>
+<TD>If true, prevents use of the <code>AUTHENTICATE XOAUTH2</code> command.
+Because the OAuth 2.0 protocol requires a special access token instead of
+a password, this mechanism is disabled by default.  Enable it by explicitly
+setting this property to "false" or by setting the "mail.imap.auth.mechanisms"
+property to "XOAUTH2".</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.proxyauth.user">mail.imap.proxyauth.user</A></TD>
+<TD>String</TD>
+<TD>If the server supports the PROXYAUTH extension, this property
+specifies the name of the user to act as.  Authenticate to the
+server using the administrator's credentials.  After authentication,
+the IMAP provider will issue the <code>PROXYAUTH</code> command with
+the user name specified in this property.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.localaddress">mail.imap.localaddress</A></TD>
+<TD>String</TD>
+<TD>
+Local address (host name) to bind to when creating the IMAP socket.
+Defaults to the address picked by the Socket class.
+Should not normally need to be set, but useful with multi-homed hosts
+where it's important to pick a particular local address to bind to.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.localport">mail.imap.localport</A></TD>
+<TD>int</TD>
+<TD>
+Local port number to bind to when creating the IMAP socket.
+Defaults to the port number picked by the Socket class.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.sasl.enable">mail.imap.sasl.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, attempt to use the javax.security.sasl package to
+choose an authentication mechanism for login.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.sasl.mechanisms">mail.imap.sasl.mechanisms</A></TD>
+<TD>String</TD>
+<TD>
+A space or comma separated list of SASL mechanism names to try
+to use.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.sasl.authorizationid">mail.imap.sasl.authorizationid</A></TD>
+<TD>String</TD>
+<TD>
+The authorization ID to use in the SASL authentication.
+If not set, the authentication ID (user name) is used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.sasl.realm">mail.imap.sasl.realm</A></TD>
+<TD>String</TD>
+<TD>The realm to use with SASL authentication mechanisms that
+require a realm, such as DIGEST-MD5.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.sasl.usecanonicalhostname">mail.imap.sasl.usecanonicalhostname</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, the canonical host name returned by
+{@link java.net.InetAddress#getCanonicalHostName InetAddress.getCanonicalHostName}
+is passed to the SASL mechanism, instead of the host name used to connect.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.sasl.xgwtrustedapphack.enable">mail.imap.sasl. xgwtrustedapphack.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, enables a workaround for a bug in the Novell Groupwise
+XGWTRUSTEDAPP SASL mechanism, when that mechanism is being used.
+Defaults to true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.socketFactory">mail.imap.socketFactory</A></TD>
+<TD>SocketFactory</TD>
+<TD>
+If set to a class that implements the
+<code>javax.net.SocketFactory</code> interface, this class
+will be used to create IMAP sockets.  Note that this is an
+instance of a class, not a name, and must be set using the
+<code>put</code> method, not the <code>setProperty</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.socketFactory.class">mail.imap.socketFactory.class</A></TD>
+<TD>String</TD>
+<TD>
+If set, specifies the name of a class that implements the
+<code>javax.net.SocketFactory</code> interface.  This class
+will be used to create IMAP sockets.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.socketFactory.fallback">mail.imap.socketFactory.fallback</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, failure to create a socket using the specified
+socket factory class will cause the socket to be created using
+the <code>java.net.Socket</code> class.
+Defaults to true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.socketFactory.port">mail.imap.socketFactory.port</A></TD>
+<TD>int</TD>
+<TD>
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.usesocketchannels">mail.imap.usesocketchannels</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, use SocketChannels instead of Sockets for connecting
+to the server.  Required if using the IdleManager.
+Ignored if a socket factory is set.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.ssl.enable">mail.imap.ssl.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, use SSL to connect and use the SSL port by default.
+Defaults to false for the "imap" protocol and true for the "imaps" protocol.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.ssl.checkserveridentity">mail.imap.ssl.checkserveridentity</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, check the server identity as specified by
+<A HREF="http://www.ietf.org/rfc/rfc2595.txt" TARGET="_top">RFC 2595</A>.
+These additional checks based on the content of the server's certificate
+are intended to prevent man-in-the-middle attacks.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.ssl.trust">mail.imap.ssl.trust</A></TD>
+<TD>String</TD>
+<TD>
+If set, and a socket factory hasn't been specified, enables use of a
+{@link com.sun.mail.util.MailSSLSocketFactory MailSSLSocketFactory}.
+If set to "*", all hosts are trusted.
+If set to a whitespace separated list of hosts, those hosts are trusted.
+Otherwise, trust depends on the certificate the server presents.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.ssl.socketFactory">mail.imap.ssl.socketFactory</A></TD>
+<TD>SSLSocketFactory</TD>
+<TD>
+If set to a class that extends the
+<code>javax.net.ssl.SSLSocketFactory</code> class, this class
+will be used to create IMAP SSL sockets.  Note that this is an
+instance of a class, not a name, and must be set using the
+<code>put</code> method, not the <code>setProperty</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.ssl.socketFactory.class">mail.imap.ssl.socketFactory.class</A></TD>
+<TD>String</TD>
+<TD>
+If set, specifies the name of a class that extends the
+<code>javax.net.ssl.SSLSocketFactory</code> class.  This class
+will be used to create IMAP SSL sockets.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.ssl.socketFactory.port">mail.imap.ssl.socketFactory.port</A></TD>
+<TD>int</TD>
+<TD>
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.ssl.protocols">mail.imap.ssl.protocols</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the SSL protocols that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the <code>javax.net.ssl.SSLSocket.setEnabledProtocols</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.ssl.ciphersuites">mail.imap.ssl.ciphersuites</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the SSL cipher suites that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the <code>javax.net.ssl.SSLSocket.setEnabledCipherSuites</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.starttls.enable">mail.imap.starttls.enable</A></TD>
+<TD>boolean</TD>
+<TD>If true, enables the use of the <code>STARTTLS</code> command (if
+supported by the server) to switch the connection to a TLS-protected
+connection before issuing any login commands.
+If the server does not support STARTTLS, the connection continues without
+the use of TLS; see the
+<A HREF="#mail.imap.starttls.required"><code>mail.imap.starttls.required</code></A>
+property to fail if STARTTLS isn't supported.
+Note that an appropriate trust store must configured so that the client
+will trust the server's certificate.
+Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.starttls.required">mail.imap.starttls.required</A></TD>
+<TD>boolean</TD>
+<TD>
+If true, requires the use of the <code>STARTTLS</code> command.
+If the server doesn't support the STARTTLS command, or the command
+fails, the connect method will fail.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.proxy.host">mail.imap.proxy.host</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the host name of an HTTP web proxy server that will be used for
+connections to the mail server.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.proxy.port">mail.imap.proxy.port</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the port number for the HTTP web proxy server.
+Defaults to port 80.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.proxy.user">mail.imap.proxy.user</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the user name to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.proxy.password">mail.imap.proxy.password</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the password to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.socks.host">mail.imap.socks.host</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the host name of a SOCKS5 proxy server that will be used for
+connections to the mail server.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.socks.port">mail.imap.socks.port</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the port number for the SOCKS5 proxy server.
+This should only need to be used if the proxy server is not using
+the standard port number of 1080.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.minidletime">mail.imap.minidletime</A></TD>
+<TD>int</TD>
+<TD>
+Applications typically call the idle method in a loop.  If another
+thread termiantes the IDLE command, it needs a chance to do its
+work before another IDLE command is issued.  The idle method enforces
+a delay to prevent thrashing between the IDLE command and regular
+commands.  This property sets the delay in milliseconds.  If not
+set, the default is 10 milliseconds.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.enableresponseevents">mail.imap.enableresponseevents</A></TD>
+<TD>boolean</TD>
+<TD>
+Enable special IMAP-specific events to be delivered to the Store's
+<code>ConnectionListener</code>.  If true, IMAP OK, NO, BAD, or BYE responses
+will be sent as <code>ConnectionEvent</code>s with a type of
+<code>IMAPStore.RESPONSE</code>.  The event's message will be the
+raw IMAP response string.
+By default, these events are not sent.
+NOTE: This capability is highly experimental and likely will change
+in future releases.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.enableimapevents">mail.imap.enableimapevents</A></TD>
+<TD>boolean</TD>
+<TD>
+Enable special IMAP-specific events to be delivered to the Store's
+<code>ConnectionListener</code>.  If true, unsolicited responses
+received during the Store's <code>idle</code> method will be sent
+as <code>ConnectionEvent</code>s with a type of
+<code>IMAPStore.RESPONSE</code>.  The event's message will be the
+raw IMAP response string.
+By default, these events are not sent.
+NOTE: This capability is highly experimental and likely will change
+in future releases.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.throwsearchexception">mail.imap.throwsearchexception</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true and a {@link javax.mail.search.SearchTerm SearchTerm}
+passed to the
+{@link javax.mail.Folder#search Folder.search}
+method is too complex for the IMAP protocol, throw a
+{@link javax.mail.search.SearchException SearchException}.
+For example, the IMAP protocol only supports less-than and greater-than
+comparisons for a {@link javax.mail.search.SizeTerm SizeTerm}.
+If false, the search will be done locally by fetching the required
+message data and comparing it locally.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.folder.class">mail.imap.folder.class</A></TD>
+<TD>String</TD>
+<TD>
+Class name of a subclass of <code>com.sun.mail.imap.IMAPFolder</code>.
+The subclass can be used to provide support for additional IMAP commands.
+The subclass must have public constructors of the form
+<code>public MyIMAPFolder(String fullName, char separator, IMAPStore store,
+Boolean isNamespace)</code> and
+<code>public MyIMAPFolder(ListInfo li, IMAPStore store)</code>
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.closefoldersonstorefailure">mail.imap.closefoldersonstorefailure</A></TD>
+<TD>boolean</TD>
+<TD>
+In some cases, a failure of the Store connection indicates a failure of the
+server, and all Folders associated with that Store should also be closed.
+In other cases, a Store connection failure may be a transient failure, and
+Folders may continue to operate normally.
+If this property is true (the default), failures in the Store connection cause
+all associated Folders to be closed.
+Set this property to false to better handle transient failures in the Store
+connection.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.finalizecleanclose">mail.imap.finalizecleanclose</A></TD>
+<TD>boolean</TD>
+<TD>
+When the finalizer for IMAPStore is called,
+should the connection to the server be closed cleanly, as if the
+application called the close method?
+Or should the connection to the server be closed without sending
+any commands to the server?
+Defaults to false, the connection is not closed cleanly.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.referralexception">mail.imap.referralexception</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true and an IMAP login referral is returned when the authentication
+succeeds, fail the connect request and throw a
+{@link com.sun.mail.imap.ReferralException ReferralException}.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.compress.enable">mail.imap.compress.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true and the IMAP server supports the COMPRESS=DEFLATE extension,
+compression will be enabled.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.compress.level">mail.imap.compress.level</A></TD>
+<TD>int</TD>
+<TD>
+The compression level to be used, in the range -1 to 9.
+See the {@link java.util.zip.Deflater Deflater} class for details.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.compress.strategy">mail.imap.compress.strategy</A></TD>
+<TD>int</TD>
+<TD>
+The compression strategy to be used, in the range 0 to 2.
+See the {@link java.util.zip.Deflater Deflater} class for details.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.imap.reusetagprefix">mail.imap.compress.strategy</A></TD>
+<TD>boolean</TD>
+<TD>
+If true, always use "A" for the IMAP command tag prefix.
+If false, the IMAP command tag prefix is different for each connection,
+from "A" through "ZZZ" and then wrapping around to "A".
+Applications should never need to set this.
+Defaults to false.
+</TD>
+</TR>
+
+</TABLE>
+<P>
+In general, applications should not need to use the classes in this
+package directly.  Instead, they should use the APIs defined by the
+<code>javax.mail</code> package (and subpackages).  Applications should
+never construct instances of <code>IMAPStore</code> or
+<code>IMAPFolder</code> directly.  Instead, they should use the
+<code>Session</code> method <code>getStore</code> to acquire an
+appropriate <code>Store</code> object, and from that acquire
+<code>Folder</code> objects.
+</P>
+<STRONG>Loggers</STRONG>
+<P>
+In addition to printing debugging output as controlled by the
+{@link javax.mail.Session Session} configuration,
+the com.sun.mail.imap provider logs the same information using
+{@link java.util.logging.Logger} as described in the following table:
+</P>
+<TABLE BORDER SUMMARY="IMAP Loggers">
+<TR>
+<TH>Logger Name</TH>
+<TH>Logging Level</TH>
+<TH>Purpose</TH>
+</TR>
+
+<TR>
+<TD>com.sun.mail.imap</TD>
+<TD>CONFIG</TD>
+<TD>Configuration of the IMAPStore</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.imap</TD>
+<TD>FINE</TD>
+<TD>General debugging output</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.imap.connectionpool</TD>
+<TD>CONFIG</TD>
+<TD>Configuration of the IMAP connection pool</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.imap.connectionpool</TD>
+<TD>FINE</TD>
+<TD>Debugging output related to the IMAP connection pool</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.imap.messagecache</TD>
+<TD>CONFIG</TD>
+<TD>Configuration of the IMAP message cache</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.imap.messagecache</TD>
+<TD>FINE</TD>
+<TD>Debugging output related to the IMAP message cache</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.imap.protocol</TD>
+<TD>FINEST</TD>
+<TD>Complete protocol trace</TD>
+</TR>
+</TABLE>
+
+<STRONG>WARNING</STRONG>
+<P>
+<strong>WARNING:</strong> The APIs unique to this package should be
+considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxDecoder.java b/mail/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxDecoder.java
new file mode 100644
index 0000000..af35020
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxDecoder.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.text.StringCharacterIterator;
+import java.text.CharacterIterator;
+
+/**
+ * See the BASE64MailboxEncoder for a description of the RFC2060 and how
+ * mailbox names should be encoded.  This class will do the correct decoding
+ * for mailbox names.
+ *
+ * @author	Christopher Cotton
+ */
+
+public class BASE64MailboxDecoder {
+    
+    public static String decode(String original) {
+	if (original == null || original.length() == 0)
+	    return original;
+
+	boolean changedString = false;
+	int copyTo = 0;
+	// it will always be less than the original
+	char[] chars = new char[original.length()]; 
+	StringCharacterIterator iter = new StringCharacterIterator(original);
+	
+	for(char c = iter.first(); c != CharacterIterator.DONE; 
+	    c = iter.next()) {
+
+	    if (c == '&') {
+		changedString = true;
+		copyTo = base64decode(chars, copyTo, iter);
+	    } else {
+		chars[copyTo++] = c;
+	    }
+	}
+	
+	// now create our string from the char array
+	if (changedString) {
+	    return new String(chars, 0, copyTo);
+	} else {
+	    return original;
+	}	
+    }
+
+
+    protected static int base64decode(char[] buffer, int offset,
+				      CharacterIterator iter) {
+	boolean firsttime = true;
+	int leftover = -1;
+
+	while(true) {
+	    // get the first byte
+	    byte orig_0 = (byte) iter.next();
+	    if (orig_0 == -1) break; // no more chars
+	    if (orig_0 == '-') {
+		if (firsttime) {
+		    // means we got the string "&-" which is turned into a "&"
+		    buffer[offset++] = '&';
+		}
+		// we are done now
+		break;
+	    }
+	    firsttime = false;
+	    
+	    // next byte
+	    byte orig_1 = (byte) iter.next();	    
+	    if (orig_1 == -1 || orig_1 == '-')
+		break; // no more chars, invalid base64
+	    
+	    byte a, b, current;
+	    a = pem_convert_array[orig_0 & 0xff];
+	    b = pem_convert_array[orig_1 & 0xff];
+	    // The first decoded byte
+	    current = (byte)(((a << 2) & 0xfc) | ((b >>> 4) & 3));
+
+	    // use the leftover to create a Unicode Character (2 bytes)
+	    if (leftover != -1) {
+		buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
+		leftover = -1;
+	    } else {
+		leftover = current & 0xff;
+	    }
+	    
+	    byte orig_2 = (byte) iter.next();
+	    if (orig_2 == '=') { // End of this BASE64 encoding
+		continue;
+	    } else if (orig_2 == -1 || orig_2 == '-') {
+	    	break; // no more chars
+	    }
+	    	    
+	    // second decoded byte
+	    a = b;
+	    b = pem_convert_array[orig_2 & 0xff];
+	    current = (byte)(((a << 4) & 0xf0) | ((b >>> 2) & 0xf));
+
+	    // use the leftover to create a Unicode Character (2 bytes)
+	    if (leftover != -1) {
+		buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
+		leftover = -1;
+	    } else {
+		leftover = current & 0xff;
+	    }
+
+	    byte orig_3 = (byte) iter.next();
+	    if (orig_3 == '=') { // End of this BASE64 encoding
+		continue;
+	    } else if (orig_3 == -1 || orig_3 == '-') {
+	    	break;  // no more chars
+	    }
+	    
+	    // The third decoded byte
+	    a = b;
+	    b = pem_convert_array[orig_3 & 0xff];
+	    current = (byte)(((a << 6) & 0xc0) | (b & 0x3f));
+	    
+	    // use the leftover to create a Unicode Character (2 bytes)
+	    if (leftover != -1) {
+		buffer[offset++] = (char)(leftover << 8 | (current & 0xff));
+		leftover = -1;
+	    } else {
+		leftover = current & 0xff;
+	    }	    
+	}
+	
+	return offset;
+    }
+
+    /**
+     * This character array provides the character to value map
+     * based on RFC1521, but with the modification from RFC2060
+     * which changes the '/' to a ','.
+     */  
+
+    // shared with BASE64MailboxEncoder
+    static final char pem_array[] = {
+	'A','B','C','D','E','F','G','H', // 0
+	'I','J','K','L','M','N','O','P', // 1
+	'Q','R','S','T','U','V','W','X', // 2
+	'Y','Z','a','b','c','d','e','f', // 3
+	'g','h','i','j','k','l','m','n', // 4
+	'o','p','q','r','s','t','u','v', // 5
+	'w','x','y','z','0','1','2','3', // 6
+	'4','5','6','7','8','9','+',','  // 7
+    };
+
+    private static final byte pem_convert_array[] = new byte[256];
+
+    static {
+	for (int i = 0; i < 255; i++)
+	    pem_convert_array[i] = -1;
+	for(int i = 0; i < pem_array.length; i++)
+	    pem_convert_array[pem_array[i]] = (byte) i;
+    }    
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxEncoder.java b/mail/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxEncoder.java
new file mode 100644
index 0000000..1013baf
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/BASE64MailboxEncoder.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+
+
+/**
+ * From RFC2060:
+ *
+ * <blockquote><pre>
+ *
+ * 5.1.3.  Mailbox International Naming Convention
+ *
+ *   By convention, international mailbox names are specified using a
+ *   modified version of the UTF-7 encoding described in [UTF-7].  The
+ *   purpose of these modifications is to correct the following problems
+ *   with UTF-7:
+ *
+ *      1) UTF-7 uses the "+" character for shifting; this conflicts with
+ *         the common use of "+" in mailbox names, in particular USENET
+ *         newsgroup names.
+ *
+ *      2) UTF-7's encoding is BASE64 which uses the "/" character; this
+ *         conflicts with the use of "/" as a popular hierarchy delimiter.
+ *
+ *      3) UTF-7 prohibits the unencoded usage of "\"; this conflicts with
+ *         the use of "\" as a popular hierarchy delimiter.
+ *
+ *      4) UTF-7 prohibits the unencoded usage of "~"; this conflicts with
+ *         the use of "~" in some servers as a home directory indicator.
+ *
+ *      5) UTF-7 permits multiple alternate forms to represent the same
+ *         string; in particular, printable US-ASCII chararacters can be
+ *         represented in encoded form.
+ *
+ *   In modified UTF-7, printable US-ASCII characters except for "&amp;"
+ *   represent themselves; that is, characters with octet values 0x20-0x25
+ *   and 0x27-0x7e.  The character "&amp;" (0x26) is represented by the two-
+ *   octet sequence "&amp;-".
+ *
+ *   All other characters (octet values 0x00-0x1f, 0x7f-0xff, and all
+ *   Unicode 16-bit octets) are represented in modified BASE64, with a
+ *   further modification from [UTF-7] that "," is used instead of "/".
+ *   Modified BASE64 MUST NOT be used to represent any printing US-ASCII
+ *   character which can represent itself.
+ *
+ *   "&amp;" is used to shift to modified BASE64 and "-" to shift back to US-
+ *   ASCII.  All names start in US-ASCII, and MUST end in US-ASCII (that
+ *   is, a name that ends with a Unicode 16-bit octet MUST end with a "-
+ *   ").
+ *
+ *   For example, here is a mailbox name which mixes English, Japanese,
+ *   and Chinese text: ~peter/mail/&amp;ZeVnLIqe-/&amp;U,BTFw-
+ *
+ * </pre></blockquote>
+ *
+ * This class will do the correct Encoding for the IMAP mailboxes.
+ *
+ * @author	Christopher Cotton
+ */
+
+public class BASE64MailboxEncoder {
+    protected byte[] buffer = new byte[4];
+    protected int bufsize = 0;
+    protected boolean started = false;
+    protected Writer out = null;
+    
+
+    public static String encode(String original) {
+	BASE64MailboxEncoder base64stream = null;
+	char origchars[] = original.toCharArray();
+	int length = origchars.length;
+	boolean changedString = false;
+	CharArrayWriter writer = new CharArrayWriter(length);
+	
+	// loop over all the chars
+	for(int index = 0; index < length; index++) {
+	    char current = origchars[index];
+
+	    // octets in the range 0x20-0x25,0x27-0x7e are themselves
+	    // 0x26 "&" is represented as "&-"
+	    if (current >= 0x20 && current <= 0x7e) {
+		if (base64stream != null) {
+		    base64stream.flush();
+		}
+		
+		if (current == '&') {
+		    changedString = true;
+		    writer.write('&');
+		    writer.write('-');
+		} else {
+		    writer.write(current);
+		}
+	    } else {
+
+		// use a B64MailboxEncoder to write out the other bytes
+		// as a modified BASE64.  The stream will write out
+		// the beginning '&' and the ending '-' which is part
+		// of every encoding.
+
+		if (base64stream == null) {
+		    base64stream = new BASE64MailboxEncoder(writer);
+		    changedString = true;
+		}
+		
+		base64stream.write(current);
+	    }
+	}
+
+
+	if (base64stream != null) {
+	    base64stream.flush();
+	}
+
+	if (changedString) {
+	    return writer.toString();
+	} else {
+	    return original;
+	}
+    }
+
+
+    /**
+     * Create a BASE64 encoder
+     *
+     * @param	what	where to write the encoded name
+     */
+    public BASE64MailboxEncoder(Writer what) {
+	out = what;
+    }
+
+    public void write(int c) {
+	try {
+	    // write out the initial character if this is the first time
+	    if (!started) {
+		started = true;
+		out.write('&');
+	    }
+	
+	    // we write each character as a 2 byte unicode character
+	    buffer[bufsize++] = (byte) (c >> 8);
+	    buffer[bufsize++] = (byte) (c & 0xff);
+
+	    if (bufsize >= 3) {
+		encode();
+		bufsize -= 3;
+	    }
+	} catch (IOException e) {
+	    //e.printStackTrace();
+	}
+    }
+    
+
+    public void flush() {
+	try {
+	    // flush any bytes we have
+	    if (bufsize > 0) {
+		encode();
+		bufsize = 0;
+	    }
+
+	    // write the terminating character of the encoding
+	    if (started) {
+		out.write('-');
+		started = false;
+	    }
+	} catch (IOException e) {
+	    //e.printStackTrace();
+	}
+    }
+
+
+    protected void encode() throws IOException {
+	byte a, b, c;
+	if (bufsize == 1) {
+	    a = buffer[0];
+	    b = 0;
+	    c = 0;
+	    out.write(pem_array[(a >>> 2) & 0x3F]);
+	    out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
+		// no padding characters are written
+	} else if (bufsize == 2) {
+	    a = buffer[0];
+	    b = buffer[1];
+	    c = 0;
+	    out.write(pem_array[(a >>> 2) & 0x3F]);
+	    out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
+	    out.write(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
+		// no padding characters are written
+	} else {
+	    a = buffer[0];
+	    b = buffer[1];
+	    c = buffer[2];
+	    out.write(pem_array[(a >>> 2) & 0x3F]);
+	    out.write(pem_array[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
+	    out.write(pem_array[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
+	    out.write(pem_array[c & 0x3F]);
+
+	    // copy back the extra byte
+	    if (bufsize == 4)
+		buffer[0] = buffer[3];
+        }
+    }
+
+    private final static char pem_array[] = {
+	'A','B','C','D','E','F','G','H', // 0
+	'I','J','K','L','M','N','O','P', // 1
+	'Q','R','S','T','U','V','W','X', // 2
+	'Y','Z','a','b','c','d','e','f', // 3
+	'g','h','i','j','k','l','m','n', // 4
+	'o','p','q','r','s','t','u','v', // 5
+	'w','x','y','z','0','1','2','3', // 6
+	'4','5','6','7','8','9','+',','  // 7
+    };
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/BODY.java b/mail/src/main/java/com/sun/mail/imap/protocol/BODY.java
new file mode 100644
index 0000000..abaed61
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/BODY.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.ByteArrayInputStream;
+import com.sun.mail.iap.*;
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * The BODY fetch response item.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class BODY implements Item {
+    
+    static final char[] name = {'B','O','D','Y'};
+
+    private final int msgno;
+    private final ByteArray data;
+    private final String section;
+    private final int origin;
+    private final boolean isHeader;
+
+    /**
+     * Constructor
+     *
+     * @param	r	the FetchResponse
+     * @exception	ParsingException	for parsing failures
+     */
+    public BODY(FetchResponse r) throws ParsingException {
+	msgno = r.getNumber();
+
+	r.skipSpaces();
+
+	if (r.readByte() != '[')
+	    throw new ParsingException(
+		    "BODY parse error: missing ``['' at section start");
+	section = r.readString(']');
+	if (r.readByte() != ']')
+	    throw new ParsingException(
+		    "BODY parse error: missing ``]'' at section end");
+	isHeader = section.regionMatches(true, 0, "HEADER", 0, 6);
+
+	if (r.readByte() == '<') { // origin
+	    origin = r.readNumber();
+	    r.skip(1); // skip '>';
+	} else
+	    origin = 0;
+
+	data = r.readByteArray();
+    }
+
+    public ByteArray getByteArray() {
+	return data;
+    }
+
+    public ByteArrayInputStream getByteArrayInputStream() {
+	if (data != null)
+	    return data.toByteArrayInputStream();
+	else
+	    return null;
+    }
+
+    public boolean isHeader() {
+	return isHeader;
+    }
+
+    public String getSection() {
+	return section;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java b/mail/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java
new file mode 100644
index 0000000..3a5945c
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/BODYSTRUCTURE.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+import javax.mail.internet.ParameterList;
+import com.sun.mail.iap.*; 
+import com.sun.mail.util.PropUtil;
+
+/**
+ * A BODYSTRUCTURE response.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class BODYSTRUCTURE implements Item {
+    
+    static final char[] name =
+	{'B','O','D','Y','S','T','R','U','C','T','U','R','E'};
+    public int msgno;
+
+    public String type;		// Type
+    public String subtype;	// Subtype
+    public String encoding;	// Encoding
+    public int lines = -1;	// Size in lines
+    public int size = -1;	// Size in bytes
+    public String disposition;	// Disposition
+    public String id;		// Content-ID
+    public String description;	// Content-Description
+    public String md5;		// MD-5 checksum
+    public String attachment;	// Attachment name
+    public ParameterList cParams; // Body parameters
+    public ParameterList dParams; // Disposition parameters
+    public String[] language;	// Language
+    public BODYSTRUCTURE[] bodies; // array of BODYSTRUCTURE objects
+				   //  for multipart & message/rfc822
+    public ENVELOPE envelope;	// for message/rfc822
+
+    private static int SINGLE	= 1;
+    private static int MULTI	= 2;
+    private static int NESTED	= 3;
+    private int processedType;	// MULTI | SINGLE | NESTED
+
+    // special debugging output to debug parsing errors
+    private static final boolean parseDebug =
+	PropUtil.getBooleanSystemProperty("mail.imap.parse.debug", false);
+
+
+    public BODYSTRUCTURE(FetchResponse r) throws ParsingException {
+	if (parseDebug)
+	    System.out.println("DEBUG IMAP: parsing BODYSTRUCTURE");
+	msgno = r.getNumber();
+	if (parseDebug)
+	    System.out.println("DEBUG IMAP: msgno " + msgno);
+
+	r.skipSpaces();
+
+	if (r.readByte() != '(')
+	    throw new ParsingException(
+		"BODYSTRUCTURE parse error: missing ``('' at start");
+
+	if (r.peekByte() == '(') { // multipart
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: parsing multipart");
+	    type = "multipart";
+	    processedType = MULTI;
+	    List<BODYSTRUCTURE> v = new ArrayList<>(1);
+	    int i = 1;
+	    do {
+		v.add(new BODYSTRUCTURE(r));
+		/*
+		 * Even though the IMAP spec says there can't be any spaces
+		 * between parts, some servers erroneously put a space in
+		 * here.  In the spirit of "be liberal in what you accept",
+		 * we skip it.
+		 */
+		r.skipSpaces();
+	    } while (r.peekByte() == '(');
+
+	    // setup bodies.
+	    bodies = v.toArray(new BODYSTRUCTURE[v.size()]);
+
+	    subtype = r.readString(); // subtype
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: subtype " + subtype);
+
+	    if (r.isNextNonSpace(')')) { // done
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: parse DONE");
+		return;
+	    }
+
+	    // Else, we have extension data
+
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: parsing extension data");
+	    // Body parameters
+	    cParams = parseParameters(r);
+	    if (r.isNextNonSpace(')')) { // done
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: body parameters DONE");
+		return;
+	    }
+	    
+	    // Disposition
+	    byte b = r.peekByte();
+	    if (b == '(') {
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: parse disposition");
+		r.readByte();
+		disposition = r.readString();
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: disposition " +
+							disposition);
+		dParams = parseParameters(r);
+		if (!r.isNextNonSpace(')')) // eat the end ')'
+		    throw new ParsingException(
+			"BODYSTRUCTURE parse error: " +
+			"missing ``)'' at end of disposition in multipart");
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: disposition DONE");
+	    } else if (b == 'N' || b == 'n') {
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: disposition NIL");
+		r.skip(3); // skip 'NIL'
+	    } else {
+		/*
+		throw new ParsingException(
+		    "BODYSTRUCTURE parse error: " +
+		    type + "/" + subtype + ": " +
+		    "bad multipart disposition, b " + b);
+		*/
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: bad multipart disposition" +
+					", applying Exchange bug workaround");
+		description = r.readString();
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: multipart description " +
+					description);
+		// Throw away whatever comes after it, since we have no
+		// idea what it's supposed to be
+		while (r.readByte() == ' ')
+		    parseBodyExtension(r);
+		return;
+	    }
+
+	    // RFC3501 allows no body-fld-lang after body-fld-disp,
+	    // even though RFC2060 required it
+	    if (r.isNextNonSpace(')')) {
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: no body-fld-lang");
+		return; // done
+	    }
+
+	    // Language
+	    if (r.peekByte() == '(') { // a list follows
+		language = r.readStringList();
+		if (parseDebug)
+		    System.out.println(
+			"DEBUG IMAP: language len " + language.length);
+	    } else {
+		String l = r.readString();
+		if (l != null) {
+		    String[] la = { l };
+		    language = la;
+		    if (parseDebug)
+			System.out.println("DEBUG IMAP: language " + l);
+		}
+	    }
+
+	    // RFC3501 defines an optional "body location" next,
+	    // but for now we ignore it along with other extensions.
+
+	    // Throw away any further extension data
+	    while (r.readByte() == ' ')
+		parseBodyExtension(r);
+	} else if (r.peekByte() == ')') {	// (illegal) empty body
+	    /*
+	     * Domino will fail to return the body structure of nested messages.
+	     * Fake it by providing an empty message.  Could probably do better
+	     * with more work...
+	     */
+	    /*
+	     * XXX - this prevents the exception, but without the exception
+	     * the application has no way to know the data from the message
+	     * is missing.
+	     *
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: empty body, fake it");
+	    r.readByte();
+	    type = "text";
+	    subtype = "plain";
+	    lines = 0;
+	    size = 0;
+	     */
+	    throw new ParsingException(
+			    "BODYSTRUCTURE parse error: missing body content");
+	} else { // Single part
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: single part");
+	    type = r.readString();
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: type " + type);
+	    processedType = SINGLE;
+	    subtype = r.readString();
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: subtype " + subtype);
+
+	    // SIMS 4.0 returns NIL for a Content-Type of "binary", fix it here
+	    if (type == null) {
+		type = "application";
+		subtype = "octet-stream";
+	    }
+	    cParams = parseParameters(r);
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: cParams " + cParams);
+	    id = r.readString();
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: id " + id);
+	    description = r.readString();
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: description " + description);
+	    /*
+	     * XXX - Work around bug in Exchange 2010 that
+	     *       returns unquoted string.
+	     */
+	    encoding = r.readAtomString();
+	    if (encoding != null && encoding.equalsIgnoreCase("NIL")) {
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: NIL encoding" +
+					", applying Exchange bug workaround");
+		encoding = null;
+	    }
+	    /*
+	     * XXX - Work around bug in office365.com that returns
+	     *	     a string with a trailing space in some cases.
+	     */
+	    if (encoding != null)
+		encoding = encoding.trim();
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: encoding " + encoding);
+	    size = r.readNumber();
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: size " + size);
+	    if (size < 0)
+		throw new ParsingException(
+			    "BODYSTRUCTURE parse error: bad ``size'' element");
+
+	    // "text/*" & "message/rfc822" types have additional data ..
+	    if (type.equalsIgnoreCase("text")) {
+		lines = r.readNumber();
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: lines " + lines);
+		if (lines < 0)
+		    throw new ParsingException(
+			    "BODYSTRUCTURE parse error: bad ``lines'' element");
+	    } else if (type.equalsIgnoreCase("message") &&
+		     subtype.equalsIgnoreCase("rfc822")) {
+		// Nested message
+		processedType = NESTED;
+		// The envelope comes next, but sadly Gmail handles nested
+		// messages just like simple body parts and fails to return
+		// the envelope and body structure of the message (sort of
+		// like IMAP4 before rev1).
+		r.skipSpaces();
+		if (r.peekByte() == '(') {	// the envelope follows
+		    envelope = new ENVELOPE(r);
+		    if (parseDebug)
+			System.out.println(
+			    "DEBUG IMAP: got envelope of nested message");
+		    BODYSTRUCTURE[] bs = { new BODYSTRUCTURE(r) };
+		    bodies = bs;
+		    lines = r.readNumber();
+		    if (parseDebug)
+			System.out.println("DEBUG IMAP: lines " + lines);
+		    if (lines < 0)
+			throw new ParsingException(
+			    "BODYSTRUCTURE parse error: bad ``lines'' element");
+		} else {
+		    if (parseDebug)
+			System.out.println("DEBUG IMAP: " +
+			    "missing envelope and body of nested message");
+		}
+	    } else {
+		// Detect common error of including lines element on other types
+		r.skipSpaces();
+		byte bn = r.peekByte();
+		if (Character.isDigit((char)bn)) // number
+		    throw new ParsingException(
+			    "BODYSTRUCTURE parse error: server erroneously " +
+				"included ``lines'' element with type " +
+				type + "/" + subtype);
+	    }
+
+	    if (r.isNextNonSpace(')')) {
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: parse DONE");
+		return; // done
+	    }
+
+	    // Optional extension data
+
+	    // MD5
+	    md5 = r.readString();
+	    if (r.isNextNonSpace(')')) {
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: no MD5 DONE");
+		return; // done
+	    }
+	    
+	    // Disposition
+	    byte b = r.readByte();
+	    if (b == '(') {
+		disposition = r.readString();
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: disposition " +
+							disposition);
+		dParams = parseParameters(r);
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: dParams " + dParams);
+		if (!r.isNextNonSpace(')')) // eat the end ')'
+		    throw new ParsingException(
+			"BODYSTRUCTURE parse error: " +
+			"missing ``)'' at end of disposition");
+	    } else if (b == 'N' || b == 'n') {
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: disposition NIL");
+		r.skip(2); // skip 'NIL'
+	    } else {
+		throw new ParsingException(
+		    "BODYSTRUCTURE parse error: " +
+		    type + "/" + subtype + ": " +
+		    "bad single part disposition, b " + b);
+	    }
+
+	    if (r.isNextNonSpace(')')) {
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: disposition DONE");
+		return; // done
+	    }
+	    
+	    // Language
+	    if (r.peekByte() == '(') { // a list follows
+		language = r.readStringList();
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: language len " +
+							language.length);
+	    } else { // protocol is unnessarily complex here
+		String l = r.readString();
+		if (l != null) {
+		    String[] la = { l };
+		    language = la;
+		    if (parseDebug)
+			System.out.println("DEBUG IMAP: language " + l);
+		}
+	    }
+
+	    // RFC3501 defines an optional "body location" next,
+	    // but for now we ignore it along with other extensions.
+
+	    // Throw away any further extension data
+	    while (r.readByte() == ' ')
+		parseBodyExtension(r);
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: all DONE");
+	}
+    }
+
+    public boolean isMulti() {
+	return processedType == MULTI;
+    }
+
+    public boolean isSingle() {
+	return processedType == SINGLE;
+    }
+
+    public boolean isNested() {
+	return processedType == NESTED;
+    }
+
+    private ParameterList parseParameters(Response r)
+			throws ParsingException {
+	r.skipSpaces();
+
+	ParameterList list = null;
+	byte b = r.readByte();
+	if (b == '(') {
+	    list = new ParameterList();
+	    do {
+		String name = r.readString();
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: parameter name " + name);
+		if (name == null)
+		    throw new ParsingException(
+			"BODYSTRUCTURE parse error: " +
+			type + "/" + subtype + ": " +
+			"null name in parameter list");
+		String value = r.readString();
+		if (parseDebug)
+		    System.out.println("DEBUG IMAP: parameter value " + value);
+		if (value == null) {	// work around buggy servers
+		    if (parseDebug)
+			System.out.println("DEBUG IMAP: NIL parameter value" +
+					", applying Exchange bug workaround");
+		    value = "";
+		}
+		list.set(name, value);
+	    } while (!r.isNextNonSpace(')'));
+	    list.combineSegments();
+	} else if (b == 'N' || b == 'n') {
+	    if (parseDebug)
+		System.out.println("DEBUG IMAP: parameter list NIL");
+	    r.skip(2);
+	} else
+	    throw new ParsingException("Parameter list parse error");
+
+	return list;
+    }
+
+    private void parseBodyExtension(Response r) throws ParsingException {
+	r.skipSpaces();
+
+	byte b = r.peekByte();
+	if (b == '(') {
+	    r.skip(1); // skip '('
+	    do {
+		parseBodyExtension(r);
+	    } while (!r.isNextNonSpace(')'));
+	} else if (Character.isDigit((char)b)) // number
+	    r.readNumber();
+	else // nstring
+	    r.readString();
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/ENVELOPE.java b/mail/src/main/java/com/sun/mail/imap/protocol/ENVELOPE.java
new file mode 100644
index 0000000..eeafd14
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/ENVELOPE.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Date;
+import java.io.UnsupportedEncodingException;
+import java.text.ParseException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.MailDateFormat;
+import javax.mail.internet.MimeUtility;
+import com.sun.mail.iap.*;
+import com.sun.mail.util.PropUtil;
+
+/**
+ * The ENEVELOPE item of an IMAP FETCH response.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class ENVELOPE implements Item {
+    
+    // IMAP item name
+    static final char[] name = {'E','N','V','E','L','O','P','E'};
+    public int msgno;
+
+    public Date date = null;
+    public String subject;
+    public InternetAddress[] from;
+    public InternetAddress[] sender;
+    public InternetAddress[] replyTo;
+    public InternetAddress[] to;
+    public InternetAddress[] cc;
+    public InternetAddress[] bcc;
+    public String inReplyTo;
+    public String messageId;
+
+    // Used to parse dates
+    private static final MailDateFormat mailDateFormat = new MailDateFormat();
+
+    // special debugging output to debug parsing errors
+    private static final boolean parseDebug =
+	PropUtil.getBooleanSystemProperty("mail.imap.parse.debug", false);
+    
+    public ENVELOPE(FetchResponse r) throws ParsingException {
+	if (parseDebug)
+	    System.out.println("parse ENVELOPE");
+	msgno = r.getNumber();
+
+	r.skipSpaces();
+
+	if (r.readByte() != '(')
+	    throw new ParsingException("ENVELOPE parse error");
+	
+	String s = r.readString();
+	if (s != null) {
+	    try {
+            synchronized (mailDateFormat) {
+                date = mailDateFormat.parse(s);
+            }
+	    } catch (ParseException pex) {
+	    }
+	}
+	if (parseDebug)
+	    System.out.println("  Date: " + date);
+
+	subject = r.readString();
+	if (parseDebug)
+	    System.out.println("  Subject: " + subject);
+	if (parseDebug)
+	    System.out.println("  From addresses:");
+	from = parseAddressList(r);
+	if (parseDebug)
+	    System.out.println("  Sender addresses:");
+	sender = parseAddressList(r);
+	if (parseDebug)
+	    System.out.println("  Reply-To addresses:");
+	replyTo = parseAddressList(r);
+	if (parseDebug)
+	    System.out.println("  To addresses:");
+	to = parseAddressList(r);
+	if (parseDebug)
+	    System.out.println("  Cc addresses:");
+	cc = parseAddressList(r);
+	if (parseDebug)
+	    System.out.println("  Bcc addresses:");
+	bcc = parseAddressList(r);
+	inReplyTo = r.readString();
+	if (parseDebug)
+	    System.out.println("  In-Reply-To: " + inReplyTo);
+	messageId = r.readString();
+	if (parseDebug)
+	    System.out.println("  Message-ID: " + messageId);
+
+	if (!r.isNextNonSpace(')'))
+	    throw new ParsingException("ENVELOPE parse error");
+    }
+
+    private InternetAddress[] parseAddressList(Response r) 
+		throws ParsingException {
+	r.skipSpaces(); // skip leading spaces
+
+	byte b = r.readByte();
+	if (b == '(') {
+	    /*
+	     * Some broken servers (e.g., Yahoo Mail) return an empty
+	     * list instead of NIL.  Handle that here even though it
+	     * doesn't conform to the IMAP spec.
+	     */
+	    if (r.isNextNonSpace(')'))
+		return null;
+
+	    List<InternetAddress> v = new ArrayList<>();
+
+	    do {
+		IMAPAddress a = new IMAPAddress(r);
+		if (parseDebug)
+		    System.out.println("    Address: " + a);
+		// if we see an end-of-group address at the top, ignore it
+		if (!a.isEndOfGroup())
+		    v.add(a);
+	    } while (!r.isNextNonSpace(')'));
+
+	    return v.toArray(new InternetAddress[v.size()]);
+	} else if (b == 'N' || b == 'n') { // NIL
+	    r.skip(2); // skip 'NIL'
+	    return null;
+	} else
+	    throw new ParsingException("ADDRESS parse error");
+    }
+}
+
+class IMAPAddress extends InternetAddress {
+    private boolean group = false;
+    private InternetAddress[] grouplist;
+    private String groupname;
+
+    private static final long serialVersionUID = -3835822029483122232L;
+
+    IMAPAddress(Response r) throws ParsingException {
+        r.skipSpaces(); // skip leading spaces
+
+        if (r.readByte() != '(')
+            throw new ParsingException("ADDRESS parse error");
+
+        encodedPersonal = r.readString();
+
+        r.readString(); // throw away address_list
+	String mb = r.readString();
+	String host = r.readString();
+	// skip bogus spaces inserted by Yahoo IMAP server if
+	// "undisclosed-recipients" is a recipient
+	r.skipSpaces();
+	if (!r.isNextNonSpace(')')) // skip past terminating ')'
+            throw new ParsingException("ADDRESS parse error");
+
+	if (host == null) {
+	    // it's a group list, start or end
+	    group = true;
+	    groupname = mb;
+	    if (groupname == null)	// end of group list
+		return;
+	    // Accumulate a group list.  The members of the group
+	    // are accumulated in a List and the corresponding string
+	    // representation of the group is accumulated in a StringBuilder.
+	    StringBuilder sb = new StringBuilder();
+	    sb.append(groupname).append(':');
+	    List<InternetAddress> v = new ArrayList<>();
+	    while (r.peekByte() != ')') {
+		IMAPAddress a = new IMAPAddress(r);
+		if (a.isEndOfGroup())	// reached end of group
+		    break;
+		if (v.size() != 0)	// if not first element, need a comma
+		    sb.append(',');
+		sb.append(a.toString());
+		v.add(a);
+	    }
+	    sb.append(';');
+	    address = sb.toString();
+	    grouplist = v.toArray(new IMAPAddress[v.size()]);
+	} else {
+	    if (mb == null || mb.length() == 0)
+		address = host;
+	    else if (host.length() == 0)
+		address = mb;
+	    else
+		address = mb + "@" + host;
+	}
+
+    }
+
+    boolean isEndOfGroup() {
+	return group && groupname == null;
+    }
+
+    @Override
+    public boolean isGroup() {
+	return group;
+    }
+
+    @Override
+    public InternetAddress[] getGroup(boolean strict) throws AddressException {
+	if (grouplist == null)
+	    return null;
+	return grouplist.clone();
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/FLAGS.java b/mail/src/main/java/com/sun/mail/imap/protocol/FLAGS.java
new file mode 100644
index 0000000..5621aa8
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/FLAGS.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import javax.mail.Flags;
+import com.sun.mail.iap.*; 
+
+/**
+ * This class 
+ *
+ * @author  John Mani
+ */
+
+public class FLAGS extends Flags implements Item {
+
+    // IMAP item name
+    static final char[] name = {'F','L','A','G','S'};
+    public int msgno;
+
+    private static final long serialVersionUID = 439049847053756670L;
+
+    /**
+     * Constructor.
+     *
+     * @param	r	the IMAPResponse
+     * @exception	ParsingException	for parsing failures
+     */
+    public FLAGS(IMAPResponse r) throws ParsingException {
+	msgno = r.getNumber();
+
+	r.skipSpaces();
+	String[] flags = r.readSimpleList();
+	if (flags != null) { // if not empty flaglist
+	    for (int i = 0; i < flags.length; i++) {
+		String s = flags[i];
+		if (s.length() >= 2 && s.charAt(0) == '\\') {
+		    switch (Character.toUpperCase(s.charAt(1))) {
+		    case 'S': // \Seen
+			add(Flags.Flag.SEEN);
+			break;
+		    case 'R': // \Recent
+			add(Flags.Flag.RECENT);
+			break;
+		    case 'D':
+			if (s.length() >= 3) {
+			    char c = s.charAt(2);
+			    if (c == 'e' || c == 'E') // \Deleted
+				add(Flags.Flag.DELETED);
+			    else if (c == 'r' || c == 'R') // \Draft
+				add(Flags.Flag.DRAFT);
+			} else
+			    add(s);	// unknown, treat it as a user flag
+			break;
+		    case 'A': // \Answered
+			add(Flags.Flag.ANSWERED);
+			break;
+		    case 'F': // \Flagged
+			add(Flags.Flag.FLAGGED);
+			break;
+		    case '*': // \*
+			add(Flags.Flag.USER);
+			break;
+		    default:
+			add(s);		// unknown, treat it as a user flag
+			break;
+		    }
+		} else
+		    add(s);
+	    }
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/FetchItem.java b/mail/src/main/java/com/sun/mail/imap/protocol/FetchItem.java
new file mode 100644
index 0000000..1eaae6c
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/FetchItem.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.lang.reflect.*;
+
+import javax.mail.FetchProfile;
+import com.sun.mail.iap.ParsingException;
+
+/**
+ * Metadata describing a FETCH item.
+ * Note that the "name" field MUST be in uppercase. <p>
+ *
+ * @author  Bill Shannon
+ * @since JavaMail 1.4.6
+ */
+
+public abstract class FetchItem { 
+    private String name;
+    private FetchProfile.Item fetchProfileItem;
+
+    public FetchItem(String name, FetchProfile.Item fetchProfileItem) {
+	this.name = name;
+	this.fetchProfileItem = fetchProfileItem;
+    }
+
+    public String getName() {
+	return name;
+    }
+
+    public FetchProfile.Item getFetchProfileItem() {
+	return fetchProfileItem;
+    }
+
+    /**
+     * Parse the item into some kind of object appropriate for the item.
+     * Note that the item name will have been parsed and skipped already.
+     *
+     * @param	r	the response
+     * @return		the fetch item
+     * @exception	ParsingException	for parsing failures
+     */
+    public abstract Object parseItem(FetchResponse r) throws ParsingException;
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/FetchResponse.java b/mail/src/main/java/com/sun/mail/imap/protocol/FetchResponse.java
new file mode 100644
index 0000000..6477ba5
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/FetchResponse.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+import java.util.*;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.iap.*;
+
+/**
+ * This class represents a FETCH response obtained from the input stream
+ * of an IMAP server.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class FetchResponse extends IMAPResponse {
+    /*
+     * Regular Items are saved in the items array.
+     * Extension items (items handled by subclasses
+     * that extend the IMAP provider) are saved in the
+     * extensionItems map, indexed by the FETCH item name.
+     * The map is only created when needed.
+     *
+     * XXX - Should consider unifying the handling of
+     * regular items and extension items.
+     */
+    private Item[] items;
+    private Map<String, Object> extensionItems;
+    private final FetchItem[] fitems;
+
+    public FetchResponse(Protocol p) 
+		throws IOException, ProtocolException {
+	super(p);
+	fitems = null;
+	parse();
+    }
+
+    public FetchResponse(IMAPResponse r)
+		throws IOException, ProtocolException {
+	this(r, null);
+    }
+
+    /**
+     * Construct a FetchResponse that handles the additional FetchItems.
+     *
+     * @param	r	the IMAPResponse
+     * @param	fitems	the fetch items
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     * @since JavaMail 1.4.6
+     */
+    public FetchResponse(IMAPResponse r, FetchItem[] fitems)
+		throws IOException, ProtocolException {
+	super(r);
+	this.fitems = fitems;
+	parse();
+    }
+
+    public int getItemCount() {
+	return items.length;
+    }
+
+    public Item getItem(int index) {
+	return items[index];
+    }
+
+    public <T extends Item> T getItem(Class<T> c) {
+	for (int i = 0; i < items.length; i++) {
+	    if (c.isInstance(items[i]))
+		return c.cast(items[i]);
+	}
+
+	return null;
+    }
+
+    /**
+     * Return the first fetch response item of the given class
+     * for the given message number.
+     *
+     * @param	r	the responses
+     * @param	msgno	the message number
+     * @param	c	the class
+     * @param	<T>	the type of fetch item
+     * @return		the fetch item
+     */
+    public static <T extends Item> T getItem(Response[] r, int msgno,
+				Class<T> c) {
+	if (r == null)
+	    return null;
+
+	for (int i = 0; i < r.length; i++) {
+
+	    if (r[i] == null ||
+		!(r[i] instanceof FetchResponse) ||
+		((FetchResponse)r[i]).getNumber() != msgno)
+		continue;
+
+	    FetchResponse f = (FetchResponse)r[i];
+	    for (int j = 0; j < f.items.length; j++) {
+		if (c.isInstance(f.items[j]))
+		    return c.cast(f.items[j]);
+	    }
+	}
+
+	return null;
+    }
+
+    /**
+     * Return all fetch response items of the given class
+     * for the given message number.
+     *
+     * @param	r	the responses
+     * @param	msgno	the message number
+     * @param	c	the class
+     * @param	<T>	the type of fetch items
+     * @return		the list of fetch items
+     * @since JavaMail 1.5.2
+     */
+    public static <T extends Item> List<T> getItems(Response[] r, int msgno,
+				Class<T> c) {
+	List<T> items = new ArrayList<>();
+
+	if (r == null)
+	    return items;
+
+	for (int i = 0; i < r.length; i++) {
+
+	    if (r[i] == null ||
+		!(r[i] instanceof FetchResponse) ||
+		((FetchResponse)r[i]).getNumber() != msgno)
+		continue;
+
+	    FetchResponse f = (FetchResponse)r[i];
+	    for (int j = 0; j < f.items.length; j++) {
+		if (c.isInstance(f.items[j]))
+		    items.add(c.cast(f.items[j]));
+	    }
+	}
+
+	return items;
+    }
+
+    /**
+     * Return a map of the extension items found in this fetch response.
+     * The map is indexed by extension item name.  Callers should not
+     * modify the map.
+     *
+     * @return	Map of extension items, or null if none
+     * @since JavaMail 1.4.6
+     */
+    public Map<String, Object> getExtensionItems() {
+	return extensionItems;
+    }
+
+    private final static char[] HEADER = {'.','H','E','A','D','E','R'};
+    private final static char[] TEXT = {'.','T','E','X','T'};
+
+    private void parse() throws ParsingException {
+	if (!isNextNonSpace('('))
+	    throw new ParsingException(
+		"error in FETCH parsing, missing '(' at index " + index);
+
+	List<Item> v = new ArrayList<>();
+	Item i = null;
+	skipSpaces();
+	do {
+
+	    if (index >= size)
+		throw new ParsingException(
+		"error in FETCH parsing, ran off end of buffer, size " + size);
+
+	    i = parseItem();
+	    if (i != null)
+		v.add(i);
+	    else if (!parseExtensionItem())
+		throw new ParsingException(
+		    "error in FETCH parsing, unrecognized item at index " +
+		    index + ", starts with \"" + next20() + "\"");
+	} while (!isNextNonSpace(')'));
+
+	items = v.toArray(new Item[v.size()]);
+    }
+
+    /**
+     * Return the next 20 characters in the buffer, for exception messages.
+     */
+    private String next20() {
+	if (index + 20 > size)
+	    return ASCIIUtility.toString(buffer, index, size);
+	else
+	    return ASCIIUtility.toString(buffer, index, index + 20) + "...";
+    }
+
+    /**
+     * Parse the item at the current position in the buffer,
+     * skipping over the item if successful.  Otherwise, return null
+     * and leave the buffer position unmodified.
+     */
+    @SuppressWarnings("empty")
+    private Item parseItem() throws ParsingException {
+	switch (buffer[index]) {
+	case 'E': case 'e':
+	    if (match(ENVELOPE.name))
+		return new ENVELOPE(this);
+	    break;
+	case 'F': case 'f':
+	    if (match(FLAGS.name))
+		return new FLAGS((IMAPResponse)this);
+	    break;
+	case 'I': case 'i':
+	    if (match(INTERNALDATE.name))
+		return new INTERNALDATE(this);
+	    break;
+	case 'B': case 'b':
+	    if (match(BODYSTRUCTURE.name))
+		return new BODYSTRUCTURE(this);
+	    else if (match(BODY.name)) {
+		if (buffer[index] == '[')
+		    return new BODY(this);
+		else
+		    return new BODYSTRUCTURE(this);
+	    }
+	    break;
+	case 'R': case 'r':
+	    if (match(RFC822SIZE.name))
+		return new RFC822SIZE(this);
+	    else if (match(RFC822DATA.name)) {
+		boolean isHeader = false;
+		if (match(HEADER))
+		    isHeader = true;	// skip ".HEADER"
+		else if (match(TEXT))
+		    isHeader = false;	// skip ".TEXT"
+		return new RFC822DATA(this, isHeader);
+	    }
+	    break;
+	case 'U': case 'u':
+	    if (match(UID.name))
+		return new UID(this);
+	    break;
+	case 'M': case 'm':
+	    if (match(MODSEQ.name))
+		return new MODSEQ(this);
+	    break;
+	default: 
+	    break;
+	}
+	return null;
+    }
+
+    /**
+     * If this item is a known extension item, parse it.
+     */
+    private boolean parseExtensionItem() throws ParsingException {
+	if (fitems == null)
+	    return false;
+	for (int i = 0; i < fitems.length; i++) {
+	    if (match(fitems[i].getName())) {
+		if (extensionItems == null)
+		    extensionItems = new HashMap<>();
+		extensionItems.put(fitems[i].getName(),
+				    fitems[i].parseItem(this));
+		return true;
+	    }
+	}
+	return false;
+    }
+
+    /**
+     * Does the current buffer match the given item name?
+     * itemName is the name of the IMAP item to compare against.
+     * NOTE that itemName *must* be all uppercase.
+     * If the match is successful, the buffer pointer (index)
+     * is incremented past the matched item.
+     */
+    private boolean match(char[] itemName) {
+	int len = itemName.length;
+	for (int i = 0, j = index; i < len;)
+	    // IMAP tokens are case-insensitive. We store itemNames in
+	    // uppercase, so convert operand to uppercase before comparing.
+	    if (Character.toUpperCase((char)buffer[j++]) != itemName[i++])
+		return false;
+	index += len;
+	return true;
+    }
+
+    /**
+     * Does the current buffer match the given item name?
+     * itemName is the name of the IMAP item to compare against.
+     * NOTE that itemName *must* be all uppercase.
+     * If the match is successful, the buffer pointer (index)
+     * is incremented past the matched item.
+     */
+    private boolean match(String itemName) {
+	int len = itemName.length();
+	for (int i = 0, j = index; i < len;)
+	    // IMAP tokens are case-insensitive. We store itemNames in
+	    // uppercase, so convert operand to uppercase before comparing.
+	    if (Character.toUpperCase((char)buffer[j++]) !=
+		    itemName.charAt(i++))
+		return false;
+	index += len;
+	return true;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/ID.java b/mail/src/main/java/com/sun/mail/imap/protocol/ID.java
new file mode 100644
index 0000000..66cb865
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/ID.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.*;
+import com.sun.mail.iap.*;
+
+/**
+ * This class represents the response to the ID command. <p>
+ *
+ * See <A HREF="http://www.ietf.org/rfc/rfc2971.txt">RFC 2971</A>.
+ *
+ * @since JavaMail 1.5.1
+ * @author Bill Shannon
+ */
+
+public class ID {
+
+    private Map<String, String> serverParams = null;
+
+    /**
+     * Parse the server parameter list out of the response.
+     *
+     * @param	r	the response
+     * @exception	ProtocolException	for protocol failures
+     */
+    public ID(Response r) throws ProtocolException {
+	// id_response ::= "ID" SPACE id_params_list
+	// id_params_list ::= "(" #(string SPACE nstring) ")" / nil
+	//       ;; list of field value pairs
+
+	r.skipSpaces();
+	int c = r.peekByte();
+	if (c == 'N' || c == 'n')	// assume NIL
+	    return;
+
+	if (c != '(')
+	    throw new ProtocolException("Missing '(' at start of ID");
+
+	serverParams = new HashMap<>();
+
+	String[] v = r.readStringList();
+	if (v != null) {
+	    for (int i = 0; i < v.length; i += 2) {
+		String name = v[i];
+		if (name == null)
+		    throw new ProtocolException("ID field name null");
+		if (i + 1 >= v.length)
+		    throw new ProtocolException("ID field without value: " +
+									name);
+		String value = v[i + 1];
+		serverParams.put(name, value);
+	    }
+	}
+	serverParams = Collections.unmodifiableMap(serverParams);
+    }
+
+    /**
+     * Return the parsed server params.
+     */
+    Map<String, String> getServerParams() {
+	return serverParams;
+    }
+
+    /**
+     * Convert the client parameters into an argument list for the ID command.
+     */
+    static Argument getArgumentList(Map<String,String> clientParams) {
+	Argument arg = new Argument();
+	if (clientParams == null) {
+	    arg.writeAtom("NIL");
+	    return arg;
+	}
+	Argument list = new Argument();
+	// add params to list
+	for (Map.Entry<String, String> e : clientParams.entrySet()) {
+	    list.writeNString(e.getKey());	// assume these are ASCII only
+	    list.writeNString(e.getValue());
+	}
+	arg.writeArgument(list);
+	return arg;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
new file mode 100644
index 0000000..acd44c8
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPProtocol.java
@@ -0,0 +1,3292 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+import java.util.*;
+import java.text.*;
+import java.lang.reflect.*;
+import java.util.logging.Level;
+import java.nio.charset.StandardCharsets;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.search.*;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.iap.*;
+import com.sun.mail.auth.Ntlm;
+
+import com.sun.mail.imap.ACL;
+import com.sun.mail.imap.Rights;
+import com.sun.mail.imap.AppendUID;
+import com.sun.mail.imap.CopyUID;
+import com.sun.mail.imap.SortTerm;
+import com.sun.mail.imap.ResyncData;
+import com.sun.mail.imap.Utility;
+
+/**
+ * This class extends the iap.Protocol object and implements IMAP
+ * semantics. In general, there is a method corresponding to each
+ * IMAP protocol command. The typical implementation issues the
+ * appropriate protocol command, collects all responses, processes
+ * those responses that are specific to this command and then
+ * dispatches the rest (the unsolicited ones) to the dispatcher
+ * using the <code>notifyResponseHandlers(r)</code>.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class IMAPProtocol extends Protocol {
+    
+    private boolean connected = false;	// did constructor succeed?
+    private boolean rev1 = false;	// REV1 server ?
+    private boolean referralException;	// throw exception for IMAP REFERRAL?
+    private boolean noauthdebug = true;	// hide auth info in debug output
+    private boolean authenticated;	// authenticated?
+    // WARNING: authenticated may be set to true in superclass
+    //		constructor, don't initialize it here.
+
+    private Map<String, String> capabilities;
+    // WARNING: capabilities may be initialized as a result of superclass
+    //		constructor, don't initialize it here.
+    private List<String> authmechs;
+    // WARNING: authmechs may be initialized as a result of superclass
+    //		constructor, don't initialize it here.
+    private boolean utf8;		// UTF-8 support enabled?
+
+    protected SearchSequence searchSequence;
+    protected String[] searchCharsets; 	// array of search charsets
+
+    protected Set<String> enabled;	// enabled capabilities - RFC 5161
+
+    private String name;
+    private SaslAuthenticator saslAuthenticator;	// if SASL is being used
+    private String proxyAuthUser;	// user name used with PROXYAUTH
+
+    private ByteArray ba;		// a buffer for fetchBody
+
+    private static final byte[] CRLF = { (byte)'\r', (byte)'\n'};
+
+    private static final FetchItem[] fetchItems = { };
+
+    /**
+     * Constructor.
+     * Opens a connection to the given host at given port.
+     *
+     * @param	name	the protocol name
+     * @param	host	host to connect to
+     * @param	port	port number to connect to
+     * @param	props	Properties object used by this protocol
+     * @param	isSSL	true if SSL should be used
+     * @param	logger	the MailLogger to use for debug output
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     */
+    public IMAPProtocol(String name, String host, int port, 
+			Properties props, boolean isSSL, MailLogger logger)
+			throws IOException, ProtocolException {
+	super(host, port, props, "mail." + name, isSSL, logger);
+
+	try {
+	    this.name = name;
+	    noauthdebug =
+		!PropUtil.getBooleanProperty(props, "mail.debug.auth", false);
+
+	    // in case it was not initialized in processGreeting
+	    referralException = PropUtil.getBooleanProperty(props,
+				prefix + ".referralexception", false);
+
+	    if (capabilities == null)
+		capability();
+
+	    if (hasCapability("IMAP4rev1"))
+		rev1 = true;
+
+	    searchCharsets = new String[2]; // 2, for now.
+	    searchCharsets[0] = "UTF-8";
+	    searchCharsets[1] = MimeUtility.mimeCharset(
+				    MimeUtility.getDefaultJavaCharset()
+				);
+
+	    connected = true;	// must be last statement in constructor
+	} finally {
+	    /*
+	     * If we get here because an exception was thrown, we need
+	     * to disconnect to avoid leaving a connected socket that
+	     * no one will be able to use because this object was never
+	     * completely constructed.
+	     */
+	    if (!connected)
+		disconnect();
+	}
+    }
+
+    /**
+     * Constructor for debugging.
+     *
+     * @param	in	the InputStream from which to read
+     * @param	out	the PrintStream to which to write
+     * @param	props	Properties object used by this protocol
+     * @param	debug	true to enable debugging output
+     * @exception	IOException	for I/O errors
+     */
+    public IMAPProtocol(InputStream in, PrintStream out,
+			Properties props, boolean debug)
+			throws IOException {
+	super(in, out, props, debug);
+
+	this.name = "imap";
+	noauthdebug =
+	    !PropUtil.getBooleanProperty(props, "mail.debug.auth", false);
+
+	if (capabilities == null)
+	    capabilities = new HashMap<>();
+
+	searchCharsets = new String[2]; // 2, for now.
+	searchCharsets[0] = "UTF-8";
+	searchCharsets[1] = MimeUtility.mimeCharset(
+				MimeUtility.getDefaultJavaCharset()
+			    );
+
+	connected = true;	// must be last statement in constructor
+    }
+
+    /**
+     * Return an array of FetchItem objects describing the
+     * FETCH items supported by this protocol.  Subclasses may
+     * override this method to combine their FetchItems with
+     * the FetchItems returned by the superclass.
+     *
+     * @return	an array of FetchItem objects
+     * @since JavaMail 1.4.6
+     */
+    public FetchItem[] getFetchItems() {
+	return fetchItems;
+    }
+
+    /**
+     * CAPABILITY command.
+     *
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.1.1"
+     */
+    public void capability() throws ProtocolException {
+	// Check CAPABILITY
+	Response[] r = command("CAPABILITY", null);
+	Response response = r[r.length-1];
+
+	if (response.isOK())
+	    handleCapabilityResponse(r);
+	handleResult(response);
+    }
+
+    /**
+     * Handle any untagged CAPABILITY response in the Response array.
+     *
+     * @param	r	the responses
+     */
+    public void handleCapabilityResponse(Response[] r) {
+	boolean first = true;
+	for (int i = 0, len = r.length; i < len; i++) {
+	    if (!(r[i] instanceof IMAPResponse))
+		continue;
+
+	    IMAPResponse ir = (IMAPResponse)r[i];
+
+	    // Handle *all* untagged CAPABILITY responses.
+	    // Though the spec seemingly states that only
+	    // one CAPABILITY response string is allowed (6.1.1),
+	    // some server vendors claim otherwise.
+	    if (ir.keyEquals("CAPABILITY")) {
+		if (first) {
+		    // clear out current when first response seen
+		    capabilities = new HashMap<>(10);
+		    authmechs = new ArrayList<>(5);
+		    first = false;
+		}
+		parseCapabilities(ir);
+	    }
+	}
+    }
+
+    /**
+     * If the response contains a CAPABILITY response code, extract
+     * it and save the capabilities.
+     *
+     * @param	r	the response
+     */
+    protected void setCapabilities(Response r) {
+	byte b;
+	while ((b = r.readByte()) > 0 && b != (byte)'[')
+	    ;
+	if (b == 0)
+	    return;
+	String s;
+	s = r.readAtom();
+	if (!s.equalsIgnoreCase("CAPABILITY"))
+	    return;
+	capabilities = new HashMap<>(10);
+	authmechs = new ArrayList<>(5);
+	parseCapabilities(r);
+    }
+
+    /**
+     * Parse the capabilities from a CAPABILITY response or from
+     * a CAPABILITY response code attached to (e.g.) an OK response.
+     *
+     * @param	r	the CAPABILITY response
+     */
+    protected void parseCapabilities(Response r) {
+	String s;
+	while ((s = r.readAtom()) != null) {
+	    if (s.length() == 0) {
+		if (r.peekByte() == (byte)']')
+		    break;
+		/*
+		 * Probably found something here that's not an atom.
+		 * Rather than loop forever or fail completely, we'll
+		 * try to skip this bogus capability.  This is known
+		 * to happen with:
+		 *   Netscape Messaging Server 4.03 (built Apr 27 1999)
+		 * that returns:
+		 *   * CAPABILITY * CAPABILITY IMAP4 IMAP4rev1 ...
+		 * The "*" in the middle of the capability list causes
+		 * us to loop forever here.
+		 */
+		r.skipToken();
+	    } else {
+		capabilities.put(s.toUpperCase(Locale.ENGLISH), s);
+		if (s.regionMatches(true, 0, "AUTH=", 0, 5)) {
+		    authmechs.add(s.substring(5));
+		    if (logger.isLoggable(Level.FINE))
+			logger.fine("AUTH: " + s.substring(5));
+		}
+	    }
+	}
+    }
+
+    /**
+     * Check the greeting when first connecting; look for PREAUTH response.
+     *
+     * @param	r	the greeting response
+     * @exception	ProtocolException	for protocol failures
+     */
+    @Override
+    protected void processGreeting(Response r) throws ProtocolException {
+	if (r.isBYE()) {
+	    checkReferral(r);	// may throw exception
+	    throw new ConnectionException(this, r);
+	}
+	if (r.isOK()) {			// check if it's OK
+	    // XXX - is a REFERRAL response code really allowed here?
+	    // XXX - referralException hasn't been initialized in c'tor yet
+	    referralException = PropUtil.getBooleanProperty(props,
+				prefix + ".referralexception", false);
+	    if (referralException)
+		checkReferral(r);
+	    setCapabilities(r);
+	    return;
+	}
+	// only other choice is PREAUTH
+	assert r instanceof IMAPResponse;
+	IMAPResponse ir = (IMAPResponse)r;
+	if (ir.keyEquals("PREAUTH")) {
+	    authenticated = true;
+	    setCapabilities(r);
+	} else {
+	    disconnect();
+	    throw new ConnectionException(this, r);
+	}
+    }
+
+    /**
+     * Check for an IMAP login REFERRAL response code.
+     *
+     * @exception	IMAPReferralException	if REFERRAL response code found
+     * @see "RFC 2221"
+     */
+    private void checkReferral(Response r) throws IMAPReferralException {
+	String s = r.getRest();	// get the text after the response
+	if (s.startsWith("[")) {	// a response code
+	    int i = s.indexOf(' ');
+	    if (i > 0 && s.substring(1, i).equalsIgnoreCase("REFERRAL")) {
+		String url, msg;
+		int j = s.indexOf(']');
+		if (j > 0) {	// should always be true;
+		    url = s.substring(i + 1, j);
+		    msg = s.substring(j + 1).trim();
+		} else {
+		    url = s.substring(i + 1);
+		    msg = "";
+		}
+		if (r.isBYE())
+		    disconnect();
+		throw new IMAPReferralException(msg, url);
+	    }
+	}
+    }
+
+    /**
+     * Returns <code>true</code> if the connection has been authenticated,
+     * either due to a successful login, or due to a PREAUTH greeting response.
+     *
+     * @return	true if the connection has been authenticated
+     */
+    public boolean isAuthenticated() {
+	return authenticated;
+    }
+
+    /**
+     * Returns <code>true</code> if this is an IMAP4rev1 server
+     *
+     * @return	true if this is an IMAP4rev1 server
+     */
+    public boolean isREV1() {
+	return rev1;
+    }
+
+    /**
+     * Returns whether this Protocol supports non-synchronizing literals.
+     *
+     * @return	true if non-synchronizing literals are supported
+     */
+    @Override
+    protected boolean supportsNonSyncLiterals() {
+	return hasCapability("LITERAL+");
+    }
+
+    /**
+     * Read a response from the server.
+     *
+     * @return	the response
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     */
+    @Override
+    public Response readResponse() throws IOException, ProtocolException {
+	// assert Thread.holdsLock(this);
+	// can't assert because it's called from constructor
+	IMAPResponse r = new IMAPResponse(this);
+	if (r.keyEquals("FETCH"))
+	    r = new FetchResponse(r, getFetchItems());
+	return r;
+    }
+
+    /**
+     * Check whether the given capability is supported by
+     * this server. Returns <code>true</code> if so, otherwise
+     * returns false.
+     *
+     * @param	c	the capability name
+     * @return		true if the server has the capability
+     */
+    public boolean hasCapability(String c) {
+	if (c.endsWith("*")) {
+	    c = c.substring(0, c.length() - 1).toUpperCase(Locale.ENGLISH);
+	    Iterator<String> it = capabilities.keySet().iterator();
+	    while (it.hasNext()) {
+		if (it.next().startsWith(c))
+		    return true;
+	    }
+	    return false;
+	}
+	return capabilities.containsKey(c.toUpperCase(Locale.ENGLISH));
+    }
+
+    /**
+     * Return the map of capabilities returned by the server.
+     *
+     * @return	the Map of capabilities
+     * @since	JavaMail 1.4.1
+     */
+    public Map<String, String> getCapabilities() {
+	return capabilities;
+    }
+
+    /**
+     * Does the server support UTF-8?
+     *
+     * @since JavaMail 1.6.0
+     */
+    public boolean supportsUtf8() {
+	return utf8;
+    }
+
+    /**
+     * Close socket connection.
+     *
+     * This method just makes the Protocol.disconnect() method
+     * public.
+     */
+    @Override
+    public void disconnect() {
+	super.disconnect();
+	authenticated = false;	// just in case
+    }
+
+    /**
+     * The NOOP command.
+     *
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.1.2"
+     */
+    public void noop() throws ProtocolException {
+	logger.fine("IMAPProtocol noop");
+	simpleCommand("NOOP", null);
+    }
+
+    /**
+     * LOGOUT Command.
+     *
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.1.3"
+     */
+    public void logout() throws ProtocolException {
+	try {
+	    Response[] r = command("LOGOUT", null);
+
+	    authenticated = false;
+	    // dispatch any unsolicited responses.
+	    //  NOTE that the BYE response is dispatched here as well
+	    notifyResponseHandlers(r);
+	} finally {
+	    disconnect();
+	}
+    }
+
+    /**
+     * LOGIN Command.
+     * 
+     * @param  u		the username
+     * @param  p		the password
+     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+     * @see "RFC2060, section 6.2.2"
+     */
+    public void login(String u, String p) throws ProtocolException {
+	Argument args = new Argument();
+	args.writeString(u);
+	args.writeString(p);
+
+	Response[] r = null;
+	try {
+	    if (noauthdebug && isTracing()) {
+		logger.fine("LOGIN command trace suppressed");
+		suspendTracing();
+	    }
+	    r = command("LOGIN", args);
+	} finally {
+	    resumeTracing();
+	}
+
+	// handle an illegal but not uncommon untagged CAPABILTY response
+	handleCapabilityResponse(r);
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+
+	// Handle result of this command
+	if (noauthdebug && isTracing())
+	    logger.fine("LOGIN command result: " + r[r.length-1]);
+	handleLoginResult(r[r.length-1]);
+	// If the response includes a CAPABILITY response code, process it
+	setCapabilities(r[r.length-1]);
+	// if we get this far without an exception, we're authenticated
+	authenticated = true;
+    }
+
+    /**
+     * The AUTHENTICATE command with AUTH=LOGIN authenticate scheme
+     *
+     * @param  u		the username
+     * @param  p		the password
+     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+     * @see "RFC2060, section 6.2.1"
+     */
+    public synchronized void authlogin(String u, String p)
+				throws ProtocolException {
+	List<Response> v = new ArrayList<>();
+	String tag = null;
+	Response r = null;
+	boolean done = false;
+
+	try {
+
+	if (noauthdebug && isTracing()) {
+	    logger.fine("AUTHENTICATE LOGIN command trace suppressed");
+	    suspendTracing();
+	}
+
+	try {
+	    tag = writeCommand("AUTHENTICATE LOGIN", null);
+	} catch (Exception ex) {
+	    // Convert this into a BYE response
+	    r = Response.byeResponse(ex);
+	    done = true;
+	}
+
+	OutputStream os = getOutputStream(); // stream to IMAP server
+
+	/* Wrap a BASE64Encoder around a ByteArrayOutputstream
+	 * to craft b64 encoded username and password strings
+	 *
+	 * Note that the encoded bytes should be sent "as-is" to the
+	 * server, *not* as literals or quoted-strings.
+	 *
+	 * Also note that unlike the B64 definition in MIME, CRLFs 
+	 * should *not* be inserted during the encoding process. So, I
+	 * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
+	 * which should be sufficiently large !
+	 *
+	 * Finally, format the line in a buffer so it can be sent as
+	 * a single packet, to avoid triggering a bug in SUN's SIMS 2.0
+	 * server caused by patch 105346.
+	 */
+
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
+	boolean first = true;
+
+	while (!done) { // loop till we are done
+	    try {
+		r = readResponse();
+	    	if (r.isContinuation()) {
+		    // Server challenge ..
+		    String s;
+		    if (first) { // Send encoded username
+			s = u;
+			first = false;
+		    } else 	// Send encoded password
+			s = p;
+		    
+		    // obtain b64 encoded bytes
+		    b64os.write(s.getBytes(StandardCharsets.UTF_8));
+		    b64os.flush(); 	// complete the encoding
+
+		    bos.write(CRLF); 	// CRLF termination
+		    os.write(bos.toByteArray()); // write out line
+		    os.flush(); 	// flush the stream
+		    bos.reset(); 	// reset buffer
+		} else if (r.isTagged() && r.getTag().equals(tag))
+		    // Ah, our tagged response
+		    done = true;
+		else if (r.isBYE()) // outta here
+		    done = true;
+		// hmm .. unsolicited response here ?!
+	    } catch (Exception ioex) {
+		// convert this into a BYE response
+		r = Response.byeResponse(ioex);
+		done = true;
+	    }
+	    v.add(r);
+	}
+
+	} finally {
+	    resumeTracing();
+	}
+
+	Response[] responses = v.toArray(new Response[v.size()]);
+
+	// handle an illegal but not uncommon untagged CAPABILTY response
+	handleCapabilityResponse(responses);
+
+	/*
+	 * Dispatch untagged responses.
+	 * NOTE: in our current upper level IMAP classes, we add the
+	 * responseHandler to the Protocol object only *after* the 
+	 * connection has been authenticated. So, for now, the below
+	 * code really ends up being just a no-op.
+	 */
+	notifyResponseHandlers(responses);
+
+	// Handle the final OK, NO, BAD or BYE response
+	if (noauthdebug && isTracing())
+	    logger.fine("AUTHENTICATE LOGIN command result: " + r);
+	handleLoginResult(r);
+	// If the response includes a CAPABILITY response code, process it
+	setCapabilities(r);
+	// if we get this far without an exception, we're authenticated
+	authenticated = true;
+    }
+
+
+    /**
+     * The AUTHENTICATE command with AUTH=PLAIN authentication scheme.
+     * This is based heavly on the {@link #authlogin} method.
+     *
+     * @param  authzid		the authorization id
+     * @param  u		the username
+     * @param  p		the password
+     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+     * @see "RFC3501, section 6.2.2"
+     * @see "RFC2595, section 6"
+     * @since  JavaMail 1.3.2
+     */
+    public synchronized void authplain(String authzid, String u, String p)
+				throws ProtocolException {
+	List<Response> v = new ArrayList<>();
+	String tag = null;
+	Response r = null;
+	boolean done = false;
+
+	try {
+
+	if (noauthdebug && isTracing()) {
+	    logger.fine("AUTHENTICATE PLAIN command trace suppressed");
+	    suspendTracing();
+	}
+
+	try {
+	    tag = writeCommand("AUTHENTICATE PLAIN", null);
+	} catch (Exception ex) {
+	    // Convert this into a BYE response
+	    r = Response.byeResponse(ex);
+	    done = true;
+	}
+
+	OutputStream os = getOutputStream(); // stream to IMAP server
+
+	/* Wrap a BASE64Encoder around a ByteArrayOutputstream
+	 * to craft b64 encoded username and password strings
+	 *
+	 * Note that the encoded bytes should be sent "as-is" to the
+	 * server, *not* as literals or quoted-strings.
+	 *
+	 * Also note that unlike the B64 definition in MIME, CRLFs
+	 * should *not* be inserted during the encoding process. So, I
+	 * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
+	 * which should be sufficiently large !
+	 *
+	 * Finally, format the line in a buffer so it can be sent as
+	 * a single packet, to avoid triggering a bug in SUN's SIMS 2.0
+	 * server caused by patch 105346.
+	 */
+
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
+
+	while (!done) { // loop till we are done
+	    try {
+		r = readResponse();
+		if (r.isContinuation()) {
+		    // Server challenge ..
+		    final String nullByte = "\0";
+		    String s = (authzid == null ? "" : authzid) +
+				    nullByte + u + nullByte + p;
+
+		    // obtain b64 encoded bytes
+		    b64os.write(s.getBytes(StandardCharsets.UTF_8));
+		    b64os.flush(); 	// complete the encoding
+
+		    bos.write(CRLF); 	// CRLF termination
+		    os.write(bos.toByteArray()); // write out line
+		    os.flush(); 	// flush the stream
+		    bos.reset(); 	// reset buffer
+		} else if (r.isTagged() && r.getTag().equals(tag))
+		    // Ah, our tagged response
+		    done = true;
+		else if (r.isBYE()) // outta here
+		    done = true;
+		// hmm .. unsolicited response here ?!
+	    } catch (Exception ioex) {
+		// convert this into a BYE response
+		r = Response.byeResponse(ioex);
+		done = true;
+	    }
+	    v.add(r);
+	}
+
+	} finally {
+	    resumeTracing();
+	}
+
+	Response[] responses = v.toArray(new Response[v.size()]);
+
+	// handle an illegal but not uncommon untagged CAPABILTY response
+	handleCapabilityResponse(responses);
+
+	/*
+	 * Dispatch untagged responses.
+	 * NOTE: in our current upper level IMAP classes, we add the
+	 * responseHandler to the Protocol object only *after* the
+	 * connection has been authenticated. So, for now, the below
+	 * code really ends up being just a no-op.
+	 */
+	notifyResponseHandlers(responses);
+
+	// Handle the final OK, NO, BAD or BYE response
+	if (noauthdebug && isTracing())
+	    logger.fine("AUTHENTICATE PLAIN command result: " + r);
+	handleLoginResult(r);
+	// If the response includes a CAPABILITY response code, process it
+	setCapabilities(r);
+	// if we get this far without an exception, we're authenticated
+	authenticated = true;
+    }
+
+    /**
+     * The AUTHENTICATE command with AUTH=NTLM authentication scheme.
+     * This is based heavly on the {@link #authlogin} method.
+     *
+     * @param  authzid		the authorization id
+     * @param  u		the username
+     * @param  p		the password
+     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+     * @see "RFC3501, section 6.2.2"
+     * @see "RFC2595, section 6"
+     * @since  JavaMail 1.4.3
+     */
+    public synchronized void authntlm(String authzid, String u, String p)
+				throws ProtocolException {
+	List<Response> v = new ArrayList<>();
+	String tag = null;
+	Response r = null;
+	boolean done = false;
+
+	String type1Msg = null;
+	int flags = PropUtil.getIntProperty(props,
+	    "mail." + name + ".auth.ntlm.flags", 0);
+	String domain = props.getProperty(
+	    "mail." + name + ".auth.ntlm.domain", "");
+	Ntlm ntlm = new Ntlm(domain, getLocalHost(), u, p, logger);
+
+	try {
+
+	if (noauthdebug && isTracing()) {
+	    logger.fine("AUTHENTICATE NTLM command trace suppressed");
+	    suspendTracing();
+	}
+
+	try {
+	    tag = writeCommand("AUTHENTICATE NTLM", null);
+	} catch (Exception ex) {
+	    // Convert this into a BYE response
+	    r = Response.byeResponse(ex);
+	    done = true;
+	}
+
+	OutputStream os = getOutputStream(); // stream to IMAP server
+	boolean first = true;
+
+	while (!done) { // loop till we are done
+	    try {
+		r = readResponse();
+	    	if (r.isContinuation()) {
+		    // Server challenge ..
+		    String s;
+		    if (first) {
+			s = ntlm.generateType1Msg(flags);
+			first = false;
+		    } else {
+			s = ntlm.generateType3Msg(r.getRest());
+		    }
+ 
+		    os.write(s.getBytes(StandardCharsets.UTF_8));
+		    os.write(CRLF); 	// CRLF termination
+		    os.flush(); 	// flush the stream
+		} else if (r.isTagged() && r.getTag().equals(tag))
+		    // Ah, our tagged response
+		    done = true;
+		else if (r.isBYE()) // outta here
+		    done = true;
+		// hmm .. unsolicited response here ?!
+	    } catch (Exception ioex) {
+		// convert this into a BYE response
+		r = Response.byeResponse(ioex);
+		done = true;
+	    }
+	    v.add(r);
+	}
+
+	} finally {
+	    resumeTracing();
+	}
+
+	Response[] responses = v.toArray(new Response[v.size()]);
+
+	// handle an illegal but not uncommon untagged CAPABILTY response
+	handleCapabilityResponse(responses);
+
+	/*
+	 * Dispatch untagged responses.
+	 * NOTE: in our current upper level IMAP classes, we add the
+	 * responseHandler to the Protocol object only *after* the
+	 * connection has been authenticated. So, for now, the below
+	 * code really ends up being just a no-op.
+	 */
+	notifyResponseHandlers(responses);
+
+	// Handle the final OK, NO, BAD or BYE response
+	if (noauthdebug && isTracing())
+	    logger.fine("AUTHENTICATE NTLM command result: " + r);
+	handleLoginResult(r);
+	// If the response includes a CAPABILITY response code, process it
+	setCapabilities(r);
+	// if we get this far without an exception, we're authenticated
+	authenticated = true;
+    }
+
+    /**
+     * The AUTHENTICATE command with AUTH=XOAUTH2 authentication scheme.
+     * This is based heavly on the {@link #authlogin} method.
+     *
+     * @param  u		the username
+     * @param  p		the password
+     * @throws ProtocolException as thrown by {@link Protocol#handleResult}.
+     * @see "RFC3501, section 6.2.2"
+     * @see "RFC2595, section 6"
+     * @since  JavaMail 1.5.5
+     */
+    public synchronized void authoauth2(String u, String p)
+				throws ProtocolException {
+	List<Response> v = new ArrayList<>();
+	String tag = null;
+	Response r = null;
+	boolean done = false;
+
+	try {
+
+	if (noauthdebug && isTracing()) {
+	    logger.fine("AUTHENTICATE XOAUTH2 command trace suppressed");
+	    suspendTracing();
+	}
+
+	try {
+	    Argument args = new Argument();
+	    args.writeAtom("XOAUTH2");
+	    if (hasCapability("SASL-IR")) {
+		String resp = "user=" + u + "\001auth=Bearer " + p + "\001\001";
+		byte[] ba = BASE64EncoderStream.encode(
+				    resp.getBytes(StandardCharsets.UTF_8));
+		String irs = ASCIIUtility.toString(ba, 0, ba.length);
+		args.writeAtom(irs);
+	    }
+	    tag = writeCommand("AUTHENTICATE", args);
+	} catch (Exception ex) {
+	    // Convert this into a BYE response
+	    r = Response.byeResponse(ex);
+	    done = true;
+	}
+
+	OutputStream os = getOutputStream(); // stream to IMAP server
+
+	while (!done) { // loop till we are done
+	    try {
+		r = readResponse();
+		if (r.isContinuation()) {
+		    // Server challenge ..
+		    String resp = "user=" + u + "\001auth=Bearer " +
+				    p + "\001\001";
+		    byte[] b = BASE64EncoderStream.encode(
+				    resp.getBytes(StandardCharsets.UTF_8));
+		    os.write(b);	// write out response
+		    os.write(CRLF); 	// CRLF termination
+		    os.flush(); 	// flush the stream
+		} else if (r.isTagged() && r.getTag().equals(tag))
+		    // Ah, our tagged response
+		    done = true;
+		else if (r.isBYE()) // outta here
+		    done = true;
+		// hmm .. unsolicited response here ?!
+	    } catch (Exception ioex) {
+		// convert this into a BYE response
+		r = Response.byeResponse(ioex);
+		done = true;
+	    }
+	    v.add(r);
+	}
+
+	} finally {
+	    resumeTracing();
+	}
+
+	Response[] responses = v.toArray(new Response[v.size()]);
+
+	// handle an illegal but not uncommon untagged CAPABILTY response
+	handleCapabilityResponse(responses);
+
+	/*
+	 * Dispatch untagged responses.
+	 * NOTE: in our current upper level IMAP classes, we add the
+	 * responseHandler to the Protocol object only *after* the
+	 * connection has been authenticated. So, for now, the below
+	 * code really ends up being just a no-op.
+	 */
+	notifyResponseHandlers(responses);
+
+	// Handle the final OK, NO, BAD or BYE response
+	if (noauthdebug && isTracing())
+	    logger.fine("AUTHENTICATE XOAUTH2 command result: " + r);
+	handleLoginResult(r);
+	// If the response includes a CAPABILITY response code, process it
+	setCapabilities(r);
+	// if we get this far without an exception, we're authenticated
+	authenticated = true;
+    }
+
+    /**
+     * SASL-based login.
+     *
+     * @param	allowed	the SASL mechanisms we're allowed to use
+     * @param	realm	the SASL realm
+     * @param	authzid	the authorization id
+     * @param	u	the username
+     * @param	p	the password
+     * @exception	ProtocolException	for protocol failures
+     */
+    public void sasllogin(String[] allowed, String realm, String authzid,
+				String u, String p) throws ProtocolException {
+	boolean useCanonicalHostName = PropUtil.getBooleanProperty(props,
+		    "mail." + name + ".sasl.usecanonicalhostname", false);
+	String serviceHost;
+	if (useCanonicalHostName)
+	    serviceHost = getInetAddress().getCanonicalHostName();
+	else
+	    serviceHost = host;
+	if (saslAuthenticator == null) {
+	    try {
+		Class<?> sac = Class.forName(
+		    "com.sun.mail.imap.protocol.IMAPSaslAuthenticator");
+		Constructor<?> c = sac.getConstructor(new Class<?>[] {
+					IMAPProtocol.class,
+					String.class,
+					Properties.class,
+					MailLogger.class,
+					String.class
+					});
+		saslAuthenticator = (SaslAuthenticator)c.newInstance(
+					new Object[] {
+					this,
+					name,
+					props,
+					logger,
+					serviceHost
+					});
+	    } catch (Exception ex) {
+		logger.log(Level.FINE, "Can't load SASL authenticator", ex);
+		// probably because we're running on a system without SASL
+		return;	// not authenticated, try without SASL
+	    }
+	}
+
+	// were any allowed mechanisms specified?
+	List<String> v;
+	if (allowed != null && allowed.length > 0) {
+	    // remove anything not supported by the server
+	    v = new ArrayList<>(allowed.length);
+	    for (int i = 0; i < allowed.length; i++)
+		if (authmechs.contains(allowed[i]))	// XXX - case must match
+		    v.add(allowed[i]);
+	} else {
+	    // everything is allowed
+	    v = authmechs;
+	}
+	String[] mechs = v.toArray(new String[v.size()]);
+
+	try {
+
+	    if (noauthdebug && isTracing()) {
+		logger.fine("SASL authentication command trace suppressed");
+		suspendTracing();
+	    }
+
+	    if (saslAuthenticator.authenticate(mechs, realm, authzid, u, p)) {
+		if (noauthdebug && isTracing())
+		    logger.fine("SASL authentication succeeded");
+		authenticated = true;
+	    } else {
+		if (noauthdebug && isTracing())
+		    logger.fine("SASL authentication failed");
+	    }
+	} finally {
+	    resumeTracing();
+	}
+    }
+
+    // XXX - for IMAPSaslAuthenticator access to protected method
+    OutputStream getIMAPOutputStream() {
+	return getOutputStream();
+    }
+
+    /**
+     * Handle the result response for a LOGIN or AUTHENTICATE command.
+     * Look for IMAP login REFERRAL.
+     *
+     * @param	r	the response
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.5.5
+     */
+    protected void handleLoginResult(Response r) throws ProtocolException {
+	if (hasCapability("LOGIN-REFERRALS") &&
+		(!r.isOK() || referralException))
+	    checkReferral(r);
+	handleResult(r);
+    }
+
+    /**
+     * PROXYAUTH Command.
+     * 
+     * @param	u	the PROXYAUTH user name
+     * @exception	ProtocolException	for protocol failures
+     * @see "Netscape/iPlanet/SunONE Messaging Server extension"
+     */
+    public void proxyauth(String u) throws ProtocolException {
+	Argument args = new Argument();
+	args.writeString(u);
+
+	simpleCommand("PROXYAUTH", args);
+	proxyAuthUser = u;
+    }
+
+    /**
+     * Get the user name used with the PROXYAUTH command.
+     * Returns null if PROXYAUTH was not used.
+     *
+     * @return	the PROXYAUTH user name
+     * @since	JavaMail 1.5.1
+     */
+    public String getProxyAuthUser() {
+	return proxyAuthUser;
+    }
+
+    /**
+     * UNAUTHENTICATE Command.
+     * 
+     * @exception	ProtocolException	for protocol failures
+     * @see "Netscape/iPlanet/SunONE Messaging Server extension"
+     * @since	JavaMail 1.5.1
+     */
+    public void unauthenticate() throws ProtocolException {
+	if (!hasCapability("X-UNAUTHENTICATE"))
+	    throw new BadCommandException("UNAUTHENTICATE not supported");
+	simpleCommand("UNAUTHENTICATE", null);
+	authenticated = false;
+    }
+
+    /**
+     * ID Command, for Yahoo! Mail IMAP server.
+     *
+     * @param	guid	the GUID
+     * @exception	ProtocolException	for protocol failures
+     * @deprecated As of JavaMail 1.5.1, replaced by
+     *		{@link #id(Map) id(Map&lt;String,String&gt;)}
+     * @since JavaMail 1.4.4
+     */
+    @Deprecated
+    public void id(String guid) throws ProtocolException {
+	// support this for now, but remove it soon
+	Map<String,String> gmap = new HashMap<>();
+	gmap.put("GUID", guid);
+	id(gmap);
+    }
+
+    /**
+     * STARTTLS Command.
+     * 
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC3501, section 6.2.1"
+     */
+    public void startTLS() throws ProtocolException {
+	try {
+	    super.startTLS("STARTTLS");
+	} catch (ProtocolException pex) {
+	    logger.log(Level.FINE, "STARTTLS ProtocolException", pex);
+	    // ProtocolException just means the command wasn't recognized,
+	    // or failed.  This should never happen if we check the
+	    // CAPABILITY first.
+	    throw pex;
+	} catch (Exception ex) {
+	    logger.log(Level.FINE, "STARTTLS Exception", ex);
+	    // any other exception means we have to shut down the connection
+	    // generate an artificial BYE response and disconnect
+	    Response[] r = { Response.byeResponse(ex) };
+	    notifyResponseHandlers(r);
+	    disconnect();
+	    throw new ProtocolException("STARTTLS failure", ex);
+	}
+    }
+
+    /**
+     * COMPRESS Command.  Only supports DEFLATE.
+     * 
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 4978"
+     */
+    public void compress() throws ProtocolException {
+	try {
+	    super.startCompression("COMPRESS DEFLATE");
+	} catch (ProtocolException pex) {
+	    logger.log(Level.FINE, "COMPRESS ProtocolException", pex);
+	    // ProtocolException just means the command wasn't recognized,
+	    // or failed.  This should never happen if we check the
+	    // CAPABILITY first.
+	    throw pex;
+	} catch (Exception ex) {
+	    logger.log(Level.FINE, "COMPRESS Exception", ex);
+	    // any other exception means we have to shut down the connection
+	    // generate an artificial BYE response and disconnect
+	    Response[] r = { Response.byeResponse(ex) };
+	    notifyResponseHandlers(r);
+	    disconnect();
+	    throw new ProtocolException("COMPRESS failure", ex);
+	}
+    }
+
+    /**
+     * Encode a mailbox name appropriately depending on whether or not
+     * the server supports UTF-8, and add the encoded name to the
+     * Argument.
+     *
+     * @param	args	the arguments
+     * @param	name	the name to encode
+     * @since	JavaMail 1.6.0
+     */
+    protected void writeMailboxName(Argument args, String name) {
+	if (utf8)
+	    args.writeString(name, StandardCharsets.UTF_8);
+	else
+	    // encode the mbox as per RFC2060
+	    args.writeString(BASE64MailboxEncoder.encode(name));
+    }
+
+    /**
+     * SELECT Command.
+     *
+     * @param	mbox	the mailbox name
+     * @return		MailboxInfo if successful
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.1"
+     */
+    public MailboxInfo select(String mbox) throws ProtocolException {
+	return select(mbox, null);
+    }
+
+    /**
+     * SELECT Command with QRESYNC data.
+     *
+     * @param	mbox	the mailbox name
+     * @param	rd	the ResyncData
+     * @return		MailboxInfo if successful
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.1"
+     * @see "RFC5162, section 3.1"
+     * @since	JavaMail 1.5.1
+     */
+    public MailboxInfo select(String mbox, ResyncData rd)
+				throws ProtocolException {
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	if (rd != null) {
+	    if (rd == ResyncData.CONDSTORE) {
+		if (!hasCapability("CONDSTORE"))
+		    throw new BadCommandException("CONDSTORE not supported");
+		args.writeArgument(new Argument().writeAtom("CONDSTORE"));
+	    } else {
+		if (!hasCapability("QRESYNC")) 
+		    throw new BadCommandException("QRESYNC not supported");
+		args.writeArgument(resyncArgs(rd));
+	    }
+	}
+
+	Response[] r = command("SELECT", args);
+
+	// Note that MailboxInfo also removes those responses 
+	// it knows about
+	MailboxInfo minfo = new MailboxInfo(r);
+	
+	// dispatch any remaining untagged responses
+	notifyResponseHandlers(r);
+
+	Response response = r[r.length-1];
+
+	if (response.isOK()) { // command succesful 
+	    if (response.toString().indexOf("READ-ONLY") != -1)
+		minfo.mode = Folder.READ_ONLY;
+	    else
+		minfo.mode = Folder.READ_WRITE;
+	} 
+	
+	handleResult(response);
+	return minfo;
+    }
+
+    /**
+     * EXAMINE Command.
+     *
+     * @param	mbox	the mailbox name
+     * @return		MailboxInfo if successful
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.2"
+     */
+    public MailboxInfo examine(String mbox) throws ProtocolException {
+	return examine(mbox, null);
+    }
+
+    /**
+     * EXAMINE Command with QRESYNC data.
+     *
+     * @param	mbox	the mailbox name
+     * @param	rd	the ResyncData
+     * @return		MailboxInfo if successful
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.2"
+     * @see "RFC5162, section 3.1"
+     * @since	JavaMail 1.5.1
+     */
+    public MailboxInfo examine(String mbox, ResyncData rd)
+				throws ProtocolException {
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	if (rd != null) {
+	    if (rd == ResyncData.CONDSTORE) {
+		if (!hasCapability("CONDSTORE"))
+		    throw new BadCommandException("CONDSTORE not supported");
+		args.writeArgument(new Argument().writeAtom("CONDSTORE"));
+	    } else {
+		if (!hasCapability("QRESYNC")) 
+		    throw new BadCommandException("QRESYNC not supported");
+		args.writeArgument(resyncArgs(rd));
+	    }
+	}
+
+	Response[] r = command("EXAMINE", args);
+
+	// Note that MailboxInfo also removes those responses
+	// it knows about
+	MailboxInfo minfo = new MailboxInfo(r);
+	minfo.mode = Folder.READ_ONLY; // Obviously
+
+	// dispatch any remaining untagged responses
+	notifyResponseHandlers(r);
+
+	handleResult(r[r.length-1]);
+	return minfo;
+    }
+
+    /**
+     * Generate a QRESYNC argument list based on the ResyncData.
+     */
+    private static Argument resyncArgs(ResyncData rd) {
+	Argument cmd = new Argument();
+	cmd.writeAtom("QRESYNC");
+	Argument args = new Argument();
+	args.writeNumber(rd.getUIDValidity());
+	args.writeNumber(rd.getModSeq());
+	UIDSet[] uids = Utility.getResyncUIDSet(rd);
+	if (uids != null)
+	    args.writeString(UIDSet.toString(uids));
+	cmd.writeArgument(args);
+	return cmd;
+    }
+
+    /**
+     * ENABLE Command.
+     *
+     * @param	cap	the name of the capability to enable
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 5161"
+     * @since	JavaMail 1.5.1
+     */
+    public void enable(String cap) throws ProtocolException {
+	if (!hasCapability("ENABLE")) 
+	    throw new BadCommandException("ENABLE not supported");
+	Argument args = new Argument();
+	args.writeAtom(cap);
+	simpleCommand("ENABLE", args);
+	if (enabled == null)
+	    enabled = new HashSet<>();
+	enabled.add(cap.toUpperCase(Locale.ENGLISH));
+
+	// update the utf8 flag
+	utf8 = isEnabled("UTF8=ACCEPT");
+    }
+
+    /**
+     * Is the capability/extension enabled?
+     *
+     * @param	cap	the capability name
+     * @return		true if enabled
+     * @see "RFC 5161"
+     * @since	JavaMail 1.5.1
+     */
+    public boolean isEnabled(String cap) {
+	if (enabled == null)
+	    return false;
+	else
+	    return enabled.contains(cap.toUpperCase(Locale.ENGLISH));
+    }
+
+    /**
+     * UNSELECT Command.
+     *
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 3691"
+     * @since	JavaMail 1.4.4
+     */
+    public void unselect() throws ProtocolException {
+	if (!hasCapability("UNSELECT")) 
+	    throw new BadCommandException("UNSELECT not supported");
+	simpleCommand("UNSELECT", null);
+    }
+
+    /**
+     * STATUS Command.
+     *
+     * @param	mbox	the mailbox
+     * @param	items	the STATUS items to request
+     * @return		STATUS results
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.10"
+     */
+    public Status status(String mbox, String[] items) 
+		throws ProtocolException {
+	if (!isREV1() && !hasCapability("IMAP4SUNVERSION")) 
+	    // STATUS is rev1 only, however the non-rev1 SIMS2.0 
+	    // does support this.
+	    throw new BadCommandException("STATUS not supported");
+
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	Argument itemArgs = new Argument();
+	if (items == null)
+	    items = Status.standardItems;
+
+	for (int i = 0, len = items.length; i < len; i++)
+	    itemArgs.writeAtom(items[i]);
+	args.writeArgument(itemArgs);
+
+	Response[] r = command("STATUS", args);
+
+	Status status = null;
+	Response response = r[r.length-1];
+
+	// Grab all STATUS responses
+	if (response.isOK()) { // command succesful 
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("STATUS")) {
+		    if (status == null)
+			status = new Status(ir);
+		    else // collect 'em all
+			Status.add(status, new Status(ir));
+		    r[i] = null;
+		}
+	    }
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return status;
+    }
+
+    /**
+     * CREATE Command.
+     *
+     * @param	mbox	the mailbox to create
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.3"
+     */
+    public void create(String mbox) throws ProtocolException {
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	simpleCommand("CREATE", args);
+    }
+
+    /**
+     * DELETE Command.
+     *
+     * @param	mbox	the mailbox to delete
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.4"
+     */
+    public void delete(String mbox) throws ProtocolException {
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	simpleCommand("DELETE", args);
+    }
+
+    /**
+     * RENAME Command.
+     *
+     * @param	o	old mailbox name
+     * @param	n	new mailbox name
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.5"
+     */
+    public void rename(String o, String n) throws ProtocolException {
+	Argument args = new Argument();	
+	writeMailboxName(args, o);
+	writeMailboxName(args, n);
+
+	simpleCommand("RENAME", args);
+    }
+
+    /**
+     * SUBSCRIBE Command.
+     *
+     * @param	mbox	the mailbox
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.6"
+     */
+    public void subscribe(String mbox) throws ProtocolException {
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	simpleCommand("SUBSCRIBE", args);
+    }
+
+    /**
+     * UNSUBSCRIBE Command.
+     *
+     * @param	mbox	the mailbox
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.7"
+     */
+    public void unsubscribe(String mbox) throws ProtocolException {
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	simpleCommand("UNSUBSCRIBE", args);
+    }
+
+    /**
+     * LIST Command.
+     *
+     * @param	ref	reference string
+     * @param	pattern	pattern to list
+     * @return		LIST results
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.8"
+     */
+    public ListInfo[] list(String ref, String pattern) 
+			throws ProtocolException {
+	return doList("LIST", ref, pattern);
+    }
+
+    /**
+     * LSUB Command.
+     *
+     * @param	ref	reference string
+     * @param	pattern	pattern to list
+     * @return		LSUB results
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.9"
+     */
+    public ListInfo[] lsub(String ref, String pattern) 
+			throws ProtocolException {
+	return doList("LSUB", ref, pattern);
+    }
+
+    /**
+     * Execute the specified LIST-like command (e.g., "LIST" or "LSUB"),
+     * using the reference and pattern.
+     *
+     * @param	cmd	the list command
+     * @param	ref	the reference string
+     * @param	pat	the pattern
+     * @return		array of ListInfo results
+     * @exception	ProtocolException	for protocol failures
+     * @since JavaMail 1.4.6
+     */
+    protected ListInfo[] doList(String cmd, String ref, String pat)
+			throws ProtocolException {
+	Argument args = new Argument();	
+	writeMailboxName(args, ref);
+	writeMailboxName(args, pat);
+
+	Response[] r = command(cmd, args);
+
+	ListInfo[] linfo = null;
+	Response response = r[r.length-1];
+
+	if (response.isOK()) { // command succesful 
+	    List<ListInfo> v = new ArrayList<>(1);
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals(cmd)) {
+		    v.add(new ListInfo(ir));
+		    r[i] = null;
+		}
+	    }
+	    if (v.size() > 0) {
+		linfo = v.toArray(new ListInfo[v.size()]);
+	    }
+	}
+	
+	// Dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return linfo;
+    }
+		
+    /**
+     * APPEND Command.
+     *
+     * @param	mbox	the mailbox
+     * @param	f	the message Flags
+     * @param	d	the message date
+     * @param	data	the message data
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.11"
+     */
+    public void append(String mbox, Flags f, Date d,
+			Literal data) throws ProtocolException {
+	appenduid(mbox, f, d, data, false);	// ignore return value
+    }
+
+    /**
+     * APPEND Command, return uid from APPENDUID response code.
+     *
+     * @param	mbox	the mailbox
+     * @param	f	the message Flags
+     * @param	d	the message date
+     * @param	data	the message data
+     * @return		APPENDUID data
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.3.11"
+     */
+    public AppendUID appenduid(String mbox, Flags f, Date d,
+			Literal data) throws ProtocolException {
+	return appenduid(mbox, f, d, data, true);
+    }
+
+    public AppendUID appenduid(String mbox, Flags f, Date d,
+			Literal data, boolean uid) throws ProtocolException {
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	if (f != null) { // set Flags in appended message
+	    // can't set the \Recent flag in APPEND
+	    if (f.contains(Flags.Flag.RECENT)) {
+		f = new Flags(f);		// copy, don't modify orig
+		f.remove(Flags.Flag.RECENT);	// remove RECENT from copy
+	    }
+
+	    /*
+	     * HACK ALERT: We want the flag_list to be written out
+	     * without any checking/processing of the bytes in it. If
+	     * I use writeString(), the flag_list will end up being
+	     * quoted since it contains "illegal" characters. So I
+	     * am depending on implementation knowledge that writeAtom()
+	     * does not do any checking/processing - it just writes out
+	     * the bytes. What we really need is a writeFoo() that just
+	     * dumps out its argument.
+	     */
+	    args.writeAtom(createFlagList(f));
+	}
+	if (d != null) // set INTERNALDATE in appended message
+	    args.writeString(INTERNALDATE.format(d));
+
+	args.writeBytes(data);
+
+	Response[] r = command("APPEND", args);
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+
+	// Handle result of this command
+	handleResult(r[r.length-1]);
+
+	if (uid)
+	    return getAppendUID(r[r.length-1]);
+	else
+	    return null;
+    }
+
+    /**
+     * If the response contains an APPENDUID response code, extract
+     * it and return an AppendUID object with the information.
+     */
+    private AppendUID getAppendUID(Response r) {
+	if (!r.isOK())
+	    return null;
+	byte b;
+	while ((b = r.readByte()) > 0 && b != (byte)'[')
+	    ;
+	if (b == 0)
+	    return null;
+	String s;
+	s = r.readAtom();
+	if (!s.equalsIgnoreCase("APPENDUID"))
+	    return null;
+
+	long uidvalidity = r.readLong();
+	long uid = r.readLong();
+	return new AppendUID(uidvalidity, uid);
+    }
+
+    /**
+     * CHECK Command.
+     *
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.4.1"
+     */
+    public void check() throws ProtocolException {
+	simpleCommand("CHECK", null);
+    }
+
+    /**
+     * CLOSE Command.
+     *
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.4.2"
+     */
+    public void close() throws ProtocolException {
+	simpleCommand("CLOSE", null);
+    }
+
+    /**
+     * EXPUNGE Command.
+     *
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2060, section 6.4.3"
+     */
+    public void expunge() throws ProtocolException {
+	simpleCommand("EXPUNGE", null);
+    }
+
+    /**
+     * UID EXPUNGE Command.
+     *
+     * @param	set	UIDs to expunge
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC4315, section 2"
+     */
+    public void uidexpunge(UIDSet[] set) throws ProtocolException {
+	if (!hasCapability("UIDPLUS")) 
+	    throw new BadCommandException("UID EXPUNGE not supported");
+	simpleCommand("UID EXPUNGE " + UIDSet.toString(set), null);
+    }
+
+    /**
+     * Fetch the BODYSTRUCTURE of the specified message.
+     *
+     * @param	msgno	the message number
+     * @return		the BODYSTRUCTURE item
+     * @exception	ProtocolException	for protocol failures
+     */
+    public BODYSTRUCTURE fetchBodyStructure(int msgno) 
+			throws ProtocolException {
+	Response[] r = fetch(msgno, "BODYSTRUCTURE");
+	notifyResponseHandlers(r);
+
+	Response response = r[r.length-1];
+	if (response.isOK())
+	    return FetchResponse.getItem(r, msgno, BODYSTRUCTURE.class);
+	else if (response.isNO())
+	    return null;
+	else {
+	    handleResult(response);
+	    return null;
+	}
+    }
+
+    /**
+     * Fetch given BODY section, without marking the message
+     * as SEEN.
+     *
+     * @param	msgno	the message number
+     * @param	section	the body section
+     * @return		the BODY item
+     * @exception	ProtocolException	for protocol failures
+     */
+    public BODY peekBody(int msgno, String section)
+			throws ProtocolException {
+	return fetchBody(msgno, section, true);
+    }
+
+    /**
+     * Fetch given BODY section.
+     *
+     * @param	msgno	the message number
+     * @param	section	the body section
+     * @return		the BODY item
+     * @exception	ProtocolException	for protocol failures
+     */
+    public BODY fetchBody(int msgno, String section)
+			throws ProtocolException {
+	return fetchBody(msgno, section, false);
+    }
+
+    protected BODY fetchBody(int msgno, String section, boolean peek)
+			throws ProtocolException {
+	Response[] r;
+
+	if (section == null)
+	    section = "";
+	String body = (peek ? "BODY.PEEK[" : "BODY[") + section + "]";
+	return fetchSectionBody(msgno, section, body);
+    }
+
+    /**
+     * Partial FETCH of given BODY section, without setting SEEN flag.
+     *
+     * @param	msgno	the message number
+     * @param	section	the body section
+     * @param	start	starting byte count
+     * @param	size	number of bytes to fetch
+     * @return		the BODY item
+     * @exception	ProtocolException	for protocol failures
+     */
+    public BODY peekBody(int msgno, String section, int start, int size)
+			throws ProtocolException {
+	return fetchBody(msgno, section, start, size, true, null);
+    }
+
+    /**
+     * Partial FETCH of given BODY section.
+     *
+     * @param	msgno	the message number
+     * @param	section	the body section
+     * @param	start	starting byte count
+     * @param	size	number of bytes to fetch
+     * @return		the BODY item
+     * @exception	ProtocolException	for protocol failures
+     */
+    public BODY fetchBody(int msgno, String section, int start, int size)
+			throws ProtocolException {
+	return fetchBody(msgno, section, start, size, false, null);
+    }
+
+    /**
+     * Partial FETCH of given BODY section, without setting SEEN flag.
+     *
+     * @param	msgno	the message number
+     * @param	section	the body section
+     * @param	start	starting byte count
+     * @param	size	number of bytes to fetch
+     * @param	ba	the buffer into which to read the response
+     * @return		the BODY item
+     * @exception	ProtocolException	for protocol failures
+     */
+    public BODY peekBody(int msgno, String section, int start, int size,
+				ByteArray ba) throws ProtocolException {
+	return fetchBody(msgno, section, start, size, true, ba);
+    }
+
+    /**
+     * Partial FETCH of given BODY section.
+     *
+     * @param	msgno	the message number
+     * @param	section	the body section
+     * @param	start	starting byte count
+     * @param	size	number of bytes to fetch
+     * @param	ba	the buffer into which to read the response
+     * @return		the BODY item
+     * @exception	ProtocolException	for protocol failures
+     */
+    public BODY fetchBody(int msgno, String section, int start, int size,
+				ByteArray ba) throws ProtocolException {
+	return fetchBody(msgno, section, start, size, false, ba);
+    }
+
+    protected BODY fetchBody(int msgno, String section, int start, int size,
+			boolean peek, ByteArray ba) throws ProtocolException {
+	this.ba = ba;	// save for later use by getResponseBuffer
+	if (section == null)
+	    section = "";
+	String body = (peek ? "BODY.PEEK[" : "BODY[") + section + "]<" +
+			String.valueOf(start) + "." +
+			String.valueOf(size) + ">";
+	return fetchSectionBody(msgno, section, body);
+    }
+
+    /**
+     * Fetch the given body section of the given message, using the
+     * body string "body".
+     *
+     * @param	msgno	the message number
+     * @param	section	the body section
+     * @param	body	the body string
+     * @return		the BODY item
+     * @exception	ProtocolException	for protocol failures
+     */
+    protected BODY fetchSectionBody(int msgno, String section, String body)
+			throws ProtocolException {
+	Response[] r;
+
+	r = fetch(msgno, body);
+	notifyResponseHandlers(r);
+
+	Response response = r[r.length-1];
+	if (response.isOK()) {
+	    List<BODY> bl = FetchResponse.getItems(r, msgno, BODY.class);
+	    if (bl.size() == 1)
+		return bl.get(0);	// the common case
+	    if (logger.isLoggable(Level.FINEST))
+		logger.finest("got " + bl.size() +
+				" BODY responses for section " + section);
+	    // more then one BODY response, have to find the right one
+	    for (BODY br : bl) {
+		if (logger.isLoggable(Level.FINEST))
+		    logger.finest("got BODY section " + br.getSection());
+		if (br.getSection().equalsIgnoreCase(section))
+		    return br;	// that's the one!
+	    }
+	    return null;	// couldn't find it
+	} else if (response.isNO())
+	    return null;
+	else {
+	    handleResult(response);
+	    return null;
+	}
+    }
+
+    /**
+     * Return a buffer to read a response into.
+     * The buffer is provided by fetchBody and is
+     * used only once.
+     *
+     * @return	the buffer to use
+     */
+    @Override
+    protected ByteArray getResponseBuffer() {
+	ByteArray ret = ba;
+	ba = null;
+	return ret;
+    }
+
+    /**
+     * Fetch the specified RFC822 Data item. 'what' names
+     * the item to be fetched. 'what' can be <code>null</code>
+     * to fetch the whole message.
+     *
+     * @param	msgno	the message number
+     * @param	what	the item to fetch
+     * @return		the RFC822DATA item
+     * @exception	ProtocolException	for protocol failures
+     */
+    public RFC822DATA fetchRFC822(int msgno, String what)
+			throws ProtocolException {
+	Response[] r = fetch(msgno,
+			     what == null ? "RFC822" : "RFC822." + what
+			    );
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+
+	Response response = r[r.length-1]; 
+	if (response.isOK())
+	    return FetchResponse.getItem(r, msgno, RFC822DATA.class);
+	else if (response.isNO())
+	    return null;
+	else {
+	    handleResult(response);
+	    return null;
+	}
+    }
+
+    /**
+     * Fetch the FLAGS for the given message.
+     *
+     * @param	msgno	the message number
+     * @return		the Flags
+     * @exception	ProtocolException	for protocol failures
+     */
+    public Flags fetchFlags(int msgno) throws ProtocolException {
+	Flags flags = null;
+	Response[] r = fetch(msgno, "FLAGS");
+
+	// Search for our FLAGS response
+	for (int i = 0, len = r.length; i < len; i++) {
+	    if (r[i] == null ||
+		!(r[i] instanceof FetchResponse) ||
+		((FetchResponse)r[i]).getNumber() != msgno)
+		continue;		
+	    
+	    FetchResponse fr = (FetchResponse)r[i];
+	    if ((flags = fr.getItem(FLAGS.class)) != null) {
+		r[i] = null; // remove this response
+		break;
+	    }
+	}
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+	handleResult(r[r.length-1]);
+	return flags;
+    }
+
+    /**
+     * Fetch the IMAP UID for the given message.
+     *
+     * @param	msgno	the message number
+     * @return		the UID
+     * @exception	ProtocolException	for protocol failures
+     */
+    public UID fetchUID(int msgno) throws ProtocolException {
+	Response[] r = fetch(msgno, "UID");
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+
+	Response response = r[r.length-1]; 
+	if (response.isOK())
+	    return FetchResponse.getItem(r, msgno, UID.class);
+	else if (response.isNO()) // XXX: Issue NOOP ?
+	    return null;
+	else {
+	    handleResult(response);
+	    return null; // NOTREACHED
+	}
+    }
+
+    /**
+     * Fetch the IMAP MODSEQ for the given message.
+     *
+     * @param	msgno	the message number
+     * @return		the MODSEQ
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.5.1
+     */
+    public MODSEQ fetchMODSEQ(int msgno) throws ProtocolException {
+	Response[] r = fetch(msgno, "MODSEQ");
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+
+	Response response = r[r.length-1]; 
+	if (response.isOK())
+	    return FetchResponse.getItem(r, msgno, MODSEQ.class);
+	else if (response.isNO()) // XXX: Issue NOOP ?
+	    return null;
+	else {
+	    handleResult(response);
+	    return null; // NOTREACHED
+	}
+    }
+		
+    /**
+     * Get the sequence number for the given UID.  Nothing is returned;
+     * the FETCH UID response must be handled by the reponse handler,
+     * along with any possible EXPUNGE responses, to ensure that the
+     * UID is matched with the correct sequence number.
+     *
+     * @param	uid	the UID
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.5.3
+     */
+    public void fetchSequenceNumber(long uid) throws ProtocolException {
+	Response[] r = fetch(String.valueOf(uid), "UID", true);	
+
+	notifyResponseHandlers(r);
+	handleResult(r[r.length-1]);
+    }
+
+    /**
+     * Get the sequence numbers for UIDs ranging from start till end.
+     * Since the range may be large and sparse, an array of the UIDs actually
+     * found is returned.  The caller must map these to messages after
+     * the FETCH UID responses have been handled by the reponse handler,
+     * along with any possible EXPUNGE responses, to ensure that the
+     * UIDs are matched with the correct sequence numbers.
+     *
+     * @param	start	first UID
+     * @param	end	last UID
+     * @return		array of sequence numbers
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.5.3
+     */
+    public long[] fetchSequenceNumbers(long start, long end)
+			throws ProtocolException {
+	Response[] r = fetch(String.valueOf(start) + ":" + 
+				(end == UIDFolder.LASTUID ? "*" : 
+				String.valueOf(end)),
+			     "UID", true);	
+
+	UID u;
+	List<UID> v = new ArrayList<>();
+	for (int i = 0, len = r.length; i < len; i++) {
+	    if (r[i] == null || !(r[i] instanceof FetchResponse))
+		continue;
+	    
+	    FetchResponse fr = (FetchResponse)r[i];
+	    if ((u = fr.getItem(UID.class)) != null)
+		v.add(u);
+	}
+		
+	notifyResponseHandlers(r);
+	handleResult(r[r.length-1]);
+
+	long[] lv = new long[v.size()];
+	for (int i = 0; i < v.size(); i++)
+	    lv[i] = v.get(i).uid;
+	return lv;
+    }
+ 
+    /**
+     * Get the sequence numbers for UIDs specified in the array.
+     * Nothing is returned.  The caller must map the UIDs to messages after
+     * the FETCH UID responses have been handled by the reponse handler,
+     * along with any possible EXPUNGE responses, to ensure that the
+     * UIDs are matched with the correct sequence numbers.
+     *
+     * @param	uids	the UIDs
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.5.3
+     */
+    public void fetchSequenceNumbers(long[] uids) throws ProtocolException {
+	StringBuilder sb = new StringBuilder();
+	for (int i = 0; i < uids.length; i++) {
+	    if (i > 0)
+		sb.append(",");
+	    sb.append(String.valueOf(uids[i]));
+	}
+
+	Response[] r = fetch(sb.toString(), "UID", true);	
+
+	notifyResponseHandlers(r);
+	handleResult(r[r.length-1]);
+    }
+
+    /**
+     * Get the sequence numbers for messages changed since the given
+     * modseq and with UIDs ranging from start till end.
+     * Also, prefetch the flags for the returned messages.
+     *
+     * @param	start	first UID
+     * @param	end	last UID
+     * @param	modseq	the MODSEQ
+     * @return		array of sequence numbers
+     * @exception	ProtocolException	for protocol failures
+     * @see	"RFC 4551"
+     * @since	JavaMail 1.5.1
+     */
+    public int[] uidfetchChangedSince(long start, long end, long modseq)
+			throws ProtocolException {
+	String msgSequence = String.valueOf(start) + ":" + 
+				(end == UIDFolder.LASTUID ? "*" : 
+				String.valueOf(end));
+	Response[] r = command("UID FETCH " + msgSequence +
+		" (FLAGS) (CHANGEDSINCE " + String.valueOf(modseq) + ")", null);
+
+	List<Integer> v = new ArrayList<>();
+	for (int i = 0, len = r.length; i < len; i++) {
+	    if (r[i] == null || !(r[i] instanceof FetchResponse))
+		continue;
+ 
+	    FetchResponse fr = (FetchResponse)r[i];
+	    v.add(Integer.valueOf(fr.getNumber()));
+	}
+		
+	notifyResponseHandlers(r);
+	handleResult(r[r.length-1]);
+
+	// Copy the list into 'matches'
+	int vsize = v.size();
+	int[] matches = new int[vsize];
+	for (int i = 0; i < vsize; i++)
+	    matches[i] = v.get(i).intValue();
+	return matches;
+    }
+
+    public Response[] fetch(MessageSet[] msgsets, String what)
+			throws ProtocolException {
+	return fetch(MessageSet.toString(msgsets), what, false);
+    }
+
+    public Response[] fetch(int start, int end, String what)
+			throws ProtocolException {
+	return fetch(String.valueOf(start) + ":" + String.valueOf(end), 
+		     what, false);
+    }
+
+    public Response[] fetch(int msg, String what) 
+			throws ProtocolException {
+	return fetch(String.valueOf(msg), what, false);
+    }
+
+    private Response[] fetch(String msgSequence, String what, boolean uid)
+			throws ProtocolException {
+	if (uid)
+	    return command("UID FETCH " + msgSequence +" (" + what + ")",null);
+	else
+	    return command("FETCH " + msgSequence + " (" + what + ")", null);
+    }
+
+    /**
+     * COPY command.
+     *
+     * @param	msgsets	the messages to copy
+     * @param	mbox	the mailbox to copy them to
+     * @exception	ProtocolException	for protocol failures
+     */
+    public void copy(MessageSet[] msgsets, String mbox)
+			throws ProtocolException {
+	copyuid(MessageSet.toString(msgsets), mbox, false);
+    }
+
+    /**
+     * COPY command.
+     *
+     * @param	start	start message number
+     * @param	end	end message number
+     * @param	mbox	the mailbox to copy them to
+     * @exception	ProtocolException	for protocol failures
+     */
+    public void copy(int start, int end, String mbox)
+			throws ProtocolException {
+	copyuid(String.valueOf(start) + ":" + String.valueOf(end),
+		    mbox, false);
+    }
+
+    /**
+     * COPY command, return uid from COPYUID response code.
+     *
+     * @param	msgsets	the messages to copy
+     * @param	mbox	the mailbox to copy them to
+     * @return		COPYUID response data
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 4315, section 3"
+     */
+    public CopyUID copyuid(MessageSet[] msgsets, String mbox)
+			throws ProtocolException {
+	return copyuid(MessageSet.toString(msgsets), mbox, true);
+    }
+
+    /**
+     * COPY command, return uid from COPYUID response code.
+     *
+     * @param	start	start message number
+     * @param	end	end message number
+     * @param	mbox	the mailbox to copy them to
+     * @return		COPYUID response data
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 4315, section 3"
+     */
+    public CopyUID copyuid(int start, int end, String mbox)
+			throws ProtocolException {
+	return copyuid(String.valueOf(start) + ":" + String.valueOf(end),
+		    mbox, true);
+    }
+
+    private CopyUID copyuid(String msgSequence, String mbox, boolean uid)
+				throws ProtocolException {
+	if (uid && !hasCapability("UIDPLUS")) 
+	    throw new BadCommandException("UIDPLUS not supported");
+
+	Argument args = new Argument();	
+	args.writeAtom(msgSequence);
+	writeMailboxName(args, mbox);
+
+	Response[] r = command("COPY", args);
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+
+	// Handle result of this command
+	handleResult(r[r.length-1]);
+
+	if (uid)
+	    return getCopyUID(r);
+	else
+	    return null;
+    }
+
+    /**
+     * MOVE command.
+     *
+     * @param	msgsets	the messages to move
+     * @param	mbox	the mailbox to move them to
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 6851"
+     * @since	JavaMail 1.5.4
+     */
+    public void move(MessageSet[] msgsets, String mbox)
+			throws ProtocolException {
+	moveuid(MessageSet.toString(msgsets), mbox, false);
+    }
+
+    /**
+     * MOVE command.
+     *
+     * @param	start	start message number
+     * @param	end	end message number
+     * @param	mbox	the mailbox to move them to
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 6851"
+     * @since	JavaMail 1.5.4
+     */
+    public void move(int start, int end, String mbox)
+			throws ProtocolException {
+	moveuid(String.valueOf(start) + ":" + String.valueOf(end),
+		    mbox, false);
+    }
+
+    /**
+     * MOVE Command, return uid from COPYUID response code.
+     *
+     * @param	msgsets	the messages to move
+     * @param	mbox	the mailbox to move them to
+     * @return		COPYUID response data
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 6851"
+     * @see "RFC 4315, section 3"
+     * @since	JavaMail 1.5.4
+     */
+    public CopyUID moveuid(MessageSet[] msgsets, String mbox)
+			throws ProtocolException {
+	return moveuid(MessageSet.toString(msgsets), mbox, true);
+    }
+
+    /**
+     * MOVE Command, return uid from COPYUID response code.
+     *
+     * @param	start	start message number
+     * @param	end	end message number
+     * @param	mbox	the mailbox to move them to
+     * @return		COPYUID response data
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 6851"
+     * @see "RFC 4315, section 3"
+     * @since	JavaMail 1.5.4
+     */
+    public CopyUID moveuid(int start, int end, String mbox)
+			throws ProtocolException {
+	return moveuid(String.valueOf(start) + ":" + String.valueOf(end),
+		    mbox, true);
+    }
+
+    /**
+     * MOVE Command, return uid from COPYUID response code.
+     *
+     * @see "RFC 6851"
+     * @see "RFC 4315, section 3"
+     * @since	JavaMail 1.5.4
+     */
+    private CopyUID moveuid(String msgSequence, String mbox, boolean uid)
+				throws ProtocolException {
+	if (!hasCapability("MOVE")) 
+	    throw new BadCommandException("MOVE not supported");
+	if (uid && !hasCapability("UIDPLUS")) 
+	    throw new BadCommandException("UIDPLUS not supported");
+
+	Argument args = new Argument();	
+	args.writeAtom(msgSequence);
+	writeMailboxName(args, mbox);
+
+	Response[] r = command("MOVE", args);
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+
+	// Handle result of this command
+	handleResult(r[r.length-1]);
+
+	if (uid)
+	    return getCopyUID(r);
+	else
+	    return null;
+    }
+
+    /**
+     * If the response contains a COPYUID response code, extract
+     * it and return a CopyUID object with the information.
+     *
+     * @param	rr	the responses to examine
+     * @return		the COPYUID response code data, or null if not found
+     * @since	JavaMail 1.5.4
+     */
+    protected CopyUID getCopyUID(Response[] rr) {
+	// most likely in the last response, so start there and work backward
+	for (int i = rr.length - 1; i >= 0; i--) {
+	    Response r = rr[i];
+	    if (r == null || !r.isOK())
+		continue;
+	    byte b;
+	    while ((b = r.readByte()) > 0 && b != (byte)'[')
+		;
+	    if (b == 0)
+		continue;
+	    String s;
+	    s = r.readAtom();
+	    if (!s.equalsIgnoreCase("COPYUID"))
+		continue;
+
+	    // XXX - need to merge more than one response for MOVE?
+	    long uidvalidity = r.readLong();
+	    String src = r.readAtom();
+	    String dst = r.readAtom();
+	    return new CopyUID(uidvalidity,
+			    UIDSet.parseUIDSets(src), UIDSet.parseUIDSets(dst));
+	}
+	return null;
+    }
+
+    public void storeFlags(MessageSet[] msgsets, Flags flags, boolean set)
+			throws ProtocolException {
+	storeFlags(MessageSet.toString(msgsets), flags, set);
+    }
+
+    public void storeFlags(int start, int end, Flags flags, boolean set)
+			throws ProtocolException {
+	storeFlags(String.valueOf(start) + ":" + String.valueOf(end),
+		   flags, set);
+    }
+
+    /**
+     * Set the specified flags on this message.
+     *
+     * @param	msg	the message number
+     * @param	flags	the flags
+     * @param	set	true to set, false to clear
+     * @exception	ProtocolException	for protocol failures
+     */
+    public void storeFlags(int msg, Flags flags, boolean set)
+			throws ProtocolException { 
+	storeFlags(String.valueOf(msg), flags, set);
+    }
+
+    private void storeFlags(String msgset, Flags flags, boolean set)
+			throws ProtocolException {
+	Response[] r;
+	if (set)
+	    r = command("STORE " + msgset + " +FLAGS " + 
+			 createFlagList(flags), null);
+	else
+	    r = command("STORE " + msgset + " -FLAGS " + 
+			createFlagList(flags), null);
+	
+	// Dispatch untagged responses
+	notifyResponseHandlers(r);
+	handleResult(r[r.length-1]);
+    }
+
+    /**
+     * Creates an IMAP flag_list from the given Flags object.
+     *
+     * @param	flags	the flags
+     * @return		the IMAP flag_list
+     * @since	JavaMail 1.5.4
+     */
+    protected String createFlagList(Flags flags) {
+	StringBuilder sb = new StringBuilder("("); // start of flag_list
+
+	Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags
+	boolean first = true;
+	for (int i = 0; i < sf.length; i++) {
+	    String s;
+	    Flags.Flag f = sf[i];
+	    if (f == Flags.Flag.ANSWERED)
+		s = "\\Answered";
+	    else if (f == Flags.Flag.DELETED)
+		s = "\\Deleted";
+	    else if (f == Flags.Flag.DRAFT)
+		s = "\\Draft";
+	    else if (f == Flags.Flag.FLAGGED)
+		s = "\\Flagged";
+	    else if (f == Flags.Flag.RECENT)
+		s = "\\Recent";
+	    else if (f == Flags.Flag.SEEN)
+		s = "\\Seen";
+	    else
+		continue;	// skip it
+	    if (first)
+		first = false;
+	    else
+		sb.append(' ');
+	    sb.append(s);
+	}
+
+	String[] uf = flags.getUserFlags(); // get the user flag strings
+	for (int i = 0; i < uf.length; i++) {
+	    if (first)
+		first = false;
+	    else
+		sb.append(' ');
+	    sb.append(uf[i]);
+	}
+
+	sb.append(")"); // terminate flag_list
+	return sb.toString();
+    }
+
+    /**
+     * Issue the given search criterion on the specified message sets.
+     * Returns array of matching sequence numbers. An empty array
+     * is returned if no matches are found.
+     *
+     * @param	msgsets	array of MessageSets
+     * @param	term	SearchTerm
+     * @return		array of matching sequence numbers.
+     * @exception	ProtocolException	for protocol failures
+     * @exception	SearchException	for search failures
+     */
+    public int[] search(MessageSet[] msgsets, SearchTerm term)
+			throws ProtocolException, SearchException {
+	return search(MessageSet.toString(msgsets), term);
+    }
+
+    /**
+     * Issue the given search criterion on all messages in this folder.
+     * Returns array of matching sequence numbers. An empty array
+     * is returned if no matches are found.
+     *
+     * @param	term	SearchTerm
+     * @return		array of matching sequence numbers.
+     * @exception	ProtocolException	for protocol failures
+     * @exception	SearchException	for search failures
+     */
+    public int[] search(SearchTerm term) 
+			throws ProtocolException, SearchException {
+	return search("ALL", term);
+    }
+
+    /*
+     * Apply the given SearchTerm on the specified sequence.
+     * Returns array of matching sequence numbers. Note that an empty
+     * array is returned for no matches.
+     */
+    private int[] search(String msgSequence, SearchTerm term)
+			throws ProtocolException, SearchException {
+	// Check if the search "text" terms contain only ASCII chars,
+	// or if utf8 support has been enabled (in which case CHARSET
+	// is not allowed; see RFC 6855, section 3, last paragraph)
+	if (supportsUtf8() || SearchSequence.isAscii(term)) {
+	    try {
+		return issueSearch(msgSequence, term, null);
+	    } catch (IOException ioex) { /* will not happen */ }
+	}
+
+	/*
+	 * The search "text" terms do contain non-ASCII chars and utf8
+	 * support has not been enabled.  We need to use:
+	 * "SEARCH CHARSET <charset> ..."
+	 * The charsets we try to use are UTF-8 and the locale's
+	 * default charset. If the server supports UTF-8, great, 
+	 * always use it. Else we try to use the default charset.
+	 */
+
+	// Cycle thru the list of charsets
+	for (int i = 0; i < searchCharsets.length; i++) {
+	    if (searchCharsets[i] == null)
+		continue;
+
+	    try {
+		return issueSearch(msgSequence, term, searchCharsets[i]);
+	    } catch (CommandFailedException cfx) {
+		/*
+		 * Server returned NO. For now, I'll just assume that 
+		 * this indicates that this charset is unsupported.
+		 * We can check the BADCHARSET response code once
+		 * that's spec'd into the IMAP RFC ..
+		 */
+		searchCharsets[i] = null;
+		continue;
+	    } catch (IOException ioex) {
+		/* Charset conversion failed. Try the next one */
+		continue;
+	    } catch (ProtocolException pex) {
+		throw pex;
+	    } catch (SearchException sex) {
+		throw sex;
+	    }
+	}
+
+	// No luck.
+	throw new SearchException("Search failed");
+    }
+
+    /* Apply the given SearchTerm on the specified sequence, using the
+     * given charset. <p>
+     * Returns array of matching sequence numbers. Note that an empty
+     * array is returned for no matches.
+     */
+    private int[] issueSearch(String msgSequence, SearchTerm term,
+      			      String charset) 
+	     throws ProtocolException, SearchException, IOException {
+
+	// Generate a search-sequence with the given charset
+	Argument args = getSearchSequence().generateSequence(term, 
+			  charset == null ? null : 
+					    MimeUtility.javaCharset(charset)
+			);
+	args.writeAtom(msgSequence);
+
+	Response[] r;
+
+	if (charset == null) // text is all US-ASCII
+	    r = command("SEARCH", args);
+	else
+	    r = command("SEARCH CHARSET " + charset, args);
+
+	Response response = r[r.length-1];
+	int[] matches = null;
+
+	// Grab all SEARCH responses
+	if (response.isOK()) { // command succesful
+	    List<Integer> v = new ArrayList<>();
+	    int num;
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		// There *will* be one SEARCH response.
+		if (ir.keyEquals("SEARCH")) {
+		    while ((num = ir.readNumber()) != -1)
+			v.add(Integer.valueOf(num));
+		    r[i] = null;
+		}
+	    }
+
+	    // Copy the list into 'matches'
+	    int vsize = v.size();
+	    matches = new int[vsize];
+	    for (int i = 0; i < vsize; i++)
+		matches[i] = v.get(i).intValue();
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return matches;
+    }
+
+    /**
+     * Get the SearchSequence object.
+     * The SearchSequence object instance is saved in the searchSequence
+     * field.  Subclasses of IMAPProtocol may override this method to
+     * return a subclass of SearchSequence, in order to add support for
+     * product-specific search terms.
+     *
+     * @return	the SearchSequence
+     * @since JavaMail 1.4.6
+     */
+    protected SearchSequence getSearchSequence() {
+	if (searchSequence == null)
+	    searchSequence = new SearchSequence(this);
+	return searchSequence;
+    }
+
+    /**
+     * Sort messages in the folder according to the specified sort criteria.
+     * If the search term is not null, limit the sort to only the messages
+     * that match the search term.
+     * Returns an array of sorted sequence numbers. An empty array
+     * is returned if no matches are found.
+     *
+     * @param	term	sort criteria
+     * @param	sterm	SearchTerm
+     * @return		array of matching sequence numbers.
+     * @exception	ProtocolException	for protocol failures
+     * @exception	SearchException	for search failures
+     *
+     * @see	"RFC 5256"
+     * @since	JavaMail 1.4.4
+     */
+    public int[] sort(SortTerm[] term, SearchTerm sterm)
+			throws ProtocolException, SearchException {
+	if (!hasCapability("SORT*")) 
+	    throw new BadCommandException("SORT not supported");
+
+	if (term == null || term.length == 0)
+	    throw new BadCommandException("Must have at least one sort term");
+
+	Argument args = new Argument();
+	Argument sargs = new Argument();
+	for (int i = 0; i < term.length; i++)
+	    sargs.writeAtom(term[i].toString());
+	args.writeArgument(sargs);	// sort criteria
+
+	args.writeAtom("UTF-8");	// charset specification
+	if (sterm != null) {
+	    try {
+		args.append(
+		    getSearchSequence().generateSequence(sterm, "UTF-8"));
+	    } catch (IOException ioex) {
+		// should never happen
+		throw new SearchException(ioex.toString());
+	    }
+	} else
+	    args.writeAtom("ALL");
+
+	Response[] r = command("SORT", args);
+	Response response = r[r.length-1];
+	int[] matches = null;
+
+	// Grab all SORT responses
+	if (response.isOK()) { // command succesful
+	    List<Integer> v = new ArrayList<>();
+	    int num;
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("SORT")) {
+		    while ((num = ir.readNumber()) != -1)
+			v.add(Integer.valueOf(num));
+		    r[i] = null;
+		}
+	    }
+
+	    // Copy the list into 'matches'
+	    int vsize = v.size();
+	    matches = new int[vsize];
+	    for (int i = 0; i < vsize; i++)
+		matches[i] = v.get(i).intValue();
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return matches;
+    }
+
+    /**
+     * NAMESPACE Command.
+     *
+     * @return	the namespaces
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2342"
+     */
+    public Namespaces namespace() throws ProtocolException {
+	if (!hasCapability("NAMESPACE")) 
+	    throw new BadCommandException("NAMESPACE not supported");
+
+	Response[] r = command("NAMESPACE", null);
+
+	Namespaces namespace = null;
+	Response response = r[r.length-1];
+
+	// Grab NAMESPACE response
+	if (response.isOK()) { // command succesful 
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("NAMESPACE")) {
+		    if (namespace == null)
+			namespace = new Namespaces(ir);
+		    r[i] = null;
+		}
+	    }
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return namespace;
+    }
+
+    /**
+     * GETQUOTAROOT Command.
+     *
+     * Returns an array of Quota objects, representing the quotas
+     * for this mailbox and, indirectly, the quotaroots for this
+     * mailbox.
+     *
+     * @param	mbox	the mailbox
+     * @return		array of Quota objects
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2087"
+     */
+    public Quota[] getQuotaRoot(String mbox) throws ProtocolException {
+	if (!hasCapability("QUOTA")) 
+	    throw new BadCommandException("GETQUOTAROOT not supported");
+
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	Response[] r = command("GETQUOTAROOT", args);
+
+	Response response = r[r.length-1];
+
+	Map<String, Quota> tab = new HashMap<>();
+
+	// Grab all QUOTAROOT and QUOTA responses
+	if (response.isOK()) { // command succesful 
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("QUOTAROOT")) {
+		    // quotaroot_response
+		    //		       ::= "QUOTAROOT" SP astring *(SP astring)
+
+		    // read name of mailbox and throw away
+		    ir.readAtomString();
+		    // for each quotaroot add a placeholder quota
+		    String root = null;
+		    while ((root = ir.readAtomString()) != null &&
+			    root.length() > 0)
+			tab.put(root, new Quota(root));
+		    r[i] = null;
+		} else if (ir.keyEquals("QUOTA")) {
+		    Quota quota = parseQuota(ir);
+		    Quota q = tab.get(quota.quotaRoot);
+		    if (q != null && q.resources != null) {
+			// merge resources
+			int newl = q.resources.length + quota.resources.length;
+			Quota.Resource[] newr = new Quota.Resource[newl];
+			System.arraycopy(q.resources, 0, newr, 0,
+							q.resources.length);
+			System.arraycopy(quota.resources, 0,
+			    newr, q.resources.length, quota.resources.length);
+			quota.resources = newr;
+		    }
+		    tab.put(quota.quotaRoot, quota);
+		    r[i] = null;
+		}
+	    }
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+
+	return tab.values().toArray(new Quota[tab.size()]);
+    }
+
+    /**
+     * GETQUOTA Command.
+     *
+     * Returns an array of Quota objects, representing the quotas
+     * for this quotaroot.
+     *
+     * @param	root	the quotaroot
+     * @return		the quotas
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2087"
+     */
+    public Quota[] getQuota(String root) throws ProtocolException {
+	if (!hasCapability("QUOTA")) 
+	    throw new BadCommandException("QUOTA not supported");
+
+	Argument args = new Argument();	
+	args.writeString(root);		// XXX - could be UTF-8?
+
+	Response[] r = command("GETQUOTA", args);
+
+	Quota quota = null;
+	List<Quota> v = new ArrayList<>();
+	Response response = r[r.length-1];
+
+	// Grab all QUOTA responses
+	if (response.isOK()) { // command succesful 
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("QUOTA")) {
+		    quota = parseQuota(ir);
+		    v.add(quota);
+		    r[i] = null;
+		}
+	    }
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return v.toArray(new Quota[v.size()]);
+    }
+
+    /**
+     * SETQUOTA Command.
+     *
+     * Set the indicated quota on the corresponding quotaroot.
+     *
+     * @param	quota	the quota to set
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2087"
+     */
+    public void setQuota(Quota quota) throws ProtocolException {
+	if (!hasCapability("QUOTA")) 
+	    throw new BadCommandException("QUOTA not supported");
+
+	Argument args = new Argument();	
+	args.writeString(quota.quotaRoot);	// XXX - could be UTF-8?
+	Argument qargs = new Argument();	
+	if (quota.resources != null) {
+	    for (int i = 0; i < quota.resources.length; i++) {
+		qargs.writeAtom(quota.resources[i].name);
+		qargs.writeNumber(quota.resources[i].limit);
+	    }
+	}
+	args.writeArgument(qargs);
+
+	Response[] r = command("SETQUOTA", args);
+	Response response = r[r.length-1];
+
+	// XXX - It's not clear from the RFC whether the SETQUOTA command
+	// will provoke untagged QUOTA responses.  If it does, perhaps
+	// we should grab them here and return them?
+
+	/*
+	Quota quota = null;
+	List<Quota> v = new ArrayList<Quota>();
+
+	// Grab all QUOTA responses
+	if (response.isOK()) { // command succesful 
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("QUOTA")) {
+		    quota = parseQuota(ir);
+		    v.add(quota);
+		    r[i] = null;
+		}
+	    }
+	}
+	*/
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	/*
+	return v.toArray(new Quota[v.size()]);
+	*/
+    }
+
+    /**
+     * Parse a QUOTA response.
+     */
+    private Quota parseQuota(Response r) throws ParsingException {
+	// quota_response ::= "QUOTA" SP astring SP quota_list
+	String quotaRoot = r.readAtomString();	// quotaroot ::= astring
+	Quota q = new Quota(quotaRoot);
+	r.skipSpaces();
+	// quota_list ::= "(" #quota_resource ")"
+	if (r.readByte() != '(')
+	    throw new ParsingException("parse error in QUOTA");
+
+	List<Quota.Resource> v = new ArrayList<>();
+	while (!r.isNextNonSpace(')')) {
+	    // quota_resource ::= atom SP number SP number
+	    String name = r.readAtom();
+	    if (name != null) {
+		long usage = r.readLong();
+		long limit = r.readLong();
+		Quota.Resource res = new Quota.Resource(name, usage, limit);
+		v.add(res);
+	    }
+	}
+	q.resources = v.toArray(new Quota.Resource[v.size()]);
+	return q;
+    }
+
+
+    /**
+     * SETACL Command.
+     *
+     * @param	mbox	the mailbox
+     * @param	modifier	the ACL modifier
+     * @param	acl	the ACL
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2086"
+     */
+    public void setACL(String mbox, char modifier, ACL acl)
+				throws ProtocolException {
+	if (!hasCapability("ACL")) 
+	    throw new BadCommandException("ACL not supported");
+
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+	args.writeString(acl.getName());
+	String rights = acl.getRights().toString();
+	if (modifier == '+' || modifier == '-')
+	    rights = modifier + rights;
+	args.writeString(rights);
+
+	Response[] r = command("SETACL", args);
+	Response response = r[r.length-1];
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+    }
+
+    /**
+     * DELETEACL Command.
+     *
+     * @param	mbox	the mailbox
+     * @param	user	the user
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2086"
+     */
+    public void deleteACL(String mbox, String user) throws ProtocolException {
+	if (!hasCapability("ACL")) 
+	    throw new BadCommandException("ACL not supported");
+
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+	args.writeString(user);		// XXX - could be UTF-8?
+
+	Response[] r = command("DELETEACL", args);
+	Response response = r[r.length-1];
+
+	// dispatch untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+    }
+
+    /**
+     * GETACL Command.
+     *
+     * @param	mbox	the mailbox
+     * @return		the ACL array
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2086"
+     */
+    public ACL[] getACL(String mbox) throws ProtocolException {
+	if (!hasCapability("ACL")) 
+	    throw new BadCommandException("ACL not supported");
+
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	Response[] r = command("GETACL", args);
+	Response response = r[r.length-1];
+
+	// Grab all ACL responses
+	List<ACL> v = new ArrayList<>();
+	if (response.isOK()) { // command succesful 
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("ACL")) {
+		    // acl_data ::= "ACL" SPACE mailbox
+		    //		*(SPACE identifier SPACE rights)
+		    // read name of mailbox and throw away
+		    ir.readAtomString();
+		    String name = null;
+		    while ((name = ir.readAtomString()) != null) {
+			String rights = ir.readAtomString();
+			if (rights == null)
+			    break;
+			ACL acl = new ACL(name, new Rights(rights));
+			v.add(acl);
+		    }
+		    r[i] = null;
+		}
+	    }
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return v.toArray(new ACL[v.size()]);
+    }
+
+    /**
+     * LISTRIGHTS Command.
+     *
+     * @param	mbox	the mailbox
+     * @param	user	the user rights to return
+     * @return		the rights array
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2086"
+     */
+    public Rights[] listRights(String mbox, String user)
+				throws ProtocolException {
+	if (!hasCapability("ACL")) 
+	    throw new BadCommandException("ACL not supported");
+
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+	args.writeString(user);		// XXX - could be UTF-8?
+
+	Response[] r = command("LISTRIGHTS", args);
+	Response response = r[r.length-1];
+
+	// Grab LISTRIGHTS response
+	List<Rights> v = new ArrayList<>();
+	if (response.isOK()) { // command succesful 
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("LISTRIGHTS")) {
+		    // listrights_data ::= "LISTRIGHTS" SPACE mailbox
+		    //		SPACE identifier SPACE rights *(SPACE rights)
+		    // read name of mailbox and throw away
+		    ir.readAtomString();
+		    // read identifier and throw away
+		    ir.readAtomString();
+		    String rights;
+		    while ((rights = ir.readAtomString()) != null)
+			v.add(new Rights(rights));
+		    r[i] = null;
+		}
+	    }
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return v.toArray(new Rights[v.size()]);
+    }
+
+    /**
+     * MYRIGHTS Command.
+     *
+     * @param	mbox	the mailbox
+     * @return		the rights
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2086"
+     */
+    public Rights myRights(String mbox) throws ProtocolException {
+	if (!hasCapability("ACL")) 
+	    throw new BadCommandException("ACL not supported");
+
+	Argument args = new Argument();	
+	writeMailboxName(args, mbox);
+
+	Response[] r = command("MYRIGHTS", args);
+	Response response = r[r.length-1];
+
+	// Grab MYRIGHTS response
+	Rights rights = null;
+	if (response.isOK()) { // command succesful 
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("MYRIGHTS")) {
+		    // myrights_data ::= "MYRIGHTS" SPACE mailbox SPACE rights
+		    // read name of mailbox and throw away
+		    ir.readAtomString();
+		    String rs = ir.readAtomString();
+		    if (rights == null)
+			rights = new Rights(rs);
+		    r[i] = null;
+		}
+	    }
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return rights;
+    }
+
+    /*
+     * The tag used on the IDLE command.  Set by idleStart() and
+     * used in processIdleResponse() to determine if the response
+     * is the matching end tag.
+     */
+    private volatile String idleTag;
+
+    /**
+     * IDLE Command. <p>
+     *
+     * If the server supports the IDLE command extension, the IDLE
+     * command is issued and this method blocks until a response has
+     * been received.  Once the first response has been received, the
+     * IDLE command is terminated and all responses are collected and
+     * handled and this method returns. <p>
+     *
+     * Note that while this method is blocked waiting for a response,
+     * no other threads may issue any commands to the server that would
+     * use this same connection.
+     *
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC2177"
+     * @since	JavaMail 1.4.1
+     */
+    public synchronized void idleStart() throws ProtocolException {
+	if (!hasCapability("IDLE")) 
+	    throw new BadCommandException("IDLE not supported");
+
+	List<Response> v = new ArrayList<>();
+	boolean done = false;
+	Response r = null;
+
+	// write the command
+	try {
+	    idleTag = writeCommand("IDLE", null);
+	} catch (LiteralException lex) {
+	    v.add(lex.getResponse());
+	    done = true;
+	} catch (Exception ex) {
+	    // Convert this into a BYE response
+	    v.add(Response.byeResponse(ex));
+	    done = true;
+	}
+
+	while (!done) {
+	    try {
+		r = readResponse();
+	    } catch (IOException ioex) {
+		// convert this into a BYE response
+		r = Response.byeResponse(ioex);
+	    } catch (ProtocolException pex) {
+		continue; // skip this response
+	    }
+
+	    v.add(r);
+
+	    if (r.isContinuation() || r.isBYE())
+		done = true;
+	}
+
+	Response[] responses = v.toArray(new Response[v.size()]);
+	r = responses[responses.length-1];
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(responses);
+	if (!r.isContinuation())
+	    handleResult(r);
+    }
+
+    /**
+     * While an IDLE command is in progress, read a response
+     * sent from the server.  The response is read with no locks
+     * held so that when the read blocks waiting for the response
+     * from the server it's not holding locks that would prevent
+     * other threads from interrupting the IDLE command.
+     *
+     * @return	the response
+     * @since	JavaMail 1.4.1
+     */
+    public synchronized Response readIdleResponse() {
+	if (idleTag == null)
+	    return null;	// IDLE not in progress
+	Response r = null;
+	try {
+	    r = readResponse();
+	} catch (IOException ioex) {
+	    // convert this into a BYE response
+	    r = Response.byeResponse(ioex);
+	} catch (ProtocolException pex) {
+	    // convert this into a BYE response
+	    r = Response.byeResponse(pex);
+	}
+	return r;
+    }
+
+    /**
+     * Process a response returned by readIdleResponse().
+     * This method will be called with appropriate locks
+     * held so that the processing of the response is safe.
+     *
+     * @param	r	the response
+     * @return		true if IDLE is done
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.4.1
+     */
+    public boolean processIdleResponse(Response r) throws ProtocolException {
+	Response[] responses = new Response[1];
+	responses[0] = r;
+	boolean done = false;		// done reading responses?
+	notifyResponseHandlers(responses);
+
+	if (r.isBYE()) // shouldn't wait for command completion response
+	    done = true;
+
+	// If this is a matching command completion response, we are done
+	if (r.isTagged() && r.getTag().equals(idleTag))
+	    done = true;
+
+	if (done)
+	    idleTag = null;	// no longer in IDLE
+
+	handleResult(r);
+	return !done;
+    }
+
+    // the DONE command to break out of IDLE
+    private static final byte[] DONE = { 'D', 'O', 'N', 'E', '\r', '\n' };
+
+    /**
+     * Abort an IDLE command.  While one thread is blocked in
+     * readIdleResponse(), another thread will use this method
+     * to abort the IDLE command, which will cause the server
+     * to send the closing tag for the IDLE command, which
+     * readIdleResponse() and processIdleResponse() will see
+     * and terminate the IDLE state.
+     *
+     * @since	JavaMail 1.4.1
+     */
+    public void idleAbort() {
+	OutputStream os = getOutputStream();
+	try {
+	    os.write(DONE);
+	    os.flush();
+	} catch (Exception ex) {
+	    // nothing to do, hope to detect it again later
+	    logger.log(Level.FINEST, "Exception aborting IDLE", ex);
+	}
+    }
+
+    /**
+     * ID Command.
+     *
+     * @param	clientParams	map of names and values
+     * @return			map of names and values from server
+     * @exception	ProtocolException	for protocol failures
+     * @see "RFC 2971"
+     * @since	JavaMail 1.5.1
+     */
+    public Map<String, String> id(Map<String, String> clientParams)
+				throws ProtocolException {
+	if (!hasCapability("ID")) 
+	    throw new BadCommandException("ID not supported");
+
+	Response[] r = command("ID", ID.getArgumentList(clientParams));
+
+	ID id = null;
+	Response response = r[r.length-1];
+
+	// Grab ID response
+	if (response.isOK()) { // command succesful 
+	    for (int i = 0, len = r.length; i < len; i++) {
+		if (!(r[i] instanceof IMAPResponse))
+		    continue;
+
+		IMAPResponse ir = (IMAPResponse)r[i];
+		if (ir.keyEquals("ID")) {
+		    if (id == null)
+			id = new ID(ir);
+		    r[i] = null;
+		}
+	    }
+	}
+
+	// dispatch remaining untagged responses
+	notifyResponseHandlers(r);
+	handleResult(response);
+	return id == null ? null : id.getServerParams();
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/IMAPReferralException.java b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPReferralException.java
new file mode 100644
index 0000000..82f1ea3
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPReferralException.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.ProtocolException;
+
+/**
+ * A ProtocolException that includes IMAP login referral information.
+ *
+ * @since JavaMail 1.5.5
+ */
+
+public class IMAPReferralException extends ProtocolException {
+
+    private String url;
+
+    private static final long serialVersionUID = 2578770669364251968L;
+
+    /**
+     * Constructs an IMAPReferralException with the specified detail message.
+     * and URL.
+     *
+     * @param s		the detail message
+     * @param url	the URL
+     */
+    public IMAPReferralException(String s, String url) {
+	super(s);
+	this.url = url;
+    }
+
+    /**
+     * Return the IMAP URL in the referral.
+     *
+     * @return	the IMAP URL
+     */
+    public String getUrl() {
+	return url;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java
new file mode 100644
index 0000000..4e6be4e
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPResponse.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+import java.util.*;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.iap.*;
+
+/**
+ * This class represents a response obtained from the input stream
+ * of an IMAP server.
+ *
+ * @author  John Mani
+ */
+
+public class IMAPResponse extends Response {
+    private String key;
+    private int number;
+
+    public IMAPResponse(Protocol c) throws IOException, ProtocolException {
+	super(c);
+	init();
+    }
+
+    private void init() throws IOException, ProtocolException {
+	// continue parsing if this is an untagged response
+	if (isUnTagged() && !isOK() && !isNO() && !isBAD() && !isBYE()) {
+	    key = readAtom();
+
+	    // Is this response of the form "* <number> <command>"
+	    try {
+		number = Integer.parseInt(key);
+		key = readAtom();
+	    } catch (NumberFormatException ne) { }
+	}
+    }
+
+    /**
+     * Copy constructor.
+     *
+     * @param	r	the IMAPResponse to copy
+     */
+    public IMAPResponse(IMAPResponse r) {
+	super((Response)r);
+	key = r.key;
+	number = r.number;
+    }
+
+    /**
+     * For testing.
+     *
+     * @param	r	the response string
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     */
+    public IMAPResponse(String r) throws IOException, ProtocolException {
+	this(r, true);
+    }
+
+    /**
+     * For testing.
+     *
+     * @param	r	the response string
+     * @param	utf8	UTF-8 allowed?
+     * @exception	IOException	for I/O errors
+     * @exception	ProtocolException	for protocol failures
+     * @since	JavaMail 1.6.0
+     */
+    public IMAPResponse(String r, boolean utf8)
+				throws IOException, ProtocolException {
+	super(r, utf8);
+	init();
+    }
+
+    /**
+     * Read a list of space-separated "flag-extension" sequences and 
+     * return the list as a array of Strings. An empty list is returned
+     * as null.  Each item is expected to be an atom, possibly preceeded
+     * by a backslash, but we aren't that strict; we just look for strings
+     * separated by spaces and terminated by a right paren.  We assume items
+     * are always ASCII.
+     *
+     * @return	the list items as a String array
+     */
+    public String[] readSimpleList() {
+	skipSpaces();
+
+	if (buffer[index] != '(') // not what we expected
+	    return null;
+	index++; // skip '('
+
+	List<String> v = new ArrayList<>();
+	int start;
+	for (start = index; buffer[index] != ')'; index++) {
+	    if (buffer[index] == ' ') { // got one item
+		v.add(ASCIIUtility.toString(buffer, start, index));
+		start = index+1; // index gets incremented at the top
+	    }
+	}
+	if (index > start) // get the last item
+	    v.add(ASCIIUtility.toString(buffer, start, index));
+	index++; // skip ')'
+	
+	int size = v.size();
+	if (size > 0)
+	    return v.toArray(new String[size]);
+	else  // empty list
+	    return null;
+    }
+
+    public String getKey() {
+	return key;
+    }
+
+    public boolean keyEquals(String k) {
+	if (key != null && key.equalsIgnoreCase(k))
+	    return true;
+	else
+	    return false;
+    }
+
+    public int getNumber() {
+	return number;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/IMAPSaslAuthenticator.java b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPSaslAuthenticator.java
new file mode 100644
index 0000000..d4187e0
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/IMAPSaslAuthenticator.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+import java.util.*;
+import java.util.logging.Level;
+import javax.security.sasl.*;
+import javax.security.auth.callback.*;
+
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.*;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.BASE64DecoderStream;
+
+/**
+ * This class contains a single method that does authentication using
+ * SASL.  This is in a separate class so that it can be compiled with
+ * J2SE 1.5.  Eventually it should be merged into IMAPProtocol.java.
+ */
+
+public class IMAPSaslAuthenticator implements SaslAuthenticator {
+
+    private IMAPProtocol pr;
+    private String name;
+    private Properties props;
+    private MailLogger logger;
+    private String host;
+
+    /*
+     * This is a hack to initialize the OAUTH SASL provider just before,
+     * and only if, we might need it.  This avoids the need for the user 
+     * to initialize it explicitly, or manually configure the security
+     * providers file.
+     */
+    static {
+	try {
+	    com.sun.mail.auth.OAuth2SaslClientFactory.init();
+	} catch (Throwable t) { }
+    }
+
+    public IMAPSaslAuthenticator(IMAPProtocol pr, String name, Properties props,
+				MailLogger logger, String host) {
+	this.pr = pr;
+	this.name = name;
+	this.props = props;
+	this.logger = logger;
+	this.host = host;
+    }
+
+    @Override
+    public boolean authenticate(String[] mechs, final String realm,
+				final String authzid, final String u,
+				final String p) throws ProtocolException {
+
+	synchronized (pr) {	// authenticate method should be synchronized
+	List<Response> v = new ArrayList<>();
+	String tag = null;
+	Response r = null;
+	boolean done = false;
+	if (logger.isLoggable(Level.FINE)) {
+	    logger.fine("SASL Mechanisms:");
+	    for (int i = 0; i < mechs.length; i++)
+		logger.fine(" " + mechs[i]);
+	    logger.fine("");
+	}
+
+	SaslClient sc;
+	CallbackHandler cbh = new CallbackHandler() {
+		@Override
+	    public void handle(Callback[] callbacks) {
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("SASL callback length: " + callbacks.length);
+		for (int i = 0; i < callbacks.length; i++) {
+		    if (logger.isLoggable(Level.FINE))
+			logger.fine("SASL callback " + i + ": " + callbacks[i]);
+		    if (callbacks[i] instanceof NameCallback) {
+			NameCallback ncb = (NameCallback)callbacks[i];
+			ncb.setName(u);
+		    } else if (callbacks[i] instanceof PasswordCallback) {
+			PasswordCallback pcb = (PasswordCallback)callbacks[i];
+			pcb.setPassword(p.toCharArray());
+		    } else if (callbacks[i] instanceof RealmCallback) {
+			RealmCallback rcb = (RealmCallback)callbacks[i];
+			rcb.setText(realm != null ?
+				    realm : rcb.getDefaultText());
+		    } else if (callbacks[i] instanceof RealmChoiceCallback) {
+			RealmChoiceCallback rcb =
+			    (RealmChoiceCallback)callbacks[i];
+			if (realm == null)
+			    rcb.setSelectedIndex(rcb.getDefaultChoice());
+			else {
+			    // need to find specified realm in list
+			    String[] choices = rcb.getChoices();
+			    for (int k = 0; k < choices.length; k++) {
+				if (choices[k].equals(realm)) {
+				    rcb.setSelectedIndex(k);
+				    break;
+				}
+			    }
+			}
+		    }
+		}
+	    }
+	};
+
+	try {
+	    @SuppressWarnings("unchecked")
+	    Map<String, ?> propsMap = (Map) props;
+	    sc = Sasl.createSaslClient(mechs, authzid, name, host,
+					propsMap, cbh);
+	} catch (SaslException sex) {
+	    logger.log(Level.FINE, "Failed to create SASL client", sex);
+	    throw new UnsupportedOperationException(sex.getMessage(), sex);
+	}
+	if (sc == null) {
+	    logger.fine("No SASL support");
+	    throw new UnsupportedOperationException("No SASL support");
+	}
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("SASL client " + sc.getMechanismName());
+
+	try {
+	    Argument args = new Argument();
+	    args.writeAtom(sc.getMechanismName());
+	    if (pr.hasCapability("SASL-IR") && sc.hasInitialResponse()) {
+		String irs;
+		byte[] ba = sc.evaluateChallenge(new byte[0]);
+		if (ba.length > 0) {
+		    ba = BASE64EncoderStream.encode(ba);
+		    irs = ASCIIUtility.toString(ba, 0, ba.length);
+		} else
+		    irs = "=";
+		args.writeAtom(irs);
+	    }
+	    tag = pr.writeCommand("AUTHENTICATE", args);
+	} catch (Exception ex) {
+	    logger.log(Level.FINE, "SASL AUTHENTICATE Exception", ex);
+	    return false;
+	}
+
+	OutputStream os = pr.getIMAPOutputStream(); // stream to IMAP server
+
+	/*
+	 * Wrap a BASE64Encoder around a ByteArrayOutputstream
+	 * to craft b64 encoded username and password strings
+	 *
+	 * Note that the encoded bytes should be sent "as-is" to the
+	 * server, *not* as literals or quoted-strings.
+	 *
+	 * Also note that unlike the B64 definition in MIME, CRLFs 
+	 * should *not* be inserted during the encoding process. So, I
+	 * use Integer.MAX_VALUE (0x7fffffff (> 1G)) as the bytesPerLine,
+	 * which should be sufficiently large !
+	 */
+
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	byte[] CRLF = { (byte)'\r', (byte)'\n'};
+
+	// Hack for Novell GroupWise XGWTRUSTEDAPP authentication mechanism
+	// http://www.novell.com/developer/documentation/gwimap/?
+	//   page=/developer/documentation/gwimap/gwimpenu/data/al7te9j.html
+	boolean isXGWTRUSTEDAPP =
+	    sc.getMechanismName().equals("XGWTRUSTEDAPP") &&
+	    PropUtil.getBooleanProperty(props,
+		"mail." + name + ".sasl.xgwtrustedapphack.enable", true);
+	while (!done) { // loop till we are done
+	    try {
+		r = pr.readResponse();
+	    	if (r.isContinuation()) {
+		    byte[] ba = null;
+		    if (!sc.isComplete()) {
+			ba = r.readByteArray().getNewBytes();
+			if (ba.length > 0)
+			    ba = BASE64DecoderStream.decode(ba);
+			if (logger.isLoggable(Level.FINE))
+			    logger.fine("SASL challenge: " +
+				ASCIIUtility.toString(ba, 0, ba.length) + " :");
+			ba = sc.evaluateChallenge(ba);
+		    }
+		    if (ba == null) {
+			logger.fine("SASL no response");
+			os.write(CRLF); // write out empty line
+			os.flush(); 	// flush the stream
+			bos.reset(); 	// reset buffer
+		    } else {
+			if (logger.isLoggable(Level.FINE))
+			    logger.fine("SASL response: " +
+				ASCIIUtility.toString(ba, 0, ba.length) + " :");
+			ba = BASE64EncoderStream.encode(ba);
+			if (isXGWTRUSTEDAPP)
+			    bos.write(ASCIIUtility.getBytes("XGWTRUSTEDAPP "));
+			bos.write(ba);
+
+			bos.write(CRLF); 	// CRLF termination
+			os.write(bos.toByteArray()); // write out line
+			os.flush(); 	// flush the stream
+			bos.reset(); 	// reset buffer
+		    }
+		} else if (r.isTagged() && r.getTag().equals(tag))
+		    // Ah, our tagged response
+		    done = true;
+		else if (r.isBYE()) // outta here
+		    done = true;
+		else // hmm .. unsolicited response here ?!
+		    v.add(r);
+	    } catch (Exception ioex) {
+		logger.log(Level.FINE, "SASL Exception", ioex);
+		// convert this into a BYE response
+		r = Response.byeResponse(ioex);
+		done = true;
+		// XXX - ultimately return true???
+	    }
+	}
+
+	if (sc.isComplete() /*&& res.status == SUCCESS*/) {
+	    String qop = (String)sc.getNegotiatedProperty(Sasl.QOP);
+	    if (qop != null && (qop.equalsIgnoreCase("auth-int") ||
+				qop.equalsIgnoreCase("auth-conf"))) {
+		// XXX - NOT SUPPORTED!!!
+		logger.fine(
+			"SASL Mechanism requires integrity or confidentiality");
+		return false;
+	    }
+	}
+
+	Response[] responses = v.toArray(new Response[v.size()]);
+
+	// handle an illegal but not uncommon untagged CAPABILTY response
+	pr.handleCapabilityResponse(responses);
+
+	/*
+	 * Dispatch untagged responses.
+	 * NOTE: in our current upper level IMAP classes, we add the
+	 * responseHandler to the Protocol object only *after* the 
+	 * connection has been authenticated. So, for now, the below
+	 * code really ends up being just a no-op.
+	 */
+	pr.notifyResponseHandlers(responses);
+
+	// Handle the final OK, NO, BAD or BYE response
+	pr.handleLoginResult(r);
+	pr.setCapabilities(r);
+
+	/*
+	 * If we're using the Novell Groupwise XGWTRUSTEDAPP mechanism
+	 * to run as a specified authorization ID, we have to issue a
+	 * LOGIN command to select the user we want to operate as.
+	 */
+	if (isXGWTRUSTEDAPP && authzid != null) {
+	    Argument args = new Argument();
+	    args.writeString(authzid);
+
+	    responses = pr.command("LOGIN", args);
+
+	    // dispatch untagged responses
+	    pr.notifyResponseHandlers(responses);
+
+	    // Handle result of this command
+	    pr.handleResult(responses[responses.length-1]);
+	    // If the response includes a CAPABILITY response code, process it
+	    pr.setCapabilities(responses[responses.length-1]);
+	}
+	return true;
+    }
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/INTERNALDATE.java b/mail/src/main/java/com/sun/mail/imap/protocol/INTERNALDATE.java
new file mode 100644
index 0000000..7f432ff
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/INTERNALDATE.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.Date;
+import java.util.TimeZone;
+import java.util.Locale;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.text.FieldPosition;
+
+import javax.mail.internet.MailDateFormat;
+
+import com.sun.mail.iap.*; 
+
+
+/**
+ * An INTERNALDATE FETCH item.
+ *
+ * @author  John Mani
+ */
+
+public class INTERNALDATE implements Item {
+
+    static final char[] name =
+	{'I','N','T','E','R','N','A','L','D','A','T','E'};
+    public int msgno;
+    protected Date date;
+
+    /*
+     * Used to parse dates only.  The parse method is thread safe
+     * so we only need to create a single object for use by all
+     * instances.  We depend on the fact that the MailDateFormat
+     * class will parse dates in INTERNALDATE format as well as
+     * dates in RFC 822 format.
+     */
+    private static final MailDateFormat mailDateFormat = new MailDateFormat();
+
+    /**
+     * Constructor.
+     *
+     * @param	r	the FetchResponse
+     * @exception	ParsingException	for parsing failures
+     */
+    public INTERNALDATE(FetchResponse r) throws ParsingException {
+	msgno = r.getNumber();
+	r.skipSpaces();
+	String s = r.readString();
+	if (s == null)
+	    throw new ParsingException("INTERNALDATE is NIL");
+	try {
+        synchronized (mailDateFormat) {
+            date = mailDateFormat.parse(s);
+        }
+	} catch (ParseException pex) {
+	    throw new ParsingException("INTERNALDATE parse error");
+	}
+    }
+
+    public Date getDate() {
+	return date;
+    }
+
+    // INTERNALDATE formatter
+
+    private static SimpleDateFormat df = 
+	// Need Locale.US, the "MMM" field can produce unexpected values
+	// in non US locales !
+	new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss ", Locale.US);
+
+    /**
+     * Format given Date object into INTERNALDATE string
+     *
+     * @param	d	the Date
+     * @return		INTERNALDATE string
+     */
+    public static String format(Date d) {
+	/*
+	 * SimpleDateFormat objects aren't thread safe, so rather
+	 * than create a separate such object for each request,
+	 * we create one object and synchronize its use here
+	 * so that only one thread is using it at a time.  This
+	 * trades off some potential concurrency for speed in the
+	 * common case.
+	 *
+	 * This method is only used when formatting the date in a
+	 * message that's being appended to a folder.
+	 */
+	StringBuffer sb = new StringBuffer();
+	synchronized (df) {
+	    df.format(d, sb, new FieldPosition(0));
+	}
+
+	// compute timezone offset string
+	TimeZone tz = TimeZone.getDefault();
+	int offset = tz.getOffset(d.getTime());	// get offset from GMT
+	int rawOffsetInMins = offset / 60 / 1000; // offset from GMT in mins
+	if (rawOffsetInMins < 0) {
+	    sb.append('-');
+	    rawOffsetInMins = (-rawOffsetInMins);
+	} else
+	    sb.append('+');
+	
+	int offsetInHrs = rawOffsetInMins / 60;
+	int offsetInMins = rawOffsetInMins % 60;
+
+	sb.append(Character.forDigit((offsetInHrs/10), 10));
+	sb.append(Character.forDigit((offsetInHrs%10), 10));
+	sb.append(Character.forDigit((offsetInMins/10), 10));
+	sb.append(Character.forDigit((offsetInMins%10), 10));
+
+	return sb.toString();
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/Item.java b/mail/src/main/java/com/sun/mail/imap/protocol/Item.java
new file mode 100644
index 0000000..8d604ab
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/Item.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+/**
+ * A tagging interface for all IMAP data items.
+ * Note that the "name" field of all IMAP items MUST be in uppercase. <p>
+ *
+ * See the BODY, BODYSTRUCTURE, ENVELOPE, FLAGS, INTERNALDATE, RFC822DATA,
+ * RFC822SIZE, and UID classes.
+ *
+ * @author  John Mani
+ */
+
+public interface Item { 
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/ListInfo.java b/mail/src/main/java/com/sun/mail/imap/protocol/ListInfo.java
new file mode 100644
index 0000000..0db1f02
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/ListInfo.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import com.sun.mail.iap.*;
+
+/**
+ * A LIST response.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class ListInfo { 
+    public String name = null;
+    public char separator = '/';
+    public boolean hasInferiors = true;
+    public boolean canOpen = true;
+    public int changeState = INDETERMINATE;
+    public String[] attrs;
+
+    public static final int CHANGED		= 1;
+    public static final int UNCHANGED		= 2;
+    public static final int INDETERMINATE	= 3;
+
+    public ListInfo(IMAPResponse r) throws ParsingException {
+	String[] s = r.readSimpleList();
+
+	List<String> v = new ArrayList<>();	// accumulate attributes
+	if (s != null) {
+	    // non-empty attribute list
+	    for (int i = 0; i < s.length; i++) {
+		if (s[i].equalsIgnoreCase("\\Marked"))
+		    changeState = CHANGED;
+		else if (s[i].equalsIgnoreCase("\\Unmarked"))
+		    changeState = UNCHANGED;
+		else if (s[i].equalsIgnoreCase("\\Noselect"))
+		    canOpen = false;
+		else if (s[i].equalsIgnoreCase("\\Noinferiors"))
+		    hasInferiors = false;
+		v.add(s[i]);
+	    }
+	}
+	attrs = v.toArray(new String[v.size()]);
+
+	r.skipSpaces();
+	if (r.readByte() == '"') {
+	    if ((separator = (char)r.readByte()) == '\\')
+		// escaped separator character
+		separator = (char)r.readByte();	
+	    r.skip(1); // skip <">
+	} else // NIL
+	    r.skip(2);
+	
+	r.skipSpaces();
+	name = r.readAtomString();
+
+	if (!r.supportsUtf8())
+	    // decode the name (using RFC2060's modified UTF7)
+	    name = BASE64MailboxDecoder.decode(name);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/MODSEQ.java b/mail/src/main/java/com/sun/mail/imap/protocol/MODSEQ.java
new file mode 100644
index 0000000..f800569
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/MODSEQ.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.*; 
+
+/**
+ * This class represents the MODSEQ data item.
+ *
+ * @since	JavaMail 1.5.1
+ * @author	Bill Shannon
+ */
+
+public class MODSEQ implements Item {
+    
+    static final char[] name = {'M','O','D','S','E','Q'};
+    public int seqnum;
+
+    public long modseq;
+
+    /**
+     * Constructor.
+     *
+     * @param	r	the FetchResponse
+     * @exception	ParsingException	for parsing failures
+     */
+    public MODSEQ(FetchResponse r) throws ParsingException {
+	seqnum = r.getNumber();
+	r.skipSpaces();
+
+	if (r.readByte() != '(')
+	    throw new ParsingException("MODSEQ parse error");
+
+	modseq = r.readLong();
+
+	if (!r.isNextNonSpace(')'))
+	    throw new ParsingException("MODSEQ parse error");
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/MailboxInfo.java b/mail/src/main/java/com/sun/mail/imap/protocol/MailboxInfo.java
new file mode 100644
index 0000000..ccff0b6
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/MailboxInfo.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import javax.mail.Flags;
+
+import com.sun.mail.iap.*;
+
+/**
+ * Information collected when opening a mailbox.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class MailboxInfo { 
+    /** The available flags. */
+    public Flags availableFlags = null;
+    /** The permanent flags. */
+    public Flags permanentFlags = null;
+    /** The total number of messages. */
+    public int total = -1;
+    /** The number of recent messages. */
+    public int recent = -1;
+    /** The first unseen message. */
+    public int first = -1;
+    /** The UIDVALIDITY. */
+    public long uidvalidity = -1;
+    /** The next UID value to be assigned. */
+    public long uidnext = -1;
+    /** UIDs are not sticky. */
+    public boolean uidNotSticky = false;	// RFC 4315
+    /** The highest MODSEQ value. */
+    public long highestmodseq = -1;	// RFC 4551 - CONDSTORE
+    /** Folder.READ_WRITE or Folder.READ_ONLY, set by IMAPProtocol. */
+    public int mode;
+    /** VANISHED or FETCH responses received while opening the mailbox. */
+    public List<IMAPResponse> responses;
+
+    /**
+     * Collect the information about this mailbox from the
+     * responses to a SELECT or EXAMINE.
+     *
+     * @param	r	the responses
+     * @exception	ParsingException	for errors parsing the responses
+     */
+    public MailboxInfo(Response[] r) throws ParsingException {
+	for (int i = 0; i < r.length; i++) {
+	    if (r[i] == null || !(r[i] instanceof IMAPResponse))
+		continue;
+
+	    IMAPResponse ir = (IMAPResponse)r[i];
+
+	    if (ir.keyEquals("EXISTS")) {
+		total = ir.getNumber();
+		r[i] = null; // remove this response
+	    } else if (ir.keyEquals("RECENT")) {
+		recent = ir.getNumber();
+		r[i] = null; // remove this response
+	    } else if (ir.keyEquals("FLAGS")) {
+		availableFlags = new FLAGS(ir);
+		r[i] = null; // remove this response
+	    } else if (ir.keyEquals("VANISHED")) {
+		if (responses == null)
+		    responses = new ArrayList<>();
+		responses.add(ir);
+		r[i] = null; // remove this response
+	    } else if (ir.keyEquals("FETCH")) {
+		if (responses == null)
+		    responses = new ArrayList<>();
+		responses.add(ir);
+		r[i] = null; // remove this response
+	    } else if (ir.isUnTagged() && ir.isOK()) {
+		/*
+		 * should be one of:
+		 * 	* OK [UNSEEN 12]
+		 * 	* OK [UIDVALIDITY 3857529045]
+		 * 	* OK [PERMANENTFLAGS (\Deleted)]
+		 * 	* OK [UIDNEXT 44]
+		 * 	* OK [HIGHESTMODSEQ 103]
+		 */
+		ir.skipSpaces();
+
+		if (ir.readByte() != '[') {	// huh ???
+		    ir.reset();
+		    continue;
+		}
+
+		boolean handled = true;
+		String s = ir.readAtom();
+		if (s.equalsIgnoreCase("UNSEEN"))
+		    first = ir.readNumber();
+		else if (s.equalsIgnoreCase("UIDVALIDITY"))
+		    uidvalidity = ir.readLong();
+		else if (s.equalsIgnoreCase("PERMANENTFLAGS"))
+		    permanentFlags = new FLAGS(ir);
+		else if (s.equalsIgnoreCase("UIDNEXT"))
+		    uidnext = ir.readLong();
+		else if (s.equalsIgnoreCase("HIGHESTMODSEQ"))
+		    highestmodseq = ir.readLong();
+		else
+		    handled = false;	// possibly an ALERT
+
+		if (handled)
+		    r[i] = null; // remove this response
+		else
+		    ir.reset();	// so ALERT can be read
+	    } else if (ir.isUnTagged() && ir.isNO()) {
+		/*
+		 * should be one of:
+		 * 	* NO [UIDNOTSTICKY]
+		 */
+		ir.skipSpaces();
+
+		if (ir.readByte() != '[') {	// huh ???
+		    ir.reset();
+		    continue;
+		}
+
+		boolean handled = true;
+		String s = ir.readAtom();
+		if (s.equalsIgnoreCase("UIDNOTSTICKY"))
+		    uidNotSticky = true;
+		else
+		    handled = false;	// possibly an ALERT
+
+		if (handled)
+		    r[i] = null; // remove this response
+		else
+		    ir.reset();	// so ALERT can be read
+	    }
+	}
+
+	/*
+	 * The PERMANENTFLAGS response code is optional, and if
+	 * not present implies that all flags in the required FLAGS
+	 * response can be changed permanently.
+	 */
+	if (permanentFlags == null) {
+	    if (availableFlags != null)
+		permanentFlags = new Flags(availableFlags);
+	    else
+		permanentFlags = new Flags();
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/MessageSet.java b/mail/src/main/java/com/sun/mail/imap/protocol/MessageSet.java
new file mode 100644
index 0000000..a7e6cc6
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/MessageSet.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * This class holds the 'start' and 'end' for a range of messages.
+ */
+public class MessageSet {
+
+    public int start;
+    public int end;
+
+    public MessageSet() { }
+
+    public MessageSet(int start, int end) {
+	this.start = start;
+	this.end = end;
+    }
+
+    /**
+     * Count the total number of elements in a MessageSet
+     *
+     * @return	how many messages in this MessageSet
+     */
+    public int size() {
+	return end - start + 1;
+    }
+
+    /**
+     * Convert an array of integers into an array of MessageSets
+     *
+     * @param	msgs	the messages
+     * @return		array of MessageSet objects
+     */
+    public static MessageSet[] createMessageSets(int[] msgs) {
+	List<MessageSet> v = new ArrayList<>();
+	int i,j;
+
+	for (i=0; i < msgs.length; i++) {
+	    MessageSet ms = new MessageSet();
+	    ms.start = msgs[i];
+
+	    // Look for contiguous elements
+	    for (j=i+1; j < msgs.length; j++) {
+		if (msgs[j] != msgs[j-1] +1)
+		    break;
+	    }
+	    ms.end = msgs[j-1];
+	    v.add(ms);
+	    i = j-1; // i gets incremented @ top of the loop
+	}
+	return v.toArray(new MessageSet[v.size()]);	
+    }
+
+    /**
+     * Convert an array of MessageSets into an IMAP sequence range
+     *
+     * @param	msgsets	the MessageSets
+     * @return		IMAP sequence string
+     */
+    public static String toString(MessageSet[] msgsets) {
+	if (msgsets == null || msgsets.length == 0) // Empty msgset
+	    return null; 
+
+	int i = 0;  // msgset index
+	StringBuilder s = new StringBuilder();
+	int size = msgsets.length;
+	int start, end;
+
+	for (;;) {
+	    start = msgsets[i].start;
+	    end = msgsets[i].end;
+
+	    if (end > start)
+		s.append(start).append(':').append(end);
+	    else // end == start means only one element
+		s.append(start);
+	
+	    i++; // Next MessageSet
+	    if (i >= size) // No more MessageSets
+		break;
+	    else
+		s.append(',');
+	}
+	return s.toString();
+    }
+
+	
+    /*
+     * Count the total number of elements in an array of MessageSets
+     */
+    public static int size(MessageSet[] msgsets) {
+	int count = 0;
+
+	if (msgsets == null) // Null msgset
+	    return 0; 
+
+	for (int i=0; i < msgsets.length; i++)
+	    count += msgsets[i].size();
+	
+	return count;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/Namespaces.java b/mail/src/main/java/com/sun/mail/imap/protocol/Namespaces.java
new file mode 100644
index 0000000..df54e67
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/Namespaces.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.*;
+import com.sun.mail.iap.*;
+
+/**
+ * This class and its inner class represent the response to the
+ * NAMESPACE command. <p>
+ *
+ * See <A HREF="http://www.ietf.org/rfc/rfc2342.txt">RFC 2342</A>.
+ *
+ * @author Bill Shannon
+ */
+
+public class Namespaces {
+
+    /**
+     * A single namespace entry.
+     */
+    public static class Namespace {
+	/**
+	 * Prefix string for the namespace.
+	 */
+	public String prefix;
+
+	/**
+	 * Delimiter between names in this namespace.
+	 */
+	public char delimiter;
+
+	/**
+	 * Parse a namespace element out of the response.
+	 *
+	 * @param	r	the Response to parse
+	 * @exception	ProtocolException	for any protocol errors
+	 */
+	public Namespace(Response r) throws ProtocolException {
+	    // Namespace_Element = "(" string SP (<"> QUOTED_CHAR <"> / nil)
+	    //		*(Namespace_Response_Extension) ")"
+	    if (!r.isNextNonSpace('('))
+		throw new ProtocolException(
+					"Missing '(' at start of Namespace");
+	    // first, the prefix
+	    prefix = r.readString();
+	    if (!r.supportsUtf8())
+		prefix = BASE64MailboxDecoder.decode(prefix);
+	    r.skipSpaces();
+	    // delimiter is a quoted character or NIL
+	    if (r.peekByte() == '"') {
+		r.readByte();
+		delimiter = (char)r.readByte();
+		if (delimiter == '\\')
+		    delimiter = (char)r.readByte();
+		if (r.readByte() != '"')
+		    throw new ProtocolException(
+				    "Missing '\"' at end of QUOTED_CHAR");
+	    } else {
+		String s = r.readAtom();
+		if (s == null)
+		    throw new ProtocolException("Expected NIL, got null");
+		if (!s.equalsIgnoreCase("NIL"))
+		    throw new ProtocolException("Expected NIL, got " + s);
+		delimiter = 0;
+	    }
+	    // at end of Namespace data?
+	    if (r.isNextNonSpace(')'))
+		return;
+
+	    // otherwise, must be a Namespace_Response_Extension
+	    //    Namespace_Response_Extension = SP string SP
+	    //	    "(" string *(SP string) ")"
+	    r.readString();
+	    r.skipSpaces();
+	    r.readStringList();
+	    if (!r.isNextNonSpace(')'))
+		throw new ProtocolException("Missing ')' at end of Namespace");
+	}
+    };
+
+    /**
+     * The personal namespaces.
+     * May be null.
+     */
+    public Namespace[] personal;
+
+    /**
+     * The namespaces for other users.
+     * May be null.
+     */
+    public Namespace[] otherUsers;
+
+    /**
+     * The shared namespace.
+     * May be null.
+     */
+    public Namespace[] shared;
+
+    /**
+     * Parse out all the namespaces.
+     *
+     * @param	r	the Response to parse
+     * @throws	ProtocolException	for any protocol errors
+     */
+    public Namespaces(Response r) throws ProtocolException {
+	personal = getNamespaces(r);
+	otherUsers = getNamespaces(r);
+	shared = getNamespaces(r);
+    }
+
+    /**
+     * Parse out one of the three sets of namespaces.
+     */
+    private Namespace[] getNamespaces(Response r) throws ProtocolException {
+	//    Namespace = nil / "(" 1*( Namespace_Element) ")"
+	if (r.isNextNonSpace('(')) {
+	    List<Namespace> v = new ArrayList<>();
+	    do {
+		Namespace ns = new Namespace(r);
+		v.add(ns);
+	    } while (!r.isNextNonSpace(')'));
+	    return v.toArray(new Namespace[v.size()]);
+	} else {
+	    String s = r.readAtom();
+	    if (s == null)
+		throw new ProtocolException("Expected NIL, got null");
+	    if (!s.equalsIgnoreCase("NIL"))
+		throw new ProtocolException("Expected NIL, got " + s);
+	    return null;
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/RFC822DATA.java b/mail/src/main/java/com/sun/mail/imap/protocol/RFC822DATA.java
new file mode 100644
index 0000000..bda754f
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/RFC822DATA.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.ByteArrayInputStream;
+import com.sun.mail.iap.*; 
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * The RFC822 response data item.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class RFC822DATA implements Item {
+   
+    static final char[] name = {'R','F','C','8','2','2'};
+    private final int msgno;
+    private final ByteArray data;
+    private final boolean isHeader;
+
+    /**
+     * Constructor, header flag is false.
+     *
+     * @param	r	the FetchResponse
+     * @exception	ParsingException	for parsing failures
+     */
+    public RFC822DATA(FetchResponse r) throws ParsingException {
+	this(r, false);
+    }
+
+    /**
+     * Constructor, specifying header flag.
+     *
+     * @param	r	the FetchResponse
+     * @param	isHeader	just header information?
+     * @exception	ParsingException	for parsing failures
+     */
+    public RFC822DATA(FetchResponse r, boolean isHeader)
+				throws ParsingException {
+	this.isHeader = isHeader;
+	msgno = r.getNumber();
+	r.skipSpaces();
+	data = r.readByteArray();
+    }
+
+    public ByteArray getByteArray() {
+	return data;
+    }
+
+    public ByteArrayInputStream getByteArrayInputStream() {
+	if (data != null)
+	    return data.toByteArrayInputStream();
+	else
+	    return null;
+    }
+
+    public boolean isHeader() {
+	return isHeader;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/RFC822SIZE.java b/mail/src/main/java/com/sun/mail/imap/protocol/RFC822SIZE.java
new file mode 100644
index 0000000..59118f1
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/RFC822SIZE.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.*; 
+
+/**
+ * An RFC822SIZE FETCH item.
+ *
+ * @author  John Mani
+ */
+
+public class RFC822SIZE implements Item {
+    
+    static final char[] name = {'R','F','C','8','2','2','.','S','I','Z','E'};
+    public int msgno;
+
+    public long size;
+
+    /**
+     * Constructor.
+     *
+     * @param	r	the FetchResponse
+     * @exception	ParsingException	for parsing failures
+     */
+    public RFC822SIZE(FetchResponse r) throws ParsingException {
+	msgno = r.getNumber();
+	r.skipSpaces();
+	size = r.readLong();		
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/SaslAuthenticator.java b/mail/src/main/java/com/sun/mail/imap/protocol/SaslAuthenticator.java
new file mode 100644
index 0000000..4c31c76
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/SaslAuthenticator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.ProtocolException;
+
+/**
+ * Interface to make it easier to call IMAPSaslAuthenticator.
+ */
+
+public interface SaslAuthenticator {
+    public boolean authenticate(String[] mechs, String realm, String authzid,
+				String u, String p) throws ProtocolException;
+
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/SearchSequence.java b/mail/src/main/java/com/sun/mail/imap/protocol/SearchSequence.java
new file mode 100644
index 0000000..c138837
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/SearchSequence.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.*;
+import java.io.IOException;
+
+import javax.mail.*;
+import javax.mail.search.*;
+import com.sun.mail.iap.*;
+import com.sun.mail.imap.OlderTerm;
+import com.sun.mail.imap.YoungerTerm;
+import com.sun.mail.imap.ModifiedSinceTerm;
+
+/**
+ * This class traverses a search-tree and generates the 
+ * corresponding IMAP search sequence. 
+ *
+ * Each IMAPProtocol instance contains an instance of this class,
+ * which might be subclassed by subclasses of IMAPProtocol to add
+ * support for additional product-specific search terms.
+ *
+ * @author	John Mani
+ * @author	Bill Shannon
+ */
+public class SearchSequence {
+
+    private IMAPProtocol protocol;	// for hasCapability checks; may be null
+
+    /**
+     * Create a SearchSequence for this IMAPProtocol.
+     *
+     * @param	p	the IMAPProtocol object for the server
+     * @since	JavaMail 1.6.0
+     */
+    public SearchSequence(IMAPProtocol p) {
+	protocol = p;
+    }
+
+    /**
+     * Create a SearchSequence.
+     */
+    @Deprecated
+    public SearchSequence() {
+    }
+
+    /**
+     * Generate the IMAP search sequence for the given search expression. 
+     *
+     * @param	term	the search term
+     * @param	charset	charset for the search
+     * @return		the SEARCH Argument
+     * @exception	SearchException	for failures
+     * @exception	IOException	for I/O errors
+     */
+    public Argument generateSequence(SearchTerm term, String charset) 
+		throws SearchException, IOException {
+	/*
+	 * Call the appropriate handler depending on the type of
+	 * the search-term ...
+	 */
+	if (term instanceof AndTerm) 		// AND
+	    return and((AndTerm)term, charset);
+	else if (term instanceof OrTerm) 	// OR
+	    return or((OrTerm)term, charset);
+	else if (term instanceof NotTerm) 	// NOT
+	    return not((NotTerm)term, charset);
+	else if (term instanceof HeaderTerm) 	// HEADER
+	    return header((HeaderTerm)term, charset);
+	else if (term instanceof FlagTerm) 	// FLAG
+	    return flag((FlagTerm)term);
+	else if (term instanceof FromTerm) {	// FROM
+	    FromTerm fterm = (FromTerm)term;
+	    return from(fterm.getAddress().toString(), charset);
+	}
+	else if (term instanceof FromStringTerm) { // FROM
+	    FromStringTerm fterm = (FromStringTerm)term;
+	    return from(fterm.getPattern(), charset);
+	}
+	else if (term instanceof RecipientTerm)	{ // RECIPIENT
+	    RecipientTerm rterm = (RecipientTerm)term;
+	    return recipient(rterm.getRecipientType(), 
+			     rterm.getAddress().toString(),
+			     charset);
+	}
+	else if (term instanceof RecipientStringTerm) { // RECIPIENT
+	    RecipientStringTerm rterm = (RecipientStringTerm)term;
+	    return recipient(rterm.getRecipientType(),
+			     rterm.getPattern(),
+			     charset);
+	}
+	else if (term instanceof SubjectTerm)	// SUBJECT
+	    return subject((SubjectTerm)term, charset);
+	else if (term instanceof BodyTerm)	// BODY
+	    return body((BodyTerm)term, charset);
+	else if (term instanceof SizeTerm)	// SIZE
+	    return size((SizeTerm)term);
+	else if (term instanceof SentDateTerm)	// SENTDATE
+	    return sentdate((SentDateTerm)term);
+	else if (term instanceof ReceivedDateTerm) // INTERNALDATE
+	    return receiveddate((ReceivedDateTerm)term);
+	else if (term instanceof OlderTerm)	// RFC 5032 OLDER
+	    return older((OlderTerm)term);
+	else if (term instanceof YoungerTerm)	// RFC 5032 YOUNGER
+	    return younger((YoungerTerm)term);
+	else if (term instanceof MessageIDTerm) // MessageID
+	    return messageid((MessageIDTerm)term, charset);
+	else if (term instanceof ModifiedSinceTerm)	// RFC 4551 MODSEQ
+	    return modifiedSince((ModifiedSinceTerm)term);
+	else
+	    throw new SearchException("Search too complex");
+    }
+
+    /**
+     * Check if the "text" terms in the given SearchTerm contain
+     * non US-ASCII characters.
+     *
+     * @param	term	the search term
+     * @return		true if only ASCII
+     */
+    public static boolean isAscii(SearchTerm term) {
+	if (term instanceof AndTerm)
+	    return isAscii(((AndTerm)term).getTerms());
+	else if (term instanceof OrTerm)
+	    return isAscii(((OrTerm)term).getTerms());
+	else if (term instanceof NotTerm)
+	    return isAscii(((NotTerm)term).getTerm());
+	else if (term instanceof StringTerm)
+	    return isAscii(((StringTerm)term).getPattern());
+	else if (term instanceof AddressTerm)
+	    return isAscii(((AddressTerm)term).getAddress().toString());
+	
+	// Any other term returns true.
+	return true;
+    }
+
+    /**
+     * Check if any of the "text" terms in the given SearchTerms contain
+     * non US-ASCII characters.
+     *
+     * @param	terms	the search terms
+     * @return		true if only ASCII
+     */
+    public static boolean isAscii(SearchTerm[] terms) {
+	for (int i = 0; i < terms.length; i++)
+	    if (!isAscii(terms[i])) // outta here !
+		return false;
+	return true;
+    }
+
+    /**
+     * Does this string contain only ASCII characters?
+     *
+     * @param	s	the string
+     * @return		true if only ASCII
+     */
+    public static boolean isAscii(String s) {
+	int l = s.length();
+
+	for (int i=0; i < l; i++) {
+	    if ((int)s.charAt(i) > 0177) // non-ascii
+		return false;
+	}
+	return true;
+    }
+
+    protected Argument and(AndTerm term, String charset) 
+			throws SearchException, IOException {
+	// Combine the sequences for both terms
+	SearchTerm[] terms = term.getTerms();
+	// Generate the search sequence for the first term
+	Argument result = generateSequence(terms[0], charset);
+	// Append other terms
+	for (int i = 1; i < terms.length; i++)
+	    result.append(generateSequence(terms[i], charset));
+	return result;
+    }
+
+    protected Argument or(OrTerm term, String charset) 
+			throws SearchException, IOException {
+	SearchTerm[] terms = term.getTerms();
+
+	/* The IMAP OR operator takes only two operands. So if
+	 * we have more than 2 operands, group them into 2-operand
+	 * OR Terms.
+	 */
+	if (terms.length > 2) {
+	    SearchTerm t = terms[0];
+
+	    // Include rest of the terms
+	    for (int i = 1; i < terms.length; i++)
+		t = new OrTerm(t, terms[i]);
+
+	    term = (OrTerm)t; 	// set 'term' to the new jumbo OrTerm we
+				// just created
+	    terms = term.getTerms();
+	}
+
+	// 'term' now has only two operands
+	Argument result = new Argument();
+
+	// Add the OR search-key, if more than one term
+	if (terms.length > 1)
+	    result.writeAtom("OR");
+
+	/* If this term is an AND expression, we need to enclose it
+	 * within paranthesis.
+	 *
+	 * AND expressions are either AndTerms or FlagTerms 
+	 */
+	if (terms[0] instanceof AndTerm || terms[0] instanceof FlagTerm)
+	    result.writeArgument(generateSequence(terms[0], charset));
+	else
+	    result.append(generateSequence(terms[0], charset));
+
+	// Repeat the above for the second term, if there is one
+	if (terms.length > 1) {
+	    if (terms[1] instanceof AndTerm || terms[1] instanceof FlagTerm)
+		result.writeArgument(generateSequence(terms[1], charset));
+	    else
+		result.append(generateSequence(terms[1], charset));
+	}
+
+	return result;
+    }
+
+    protected Argument not(NotTerm term, String charset) 
+			throws SearchException, IOException {
+	Argument result = new Argument();
+
+	// Add the NOT search-key
+	result.writeAtom("NOT");
+
+	/* If this term is an AND expression, we need to enclose it
+	 * within paranthesis. 
+	 *
+	 * AND expressions are either AndTerms or FlagTerms 
+	 */
+	SearchTerm nterm = term.getTerm();
+	if (nterm instanceof AndTerm || nterm instanceof FlagTerm)
+	    result.writeArgument(generateSequence(nterm, charset));
+	else
+	    result.append(generateSequence(nterm, charset));
+
+	return result;
+    }
+
+    protected Argument header(HeaderTerm term, String charset) 
+			throws SearchException, IOException {
+	Argument result = new Argument();
+	result.writeAtom("HEADER");
+	result.writeString(term.getHeaderName());
+	result.writeString(term.getPattern(), charset);
+	return result;
+    }
+
+    protected Argument messageid(MessageIDTerm term, String charset) 
+			throws SearchException, IOException {
+	Argument result = new Argument();
+	result.writeAtom("HEADER");
+	result.writeString("Message-ID");
+	// XXX confirm that charset conversion ought to be done
+	result.writeString(term.getPattern(), charset); 
+	return result;
+    }
+
+    protected Argument flag(FlagTerm term) throws SearchException {
+	boolean set = term.getTestSet();
+
+	Argument result = new Argument();
+
+	Flags flags = term.getFlags();
+	Flags.Flag[] sf = flags.getSystemFlags();
+	String[] uf = flags.getUserFlags();
+	if (sf.length == 0 && uf.length == 0)
+	    throw new SearchException("Invalid FlagTerm");
+
+	for (int i = 0; i < sf.length; i++) {
+	    if (sf[i] == Flags.Flag.DELETED)
+		result.writeAtom(set ? "DELETED": "UNDELETED");
+	    else if (sf[i] == Flags.Flag.ANSWERED)
+		result.writeAtom(set ? "ANSWERED": "UNANSWERED");
+	    else if (sf[i] == Flags.Flag.DRAFT)
+		result.writeAtom(set ? "DRAFT": "UNDRAFT");
+	    else if (sf[i] == Flags.Flag.FLAGGED)
+		result.writeAtom(set ? "FLAGGED": "UNFLAGGED");
+	    else if (sf[i] == Flags.Flag.RECENT)
+		result.writeAtom(set ? "RECENT": "OLD");
+	    else if (sf[i] == Flags.Flag.SEEN)
+		result.writeAtom(set ? "SEEN": "UNSEEN");
+	}
+
+	for (int i = 0; i < uf.length; i++) {
+	    result.writeAtom(set ? "KEYWORD" : "UNKEYWORD");
+	    result.writeAtom(uf[i]);
+	}
+	
+	return result;
+    }
+
+    protected Argument from(String address, String charset) 
+			throws SearchException, IOException {
+	Argument result = new Argument();
+	result.writeAtom("FROM");
+	result.writeString(address, charset);
+	return result;
+    }
+
+    protected Argument recipient(Message.RecipientType type,
+				      String address, String charset)
+			throws SearchException, IOException {
+	Argument result = new Argument();
+
+	if (type == Message.RecipientType.TO)
+	    result.writeAtom("TO");
+	else if (type == Message.RecipientType.CC)
+	    result.writeAtom("CC");
+	else if (type == Message.RecipientType.BCC)
+	    result.writeAtom("BCC");
+	else
+	    throw new SearchException("Illegal Recipient type");
+
+	result.writeString(address, charset);
+	return result;
+    }
+
+    protected Argument subject(SubjectTerm term, String charset) 
+			throws SearchException, IOException {
+	Argument result = new Argument();
+	
+	result.writeAtom("SUBJECT");
+	result.writeString(term.getPattern(), charset);
+	return result;
+    }
+
+    protected Argument body(BodyTerm term, String charset) 
+			throws SearchException, IOException {
+	Argument result = new Argument();
+
+	result.writeAtom("BODY");
+	result.writeString(term.getPattern(), charset);
+	return result;
+    }
+
+    protected Argument size(SizeTerm term) 
+			throws SearchException {
+	Argument result = new Argument();
+
+	switch (term.getComparison()) {
+	    case ComparisonTerm.GT:
+		result.writeAtom("LARGER");
+		break;
+	    case ComparisonTerm.LT:
+		result.writeAtom("SMALLER");
+		break;
+	    default:
+		// GT and LT is all we get from IMAP for size
+	    	throw new SearchException("Cannot handle Comparison");
+	}
+
+	result.writeNumber(term.getNumber());
+	return result;
+    }
+
+    // Date SEARCH stuff ...
+
+    // NOTE: The built-in IMAP date comparisons are equivalent to
+    //       "<" (BEFORE), "=" (ON), and ">=" (SINCE)!!!
+    //       There is no built-in greater-than comparison!
+
+    /**
+     * Print an IMAP Date string, that is suitable for the Date
+     * SEARCH commands.
+     *
+     * The IMAP Date string is :
+     *	date ::= date_day "-" date_month "-" date_year	
+     *
+     * Note that this format does not contain the TimeZone
+     */
+    private static String monthTable[] = { 
+	  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+	  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+    };
+
+    // A GregorianCalendar object in the current timezone
+    protected Calendar cal = new GregorianCalendar();
+
+    protected String toIMAPDate(Date date) {
+	StringBuilder s = new StringBuilder();
+
+	cal.setTime(date);
+
+	s.append(cal.get(Calendar.DATE)).append("-");
+	s.append(monthTable[cal.get(Calendar.MONTH)]).append('-');
+	s.append(cal.get(Calendar.YEAR));
+
+	return s.toString();
+    }
+
+    protected Argument sentdate(DateTerm term) 
+			throws SearchException {
+	Argument result = new Argument();
+	String date = toIMAPDate(term.getDate());
+
+	switch (term.getComparison()) {
+	    case ComparisonTerm.GT:
+		result.writeAtom("NOT SENTON " + date + " SENTSINCE " + date);
+		break;
+	    case ComparisonTerm.EQ:
+		result.writeAtom("SENTON " + date);
+		break;
+	    case ComparisonTerm.LT:
+		result.writeAtom("SENTBEFORE " + date);
+		break;
+	    case ComparisonTerm.GE:
+		result.writeAtom("SENTSINCE " + date);
+		break;
+	    case ComparisonTerm.LE:
+		result.writeAtom("OR SENTBEFORE " + date + " SENTON " + date);
+		break;
+	    case ComparisonTerm.NE:
+		result.writeAtom("NOT SENTON " + date);
+		break;
+	    default:
+	    	throw new SearchException("Cannot handle Date Comparison");
+	}
+
+	return result;
+    }
+
+    protected Argument receiveddate(DateTerm term) 
+			throws SearchException {
+	Argument result = new Argument();
+	String date = toIMAPDate(term.getDate());
+
+	switch (term.getComparison()) {
+	    case ComparisonTerm.GT:
+		result.writeAtom("NOT ON " + date + " SINCE " + date);
+		break;
+	    case ComparisonTerm.EQ:
+		result.writeAtom("ON " + date);
+		break;
+	    case ComparisonTerm.LT:
+		result.writeAtom("BEFORE " + date);
+		break;
+	    case ComparisonTerm.GE:
+		result.writeAtom("SINCE " + date);
+		break;
+	    case ComparisonTerm.LE:
+		result.writeAtom("OR BEFORE " + date + " ON " + date);
+		break;
+	    case ComparisonTerm.NE:
+		result.writeAtom("NOT ON " + date);
+		break;
+	    default:
+	    	throw new SearchException("Cannot handle Date Comparison");
+	}
+
+	return result;
+    }
+
+    /**
+     * Generate argument for OlderTerm.
+     *
+     * @param	term	the search term
+     * @return		the SEARCH Argument
+     * @exception	SearchException	for failures
+     * @since	JavaMail 1.5.1
+     */
+    protected Argument older(OlderTerm term) throws SearchException {
+	if (protocol != null && !protocol.hasCapability("WITHIN"))
+	    throw new SearchException("Server doesn't support OLDER searches");
+	Argument result = new Argument();
+	result.writeAtom("OLDER");
+	result.writeNumber(term.getInterval());
+	return result;
+    }
+
+    /**
+     * Generate argument for YoungerTerm.
+     *
+     * @param	term	the search term
+     * @return		the SEARCH Argument
+     * @exception	SearchException	for failures
+     * @since	JavaMail 1.5.1
+     */
+    protected Argument younger(YoungerTerm term) throws SearchException {
+	if (protocol != null && !protocol.hasCapability("WITHIN"))
+	    throw new SearchException("Server doesn't support YOUNGER searches");
+	Argument result = new Argument();
+	result.writeAtom("YOUNGER");
+	result.writeNumber(term.getInterval());
+	return result;
+    }
+
+    /**
+     * Generate argument for ModifiedSinceTerm.
+     *
+     * @param	term	the search term
+     * @return		the SEARCH Argument
+     * @exception	SearchException	for failures
+     * @since	JavaMail 1.5.1
+     */
+    protected Argument modifiedSince(ModifiedSinceTerm term)
+				throws SearchException {
+	if (protocol != null && !protocol.hasCapability("CONDSTORE"))
+	    throw new SearchException("Server doesn't support MODSEQ searches");
+	Argument result = new Argument();
+	result.writeAtom("MODSEQ");
+	result.writeNumber(term.getModSeq());
+	return result;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/Status.java b/mail/src/main/java/com/sun/mail/imap/protocol/Status.java
new file mode 100644
index 0000000..76d29c3
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/Status.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Locale;
+
+import com.sun.mail.iap.*;
+
+/**
+ * STATUS response.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class Status { 
+    public String mbox = null;
+    public int total = -1;
+    public int recent = -1;
+    public long uidnext = -1;
+    public long uidvalidity = -1;
+    public int unseen = -1;
+    public long highestmodseq = -1;
+    public Map<String,Long> items;	// any unknown items
+
+    static final String[] standardItems =
+	{ "MESSAGES", "RECENT", "UNSEEN", "UIDNEXT", "UIDVALIDITY" };
+
+    public Status(Response r) throws ParsingException {
+	// mailbox := astring
+	mbox = r.readAtomString();
+	if (!r.supportsUtf8())
+	    mbox = BASE64MailboxDecoder.decode(mbox);
+
+	// Workaround buggy IMAP servers that don't quote folder names
+	// with spaces.
+	final StringBuilder buffer = new StringBuilder();
+	boolean onlySpaces = true;
+
+	while (r.peekByte() != '(' && r.peekByte() != 0) {
+	    final char next = (char)r.readByte();
+
+	    buffer.append(next);
+
+	    if (next != ' ') {
+		onlySpaces = false;
+	    }
+	}
+
+	if (!onlySpaces) {
+	    mbox = (mbox + buffer).trim();
+	}
+
+	if (r.readByte() != '(')
+	    throw new ParsingException("parse error in STATUS");
+	
+	do {
+	    String attr = r.readAtom();
+	    if (attr == null)
+		throw new ParsingException("parse error in STATUS");
+	    if (attr.equalsIgnoreCase("MESSAGES"))
+		total = r.readNumber();
+	    else if (attr.equalsIgnoreCase("RECENT"))
+		recent = r.readNumber();
+	    else if (attr.equalsIgnoreCase("UIDNEXT"))
+		uidnext = r.readLong();
+	    else if (attr.equalsIgnoreCase("UIDVALIDITY"))
+		uidvalidity = r.readLong();
+	    else if (attr.equalsIgnoreCase("UNSEEN"))
+		unseen = r.readNumber();
+	    else if (attr.equalsIgnoreCase("HIGHESTMODSEQ"))
+		highestmodseq = r.readLong();
+	    else {
+		if (items == null)
+		    items = new HashMap<>();
+		items.put(attr.toUpperCase(Locale.ENGLISH),
+			    Long.valueOf(r.readLong()));
+	    }
+	} while (!r.isNextNonSpace(')'));
+    }
+
+    /**
+     * Get the value for the STATUS item.
+     *
+     * @param	item	the STATUS item
+     * @return		the value
+     * @since	JavaMail 1.5.2
+     */
+    public long getItem(String item) {
+	item = item.toUpperCase(Locale.ENGLISH);
+	Long v;
+	long ret = -1;
+	if (items != null && (v = items.get(item)) != null)
+	    ret = v.longValue();
+	else if (item.equals("MESSAGES"))
+	    ret = total;
+	else if (item.equals("RECENT"))
+	    ret = recent;
+	else if (item.equals("UIDNEXT"))
+	    ret = uidnext;
+	else if (item.equals("UIDVALIDITY"))
+	    ret = uidvalidity;
+	else if (item.equals("UNSEEN"))
+	    ret = unseen;
+	else if (item.equals("HIGHESTMODSEQ"))
+	    ret = highestmodseq;
+	return ret;
+    }
+
+    public static void add(Status s1, Status s2) {
+	if (s2.total != -1)
+	    s1.total = s2.total;
+	if (s2.recent != -1)
+	    s1.recent = s2.recent;
+	if (s2.uidnext != -1)
+	    s1.uidnext = s2.uidnext;
+	if (s2.uidvalidity != -1)
+	    s1.uidvalidity = s2.uidvalidity;
+	if (s2.unseen != -1)
+	    s1.unseen = s2.unseen;
+	if (s2.highestmodseq != -1)
+	    s1.highestmodseq = s2.highestmodseq;
+	if (s1.items == null)
+	    s1.items = s2.items;
+	else if (s2.items != null)
+	    s1.items.putAll(s2.items);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/UID.java b/mail/src/main/java/com/sun/mail/imap/protocol/UID.java
new file mode 100644
index 0000000..58082c2
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/UID.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.*; 
+
+/**
+ * This class represents the UID data item.
+ *
+ * @author  John Mani
+ */
+
+public class UID implements Item {
+    
+    static final char[] name = {'U','I','D'};
+    public int seqnum;
+
+    public long uid;
+
+    /**
+     * Constructor.
+     *
+     * @param	r	the FetchResponse
+     * @exception	ParsingException	for parsing failures
+     */
+    public UID(FetchResponse r) throws ParsingException {
+	seqnum = r.getNumber();
+	r.skipSpaces();
+	uid = r.readLong();
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/UIDSet.java b/mail/src/main/java/com/sun/mail/imap/protocol/UIDSet.java
new file mode 100644
index 0000000..2228581
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/UIDSet.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/**
+ * This class holds the 'start' and 'end' for a range of UIDs.
+ * Just like MessageSet except using long instead of int.
+ */
+public class UIDSet {
+
+    public long start;
+    public long end;
+
+    public UIDSet() { }
+
+    public UIDSet(long start, long end) {
+	this.start = start;
+	this.end = end;
+    }
+
+    /**
+     * Count the total number of elements in a UIDSet
+     *
+     * @return	the number of elements
+     */
+    public long size() {
+	return end - start + 1;
+    }
+
+    /**
+     * Convert an array of longs into an array of UIDSets
+     *
+     * @param	uids	the UIDs
+     * @return		array of UIDSet objects
+     */
+    public static UIDSet[] createUIDSets(long[] uids) {
+	if (uids == null)
+	    return null;
+	List<UIDSet> v = new ArrayList<>();
+	int i,j;
+
+	for (i=0; i < uids.length; i++) {
+	    UIDSet ms = new UIDSet();
+	    ms.start = uids[i];
+
+	    // Look for contiguous elements
+	    for (j=i+1; j < uids.length; j++) {
+		if (uids[j] != uids[j-1] +1)
+		    break;
+	    }
+	    ms.end = uids[j-1];
+	    v.add(ms);
+	    i = j-1; // i gets incremented @ top of the loop
+	}
+	UIDSet[] uidset = new UIDSet[v.size()];	
+	return v.toArray(uidset);
+    }
+
+    /**
+     * Parse a string in IMAP UID range format.
+     *
+     * @param	uids	UID string
+     * @return		array of UIDSet objects
+     * @since	JavaMail 1.5.1
+     */
+    public static UIDSet[] parseUIDSets(String uids) {
+	if (uids == null)
+	    return null;
+	List<UIDSet> v = new ArrayList<>();
+	StringTokenizer st = new StringTokenizer(uids, ",:", true);
+	long start = -1;
+	UIDSet cur = null;
+	try {
+	    while(st.hasMoreTokens()) {
+		String s = st.nextToken();
+		if (s.equals(",")) {
+		    if (cur != null)
+			v.add(cur);
+		    cur = null;
+		} else if (s.equals(":")) {
+		    // nothing to do, wait for next number
+		} else {	// better be a number
+		    long n = Long.parseLong(s);
+		    if (cur != null)
+			cur.end = n;
+		    else
+			cur = new UIDSet(n, n);
+		}
+	    }
+	} catch (NumberFormatException nex) {
+	    // give up and return what we have so far
+	}
+	if (cur != null)
+	    v.add(cur);
+	UIDSet[] uidset = new UIDSet[v.size()];
+	return v.toArray(uidset);
+    }
+
+    /**
+     * Convert an array of UIDSets into an IMAP sequence range.
+     *
+     * @param	uidset	the UIDSets
+     * @return		the IMAP sequence string
+     */
+    public static String toString(UIDSet[] uidset) {
+	if (uidset == null)
+	    return null;
+	if (uidset.length == 0) // Empty uidset
+	    return "";
+
+	int i = 0;  // uidset index
+	StringBuilder s = new StringBuilder();
+	int size = uidset.length;
+	long start, end;
+
+	for (;;) {
+	    start = uidset[i].start;
+	    end = uidset[i].end;
+
+	    if (end > start)
+		s.append(start).append(':').append(end);
+	    else // end == start means only one element
+		s.append(start);
+	
+	    i++; // Next UIDSet
+	    if (i >= size) // No more UIDSets
+		break;
+	    else
+		s.append(',');
+	}
+	return s.toString();
+    }
+
+    /**
+     * Convert an array of UIDSets into a array of long UIDs.
+     *
+     * @param	uidset	the UIDSets
+     * @return		arrray of UIDs
+     * @since	JavaMail 1.5.1
+     */
+    public static long[] toArray(UIDSet[] uidset) {
+	//return toArray(uidset, -1);
+	if (uidset == null)
+	    return null;
+	long[] uids = new long[(int)UIDSet.size(uidset)];
+	int i = 0;
+	for (UIDSet u : uidset) {
+	    for (long n = u.start; n <= u.end; n++)
+		uids[i++] = n;
+	}
+	return uids;
+    }
+
+    /**
+     * Convert an array of UIDSets into a array of long UIDs.
+     * Don't include any UIDs larger than uidmax.
+     *
+     * @param	uidset	the UIDSets
+     * @param	uidmax	maximum UID
+     * @return		arrray of UIDs
+     * @since	JavaMail 1.5.1
+     */
+    public static long[] toArray(UIDSet[] uidset, long uidmax) {
+	if (uidset == null)
+	    return null;
+	long[] uids = new long[(int)UIDSet.size(uidset, uidmax)];
+	int i = 0;
+	for (UIDSet u : uidset) {
+	    for (long n = u.start; n <= u.end; n++) {
+		if (uidmax >= 0 && n > uidmax)
+		    break;
+		uids[i++] = n;
+	    }
+	}
+	return uids;
+    }
+
+    /**
+     * Count the total number of elements in an array of UIDSets.
+     *
+     * @param	uidset	the UIDSets
+     * @return		the number of elements
+     */
+    public static long size(UIDSet[] uidset) {
+	long count = 0;
+
+	if (uidset != null)
+	    for (UIDSet u : uidset)
+		count += u.size();
+	
+	return count;
+    }
+
+    /**
+     * Count the total number of elements in an array of UIDSets.
+     * Don't count UIDs greater then uidmax.
+     *
+     * @since	JavaMail 1.5.1
+     */
+    private static long size(UIDSet[] uidset, long uidmax) {
+	long count = 0;
+
+	if (uidset != null)
+	    for (UIDSet u : uidset) {
+		if (uidmax < 0)
+		    count += u.size();
+		else if (u.start <= uidmax) {
+		    if (u.end < uidmax)
+			count += u.end - u.start + 1;
+		    else
+			count += uidmax - u.start + 1;
+		}
+	    }
+	
+	return count;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/imap/protocol/package.html b/mail/src/main/java/com/sun/mail/imap/protocol/package.html
new file mode 100644
index 0000000..370726e
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/imap/protocol/package.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.imap.protocol package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+This package includes internal IMAP support classes and
+<strong>SHOULD NOT BE USED DIRECTLY BY APPLICATIONS</strong>.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/com/sun/mail/pop3/AppendStream.java b/mail/src/main/java/com/sun/mail/pop3/AppendStream.java
new file mode 100644
index 0000000..cc56ce3
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/AppendStream.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+
+/**
+ * A stream for writing to the temp file, and when done can return a stream for
+ * reading the data just written. NOTE: We assume that only one thread is
+ * writing to the file at a time.
+ */
+class AppendStream extends OutputStream {
+
+    private final WritableSharedFile tf;
+    private RandomAccessFile raf;
+    private final long start;
+    private long end;
+
+    public AppendStream(WritableSharedFile tf) throws IOException {
+	this.tf = tf;
+	raf = tf.getWritableFile();
+	start = raf.length();
+	raf.seek(start);
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+	raf.write(b);
+    }
+
+    @Override
+    public void write(byte[] b) throws IOException {
+	raf.write(b);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+	raf.write(b, off, len);
+    }
+
+    @Override
+    public synchronized void close() throws IOException {
+	end = tf.updateLength();
+	raf = null;	// no more writing allowed
+    }
+
+    public synchronized InputStream getInputStream() throws IOException {
+	return tf.newStream(start, end);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/DefaultFolder.java b/mail/src/main/java/com/sun/mail/pop3/DefaultFolder.java
new file mode 100644
index 0000000..eaf9d63
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/DefaultFolder.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.*;
+
+/**
+ * The POP3 DefaultFolder.  Only contains the "INBOX" folder.
+ *
+ * @author Christopher Cotton
+ */
+public class DefaultFolder extends Folder {
+
+    DefaultFolder(POP3Store store) {
+	super(store);
+    }
+
+    @Override
+    public String getName() {
+	return "";
+    }
+
+    @Override
+    public String getFullName() {
+	return "";
+    }
+
+    @Override
+    public Folder getParent() {
+	return null;
+    }
+
+    @Override
+    public boolean exists() {
+	return true;
+    }
+
+    @Override
+    public Folder[] list(String pattern) throws MessagingException {
+	Folder[] f = { getInbox() };
+	return f;
+    }
+
+    @Override
+    public char getSeparator() {
+	return '/';
+    }
+
+    @Override
+    public int getType() {
+	return HOLDS_FOLDERS;
+    }
+
+    @Override
+    public boolean create(int type) throws MessagingException {
+	return false;
+    }
+
+    @Override
+    public boolean hasNewMessages() throws MessagingException {
+	return false;
+    }
+
+    @Override
+    public Folder getFolder(String name) throws MessagingException {
+	if (!name.equalsIgnoreCase("INBOX")) {
+	    throw new MessagingException("only INBOX supported");
+	} else {
+	    return getInbox();
+	}
+    }
+
+    protected Folder getInbox() throws MessagingException {
+	return getStore().getFolder("INBOX");
+    }
+    
+
+    @Override
+    public boolean delete(boolean recurse) throws MessagingException {
+	throw new MethodNotSupportedException("delete");
+    }
+
+    @Override
+    public boolean renameTo(Folder f) throws MessagingException {
+	throw new MethodNotSupportedException("renameTo");
+    }
+
+    @Override
+    public void open(int mode) throws MessagingException {
+	throw new MethodNotSupportedException("open");
+    }
+
+    @Override
+    public void close(boolean expunge) throws MessagingException {
+	throw new MethodNotSupportedException("close");
+    }
+
+    @Override
+    public boolean isOpen() {
+	return false;
+    }
+
+    @Override
+    public Flags getPermanentFlags() {
+	return new Flags(); // empty flags object
+    }
+
+    @Override
+    public int getMessageCount() throws MessagingException {
+	return 0;
+    }
+
+    @Override
+    public Message getMessage(int msgno) throws MessagingException {
+	throw new MethodNotSupportedException("getMessage");
+    }
+
+    @Override
+    public void appendMessages(Message[] msgs) throws MessagingException {
+	throw new MethodNotSupportedException("Append not supported");	
+    }
+
+    @Override
+    public Message[] expunge() throws MessagingException {
+	throw new MethodNotSupportedException("expunge");	
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/POP3Folder.java b/mail/src/main/java/com/sun/mail/pop3/POP3Folder.java
new file mode 100644
index 0000000..a045a24
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/POP3Folder.java
@@ -0,0 +1,611 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.*;
+import javax.mail.event.*;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.lang.reflect.Constructor;
+
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.MailLogger;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A POP3 Folder (can only be "INBOX").
+ *
+ * See the <a href="package-summary.html">com.sun.mail.pop3</a> package
+ * documentation for further information on the POP3 protocol provider. <p>
+ *
+ * @author      Bill Shannon
+ * @author	John Mani (ported to the javax.mail APIs)
+ */
+public class POP3Folder extends Folder {
+
+    private String name;
+    private POP3Store store;
+    private volatile Protocol port;
+    private int total;
+    private int size;
+    private boolean exists = false;
+    private volatile boolean opened = false;
+    private POP3Message[] message_cache;
+    private boolean doneUidl = false;
+    private volatile TempFile fileCache = null;
+    private boolean forceClose;
+
+    MailLogger logger;	// package private, for POP3Message
+
+    protected POP3Folder(POP3Store store, String name) {
+	super(store);
+	this.name = name;
+	this.store = store;
+	if (name.equalsIgnoreCase("INBOX"))
+	    exists = true;
+	logger = new MailLogger(this.getClass(), "DEBUG POP3",
+	    store.getSession().getDebug(), store.getSession().getDebugOut());
+    }
+
+    @Override
+    public String getName() {
+	return name;
+    }
+
+    @Override
+    public String getFullName() {
+	return name;
+    }
+
+    @Override
+    public Folder getParent() {
+	return new DefaultFolder(store);
+    }
+
+    /**
+     * Always true for the folder "INBOX", always false for
+     * any other name.
+     *
+     * @return	true for INBOX, false otherwise
+     */
+    @Override
+    public boolean exists() {
+	return exists;
+    }
+
+    /**
+     * Always throws <code>MessagingException</code> because no POP3 folders
+     * can contain subfolders.
+     *
+     * @exception	MessagingException	always
+     */
+    @Override
+    public Folder[] list(String pattern) throws MessagingException {
+	throw new MessagingException("not a directory");
+    }
+
+    /**
+     * Always returns a NUL character because POP3 doesn't support a hierarchy.
+     *
+     * @return	NUL
+     */
+    @Override
+    public char getSeparator() {
+	return '\0';
+    }
+
+    /**
+     * Always returns Folder.HOLDS_MESSAGES.
+     *
+     * @return	Folder.HOLDS_MESSAGES
+     */
+    @Override
+    public int getType() {
+	return HOLDS_MESSAGES;
+    }
+
+    /**
+     * Always returns <code>false</code>; the POP3 protocol doesn't
+     * support creating folders.
+     *
+     * @return	false
+     */
+    @Override
+    public boolean create(int type) throws MessagingException {
+	return false;
+    }
+
+    /**
+     * Always returns <code>false</code>; the POP3 protocol provides
+     * no way to determine when a new message arrives.
+     *
+     * @return	false
+     */
+    @Override
+    public boolean hasNewMessages() throws MessagingException {
+	return false;    // no way to know
+    }
+
+    /**
+     * Always throws <code>MessagingException</code> because no POP3 folders
+     * can contain subfolders.
+     *
+     * @exception	MessagingException	always
+     */
+    @Override
+    public Folder getFolder(String name) throws MessagingException {
+	throw new MessagingException("not a directory");
+    }
+
+    /**
+     * Always throws <code>MethodNotSupportedException</code>
+     * because the POP3 protocol doesn't allow the INBOX to
+     * be deleted.
+     *
+     * @exception	MethodNotSupportedException	always
+     */
+    @Override
+    public boolean delete(boolean recurse) throws MessagingException {
+	throw new MethodNotSupportedException("delete");
+    }
+
+    /**
+     * Always throws <code>MethodNotSupportedException</code>
+     * because the POP3 protocol doesn't support multiple folders.
+     *
+     * @exception	MethodNotSupportedException	always
+     */
+    @Override
+    public boolean renameTo(Folder f) throws MessagingException {
+	throw new MethodNotSupportedException("renameTo");
+    }
+
+    /**
+     * Throws <code>FolderNotFoundException</code> unless this
+     * folder is named "INBOX".
+     *
+     * @exception	FolderNotFoundException	if not INBOX
+     * @exception	AuthenticationFailedException	authentication failures
+     * @exception	MessagingException	other open failures
+     */
+    @Override
+    public synchronized void open(int mode) throws MessagingException {
+	checkClosed();
+	if (!exists)
+	    throw new FolderNotFoundException(this, "folder is not INBOX");
+
+	try {
+	    port = store.getPort(this);
+	    Status s = port.stat();
+	    total = s.total;
+	    size = s.size;
+	    this.mode = mode;
+	    if (store.useFileCache) {
+		try {
+		    fileCache = new TempFile(store.fileCacheDir);
+		} catch (IOException ex) {
+		    logger.log(Level.FINE, "failed to create file cache", ex);
+		    throw ex;	// caught below
+		}
+	    }
+	    opened = true;
+	} catch (IOException ioex) {
+	    try {
+		if (port != null)
+		    port.quit();
+	    } catch (IOException ioex2) {
+		// ignore
+	    } finally {
+		port = null;
+		store.closePort(this);
+	    }
+	    throw new MessagingException("Open failed", ioex);
+	}
+
+	// Create the message cache array of appropriate size
+	message_cache = new POP3Message[total];
+	doneUidl = false;
+
+	notifyConnectionListeners(ConnectionEvent.OPENED);
+    }
+
+    @Override
+    public synchronized void close(boolean expunge) throws MessagingException {
+	checkOpen();
+
+	try {
+	    /*
+	     * Some POP3 servers will mark messages for deletion when
+	     * they're read.  To prevent such messages from being
+	     * deleted before the client deletes them, you can set
+	     * the mail.pop3.rsetbeforequit property to true.  This
+	     * causes us to issue a POP3 RSET command to clear all
+	     * the "marked for deletion" flags.  We can then explicitly
+	     * delete messages as desired.
+	     */
+	    if (store.rsetBeforeQuit && !forceClose)
+		port.rset();
+	    POP3Message m;
+	    if (expunge && mode == READ_WRITE && !forceClose) {
+		// find all messages marked deleted and issue DELE commands
+		for (int i = 0; i < message_cache.length; i++) {
+		    if ((m = message_cache[i]) != null) {
+			if (m.isSet(Flags.Flag.DELETED))
+			    try {
+				port.dele(i + 1);
+			    } catch (IOException ioex) {
+				throw new MessagingException(
+				    "Exception deleting messages during close",
+				    ioex);
+			    }
+		    }
+		}
+	    }
+
+	    /*
+	     * Flush and free all cached data for the messages.
+	     */
+	    for (int i = 0; i < message_cache.length; i++) {
+		if ((m = message_cache[i]) != null)
+		    m.invalidate(true);
+	    }
+
+	    if (forceClose)
+		port.close();
+	    else
+		port.quit();
+	} catch (IOException ex) {
+	    // do nothing
+	} finally {
+	    port = null;
+	    store.closePort(this);
+	    message_cache = null;
+	    opened = false;
+	    notifyConnectionListeners(ConnectionEvent.CLOSED);
+	    if (fileCache != null) {
+		fileCache.close();
+		fileCache = null;
+	    }
+	}
+    }
+
+    @Override
+    public synchronized boolean isOpen() {
+	if (!opened)
+	    return false;
+	try {
+	    if (!port.noop())
+		throw new IOException("NOOP failed");
+	} catch (IOException ioex) {
+	    try {
+		close(false);
+	    } catch (MessagingException mex) {
+		// ignore it
+	    }
+	    return false;
+	}
+	return true;
+    }
+
+    /**
+     * Always returns an empty <code>Flags</code> object because
+     * the POP3 protocol doesn't support any permanent flags.
+     *
+     * @return	empty Flags object
+     */
+    @Override
+    public Flags getPermanentFlags() {
+	return new Flags(); // empty flags object
+    }
+
+    /**
+     * Will not change while the folder is open because the POP3
+     * protocol doesn't support notification of new messages
+     * arriving in open folders.
+     */
+    @Override
+    public synchronized int getMessageCount() throws MessagingException {
+	if (!opened)
+	    return -1;
+	checkReadable();
+	return total;
+    }
+
+    @Override
+    public synchronized Message getMessage(int msgno)
+					throws MessagingException {
+	checkOpen();
+
+	POP3Message m;
+
+	// Assuming that msgno is <= total 
+	if ((m = message_cache[msgno-1]) == null) {
+	    m = createMessage(this, msgno);
+	    message_cache[msgno-1] = m;
+	}
+	return m;
+    }
+
+    protected POP3Message createMessage(Folder f, int msgno)
+				throws MessagingException {
+	POP3Message m = null;
+	Constructor<?> cons = store.messageConstructor;
+	if (cons != null) {
+	    try {
+		Object[] o = { this, Integer.valueOf(msgno) };
+		m = (POP3Message)cons.newInstance(o);
+	    } catch (Exception ex) {
+		// ignore
+	    }
+	}
+	if (m == null)
+	    m = new POP3Message(this, msgno);
+	return m;
+    }
+
+    /**
+     * Always throws <code>MethodNotSupportedException</code>
+     * because the POP3 protocol doesn't support appending messages.
+     *
+     * @exception	MethodNotSupportedException	always
+     */
+    @Override
+    public void appendMessages(Message[] msgs) throws MessagingException {
+	throw new MethodNotSupportedException("Append not supported");	
+    }
+
+    /**
+     * Always throws <code>MethodNotSupportedException</code>
+     * because the POP3 protocol doesn't support expunging messages
+     * without closing the folder; call the {@link #close close} method
+     * with the <code>expunge</code> argument set to <code>true</code>
+     * instead.
+     *
+     * @exception	MethodNotSupportedException	always
+     */
+    @Override
+    public Message[] expunge() throws MessagingException {
+	throw new MethodNotSupportedException("Expunge not supported");
+    }
+
+    /**
+     * Prefetch information about POP3 messages.
+     * If the FetchProfile contains <code>UIDFolder.FetchProfileItem.UID</code>,
+     * POP3 UIDs for all messages in the folder are fetched using the POP3
+     * UIDL command.
+     * If the FetchProfile contains <code>FetchProfile.Item.ENVELOPE</code>,
+     * the headers and size of all messages are fetched using the POP3 TOP
+     * and LIST commands.
+     */
+    @Override
+    public synchronized void fetch(Message[] msgs, FetchProfile fp)
+				throws MessagingException {
+	checkReadable();
+	if (!doneUidl && store.supportsUidl &&
+		fp.contains(UIDFolder.FetchProfileItem.UID)) {
+	    /*
+	     * Since the POP3 protocol only lets us fetch the UID
+	     * for a single message or for all messages, we go ahead
+	     * and fetch UIDs for all messages here, ignoring the msgs
+	     * parameter.  We could be more intelligent and base this
+	     * decision on the number of messages fetched, or the
+	     * percentage of the total number of messages fetched.
+	     */
+	    String[] uids = new String[message_cache.length];
+	    try {
+		if (!port.uidl(uids))
+		    return;
+	    } catch (EOFException eex) {
+		close(false);
+		throw new FolderClosedException(this, eex.toString());
+	    } catch (IOException ex) {
+		throw new MessagingException("error getting UIDL", ex);
+	    }
+	    for (int i = 0; i < uids.length; i++) {
+		if (uids[i] == null)
+		    continue;
+		POP3Message m = (POP3Message)getMessage(i + 1);
+		m.uid = uids[i];
+	    }
+	    doneUidl = true;	// only do this once
+	}
+	if (fp.contains(FetchProfile.Item.ENVELOPE)) {
+	    for (int i = 0; i < msgs.length; i++) {
+		try {
+		    POP3Message msg = (POP3Message)msgs[i];
+		    // fetch headers
+		    msg.getHeader("");
+		    // fetch message size
+		    msg.getSize();
+		} catch (MessageRemovedException mex) {
+		    // should never happen, but ignore it if it does
+		}
+	    }
+	}
+    }
+
+    /**
+     * Return the unique ID string for this message, or null if
+     * not available.  Uses the POP3 UIDL command.
+     *
+     * @param	msg	the message
+     * @return          unique ID string
+     * @exception	MessagingException for failures
+     */
+    public synchronized String getUID(Message msg) throws MessagingException {
+	checkOpen();
+	if (!(msg instanceof POP3Message))
+	    throw new MessagingException("message is not a POP3Message");
+	POP3Message m = (POP3Message)msg;
+	try {
+	    if (!store.supportsUidl)
+		return null;
+	    if (m.uid == POP3Message.UNKNOWN)
+		m.uid = port.uidl(m.getMessageNumber());
+	    return m.uid;
+	} catch (EOFException eex) {
+	    close(false);
+	    throw new FolderClosedException(this, eex.toString());
+	} catch (IOException ex) {
+	    throw new MessagingException("error getting UIDL", ex);
+	}
+    }
+
+    /**
+     * Return the size of this folder, as was returned by the POP3 STAT
+     * command when this folder was opened.
+     *
+     * @return		folder size
+     * @exception	IllegalStateException	if the folder isn't open
+     * @exception	MessagingException for other failures
+     */
+    public synchronized int getSize() throws MessagingException {
+	checkOpen();
+	return size;
+    }
+
+    /**
+     * Return the sizes of all messages in this folder, as returned
+     * by the POP3 LIST command.  Each entry in the array corresponds
+     * to a message; entry <i>i</i> corresponds to message number <i>i+1</i>.
+     *
+     * @return		array of message sizes
+     * @exception	IllegalStateException	if the folder isn't open
+     * @exception	MessagingException for other failures
+     * @since		JavaMail 1.3.3
+     */
+    public synchronized int[] getSizes() throws MessagingException {
+	checkOpen();
+	int sizes[] = new int[total];
+	InputStream is = null;
+	LineInputStream lis = null;
+	try {
+	    is = port.list();
+	    lis = new LineInputStream(is);
+	    String line;
+	    while ((line = lis.readLine()) != null) {
+		try {
+		    StringTokenizer st = new StringTokenizer(line);
+		    int msgnum = Integer.parseInt(st.nextToken());
+		    int size = Integer.parseInt(st.nextToken());
+		    if (msgnum > 0 && msgnum <= total)
+			sizes[msgnum - 1] = size;
+		} catch (RuntimeException e) {
+		}
+	    }
+	} catch (IOException ex) {
+	    // ignore it?
+	} finally {
+	    try {
+		if (lis != null)
+		    lis.close();
+	    } catch (IOException cex) { }
+	    try {
+		if (is != null)
+		    is.close();
+	    } catch (IOException cex) { }
+	}
+	return sizes;
+    }
+
+    /**
+     * Return the raw results of the POP3 LIST command with no arguments.
+     *
+     * @return		InputStream containing results
+     * @exception	IllegalStateException	if the folder isn't open
+     * @exception	IOException for I/O errors talking to the server
+     * @exception	MessagingException for other errors
+     * @since		JavaMail 1.3.3
+     */
+    public synchronized InputStream listCommand()
+				throws MessagingException, IOException {
+	checkOpen();
+	return port.list();
+    }
+
+    /**
+     * Close the folder when we're finalized.
+     */
+    @Override
+    protected void finalize() throws Throwable {
+	forceClose = !store.finalizeCleanClose;
+	try {
+	    if (opened)
+		close(false);
+	} finally {
+	    super.finalize();
+	    forceClose = false;
+	}
+    }
+
+    /* Ensure the folder is open */
+    private void checkOpen() throws IllegalStateException {
+	if (!opened) 
+	    throw new IllegalStateException("Folder is not Open");
+    }
+
+    /* Ensure the folder is not open */
+    private void checkClosed() throws IllegalStateException {
+	if (opened) 
+	    throw new IllegalStateException("Folder is Open");
+    }
+
+    /* Ensure the folder is open & readable */
+    private void checkReadable() throws IllegalStateException {
+	if (!opened || (mode != READ_ONLY && mode != READ_WRITE))
+	    throw new IllegalStateException("Folder is not Readable");
+    }
+
+    /* Ensure the folder is open & writable */
+    /*
+    private void checkWritable() throws IllegalStateException {
+	if (!opened || mode != READ_WRITE)
+	    throw new IllegalStateException("Folder is not Writable");
+    }
+    */
+
+    /**
+     * Centralize access to the Protocol object by POP3Message
+     * objects so that they will fail appropriately when the folder
+     * is closed.
+     */
+    Protocol getProtocol() throws MessagingException {
+	Protocol p = port;	// read it before close() can set it to null
+	checkOpen();
+	// close() might happen here
+	return p;
+    }
+
+    /*
+     * Only here to make accessible to POP3Message.
+     */
+    @Override
+    protected void notifyMessageChangedListeners(int type, Message m) {
+	super.notifyMessageChangedListeners(type, m);
+    }
+
+    /**
+     * Used by POP3Message.
+     */
+    TempFile getFileCache() {
+	return fileCache;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/POP3Message.java b/mail/src/main/java/com/sun/mail/pop3/POP3Message.java
new file mode 100644
index 0000000..7bc9c9f
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/POP3Message.java
@@ -0,0 +1,644 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.*;
+import java.util.Enumeration;
+import java.util.logging.Level;
+import java.lang.ref.SoftReference;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.event.*;
+import com.sun.mail.util.ReadableMime;
+
+/**
+ * A POP3 Message.  Just like a MimeMessage except that
+ * some things are not supported.
+ *
+ * @author      Bill Shannon
+ */
+public class POP3Message extends MimeMessage implements ReadableMime {
+
+    /*
+     * Our locking strategy is to always lock the POP3Folder before the
+     * POP3Message so we have to be careful to drop our lock before calling
+     * back to the folder to close it and notify of connection lost events.
+     */
+
+    // flag to indicate we haven't tried to fetch the UID yet
+    static final String UNKNOWN = "UNKNOWN";
+
+    private POP3Folder folder;	// overrides folder in MimeMessage
+    private int hdrSize = -1;
+    private int msgSize = -1;
+    String uid = UNKNOWN;	// controlled by folder lock
+
+    // rawData itself is never null
+    private SoftReference<InputStream> rawData
+	    = new SoftReference<>(null);
+
+    public POP3Message(Folder folder, int msgno)
+			throws MessagingException {
+	super(folder, msgno);
+	assert folder instanceof POP3Folder;
+	this.folder = (POP3Folder)folder;
+    }
+
+    /**
+     * Set the specified flags on this message to the specified value.
+     *
+     * @param newFlags	the flags to be set
+     * @param set	the value to be set
+     */
+    @Override
+    public synchronized void setFlags(Flags newFlags, boolean set)
+				throws MessagingException {
+	Flags oldFlags = (Flags)flags.clone();
+	super.setFlags(newFlags, set);
+	if (!flags.equals(oldFlags))
+	    folder.notifyMessageChangedListeners(
+				MessageChangedEvent.FLAGS_CHANGED, this);
+    }
+
+    /**
+     * Return the size of the content of this message in bytes. 
+     * Returns -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>
+     *
+     * @return          size of content in bytes
+     * @exception	MessagingException for failures
+     */  
+    @Override
+    public int getSize() throws MessagingException {
+	try {
+	    synchronized (this) {
+		// if we already have the size, return it
+		if (msgSize > 0)
+		    return msgSize;
+	    }
+
+	    /*
+	     * Use LIST to determine the entire message
+	     * size and subtract out the header size
+	     * (which may involve loading the headers,
+	     * which may load the content as a side effect).
+	     * If the content is loaded as a side effect of
+	     * loading the headers, it will set the size.
+	     *
+	     * Make sure to call loadHeaders() outside of the
+	     * synchronization block.  There's a potential race
+	     * condition here but synchronization will occur in
+	     * loadHeaders() to make sure the headers are only
+	     * loaded once, and again in the following block to
+	     * only compute msgSize once.
+	     */
+	    if (headers == null)
+		loadHeaders();
+
+	    synchronized (this) {
+		if (msgSize < 0)
+		    msgSize = folder.getProtocol().list(msgnum) - hdrSize;
+		return msgSize;
+	    }
+	} catch (EOFException eex) {
+	    folder.close(false);
+	    throw new FolderClosedException(folder, eex.toString());
+	} catch (IOException ex) {
+	    throw new MessagingException("error getting size", ex);
+	}
+    }
+
+    /**
+     * Produce the raw bytes of the message.  The data is fetched using
+     * the POP3 RETR command.  If skipHeader is true, just the content
+     * is returned.
+     */
+    private InputStream getRawStream(boolean skipHeader)
+				throws MessagingException {
+	InputStream rawcontent = null;
+	try {
+	synchronized(this) {
+	    rawcontent = rawData.get();
+	    if (rawcontent == null) {
+		TempFile cache = folder.getFileCache();
+		if (cache != null) {
+		    if (folder.logger.isLoggable(Level.FINE))
+			folder.logger.fine("caching message #" + msgnum +
+					    " in temp file");
+		    AppendStream os = cache.getAppendStream();
+		    BufferedOutputStream bos = new BufferedOutputStream(os);
+		    try {
+			folder.getProtocol().retr(msgnum, bos);
+		    } finally {
+			bos.close();
+		    }
+		    rawcontent = os.getInputStream();
+		} else {
+		    rawcontent = folder.getProtocol().retr(msgnum,
+					msgSize > 0 ? msgSize + hdrSize : 0);
+		}
+		if (rawcontent == null) {
+		    expunged = true;
+		    throw new MessageRemovedException(
+			"can't retrieve message #" + msgnum +
+			" in POP3Message.getContentStream"); // XXX - what else?
+		}
+
+		if (headers == null ||
+			((POP3Store)(folder.getStore())).forgetTopHeaders) {
+		    headers = new InternetHeaders(rawcontent);
+		    hdrSize =
+			(int)((SharedInputStream)rawcontent).getPosition();
+		} else {
+		    /*
+		     * Already have the headers, have to skip the headers
+		     * in the content array and return the body.
+		     *
+		     * XXX - It seems that some mail servers return slightly
+		     * different headers in the RETR results than were returned
+		     * in the TOP results, so we can't depend on remembering
+		     * the size of the headers from the TOP command and just
+		     * skipping that many bytes.  Instead, we have to process
+		     * the content, skipping over the header until we come to
+		     * the empty line that separates the header from the body.
+		     */
+		    int offset = 0;
+		    for (;;) {
+			int len = 0;	// number of bytes in this line
+			int c1;
+			while ((c1 = rawcontent.read()) >= 0) {
+			    if (c1 == '\n')	// end of line
+				break;
+			    else if (c1 == '\r') {
+				// got CR, is the next char LF?
+				if (rawcontent.available() > 0) {
+				    rawcontent.mark(1);
+				    if (rawcontent.read() != '\n')
+					rawcontent.reset();
+				}
+				break;	// in any case, end of line
+			    }
+
+			    // not CR, NL, or CRLF, count the byte
+			    len++;
+			}
+			// here when end of line or out of data
+
+			// if out of data, we're done
+			if (rawcontent.available() == 0)
+			    break;
+			
+			// if it was an empty line, we're done
+			if (len == 0)
+			    break;
+		    }
+		    hdrSize =
+			(int)((SharedInputStream)rawcontent).getPosition();
+		}
+
+		// skipped the header, the message is what's left
+		msgSize = rawcontent.available();
+
+		rawData = new SoftReference<>(rawcontent);
+	    }
+	}
+	} catch (EOFException eex) {
+	    folder.close(false);
+	    throw new FolderClosedException(folder, eex.toString());
+	} catch (IOException ex) {
+	    throw new MessagingException("error fetching POP3 content", ex);
+	}
+
+	/*
+	 * We have a cached stream, but we need to return
+	 * a fresh stream to read from the beginning and
+	 * that can be safely closed.
+	 */
+	rawcontent = ((SharedInputStream)rawcontent).newStream(
+						skipHeader ? hdrSize : 0, -1);
+	return rawcontent;
+    }
+
+    /**
+     * Produce the raw bytes of the content.  The data is fetched using
+     * the POP3 RETR command.
+     *
+     * @see #contentStream
+     */
+    @Override
+    protected synchronized InputStream getContentStream()
+				throws MessagingException {
+	if (contentStream != null)
+	    return ((SharedInputStream)contentStream).newStream(0, -1);
+
+	InputStream cstream = getRawStream(true);
+
+	/*
+	 * Keep a hard reference to the data if we're using a file
+	 * cache or if the "mail.pop3.keepmessagecontent" prop is set.
+	 */
+	TempFile cache = folder.getFileCache();
+	if (cache != null ||
+		((POP3Store)(folder.getStore())).keepMessageContent)
+	    contentStream = ((SharedInputStream)cstream).newStream(0, -1);
+	return cstream;
+    }
+
+    /**
+     * Return the MIME format stream corresponding to this message part.
+     *
+     * @return	the MIME format stream
+     * @since	JavaMail 1.4.5
+     */
+    @Override
+    public InputStream getMimeStream() throws MessagingException {
+	return getRawStream(false);
+    }
+
+    /**
+     * Invalidate the cache of content for this message object, causing 
+     * it to be fetched again from the server the next time it is needed.
+     * If <code>invalidateHeaders</code> is true, invalidate the headers
+     * as well.
+     *
+     * @param	invalidateHeaders	invalidate the headers as well?
+     */
+    public synchronized void invalidate(boolean invalidateHeaders) {
+	content = null;
+	InputStream rstream = rawData.get();
+	if (rstream != null) {
+	    // note that if the content is in the file cache, it will be lost
+	    // and fetched from the server if it's needed again
+	    try {
+		rstream.close();
+	    } catch (IOException ex) {
+		// ignore it
+	    }
+	    rawData = new SoftReference<>(null);
+	}
+	if (contentStream != null) {
+	    try {
+		contentStream.close();
+	    } catch (IOException ex) {
+		// ignore it
+	    }
+	    contentStream = null;
+	}
+	msgSize = -1;
+	if (invalidateHeaders) {
+	    headers = null;
+	    hdrSize = -1;
+	}
+    }
+
+    /**
+     * Fetch the header of the message and the first <code>n</code> lines
+     * of the raw content of the message.  The headers and data are
+     * available in the returned InputStream.
+     *
+     * @param	n	number of lines of content to fetch
+     * @return	InputStream containing the message headers and n content lines
+     * @exception	MessagingException for failures
+     */
+    public InputStream top(int n) throws MessagingException {
+	try {
+	    synchronized (this) {
+		return folder.getProtocol().top(msgnum, n);
+	    }
+	} catch (EOFException eex) {
+	    folder.close(false);
+	    throw new FolderClosedException(folder, eex.toString());
+	} catch (IOException ex) {
+	    throw new MessagingException("error getting size", ex);
+	}
+    }
+
+    /**
+     * 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>
+     *
+     * @param	name	name of header
+     * @return	array of headers
+     * @exception	MessagingException for failures
+     * @see 	javax.mail.internet.MimeUtility
+     */
+    @Override
+    public String[] getHeader(String name)
+			throws MessagingException {
+	if (headers == null)
+	    loadHeaders();
+	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 <code>null</code>, only the first header is 
+     * returned.
+     *
+     * @param name		the name of this header
+     * @param delimiter		delimiter between returned headers
+     * @return                  the value fields for all headers with 
+     *				this name
+     * @exception		MessagingException for failures
+     */
+    @Override
+    public String getHeader(String name, String delimiter)
+				throws MessagingException {
+	if (headers == null)
+	    loadHeaders();
+	return headers.getHeader(name, delimiter);
+    }
+
+    /**
+     * Set the value for this header_name.  Throws IllegalWriteException
+     * because POP3 messages are read-only.
+     *
+     * @param	name 	header name
+     * @param	value	header value
+     * @see 	javax.mail.internet.MimeUtility
+     * @exception	IllegalWriteException because the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void setHeader(String name, String value)
+                                throws MessagingException {
+	// XXX - should check for read-only folder?
+	throw new IllegalWriteException("POP3 messages are read-only");
+    }
+
+    /**
+     * Add this value to the existing values for this header_name.
+     * Throws IllegalWriteException because POP3 messages are read-only.
+     *
+     * @param	name 	header name
+     * @param	value	header value
+     * @see 	javax.mail.internet.MimeUtility
+     * @exception	IllegalWriteException because the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     */
+    @Override
+    public void addHeader(String name, String value)
+                                throws MessagingException {
+	// XXX - should check for read-only folder?
+	throw new IllegalWriteException("POP3 messages are read-only");
+    }
+
+    /**
+     * Remove all headers with this name.
+     * Throws IllegalWriteException because POP3 messages are read-only.
+     *
+     * @exception	IllegalWriteException because the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     */
+    @Override
+    public void removeHeader(String name)
+                                throws MessagingException {
+	// XXX - should check for read-only folder?
+	throw new IllegalWriteException("POP3 messages are read-only");
+    }
+
+    /**
+     * 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>
+     *
+     * @return	array of header objects
+     * @exception	MessagingException for failures
+     * @see 	javax.mail.internet.MimeUtility
+     */
+    @Override
+    public Enumeration<Header> getAllHeaders() throws MessagingException {
+	if (headers == null)
+	    loadHeaders();
+	return headers.getAllHeaders();	
+    }
+
+    /**
+     * Return matching headers from this Message as an Enumeration of
+     * Header objects.
+     *
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public Enumeration<Header> getMatchingHeaders(String[] names)
+			throws MessagingException {
+	if (headers == null)
+	    loadHeaders();
+	return headers.getMatchingHeaders(names);
+    }
+
+    /**
+     * Return non-matching headers from this Message as an
+     * Enumeration of Header objects.
+     *
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public Enumeration<Header> getNonMatchingHeaders(String[] names)
+			throws MessagingException {
+	if (headers == null)
+	    loadHeaders();
+	return headers.getNonMatchingHeaders(names);
+    }
+
+    /**
+     * Add a raw RFC822 header-line. 
+     * Throws IllegalWriteException because POP3 messages are read-only.
+     *
+     * @exception	IllegalWriteException because the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     */
+    @Override
+    public void addHeaderLine(String line) throws MessagingException {
+	// XXX - should check for read-only folder?
+	throw new IllegalWriteException("POP3 messages are read-only");
+    }
+
+    /**
+     * Get all header lines as an Enumeration of Strings. A Header
+     * line is a raw RFC822 header-line, containing both the "name" 
+     * and "value" field. 
+     *
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public Enumeration<String> getAllHeaderLines() throws MessagingException {
+	if (headers == null)
+	    loadHeaders();
+	return headers.getAllHeaderLines();
+    }
+
+    /**
+     * Get matching header lines as an Enumeration of Strings. 
+     * A Header line is a raw RFC822 header-line, containing both 
+     * the "name" and "value" field.
+     *
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public Enumeration<String> getMatchingHeaderLines(String[] names)
+                                        throws MessagingException {
+	if (headers == null)
+	    loadHeaders();
+	return headers.getMatchingHeaderLines(names);
+    }
+
+    /**
+     * Get non-matching header lines as an Enumeration of Strings. 
+     * A Header line is a raw RFC822 header-line, containing both 
+     * the "name" and "value" field.
+     *
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public Enumeration<String> getNonMatchingHeaderLines(String[] names)
+                                        throws MessagingException {
+	if (headers == null)
+	    loadHeaders();
+	return headers.getNonMatchingHeaderLines(names);
+    }
+
+    /**
+     * POP3 message can't be changed.  This method throws
+     * IllegalWriteException.
+     *
+     * @exception	IllegalWriteException because the underlying
+     *			implementation does not support modification
+     */
+    @Override
+    public void saveChanges() throws MessagingException {
+	// POP3 Messages are read-only
+	throw new IllegalWriteException("POP3 messages are read-only");
+    }
+
+    /**
+     * Output the message as an RFC 822 format stream, without
+     * specified headers.  If the property "mail.pop3.cachewriteto"
+     * is set to "true", and ignoreList is null, and the message hasn't
+     * already been cached as a side effect of other operations, the message
+     * content is cached before being written.  Otherwise, the message is
+     * streamed directly to the output stream without being cached.
+     *
+     * @exception IOException	if an error occurs writing to the stream
+     *				or if an error is generated by the
+     *				javax.activation layer.
+     * @exception MessagingException for other failures
+     * @see javax.activation.DataHandler#writeTo
+     */
+    @Override
+    public synchronized void writeTo(OutputStream os, String[] ignoreList)
+				throws IOException, MessagingException {
+	InputStream rawcontent = rawData.get();
+	if (rawcontent == null && ignoreList == null &&
+			!((POP3Store)(folder.getStore())).cacheWriteTo) {
+	    if (folder.logger.isLoggable(Level.FINE))
+		folder.logger.fine("streaming msg " + msgnum);
+	    if (!folder.getProtocol().retr(msgnum, os)) {
+		expunged = true;
+		throw new MessageRemovedException("can't retrieve message #" +
+		    msgnum + " in POP3Message.writeTo");    // XXX - what else?
+	    }
+	} else if (rawcontent != null && ignoreList == null) {
+	    // can just copy the cached data
+	    InputStream in = ((SharedInputStream)rawcontent).newStream(0, -1);
+	    try {
+		byte[] buf = new byte[16*1024];
+		int len;
+		while ((len = in.read(buf)) > 0)
+		    os.write(buf, 0, len); 
+	    } finally {
+		try {
+		    if (in != null)
+			in.close();
+		} catch (IOException ex) { }
+	    }
+	} else
+	    super.writeTo(os, ignoreList);
+    }
+
+    /**
+     * Load the headers for this message into the InternetHeaders object.
+     * The headers are fetched using the POP3 TOP command.
+     */
+    private void loadHeaders() throws MessagingException {
+	assert !Thread.holdsLock(this);
+	try {
+	    boolean fetchContent = false;
+	    synchronized (this) {
+		if (headers != null)    // check again under lock
+		    return;
+		InputStream hdrs = null;
+		if (((POP3Store)(folder.getStore())).disableTop ||
+			(hdrs = folder.getProtocol().top(msgnum, 0)) == null) {
+		    // possibly because the TOP command isn't supported,
+		    // load headers as a side effect of loading the entire
+		    // content.
+		    fetchContent = true;
+		} else {
+		    try {
+			hdrSize = hdrs.available();
+			headers = new InternetHeaders(hdrs);
+		    } finally {
+			hdrs.close();
+		    }
+		}
+	    }
+
+	    /*
+	     * Outside the synchronization block...
+	     *
+	     * Do we need to fetch the entire mesage content in order to
+	     * load the headers as a side effect?  Yes, there's a race
+	     * condition here - multiple threads could decide that the
+	     * content needs to be fetched.  Fortunately, they'll all
+	     * synchronize in the getContentStream method and the content
+	     * will only be loaded once.
+	     */
+	    if (fetchContent) {
+		InputStream cs = null;
+		try {
+		    cs = getContentStream();
+		} finally {
+		    if (cs != null)
+			cs.close();
+		}
+	    }
+	} catch (EOFException eex) {
+	    folder.close(false);
+	    throw new FolderClosedException(folder, eex.toString());
+	} catch (IOException ex) {
+	    throw new MessagingException("error loading POP3 headers", ex);
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/POP3Provider.java b/mail/src/main/java/com/sun/mail/pop3/POP3Provider.java
new file mode 100644
index 0000000..73488e8
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/POP3Provider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.Provider;
+
+/**
+ * The POP3 protocol provider.
+ */
+public class POP3Provider extends Provider {
+    public POP3Provider() {
+	super(Provider.Type.STORE, "pop3", POP3Store.class.getName(),
+	    "Oracle", null);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/POP3SSLProvider.java b/mail/src/main/java/com/sun/mail/pop3/POP3SSLProvider.java
new file mode 100644
index 0000000..5ab7c2c
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/POP3SSLProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.Provider;
+
+/**
+ * The POP3 SSL protocol provider.
+ */
+public class POP3SSLProvider extends Provider {
+    public POP3SSLProvider() {
+	super(Provider.Type.STORE, "pop3s", POP3SSLStore.class.getName(),
+	    "Oracle", null);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/POP3SSLStore.java b/mail/src/main/java/com/sun/mail/pop3/POP3SSLStore.java
new file mode 100644
index 0000000..fa0054c
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/POP3SSLStore.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import javax.mail.*;
+
+/**
+ * A POP3 Message Store using SSL.  Contains only one folder, "INBOX".
+ *
+ * @author      Bill Shannon
+ */
+public class POP3SSLStore extends POP3Store {
+
+    public POP3SSLStore(Session session, URLName url) {
+	super(session, url, "pop3s", true);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/POP3Store.java b/mail/src/main/java/com/sun/mail/pop3/POP3Store.java
new file mode 100644
index 0000000..c9818a1
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/POP3Store.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.util.Properties;
+import java.util.logging.Level;
+import java.lang.reflect.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import java.io.File;
+import java.io.PrintStream;
+import java.io.IOException;
+import java.io.EOFException;
+import java.util.Collections;
+import java.util.Map;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.SocketConnectException;
+import com.sun.mail.util.MailConnectException;
+
+/**
+ * A POP3 Message Store.  Contains only one folder, "INBOX".
+ *
+ * See the <a href="package-summary.html">com.sun.mail.pop3</a> package
+ * documentation for further information on the POP3 protocol provider. <p>
+ *
+ * @author      Bill Shannon
+ * @author      John Mani
+ */
+public class POP3Store extends Store {
+
+    private String name = "pop3";		// my protocol name
+    private int defaultPort = 110;		// default POP3 port
+    private boolean isSSL = false;		// use SSL?
+
+    private Protocol port = null;		// POP3 port for self
+    private POP3Folder portOwner = null;	// folder owning port
+    private String host = null;			// host
+    private int portNum = -1;
+    private String user = null;
+    private String passwd = null;
+    private boolean useStartTLS = false;
+    private boolean requireStartTLS = false;
+    private boolean usingSSL = false;
+    private Map<String, String> capabilities;
+    private MailLogger logger;
+
+    // following set here and accessed by other classes in this package
+    volatile Constructor<?> messageConstructor = null;
+    volatile boolean rsetBeforeQuit = false;
+    volatile boolean disableTop = false;
+    volatile boolean forgetTopHeaders = false;
+    volatile boolean supportsUidl = true;
+    volatile boolean cacheWriteTo = false;
+    volatile boolean useFileCache = false;
+    volatile File fileCacheDir = null;
+    volatile boolean keepMessageContent = false;
+    volatile boolean finalizeCleanClose = false;
+
+    public POP3Store(Session session, URLName url) {
+	this(session, url, "pop3", false);
+    }
+
+    public POP3Store(Session session, URLName url,
+				String name, boolean isSSL) {
+	super(session, url);
+	if (url != null)
+	    name = url.getProtocol();
+	this.name = name;
+	logger = new MailLogger(this.getClass(), "DEBUG POP3",
+				session.getDebug(), session.getDebugOut());
+
+	if (!isSSL)
+	    isSSL = PropUtil.getBooleanProperty(session.getProperties(),
+				"mail." + name + ".ssl.enable", false);
+	if (isSSL)
+	    this.defaultPort = 995;
+	else
+	    this.defaultPort = 110;
+	this.isSSL = isSSL;
+
+	rsetBeforeQuit = getBoolProp("rsetbeforequit");
+	disableTop = getBoolProp("disabletop");
+	forgetTopHeaders = getBoolProp("forgettopheaders");
+	cacheWriteTo = getBoolProp("cachewriteto");
+	useFileCache = getBoolProp("filecache.enable");
+	String dir = session.getProperty("mail." + name + ".filecache.dir");
+	if (dir != null && logger.isLoggable(Level.CONFIG))
+	    logger.config("mail." + name + ".filecache.dir: " + dir);
+	if (dir != null)
+	    fileCacheDir = new File(dir);
+	keepMessageContent = getBoolProp("keepmessagecontent");
+
+	// mail.pop3.starttls.enable enables use of STLS command
+	useStartTLS = getBoolProp("starttls.enable");
+
+	// mail.pop3.starttls.required requires use of STLS command
+	requireStartTLS = getBoolProp("starttls.required");
+
+	// mail.pop3.finalizecleanclose requires clean close when finalizing
+	finalizeCleanClose = getBoolProp("finalizecleanclose");
+
+	String s = session.getProperty("mail." + name + ".message.class");
+	if (s != null) {
+	    logger.log(Level.CONFIG, "message class: {0}", s);
+	    try {
+		ClassLoader cl = this.getClass().getClassLoader();
+
+		// now load the class
+		Class<?> messageClass = null;
+		try {
+		    // First try the "application's" class loader.
+		    // This should eventually be replaced by
+		    // Thread.currentThread().getContextClassLoader().
+		    messageClass = Class.forName(s, false, cl);
+		} catch (ClassNotFoundException ex1) {
+		    // That didn't work, now try the "system" class loader.
+		    // (Need both of these because JDK 1.1 class loaders
+		    // may not delegate to their parent class loader.)
+		    messageClass = Class.forName(s);
+		}
+
+		Class<?>[] c = {javax.mail.Folder.class, int.class};
+		messageConstructor = messageClass.getConstructor(c);
+	    } catch (Exception ex) {
+		logger.log(Level.CONFIG, "failed to load message class", ex);
+	    }
+	}
+    }
+
+    /**
+     * Get the value of a boolean property.
+     * Print out the value if logging is enabled.
+     */
+    private final synchronized boolean getBoolProp(String prop) {
+	prop = "mail." + name + "." + prop;
+	boolean val = PropUtil.getBooleanProperty(session.getProperties(),
+						    prop, false);
+	if (logger.isLoggable(Level.CONFIG))
+	    logger.config(prop + ": " + val);
+	return val;
+    }
+
+    /**
+     * Get a reference to the session.
+     */
+    synchronized Session getSession() {
+        return session;
+    }
+
+    @Override
+    protected synchronized boolean protocolConnect(String host, int portNum,
+		String user, String passwd) throws MessagingException {
+		    
+	// check for non-null values of host, password, user
+	if (host == null || passwd == null || user == null)
+	    return false;
+
+	// if port is not specified, set it to value of mail.pop3.port
+        // property if it exists, otherwise default to 110
+        if (portNum == -1)
+	    portNum = PropUtil.getIntProperty(session.getProperties(),
+				"mail." + name + ".port", -1);
+
+	if (portNum == -1)
+	    portNum = defaultPort;
+
+	this.host = host;
+	this.portNum = portNum;
+	this.user = user;
+	this.passwd = passwd;
+	try {
+	    port = getPort(null);
+	} catch (EOFException eex) { 
+		throw new AuthenticationFailedException(eex.getMessage());
+	} catch (SocketConnectException scex) {
+	    throw new MailConnectException(scex);
+	} catch (IOException ioex) { 
+	    throw new MessagingException("Connect failed", ioex);
+	}
+
+	return true;
+    }
+
+    /**
+     * Check whether this store is connected. Override superclass
+     * method, to actually ping our server connection.
+     */
+    /*
+     * Note that we maintain somewhat of an illusion of being connected
+     * even if we're not really connected.  This is because a Folder
+     * can use the connection and close it when it's done.  If we then
+     * ask whether the Store's connected we want the answer to be true,
+     * as long as we can reconnect at that point.  This means that we
+     * need to be able to reconnect the Store on demand.
+     */
+    @Override
+    public synchronized boolean isConnected() {
+	if (!super.isConnected())
+	    // if we haven't been connected at all, don't bother with
+	    // the NOOP.
+	    return false;
+	try {
+	    if (port == null)
+		port = getPort(null);
+	    else if (!port.noop())
+		throw new IOException("NOOP failed");
+	    return true;
+	} catch (IOException ioex) {
+	    // no longer connected, close it down
+	    try {
+		super.close();		// notifies listeners
+	    } catch (MessagingException mex) {
+		// ignore it
+	    }
+	    return false;
+	}
+    }
+
+    synchronized Protocol getPort(POP3Folder owner) throws IOException {
+	Protocol p;
+
+	// if we already have a port, remember who's using it
+	if (port != null && portOwner == null) {
+	    portOwner = owner;
+	    return port;
+	}
+
+	// need a new port, create it and try to login
+	p = new Protocol(host, portNum, logger,
+	    session.getProperties(), "mail." + name, isSSL);
+
+	if (useStartTLS || requireStartTLS) {
+	    if (p.hasCapability("STLS")) {
+		if (p.stls()) {
+		    // success, refresh capabilities
+		    p.setCapabilities(p.capa());
+		} else if (requireStartTLS) {
+		    logger.fine("STLS required but failed");
+		    throw cleanupAndThrow(p,
+			    new EOFException("STLS required but failed"));
+		}
+	    } else if (requireStartTLS) {
+		logger.fine("STLS required but not supported");
+		throw cleanupAndThrow(p,
+			new EOFException("STLS required but not supported"));
+	    }
+	}
+
+	capabilities = p.getCapabilities();	// save for later, may be null
+	usingSSL = p.isSSL();			// in case anyone asks
+
+	/*
+	 * If we haven't explicitly disabled use of the TOP command,
+	 * and the server has provided its capabilities,
+	 * and the server doesn't support the TOP command,
+	 * disable the TOP command.
+	 */
+	if (!disableTop &&
+		capabilities != null && !capabilities.containsKey("TOP")) {
+	    disableTop = true;
+	    logger.fine("server doesn't support TOP, disabling it");
+	}
+
+	supportsUidl = capabilities == null || capabilities.containsKey("UIDL");
+
+	String msg = null;
+	if ((msg = p.login(user, passwd)) != null) {
+	    throw cleanupAndThrow(p, new EOFException(msg));
+	}
+
+	/*
+	 * If a Folder closes the port, and then a Folder
+	 * is opened, the Store won't have a port.  In that
+	 * case, the getPort call will come from Folder.open,
+	 * but we need to keep track of the port in the Store
+	 * so that a later call to Folder.isOpen, which calls
+	 * Store.isConnected, will use the same port.
+	 */
+	if (port == null && owner != null) {
+	    port = p;
+	    portOwner = owner;
+	}
+	if (portOwner == null)
+	    portOwner = owner;
+	return p;
+    }
+
+    private static IOException cleanupAndThrow(Protocol p, IOException ife) {
+	try {
+	    p.quit();
+	} catch (Throwable thr) {
+	    if (isRecoverable(thr)) {
+		ife.addSuppressed(thr);
+	    } else {
+		thr.addSuppressed(ife);
+		if (thr instanceof Error) {
+		    throw (Error) thr;
+		}
+		if (thr instanceof RuntimeException) {
+		    throw (RuntimeException) thr;
+		}
+		throw new RuntimeException("unexpected exception", thr);
+	    }
+	}
+	return ife;
+    }
+
+    private static boolean isRecoverable(Throwable t) {
+	return (t instanceof Exception) || (t instanceof LinkageError);
+    }
+
+    synchronized void closePort(POP3Folder owner) {
+	if (portOwner == owner) {
+	    port = null;
+	    portOwner = null;
+	}
+    }
+
+    @Override
+    public synchronized void close() throws MessagingException {
+	close(false);
+    }
+
+    synchronized void close(boolean force) throws MessagingException {
+	try {
+	    if (port != null) {
+		if (force)
+		    port.close();
+		else
+		    port.quit();
+	    }
+	} catch (IOException ioex) {
+	} finally {
+	    port = null;
+
+	    // to set the state and send the closed connection event
+	    super.close();
+	}
+    }
+
+    @Override
+    public Folder getDefaultFolder() throws MessagingException {
+	checkConnected();
+	return new DefaultFolder(this);
+    }
+
+    /**
+     * Only the name "INBOX" is supported.
+     */
+    @Override
+    public Folder getFolder(String name) throws MessagingException {
+	checkConnected();
+	return new POP3Folder(this, name);
+    }
+
+    @Override
+    public Folder getFolder(URLName url) throws MessagingException {
+	checkConnected();
+	return new POP3Folder(this, url.getFile());
+    }
+
+    /**
+     * Return a Map of the capabilities the server provided,
+     * as per RFC 2449.  If the server doesn't support RFC 2449,
+     * an emtpy Map is returned.  The returned Map can not be modified.
+     * The key to the Map is the upper case capability name as
+     * a String.  The value of the entry is the entire String
+     * capability line returned by the server. <p>
+     *
+     * For example, to check if the server supports the STLS capability, use:
+     * <code>if (store.capabilities().containsKey("STLS")) ...</code>
+     *
+     * @return	Map of capabilities
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.4.3
+     */
+    public Map<String, String> capabilities() throws MessagingException {
+	Map<String, String> c;
+	synchronized (this) {
+	    c = capabilities;
+	}
+	if (c != null)
+	    return Collections.unmodifiableMap(c);
+	else
+	    return Collections.<String, String>emptyMap();
+    }
+
+    /**
+     * Is this POP3Store using SSL to connect to the server?
+     *
+     * @return	true if using SSL
+     * @since	JavaMail 1.4.6
+     */
+    public synchronized boolean isSSL() {
+	return usingSSL;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+	try {
+	    if (port != null)	// don't force a connection attempt
+		close(!finalizeCleanClose);
+	} finally {
+	    super.finalize();
+	}
+    }
+
+    private void checkConnected() throws MessagingException {
+	if (!super.isConnected())
+	    throw new MessagingException("Not connected");
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/Protocol.java b/mail/src/main/java/com/sun/mail/pop3/Protocol.java
new file mode 100644
index 0000000..bfba0d9
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/Protocol.java
@@ -0,0 +1,861 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.util.*;
+import java.net.*;
+import java.io.*;
+import java.security.*;
+import java.util.logging.Level;
+import javax.net.ssl.SSLSocket;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.SocketFetcher;
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.TraceInputStream;
+import com.sun.mail.util.TraceOutputStream;
+import com.sun.mail.util.SharedByteArrayOutputStream;
+
+class Response {
+    boolean ok = false;		// true if "+OK"
+    String data = null;		// rest of line after "+OK" or "-ERR"
+    InputStream bytes = null;	// all the bytes from a multi-line response
+}
+
+/**
+ * This class provides a POP3 connection and implements 
+ * the POP3 protocol requests.
+ *
+ * APOP support courtesy of "chamness".
+ *
+ * @author      Bill Shannon
+ */
+class Protocol {
+    private Socket socket;		// POP3 socket
+    private String host;		// host we're connected to
+    private Properties props;		// session properties
+    private String prefix;		// protocol name prefix, for props
+    private BufferedReader input;	// input buf
+    private PrintWriter output;		// output buf
+    private TraceInputStream traceInput;
+    private TraceOutputStream traceOutput;
+    private MailLogger logger;
+    private MailLogger traceLogger;
+    private String apopChallenge = null;
+    private Map<String, String> capabilities = null;
+    private boolean pipelining;
+    private boolean noauthdebug = true;	// hide auth info in debug output
+    private boolean traceSuspended;	// temporarily suspend tracing
+
+    private static final int POP3_PORT = 110; // standard POP3 port
+    private static final String CRLF = "\r\n";
+    // sometimes the returned size isn't quite big enough
+    private static final int SLOP = 128;
+
+    /** 
+     * Open a connection to the POP3 server.
+     */
+    Protocol(String host, int port, MailLogger logger,
+			Properties props, String prefix, boolean isSSL)
+			throws IOException {
+	this.host = host;
+	this.props = props;
+	this.prefix = prefix;
+	this.logger = logger;
+	traceLogger = logger.getSubLogger("protocol", null);
+	noauthdebug = !PropUtil.getBooleanProperty(props,
+			    "mail.debug.auth", false);
+
+	Response r;
+	boolean enableAPOP = getBoolProp(props, prefix + ".apop.enable");
+	boolean disableCapa = getBoolProp(props, prefix + ".disablecapa");
+	try {
+	    if (port == -1)
+		port = POP3_PORT;
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("connecting to host \"" + host +
+				"\", port " + port + ", isSSL " + isSSL);
+
+	    socket = SocketFetcher.getSocket(host, port, props, prefix, isSSL);
+	    initStreams();
+	    r = simpleCommand(null);
+	} catch (IOException ioe) {
+	    throw cleanupAndThrow(socket, ioe);
+	}
+
+	if (!r.ok) {
+	    throw cleanupAndThrow(socket, new IOException("Connect failed"));
+	}
+	if (enableAPOP && r.data != null) {
+	    int challStart = r.data.indexOf('<');	// start of challenge
+	    int challEnd = r.data.indexOf('>', challStart); // end of challenge
+	    if (challStart != -1 && challEnd != -1)
+		apopChallenge = r.data.substring(challStart, challEnd + 1);
+	    logger.log(Level.FINE, "APOP challenge: {0}", apopChallenge);
+	}
+
+	// if server supports RFC 2449, set capabilities
+	if (!disableCapa)
+	    setCapabilities(capa());
+
+	pipelining = hasCapability("PIPELINING") ||
+	    PropUtil.getBooleanProperty(props, prefix + ".pipelining", false);
+	if (pipelining)
+	    logger.config("PIPELINING enabled");
+    }
+
+    private static IOException cleanupAndThrow(Socket socket, IOException ife) {
+	try {
+	    socket.close();
+	} catch (Throwable thr) {
+	    if (isRecoverable(thr)) {
+		ife.addSuppressed(thr);
+	    } else {
+		thr.addSuppressed(ife);
+		if (thr instanceof Error) {
+		    throw (Error) thr;
+		}
+		if (thr instanceof RuntimeException) {
+		    throw (RuntimeException) thr;
+		}
+		throw new RuntimeException("unexpected exception", thr);
+	    }
+	}
+	return ife;
+    }
+
+    private static boolean isRecoverable(Throwable t) {
+	return (t instanceof Exception) || (t instanceof LinkageError);
+    }
+
+    /**
+     * Get the value of a boolean property.
+     * Print out the value if logging is enabled.
+     */
+    private final synchronized boolean getBoolProp(Properties props,
+				String prop) {
+	boolean val = PropUtil.getBooleanProperty(props, prop, false);
+	if (logger.isLoggable(Level.CONFIG))
+	    logger.config(prop + ": " + val);
+	return val;
+    }
+
+    private void initStreams() throws IOException {
+	boolean quote = PropUtil.getBooleanProperty(props,
+					"mail.debug.quote", false);
+	traceInput =
+	    new TraceInputStream(socket.getInputStream(), traceLogger);
+	traceInput.setQuote(quote);
+
+	traceOutput =
+	    new TraceOutputStream(socket.getOutputStream(), traceLogger);
+	traceOutput.setQuote(quote);
+
+	// should be US-ASCII, but not all JDK's support it so use iso-8859-1
+	input = new BufferedReader(new InputStreamReader(traceInput,
+							    "iso-8859-1"));
+	output = new PrintWriter(
+		    new BufferedWriter(
+			new OutputStreamWriter(traceOutput, "iso-8859-1")));
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+	try {
+	    if (socket != null)	// Forgot to logout ?!
+		quit();
+	} finally {
+	    super.finalize();
+	}
+    }
+
+    /**
+     * Parse the capabilities from a CAPA response.
+     */
+    synchronized void setCapabilities(InputStream in) {
+	if (in == null) {
+	    capabilities = null;
+	    return;
+	}
+
+	capabilities = new HashMap<>(10);
+	BufferedReader r = null;
+	try {
+	    r = new BufferedReader(new InputStreamReader(in, "us-ascii"));
+	} catch (UnsupportedEncodingException ex) {
+	    // should never happen
+	    assert false;
+	}
+	String s;
+	try {
+	    while ((s = r.readLine()) != null) {
+		String cap = s;
+		int i = cap.indexOf(' ');
+		if (i > 0)
+		    cap = cap.substring(0, i);
+		capabilities.put(cap.toUpperCase(Locale.ENGLISH), s);
+	    }
+	} catch (IOException ex) {
+	    // should never happen
+	} finally {
+	    try {
+		in.close();
+	    } catch (IOException ex) { }
+	}
+    }
+
+    /**
+     * Check whether the given capability is supported by
+     * this server. Returns <code>true</code> if so, otherwise
+     * returns false.
+     */
+    synchronized boolean hasCapability(String c) {
+	return capabilities != null &&
+		capabilities.containsKey(c.toUpperCase(Locale.ENGLISH));
+    }
+
+    /**
+     * Return the map of capabilities returned by the server.
+     */
+    synchronized Map<String, String> getCapabilities() {
+	return capabilities;
+    }
+
+    /**
+     * Login to the server, using the USER and PASS commands.
+     */
+    synchronized String login(String user, String password)
+					throws IOException {
+	Response r;
+	// only pipeline password if connection is secure
+	boolean batch = pipelining && socket instanceof SSLSocket;
+
+	try {
+
+	if (noauthdebug && isTracing()) {
+	    logger.fine("authentication command trace suppressed");
+	    suspendTracing();
+	}
+	String dpw = null;
+	if (apopChallenge != null)
+	    dpw = getDigest(password);
+	if (apopChallenge != null && dpw != null) {
+	    r = simpleCommand("APOP " + user + " " + dpw);
+	} else if (batch) {
+	    String cmd = "USER " + user;
+	    batchCommandStart(cmd);
+	    issueCommand(cmd);
+	    cmd = "PASS " + password;
+	    batchCommandContinue(cmd);
+	    issueCommand(cmd);
+	    r = readResponse();
+	    if (!r.ok) {
+		String err = r.data != null ? r.data : "USER command failed";
+		readResponse();	// read and ignore PASS response
+		batchCommandEnd();
+		return err;
+	    }
+	    r = readResponse();
+	    batchCommandEnd();
+	} else {
+	    r = simpleCommand("USER " + user);
+	    if (!r.ok)
+		return r.data != null ? r.data : "USER command failed";
+	    r = simpleCommand("PASS " + password);
+	}
+	if (noauthdebug && isTracing())
+	    logger.log(Level.FINE, "authentication command {0}",
+			(r.ok ? "succeeded" : "failed"));
+	if (!r.ok)
+	    return r.data != null ? r.data : "login failed";
+	return null;
+
+	} finally {
+	    resumeTracing();
+	}
+    }
+
+    /**
+     * Gets the APOP message digest. 
+     * From RFC 1939:
+     *
+     * The 'digest' parameter is calculated by applying the MD5
+     * algorithm [RFC1321] to a string consisting of the timestamp
+     * (including angle-brackets) followed by a shared secret.
+     * The 'digest' parameter itself is a 16-octet value which is
+     * sent in hexadecimal format, using lower-case ASCII characters.
+     *
+     * @param	password	The APOP password
+     * @return		The APOP digest or an empty string if an error occurs.
+     */
+    private String getDigest(String password) {
+	String key = apopChallenge + password;
+	byte[] digest;
+	try {
+	    MessageDigest md = MessageDigest.getInstance("MD5");
+	    digest = md.digest(key.getBytes("iso-8859-1"));	// XXX
+	} catch (NoSuchAlgorithmException nsae) {
+	    return null;
+	} catch (UnsupportedEncodingException uee) {
+	    return null;
+	}
+	return toHex(digest);
+    }
+
+    private static char[] digits = {
+	'0', '1', '2', '3', '4', '5', '6', '7',
+	'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+    };
+
+    /**
+     * Convert a byte array to a string of hex digits representing the bytes.
+     */
+    private static String toHex(byte[] bytes) {
+	char[] result = new char[bytes.length * 2];
+
+	for (int index = 0, i = 0; index < bytes.length; index++) {
+	    int temp = bytes[index] & 0xFF;
+	    result[i++] = digits[temp >> 4];
+	    result[i++] = digits[temp & 0xF];
+	}
+	return new String(result);
+    }
+
+    /**
+     * Close down the connection, sending the QUIT command.
+     */
+    synchronized boolean quit() throws IOException {
+	boolean ok = false;
+	try {
+	    Response r = simpleCommand("QUIT");
+	    ok = r.ok;
+	} finally {
+	    close();
+	}
+	return ok;
+    }
+
+    /**
+     * Close the connection without sending any commands.
+     */
+    void close() {
+	try {
+	    socket.close();
+	} catch (IOException ex) {
+	    // ignore it
+	} finally {
+	    socket = null;
+	    input = null;
+	    output = null;
+	}
+    }
+
+    /**
+     * Return the total number of messages and mailbox size,
+     * using the STAT command.
+     */
+    synchronized Status stat() throws IOException {
+	Response r = simpleCommand("STAT");
+	Status s = new Status();
+
+	/*
+	 * Normally the STAT command shouldn't fail but apparently it
+	 * does when accessing Hotmail too often, returning:
+	 * -ERR login allowed only every 15 minutes
+	 * (Why it doesn't just fail the login, I don't know.)
+	 * This is a serious failure that we don't want to hide
+	 * from the user.
+	 */
+	if (!r.ok)
+	    throw new IOException("STAT command failed: " + r.data);
+
+	if (r.data != null) {
+	    try {
+		StringTokenizer st = new StringTokenizer(r.data);
+		s.total = Integer.parseInt(st.nextToken());
+		s.size = Integer.parseInt(st.nextToken());
+	    } catch (RuntimeException e) {
+	    }
+	}
+	return s;
+    }
+
+    /**
+     * Return the size of the message using the LIST command.
+     */
+    synchronized int list(int msg) throws IOException {
+	Response r = simpleCommand("LIST " + msg);
+	int size = -1;
+	if (r.ok && r.data != null) {
+	    try {
+		StringTokenizer st = new StringTokenizer(r.data);
+		st.nextToken();    // skip message number
+		size = Integer.parseInt(st.nextToken());
+	    } catch (RuntimeException e) {
+		// ignore it
+	    }
+	}
+	return size;
+    }
+
+    /**
+     * Return the size of all messages using the LIST command.
+     */
+    synchronized InputStream list() throws IOException {
+	Response r = multilineCommand("LIST", 128); // 128 == output size est
+	return r.bytes;
+    }
+
+    /**
+     * Retrieve the specified message.
+     * Given an estimate of the message's size we can be more efficient,
+     * preallocating the array and returning a SharedInputStream to allow
+     * us to share the array.
+     */
+    synchronized InputStream retr(int msg, int size) throws IOException {
+	Response r;
+	String cmd;
+	boolean batch = size == 0 && pipelining;
+	if (batch) {
+	    cmd = "LIST " + msg;
+	    batchCommandStart(cmd);
+	    issueCommand(cmd);
+	    cmd = "RETR " + msg;
+	    batchCommandContinue(cmd);
+	    issueCommand(cmd);
+	    r = readResponse();
+	    if (r.ok && r.data != null) {
+		// parse the LIST response to get the message size
+		try {
+		    StringTokenizer st = new StringTokenizer(r.data);
+		    st.nextToken();    // skip message number
+		    size = Integer.parseInt(st.nextToken());
+		    // don't allow ridiculous sizes
+		    if (size > 1024*1024*1024 || size < 0)
+			size = 0;
+		    else {
+			if (logger.isLoggable(Level.FINE))
+			    logger.fine("pipeline message size " + size);
+			size += SLOP;
+		    }
+		} catch (RuntimeException e) {
+		}
+	    }
+	    r = readResponse();
+	    if (r.ok)
+		r.bytes = readMultilineResponse(size + SLOP);
+	    batchCommandEnd();
+	} else {
+	    cmd = "RETR " + msg;
+	    multilineCommandStart(cmd);
+	    issueCommand(cmd);
+	    r = readResponse();
+	    if (!r.ok) {
+		multilineCommandEnd();
+		return null;
+	    }
+
+	    /*
+	     * Many servers return a response to the RETR command of the form:
+	     * +OK 832 octets
+	     * If we don't have a size guess already, try to parse the response
+	     * for data in that format and use it if found.  It's only a guess,
+	     * but it might be a good guess.
+	     */
+	    if (size <= 0 && r.data != null) {
+		try {
+		    StringTokenizer st = new StringTokenizer(r.data);
+		    String s = st.nextToken();
+		    String octets = st.nextToken();
+		    if (octets.equals("octets")) {
+			size = Integer.parseInt(s);
+			// don't allow ridiculous sizes
+			if (size > 1024*1024*1024 || size < 0)
+			    size = 0;
+			else {
+			    if (logger.isLoggable(Level.FINE))
+				logger.fine("guessing message size: " + size);
+			    size += SLOP;
+			}
+		    }
+		} catch (RuntimeException e) {
+		}
+	    }
+	    r.bytes = readMultilineResponse(size);
+	    multilineCommandEnd();
+	}
+	if (r.ok) {
+	    if (size > 0 && logger.isLoggable(Level.FINE))
+		logger.fine("got message size " + r.bytes.available());
+	}
+	return r.bytes;
+    }
+
+    /**
+     * Retrieve the specified message and stream the content to the
+     * specified OutputStream.  Return true on success.
+     */
+    synchronized boolean retr(int msg, OutputStream os) throws IOException {
+	String cmd = "RETR " + msg;
+	multilineCommandStart(cmd);
+	issueCommand(cmd);
+	Response r = readResponse();
+	if (!r.ok) {
+	    multilineCommandEnd();
+	    return false;
+	}
+
+	Throwable terr = null;
+	int b, lastb = '\n';
+	try {
+	    while ((b = input.read()) >= 0) {
+		if (lastb == '\n' && b == '.') {
+		    b = input.read();
+		    if (b == '\r') {
+			// end of response, consume LF as well
+			b = input.read();
+			break;
+		    }
+		}
+
+		/*
+		 * Keep writing unless we get an error while writing,
+		 * which we defer until all of the data has been read.
+		 */
+		if (terr == null) {
+		    try {
+			os.write(b);
+		    } catch (IOException ex) {
+			logger.log(Level.FINE, "exception while streaming", ex);
+			terr = ex;
+		    } catch (RuntimeException ex) {
+			logger.log(Level.FINE, "exception while streaming", ex);
+			terr = ex;
+		    }
+		}
+		lastb = b;
+	    }
+	} catch (InterruptedIOException iioex) {
+	    /*
+	     * As above in simpleCommand, close the socket to recover.
+	     */
+	    try {
+		socket.close();
+	    } catch (IOException cex) { }
+	    throw iioex;
+	}
+	if (b < 0)
+	    throw new EOFException("EOF on socket");
+
+	// was there a deferred error?
+	if (terr != null) {
+	    if (terr instanceof IOException)
+		throw (IOException)terr;
+	    if (terr instanceof RuntimeException)
+		throw (RuntimeException)terr;
+	    assert false;	// can't get here
+	}
+	multilineCommandEnd();
+	return true;
+    }
+
+    /**
+     * Return the message header and the first n lines of the message.
+     */
+    synchronized InputStream top(int msg, int n) throws IOException {
+	Response r = multilineCommand("TOP " + msg + " " + n, 0);
+	return r.bytes;
+    }
+
+    /**
+     * Delete (permanently) the specified message.
+     */
+    synchronized boolean dele(int msg) throws IOException {
+	Response r = simpleCommand("DELE " + msg);
+	return r.ok;
+    }
+
+    /**
+     * Return the UIDL string for the message.
+     */
+    synchronized String uidl(int msg) throws IOException {
+	Response r = simpleCommand("UIDL " + msg);
+	if (!r.ok)
+	    return null;
+	int i = r.data.indexOf(' ');
+	if (i > 0)
+	    return r.data.substring(i + 1);
+	else
+	    return null;
+    }
+
+    /**
+     * Return the UIDL strings for all messages.
+     * The UID for msg #N is returned in uids[N-1].
+     */
+    synchronized boolean uidl(String[] uids) throws IOException {
+	Response r = multilineCommand("UIDL", 15 * uids.length);
+	if (!r.ok)
+	    return false;
+	LineInputStream lis = new LineInputStream(r.bytes);
+	String line = null;
+	while ((line = lis.readLine()) != null) {
+	    int i = line.indexOf(' ');
+	    if (i < 1 || i >= line.length())
+		continue;
+	    int n = Integer.parseInt(line.substring(0, i));
+	    if (n > 0 && n <= uids.length)
+		uids[n - 1] = line.substring(i + 1);
+	}
+	try {
+	    r.bytes.close();
+	} catch (IOException ex) {
+	    // ignore it
+	}
+	return true;
+    }
+
+    /**
+     * Do a NOOP.
+     */
+    synchronized boolean noop() throws IOException {
+	Response r = simpleCommand("NOOP");
+	return r.ok;
+    }
+
+    /**
+     * Do an RSET.
+     */
+    synchronized boolean rset() throws IOException {
+	Response r = simpleCommand("RSET");
+	return r.ok;
+    }
+
+    /**
+     * Start TLS using STLS command specified by RFC 2595.
+     * If already using SSL, this is a nop and the STLS command is not issued.
+     */
+    synchronized boolean stls() throws IOException {
+	if (socket instanceof SSLSocket)
+	    return true;	// nothing to do
+	Response r = simpleCommand("STLS");
+	if (r.ok) {
+	    // it worked, now switch the socket into TLS mode
+	    try {
+		socket = SocketFetcher.startTLS(socket, host, props, prefix);
+		initStreams();
+	    } catch (IOException ioex) {
+		try {
+		    socket.close();
+		} finally {
+		    socket = null;
+		    input = null;
+		    output = null;
+		}
+		IOException sioex =
+		    new IOException("Could not convert socket to TLS");
+		sioex.initCause(ioex);
+		throw sioex;
+	    }
+	}
+	return r.ok;
+    }
+
+    /**
+     * Is this connection using SSL?
+     */
+    synchronized boolean isSSL() {
+	return socket instanceof SSLSocket;
+    }
+
+    /**
+     * Get server capabilities using CAPA command specified by RFC 2449.
+     * Returns null if not supported.
+     */
+    synchronized InputStream capa() throws IOException {
+	Response r = multilineCommand("CAPA", 128); // 128 == output size est
+	if (!r.ok)
+	    return null;
+	return r.bytes;
+    }
+
+    /**
+     * Issue a simple POP3 command and return the response.
+     */
+    private Response simpleCommand(String cmd) throws IOException {
+	simpleCommandStart(cmd);
+	issueCommand(cmd);
+	Response r = readResponse();
+	simpleCommandEnd();
+	return r;
+    }
+
+    /**
+     * Send the specified command.
+     */
+    private void issueCommand(String cmd) throws IOException {
+	if (socket == null)
+	    throw new IOException("Folder is closed");	// XXX
+
+	if (cmd != null) {
+	    cmd += CRLF;
+	    output.print(cmd);	// do it in one write
+	    output.flush();
+	}
+    }
+
+    /**
+     * Read the response to a command.
+     */
+    private Response readResponse() throws IOException {
+	String line = null;
+	try {
+	    line = input.readLine();
+	} catch (InterruptedIOException iioex) {
+	    /*
+	     * If we get a timeout while using the socket, we have no idea
+	     * what state the connection is in.  The server could still be
+	     * alive, but slow, and could still be sending data.  The only
+	     * safe way to recover is to drop the connection.
+	     */
+	    try {
+		socket.close();
+	    } catch (IOException cex) { }
+	    throw new EOFException(iioex.getMessage());
+	} catch (SocketException ex) {
+	    /*
+	     * If we get an error while using the socket, we have no idea
+	     * what state the connection is in.  The server could still be
+	     * alive, but slow, and could still be sending data.  The only
+	     * safe way to recover is to drop the connection.
+	     */
+	    try {
+		socket.close();
+	    } catch (IOException cex) { }
+	    throw new EOFException(ex.getMessage());
+	}
+
+	if (line == null) {
+	    traceLogger.finest("<EOF>");
+	    throw new EOFException("EOF on socket");
+	}
+	Response r = new Response();
+	if (line.startsWith("+OK"))
+	    r.ok = true;
+	else if (line.startsWith("-ERR"))
+	    r.ok = false;
+	else
+	    throw new IOException("Unexpected response: " + line);
+	int i;
+	if ((i = line.indexOf(' ')) >= 0)
+	    r.data = line.substring(i + 1);
+	return r;
+    }
+
+    /**
+     * Issue a POP3 command that expects a multi-line response.
+     * <code>size</code> is an estimate of the response size.
+     */
+    private Response multilineCommand(String cmd, int size) throws IOException {
+	multilineCommandStart(cmd);
+	issueCommand(cmd);
+	Response r = readResponse();
+	if (!r.ok) {
+	    multilineCommandEnd();
+	    return r;
+	}
+	r.bytes = readMultilineResponse(size);
+	multilineCommandEnd();
+	return r;
+    }
+
+    /**
+     * Read the response to a multiline command after the command response.
+     * The size parameter indicates the expected size of the response;
+     * the actual size can be different.  Returns an InputStream to the
+     * response bytes.
+     */
+    private InputStream readMultilineResponse(int size) throws IOException {
+	SharedByteArrayOutputStream buf = new SharedByteArrayOutputStream(size);
+	int b, lastb = '\n';
+	try {
+	    while ((b = input.read()) >= 0) {
+		if (lastb == '\n' && b == '.') {
+		    b = input.read();
+		    if (b == '\r') {
+			// end of response, consume LF as well
+			b = input.read();
+			break;
+		    }
+		}
+		buf.write(b);
+		lastb = b;
+	    }
+	} catch (InterruptedIOException iioex) {
+	    /*
+	     * As above in readResponse, close the socket to recover.
+	     */
+	    try {
+		socket.close();
+	    } catch (IOException cex) { }
+	    throw iioex;
+	}
+	if (b < 0)
+	    throw new EOFException("EOF on socket");
+	return buf.toStream();
+    }
+
+    /**
+     * Is protocol tracing enabled?
+     */
+    protected boolean isTracing() {
+	return traceLogger.isLoggable(Level.FINEST);
+    }
+
+    /**
+     * Temporarily turn off protocol tracing, e.g., to prevent
+     * tracing the authentication sequence, including the password.
+     */
+    private void suspendTracing() {
+	if (traceLogger.isLoggable(Level.FINEST)) {
+	    traceInput.setTrace(false);
+	    traceOutput.setTrace(false);
+	}
+    }
+
+    /**
+     * Resume protocol tracing, if it was enabled to begin with.
+     */
+    private void resumeTracing() {
+	if (traceLogger.isLoggable(Level.FINEST)) {
+	    traceInput.setTrace(true);
+	    traceOutput.setTrace(true);
+	}
+    }
+
+    /*
+     * Probe points for GlassFish monitoring.
+     */
+    private void simpleCommandStart(String command) { }
+    private void simpleCommandEnd() { }
+    private void multilineCommandStart(String command) { }
+    private void multilineCommandEnd() { }
+    private void batchCommandStart(String command) { }
+    private void batchCommandContinue(String command) { }
+    private void batchCommandEnd() { }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/Status.java b/mail/src/main/java/com/sun/mail/pop3/Status.java
new file mode 100644
index 0000000..bbfbd9c
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/Status.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+/**
+ * Result of POP3 STAT command.
+ */
+class Status {
+    int total = 0;		// number of messages in the mailbox
+    int size = 0;		// size of the mailbox
+};
diff --git a/mail/src/main/java/com/sun/mail/pop3/TempFile.java b/mail/src/main/java/com/sun/mail/pop3/TempFile.java
new file mode 100644
index 0000000..0d769e4
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/TempFile.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.*;
+
+/**
+ * A temporary file used to cache POP3 messages.
+ */
+class TempFile {
+
+    private File file;	// the temp file name
+    private WritableSharedFile sf;
+
+    /**
+     * Create a temp file in the specified directory (if not null).
+     * The file will be deleted when the JVM exits.
+     */
+    public TempFile(File dir) throws IOException {
+	file = File.createTempFile("pop3.", ".mbox", dir);
+	// XXX - need JDK 6 to set permissions on the file to owner-only
+	file.deleteOnExit();
+	sf = new WritableSharedFile(file);
+    }
+
+    /**
+     * Return a stream for appending to the temp file.
+     */
+    public AppendStream getAppendStream() throws IOException {
+	return sf.getAppendStream();
+    }
+
+    /**
+     * Close and remove this temp file.
+     */
+    public void close() {
+	try {
+	    sf.close();
+	} catch (IOException ex) {
+	    // ignore it
+	}
+	file.delete();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+	try {
+	    close();
+	} finally {
+	    super.finalize();
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/WritableSharedFile.java b/mail/src/main/java/com/sun/mail/pop3/WritableSharedFile.java
new file mode 100644
index 0000000..8daa0c5
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/WritableSharedFile.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import javax.mail.util.SharedFileInputStream;
+
+/**
+ * A subclass of SharedFileInputStream that also allows writing.
+ */
+class WritableSharedFile extends SharedFileInputStream {
+
+    private RandomAccessFile raf;
+    private AppendStream af;
+
+    public WritableSharedFile(File file) throws IOException {
+	super(file);
+	try {
+	    raf = new RandomAccessFile(file, "rw");
+	} catch (IOException ex) {
+	    // if anything goes wrong opening the writable file,
+	    // close the readable file too
+	    super.close();
+	}
+    }
+
+    /**
+     * Return the writable version of this file.
+     */
+    public RandomAccessFile getWritableFile() {
+	return raf;
+    }
+
+    /**
+     * Close the readable and writable files.
+     */
+    @Override
+    public void close() throws IOException {
+	try {
+	    super.close();
+	} finally {
+	    raf.close();
+	}
+    }
+
+    /**
+     * Update the size of the readable file after writing to the file. Updates
+     * the length to be the current size of the file.
+     */
+    synchronized long updateLength() throws IOException {
+	datalen = in.length();
+	af = null;
+	return datalen;
+    }
+
+    /**
+     * Return a new AppendStream, but only if one isn't in active use.
+     */
+    public synchronized AppendStream getAppendStream() throws IOException {
+	if (af != null) {
+	    throw new IOException(
+		    "POP3 file cache only supports single threaded access");
+	}
+	af = new AppendStream(this);
+	return af;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/pop3/package.html b/mail/src/main/java/com/sun/mail/pop3/package.html
new file mode 100644
index 0000000..ed76cb7
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/pop3/package.html
@@ -0,0 +1,672 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.pop3 package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+A POP3 protocol provider for the JavaMail API
+that provides access to a POP3 message store.
+Refer to <A HREF="http://www.ietf.org/rfc/rfc1939.txt" TARGET="_top">
+RFC 1939</A>
+for more information.
+<P>
+The POP3 provider provides a Store object that contains a single Folder
+named "INBOX". Due to the limitations of the POP3 protocol, many of
+the JavaMail API capabilities like event notification, folder management,
+flag management, etc. are not allowed.  The corresponding methods throw
+the MethodNotSupportedException exception; see below for details.
+</P>
+<P>
+Note that JavaMail does <strong>not</strong> include a local store into
+which messages can be downloaded and stored.  See our
+<A HREF="http://java.sun.com/products/javamail/Third_Party.html" TARGET="_top">
+Third Party Products</A>
+web page for availability of "mbox" and "MH" local store providers.
+</P>
+<P>
+The POP3 provider is accessed through the JavaMail APIs by using the protocol
+name "pop3" or a URL of the form "pop3://user:password@host:port/INBOX".
+</P>
+<P>
+POP3 supports only a single folder named "INBOX".
+</P>
+<P>
+POP3 supports <strong>no</strong> permanent flags (see
+{@link javax.mail.Folder#getPermanentFlags Folder.getPermanentFlags()}).
+In particular, the <code>Flags.Flag.RECENT</code> flag will never be set
+for POP3
+messages.  It's up to the application to determine which messages in a
+POP3 mailbox are "new".  There are several strategies to accomplish
+this, depending on the needs of the application and the environment:
+</P>
+<UL>
+<LI>
+A simple approach would be to keep track of the newest
+message seen by the application.
+</LI>
+<LI>
+An alternative would be to keep track of the UIDs (see below)
+of all messages that have been seen.
+</LI>
+<LI>
+Another approach is to download <strong>all</strong> messages into a local
+mailbox, so that all messages in the POP3 mailbox are, by
+definition, new.
+</LI>
+</UL>
+<P>
+All approaches will require some permanent storage associated with the client.
+</P>
+<P>
+POP3 does not support the <code>Folder.expunge()</code> method.  To delete and
+expunge messages, set the <code>Flags.Flag.DELETED</code> flag on the messages
+and close the folder using the <code>Folder.close(true)</code> method.  You
+cannot expunge without closing the folder.
+</P>
+<P>
+POP3 does not provide a "received date", so the <code>getReceivedDate</code>
+method will return null.
+It may be possible to examine other message headers (e.g., the
+"Received" headers) to estimate the received date, but these techniques
+are error-prone at best.
+</P>
+<P>
+The POP3 provider supports the POP3 UIDL command, see
+{@link com.sun.mail.pop3.POP3Folder#getUID POP3Folder.getUID()}.
+You can use it as follows:
+</P>
+<BLOCKQUOTE><PRE>
+if (folder instanceof com.sun.mail.pop3.POP3Folder) {
+    com.sun.mail.pop3.POP3Folder pf =
+	(com.sun.mail.pop3.POP3Folder)folder;
+    String uid = pf.getUID(msg);
+    if (uid != null)
+	... // use it
+}
+</PRE></BLOCKQUOTE>
+<P>
+You can also pre-fetch all the UIDs for all messages like this:
+</P>
+<BLOCKQUOTE><PRE>
+FetchProfile fp = new FetchProfile();
+fp.add(UIDFolder.FetchProfileItem.UID);
+folder.fetch(folder.getMessages(), fp);
+</PRE></BLOCKQUOTE>
+<P>
+Then use the technique above to get the UID for each message.  This is
+similar to the technique used with the UIDFolder interface supported by
+IMAP, but note that POP3 UIDs are strings, not integers like IMAP
+UIDs.  See the POP3 spec for details.
+</P>
+<P>
+When the headers of a POP3 message are accessed, the POP3 provider uses
+the TOP command to fetch all headers, which are then cached.  Use of the
+TOP command can be disabled with the <CODE>mail.pop3.disabletop</CODE>
+property, in which case the entire message content is fetched with the
+RETR command.
+</P>
+<P>
+When the content of a POP3 message is accessed, the POP3 provider uses
+the RETR command to fetch the entire message.  Normally the message
+content is cached in memory.  By setting the
+<CODE>mail.pop3.filecache.enable</CODE> property, the message content
+will instead be cached in a temporary file.  The file will be removed
+when the folder is closed.  Caching message content in a file is generally
+slower, but uses substantially less memory and may be helpful when dealing
+with very large messages.
+</P>
+<P>
+The {@link com.sun.mail.pop3.POP3Message#invalidate POP3Message.invalidate}
+method can be used to invalidate cached data without closing the folder.
+Note that if the file cache is being used the data in the file will be
+forgotten and fetched from the server if it's needed again, and stored again
+in the file cache.
+</P>
+<P>
+The POP3 CAPA command (defined by
+<A HREF="http://www.ietf.org/rfc/rfc2449.txt" TARGET="_top">RFC 2449</A>)
+will be used to determine the capabilities supported by the server.
+Some servers don't implement the CAPA command, and some servers don't
+return correct information, so various properties are available to
+disable use of certain POP3 commands, including CAPA.
+</P>
+<P>
+If the server advertises the PIPELINING capability (defined by
+<A HREF="http://www.ietf.org/rfc/rfc2449.txt" TARGET="_top">RFC 2449</A>),
+or the <CODE>mail.pop3.pipelining</CODE> property is set, the POP3
+provider will send some commands in batches, which can significantly
+improve performance and memory use.
+Some servers that don't support the CAPA command or don't advertise
+PIPELINING may still support pipelining; experimentation may be required.
+</P>
+<P>
+If pipelining is supported and the connection is using
+SSL, the USER and PASS commands will be sent as a batch.
+(If SSL is not being used, the PASS command isn't sent
+until the user is verified to avoid exposing the password
+if the user name is bad.)
+</P>
+<P>
+If pipelining is supported, when fetching a message with the RETR command,
+the LIST command will be sent as well, and the result will be used to size
+the I/O buffer, greatly reducing memory usage when fetching messages.
+</P>
+<A NAME="properties"><STRONG>Properties</STRONG></A>
+<P>
+The POP3 protocol provider supports the following properties,
+which may be set in the JavaMail <code>Session</code> object.
+The properties are always set as strings; the Type column describes
+how the string is interpreted.  For example, use
+</P>
+<PRE>
+	props.put("mail.pop3.port", "888");
+</PRE>
+<P>
+to set the <CODE>mail.pop3.port</CODE> property, which is of type int.
+</P>
+<P>
+Note that if you're using the "pop3s" protocol to access POP3 over SSL,
+all the properties would be named "mail.pop3s.*".
+</P>
+<TABLE BORDER SUMMARY="POP3 properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.user">mail.pop3.user</A></TD>
+<TD>String</TD>
+<TD>Default user name for POP3.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.host">mail.pop3.host</A></TD>
+<TD>String</TD>
+<TD>The POP3 server to connect to.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.port">mail.pop3.port</A></TD>
+<TD>int</TD>
+<TD>The POP3 server port to connect to, if the connect() method doesn't
+explicitly specify one. Defaults to 110.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.connectiontimeout">mail.pop3.connectiontimeout</A></TD>
+<TD>int</TD>
+<TD>Socket connection timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.timeout">mail.pop3.timeout</A></TD>
+<TD>int</TD>
+<TD>Socket read timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.writetimeout">mail.pop3.writetimeout</A></TD>
+<TD>int</TD>
+<TD>Socket write timeout value in milliseconds.
+This timeout is implemented by using a
+java.util.concurrent.ScheduledExecutorService per connection
+that schedules a thread to close the socket if the timeout expires.
+Thus, the overhead of using this timeout is one thread per connection.
+Default is infinite timeout.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.rsetbeforequit">mail.pop3.rsetbeforequit</A></TD>
+<TD>boolean</TD>
+<TD>
+Send a POP3 RSET command when closing the folder, before sending the
+QUIT command.  Useful with POP3 servers that implicitly mark all
+messages that are read as "deleted"; this will prevent such messages
+from being deleted and expunged unless the client requests so.  Default
+is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.message.class">mail.pop3.message.class</A></TD>
+<TD>String</TD>
+<TD>
+Class name of a subclass of <code>com.sun.mail.pop3.POP3Message</code>.
+The subclass can be used to handle (for example) non-standard
+Content-Type headers.  The subclass must have a public constructor
+of the form <code>MyPOP3Message(Folder f, int msgno)
+throws MessagingException</code>.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.localaddress">mail.pop3.localaddress</A></TD>
+<TD>String</TD>
+<TD>
+Local address (host name) to bind to when creating the POP3 socket.
+Defaults to the address picked by the Socket class.
+Should not normally need to be set, but useful with multi-homed hosts
+where it's important to pick a particular local address to bind to.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.localport">mail.pop3.localport</A></TD>
+<TD>int</TD>
+<TD>
+Local port number to bind to when creating the POP3 socket.
+Defaults to the port number picked by the Socket class.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.apop.enable">mail.pop3.apop.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, use APOP instead of USER/PASS to login to the
+POP3 server, if the POP3 server supports APOP.  APOP sends a
+digest of the password rather than the clear text password.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.socketFactory">mail.pop3.socketFactory</A></TD>
+<TD>SocketFactory</TD>
+<TD>
+If set to a class that implements the
+<code>javax.net.SocketFactory</code> interface, this class
+will be used to create POP3 sockets.  Note that this is an
+instance of a class, not a name, and must be set using the
+<code>put</code> method, not the <code>setProperty</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.socketFactory.class">mail.pop3.socketFactory.class</A></TD>
+<TD>String</TD>
+<TD>
+If set, specifies the name of a class that implements the
+<code>javax.net.SocketFactory</code> interface.  This class
+will be used to create POP3 sockets.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.socketFactory.fallback">mail.pop3.socketFactory.fallback</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, failure to create a socket using the specified
+socket factory class will cause the socket to be created using
+the <code>java.net.Socket</code> class.
+Defaults to true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.socketFactory.port">mail.pop3.socketFactory.port</A></TD>
+<TD>int</TD>
+<TD>
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.ssl.enable">mail.pop3.ssl.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, use SSL to connect and use the SSL port by default.
+Defaults to false for the "pop3" protocol and true for the "pop3s" protocol.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.ssl.checkserveridentity">mail.pop3.ssl.checkserveridentity</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, check the server identity as specified by
+<A HREF="http://www.ietf.org/rfc/rfc2595.txt" TARGET="_top">RFC 2595</A>.
+These additional checks based on the content of the server's certificate
+are intended to prevent man-in-the-middle attacks.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.ssl.trust">mail.pop3.ssl.trust</A></TD>
+<TD>String</TD>
+<TD>
+If set, and a socket factory hasn't been specified, enables use of a
+{@link com.sun.mail.util.MailSSLSocketFactory MailSSLSocketFactory}.
+If set to "*", all hosts are trusted.
+If set to a whitespace separated list of hosts, those hosts are trusted.
+Otherwise, trust depends on the certificate the server presents.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.ssl.socketFactory">mail.pop3.ssl.socketFactory</A></TD>
+<TD>SSLSocketFactory</TD>
+<TD>
+If set to a class that extends the
+<code>javax.net.ssl.SSLSocketFactory</code> class, this class
+will be used to create POP3 SSL sockets.  Note that this is an
+instance of a class, not a name, and must be set using the
+<code>put</code> method, not the <code>setProperty</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.ssl.socketFactory.class">mail.pop3.ssl.socketFactory.class</A></TD>
+<TD>String</TD>
+<TD>
+If set, specifies the name of a class that extends the
+<code>javax.net.ssl.SSLSocketFactory</code> class.  This class
+will be used to create POP3 SSL sockets.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.ssl.socketFactory.port">mail.pop3.ssl.socketFactory.port</A></TD>
+<TD>int</TD>
+<TD>
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.ssl.protocols">mail.pop3.ssl.protocols</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the SSL protocols that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the <code>javax.net.ssl.SSLSocket.setEnabledProtocols</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.ssl.ciphersuites">mail.pop3.ssl.ciphersuites</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the SSL cipher suites that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the <code>javax.net.ssl.SSLSocket.setEnabledCipherSuites</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.starttls.enable">mail.pop3.starttls.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If true, enables the use of the <code>STLS</code> command (if
+supported by the server) to switch the connection to a TLS-protected
+connection before issuing any login commands.
+If the server does not support STARTTLS, the connection continues without
+the use of TLS; see the
+<A HREF="#mail.pop3.starttls.required"><code>mail.pop3.starttls.required</code></A>
+property to fail if STARTTLS isn't supported.
+Note that an appropriate trust store must configured so that the client
+will trust the server's certificate.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.starttls.required">mail.pop3.starttls.required</A></TD>
+<TD>boolean</TD>
+<TD>
+If true, requires the use of the <code>STLS</code> command.
+If the server doesn't support the STLS command, or the command
+fails, the connect method will fail.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.proxy.host">mail.pop3.proxy.host</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the host name of an HTTP web proxy server that will be used for
+connections to the mail server.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.proxy.port">mail.pop3.proxy.port</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the port number for the HTTP web proxy server.
+Defaults to port 80.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.proxy.user">mail.pop3.proxy.user</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the user name to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.proxy.password">mail.pop3.proxy.password</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the password to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.socks.host">mail.pop3.socks.host</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the host name of a SOCKS5 proxy server that will be used for
+connections to the mail server.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.socks.port">mail.pop3.socks.port</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the port number for the SOCKS5 proxy server.
+This should only need to be used if the proxy server is not using
+the standard port number of 1080.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.disabletop">mail.pop3.disabletop</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, the POP3 TOP command will not be used to fetch
+message headers.  This is useful for POP3 servers that don't
+properly implement the TOP command, or that provide incorrect
+information in the TOP command results.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.disablecapa">mail.pop3.disablecapa</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, the POP3 CAPA command will not be used to fetch
+server capabilities.  This is useful for POP3 servers that don't
+properly implement the CAPA command, or that provide incorrect
+information in the CAPA command results.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.forgettopheaders">mail.pop3.forgettopheaders</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, the headers that might have been retrieved using
+the POP3 TOP command will be forgotten and replaced by headers
+retrieved as part of the POP3 RETR command.  Some servers, such
+as some versions of Microsft Exchange and IBM Lotus Notes,
+will return slightly different
+headers each time the TOP or RETR command is used.  To allow the
+POP3 provider to properly parse the message content returned from
+the RETR command, the headers also returned by the RETR command
+must be used.  Setting this property to true will cause these
+headers to be used, even if they differ from the headers returned
+previously as a result of using the TOP command.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.filecache.enable">mail.pop3.filecache.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, the POP3 provider will cache message data in a temporary
+file rather than in memory.  Messages are only added to the cache when
+accessing the message content.  Message headers are always cached in
+memory (on demand).  The file cache is removed when the folder is closed
+or the JVM terminates.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.filecache.dir">mail.pop3.filecache.dir</A></TD>
+<TD>String</TD>
+<TD>
+If the file cache is enabled, this property can be used to override the
+default directory used by the JDK for temporary files.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.cachewriteto">mail.pop3.cachewriteto</A></TD>
+<TD>boolean</TD>
+<TD>
+Controls the behavior of the
+{@link com.sun.mail.pop3.POP3Message#writeTo writeTo} method
+on a POP3 message object.
+If set to true, and the message content hasn't yet been cached,
+and ignoreList is null, the message is cached before being written.
+Otherwise, the message is streamed directly
+to the output stream without being cached.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.keepmessagecontent">mail.pop3.keepmessagecontent</A></TD>
+<TD>boolean</TD>
+<TD>
+The content of a message is cached when it is first fetched.
+Normally this cache uses a {@link java.lang.ref.SoftReference SoftReference}
+to refer to the cached content.  This allows the cached content to be purged
+if memory is low, in which case the content will be fetched again if it's
+needed.
+If this property is set to true, a hard reference to the cached content
+will be kept, preventing the memory from being reused until the folder
+is closed or the cached content is explicitly invalidated (using the
+{@link com.sun.mail.pop3.POP3Message#invalidate invalidate} method).
+(This was the behavior in previous versions of JavaMail.)
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.pop3.finalizecleanclose">mail.pop3.finalizecleanclose</A></TD>
+<TD>boolean</TD>
+<TD>
+When the finalizer for POP3Store or POP3Folder is called,
+should the connection to the server be closed cleanly, as if the
+application called the close method?
+Or should the connection to the server be closed without sending
+any commands to the server?
+Defaults to false, the connection is not closed cleanly.
+</TD>
+</TR>
+
+</TABLE>
+<P>
+In general, applications should not need to use the classes in this
+package directly.  Instead, they should use the APIs defined by
+<code>javax.mail</code> package (and subpackages).  Applications should
+never construct instances of <code>POP3Store</code> or
+<code>POP3Folder</code> directly.  Instead, they should use the
+<code>Session</code> method <code>getStore</code> to acquire an
+appropriate <code>Store</code> object, and from that acquire
+<code>Folder</code> objects.
+</P>
+<P>
+In addition to printing debugging output as controlled by the
+{@link javax.mail.Session Session} configuration,
+the com.sun.mail.pop3 provider logs the same information using
+{@link java.util.logging.Logger} as described in the following table:
+</P>
+<TABLE BORDER SUMMARY="POP3 Loggers">
+<TR>
+<TH>Logger Name</TH>
+<TH>Logging Level</TH>
+<TH>Purpose</TH>
+</TR>
+
+<TR>
+<TD>com.sun.mail.pop3</TD>
+<TD>CONFIG</TD>
+<TD>Configuration of the POP3Store</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.pop3</TD>
+<TD>FINE</TD>
+<TD>General debugging output</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.pop3.protocol</TD>
+<TD>FINEST</TD>
+<TD>Complete protocol trace</TD>
+</TR>
+</TABLE>
+
+<P>
+<strong>WARNING:</strong> The APIs unique to this package should be
+considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/com/sun/mail/smtp/DigestMD5.java b/mail/src/main/java/com/sun/mail/smtp/DigestMD5.java
new file mode 100644
index 0000000..401834a
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/DigestMD5.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import java.util.*;
+import java.util.logging.Level;
+import java.security.*;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.BASE64DecoderStream;
+
+/**
+ * DIGEST-MD5 authentication support.
+ *
+ * @author Dean Gibson
+ * @author Bill Shannon
+ */
+
+public class DigestMD5 {
+
+    private MailLogger logger;
+    private MessageDigest md5;
+    private String uri;
+    private String clientResponse;
+
+    public DigestMD5(MailLogger logger) {
+	this.logger = logger.getLogger(this.getClass(), "DEBUG DIGEST-MD5");
+	logger.config("DIGEST-MD5 Loaded");
+    }
+
+    /**
+     * Return client's authentication response to server's challenge.
+     *
+     * @param	host	the host name
+     * @param	user	the user name
+     * @param	passwd	the user's password
+     * @param	realm	the security realm
+     * @param	serverChallenge	the challenge from the server
+     * @return byte array with client's response
+     * @exception	IOException	for I/O errors
+     */
+    public byte[] authClient(String host, String user, String passwd,
+				String realm, String serverChallenge)
+				throws IOException {
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	OutputStream b64os = new BASE64EncoderStream(bos, Integer.MAX_VALUE);
+	SecureRandom random;
+	try {
+	    //random = SecureRandom.getInstance("SHA1PRNG");
+	    random = new SecureRandom();
+	    md5 = MessageDigest.getInstance("MD5");
+	} catch (NoSuchAlgorithmException ex) {
+	    logger.log(Level.FINE, "NoSuchAlgorithmException", ex);
+	    throw new IOException(ex.toString());
+	}
+	StringBuilder result = new StringBuilder();
+
+	uri = "smtp/" + host;
+	String nc = "00000001";
+	String qop = "auth";
+	byte[] bytes = new byte[32];	// arbitrary size ...
+	int resp;
+
+	logger.fine("Begin authentication ...");
+
+	// Code based on http://www.ietf.org/rfc/rfc2831.txt
+	Map<String, String> map = tokenize(serverChallenge);
+
+	if (realm == null) {
+	    String text = map.get("realm");
+	    realm = text != null ? new StringTokenizer(text, ",").nextToken()
+				 : host;
+	}
+
+	// server challenge random value
+	String nonce = map.get("nonce");
+
+	// Does server support UTF-8 usernames and passwords?
+	String charset = map.get("charset");
+	boolean utf8 = charset != null && charset.equalsIgnoreCase("utf-8");
+
+	random.nextBytes(bytes);
+	b64os.write(bytes);
+	b64os.flush();
+
+	// client challenge random value
+	String cnonce = bos.toString("iso-8859-1");	// really ASCII?
+	bos.reset();
+
+	// DIGEST-MD5 computation, common portion (order critical)
+	if (utf8) {
+	    String up = user + ":" + realm + ":" + passwd;
+	    md5.update(md5.digest(up.getBytes(StandardCharsets.UTF_8)));
+	} else
+	    md5.update(md5.digest(
+		ASCIIUtility.getBytes(user + ":" + realm + ":" + passwd)));
+	md5.update(ASCIIUtility.getBytes(":" + nonce + ":" + cnonce));
+	clientResponse = toHex(md5.digest())
+		+ ":" + nonce  + ":" + nc + ":" + cnonce + ":" + qop + ":";
+	
+	// DIGEST-MD5 computation, client response (order critical)
+	md5.update(ASCIIUtility.getBytes("AUTHENTICATE:" + uri));
+	md5.update(ASCIIUtility.getBytes(clientResponse + toHex(md5.digest())));
+
+	// build response text (order not critical)
+	result.append("username=\"" + user + "\"");
+	result.append(",realm=\"" + realm + "\"");
+	result.append(",qop=" + qop);
+	result.append(",nc=" + nc);
+	result.append(",nonce=\"" + nonce + "\"");
+	result.append(",cnonce=\"" + cnonce + "\"");
+	result.append(",digest-uri=\"" + uri + "\"");
+	if (utf8)
+	    result.append(",charset=\"utf-8\"");
+	result.append(",response=" + toHex(md5.digest()));
+
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("Response => " + result.toString());
+	b64os.write(ASCIIUtility.getBytes(result.toString()));
+	b64os.flush();
+	return bos.toByteArray();
+    }
+
+    /**
+     * Allow the client to authenticate the server based on its
+     * response.
+     *
+     * @param	serverResponse	the response that was received from the server
+     * @return	true if server is authenticated
+     * @exception	IOException	for character conversion failures
+     */
+    public boolean authServer(String serverResponse) throws IOException {
+	Map<String, String> map = tokenize(serverResponse);
+	// DIGEST-MD5 computation, server response (order critical)
+	md5.update(ASCIIUtility.getBytes(":" + uri));
+	md5.update(ASCIIUtility.getBytes(clientResponse + toHex(md5.digest())));
+	String text = toHex(md5.digest());
+	if (!text.equals(map.get("rspauth"))) {
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("Expected => rspauth=" + text);
+	    return false;	// server NOT authenticated by client !!!
+	}
+	return true;
+    }
+
+    /**
+     * Tokenize a response from the server.
+     *
+     * @return	Map containing key/value pairs from server
+     */
+    @SuppressWarnings("fallthrough")
+    private Map<String, String> tokenize(String serverResponse)
+	    throws IOException {
+	Map<String, String> map	= new HashMap<>();
+	byte[] bytes = serverResponse.getBytes("iso-8859-1");	// really ASCII?
+	String key = null;
+	int ttype;
+	StreamTokenizer	tokens
+		= new StreamTokenizer(
+		    new InputStreamReader(
+		      new BASE64DecoderStream(
+			new ByteArrayInputStream(bytes, 4, bytes.length - 4)
+		      ), "iso-8859-1"	// really ASCII?
+		    )
+		  );
+
+	tokens.ordinaryChars('0', '9');	// reset digits
+	tokens.wordChars('0', '9');	// digits may start words
+	while ((ttype = tokens.nextToken()) != StreamTokenizer.TT_EOF) {
+	    switch (ttype) {
+	    case StreamTokenizer.TT_WORD:
+		if (key == null) {
+		    key = tokens.sval;
+		    break;
+		}
+		// fall-thru
+	    case '"':
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("Received => " +
+			 	 key + "='" + tokens.sval + "'");
+		if (map.containsKey(key)) {  // concatenate multiple values
+		    map.put(key, map.get(key) + "," + tokens.sval);
+		} else {
+		    map.put(key, tokens.sval);
+		}
+		key = null;
+		break;
+	    default:	// XXX - should never happen?
+		break;
+	    }
+	}
+	return map;
+    }
+
+    private static char[] digits = {
+	'0', '1', '2', '3', '4', '5', '6', '7',
+	'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'
+    };
+
+    /**
+     * Convert a byte array to a string of hex digits representing the bytes.
+     */
+    private static String toHex(byte[] bytes) {
+	char[] result = new char[bytes.length * 2];
+
+	for (int index = 0, i = 0; index < bytes.length; index++) {
+	    int temp = bytes[index] & 0xFF;
+	    result[i++] = digits[temp >> 4];
+	    result[i++] = digits[temp & 0xF];
+	}
+	return new String(result);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPAddressFailedException.java b/mail/src/main/java/com/sun/mail/smtp/SMTPAddressFailedException.java
new file mode 100644
index 0000000..234820e
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPAddressFailedException.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.SendFailedException;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * This exception is thrown when the message cannot be sent. <p>
+ * 
+ * The exception includes the address to which the message could not be
+ * sent.  This will usually appear in a chained list of exceptions,
+ * one per address, attached to a top level SendFailedException that
+ * aggregates all the addresses.
+ *
+ * @since JavaMail 1.3.2
+ */
+
+public class SMTPAddressFailedException extends SendFailedException {
+    protected InternetAddress addr;	// address that failed
+    protected String cmd;		// command issued to server
+    protected int rc;			// return code from SMTP server
+
+    private static final long serialVersionUID = 804831199768630097L;
+
+    /**
+     * Constructs an SMTPAddressFailedException with the specified 
+     * address, return code, and error string.
+     *
+     * @param addr	the address that failed
+     * @param cmd	the command that was sent to the SMTP server
+     * @param rc	the SMTP return code indicating the failure
+     * @param err	the error string from the SMTP server
+     */
+    public SMTPAddressFailedException(InternetAddress addr, String cmd, int rc,
+				String err) {
+	super(err);
+	this.addr = addr;
+	this.cmd = cmd;
+	this.rc = rc;
+    }
+
+    /**
+     * Return the address that failed.
+     *
+     * @return	the address
+     */
+    public InternetAddress getAddress() {
+	return addr;
+    }
+
+    /**
+     * Return the command that failed.
+     *
+     * @return	the command
+     */
+    public String getCommand() {
+	return cmd;
+    }
+
+
+    /**
+     * Return the return code from the SMTP server that indicates the
+     * reason for the failure.  See
+     * <A HREF="http://www.ietf.org/rfc/rfc821.txt">RFC 821</A>
+     * for interpretation of the return code.
+     *
+     * @return	the return code
+     */
+    public int getReturnCode() {
+	return rc;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPAddressSucceededException.java b/mail/src/main/java/com/sun/mail/smtp/SMTPAddressSucceededException.java
new file mode 100644
index 0000000..2ddb29a
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPAddressSucceededException.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * This exception is chained off a SendFailedException when the
+ * <code>mail.smtp.reportsuccess</code> property is true.  It
+ * indicates an address to which the message was sent.  The command
+ * will be an SMTP RCPT command and the return code will be the
+ * return code from that command.
+ *
+ * @since JavaMail 1.3.2
+ */
+
+public class SMTPAddressSucceededException extends MessagingException {
+    protected InternetAddress addr;	// address that succeeded
+    protected String cmd;		// command issued to server
+    protected int rc;			// return code from SMTP server
+
+    private static final long serialVersionUID = -1168335848623096749L;
+
+    /**
+     * Constructs an SMTPAddressSucceededException with the specified 
+     * address, return code, and error string.
+     *
+     * @param addr	the address that succeeded
+     * @param cmd	the command that was sent to the SMTP server
+     * @param rc	the SMTP return code indicating the success
+     * @param err	the error string from the SMTP server
+     */
+    public SMTPAddressSucceededException(InternetAddress addr,
+				String cmd, int rc, String err) {
+	super(err);
+	this.addr = addr;
+	this.cmd = cmd;
+	this.rc = rc;
+    }
+
+    /**
+     * Return the address that succeeded.
+     *
+     * @return	the address
+     */
+    public InternetAddress getAddress() {
+	return addr;
+    }
+
+    /**
+     * Return the command that succeeded.
+     *
+     * @return	the command
+     */
+    public String getCommand() {
+	return cmd;
+    }
+
+
+    /**
+     * Return the return code from the SMTP server that indicates the
+     * reason for the success.  See
+     * <A HREF="http://www.ietf.org/rfc/rfc821.txt">RFC 821</A>
+     * for interpretation of the return code.
+     *
+     * @return	the return code
+     */
+    public int getReturnCode() {
+	return rc;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPMessage.java b/mail/src/main/java/com/sun/mail/smtp/SMTPMessage.java
new file mode 100644
index 0000000..826ea44
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPMessage.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * This class is a specialization of the MimeMessage class that allows
+ * you to specify various SMTP options and parameters that will be
+ * used when this message is sent over SMTP.  Simply use this class
+ * instead of MimeMessage and set SMTP options using the methods on
+ * this class. <p>
+ *
+ * See the <a href="package-summary.html">com.sun.mail.smtp</a> package
+ * documentation for further information on the SMTP protocol provider. <p>
+ *
+ * @author Bill Shannon
+ * @see	javax.mail.internet.MimeMessage
+ */
+
+public class SMTPMessage extends MimeMessage {
+
+    /** Never notify of delivery status */
+    public static final int NOTIFY_NEVER = -1;
+    /** Notify of delivery success */
+    public static final int NOTIFY_SUCCESS = 1;
+    /** Notify of delivery failure */
+    public static final int NOTIFY_FAILURE = 2;
+    /** Notify of delivery delay */
+    public static final int NOTIFY_DELAY = 4;
+
+    /** Return full message with delivery status notification */
+    public static final int RETURN_FULL = 1;
+    /** Return only message headers with delivery status notification */
+    public static final int RETURN_HDRS = 2;
+
+    private static final String[] returnOptionString = { null, "FULL", "HDRS" };
+
+    private String envelopeFrom; // the string to use in the MAIL FROM: command
+    private int notifyOptions = 0;
+    private int returnOption = 0;
+    private boolean sendPartial = false;
+    private boolean allow8bitMIME = false;
+    private String submitter = null;	// RFC 2554 AUTH=submitter
+    private String extension = null;	// extensions to use with MAIL command
+
+    /**
+     * Default constructor. An empty message object is created.
+     * The <code>headers</code> field is set to an empty InternetHeaders
+     * object. The <code>flags</code> field is set to an empty Flags
+     * object. The <code>modified</code> flag is set to true.
+     *
+     * @param	session	the Session
+     */
+    public SMTPMessage(Session session) {
+	super(session);
+    }
+
+    /**
+     * Constructs an SMTPMessage 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
+     * @exception	MessagingException for failures
+     */
+    public SMTPMessage(Session session, InputStream is) 
+			throws MessagingException {
+	super(session, is);
+    }
+
+    /**
+     * Constructs a new SMTPMessage with content initialized from the
+     * <code>source</code> 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
+     * @exception	MessagingException for failures
+     */
+    public SMTPMessage(MimeMessage source) throws MessagingException {
+	super(source);
+    }
+
+    /**
+     * Set the From address to appear in the SMTP envelope.  Note that this
+     * is different than the From address that appears in the message itself.
+     * The envelope From address is typically used when reporting errors.
+     * See <A HREF="http://www.ietf.org/rfc/rfc821.txt">RFC 821</A> for
+     * details. <p>
+     *
+     * If set, overrides the <code>mail.smtp.from</code> property.
+     *
+     * @param	from	the envelope From address
+     */
+    public void setEnvelopeFrom(String from) {
+	envelopeFrom = from;
+    }
+
+    /**
+     * Return the envelope From address.
+     *
+     * @return	the envelope From address, or null if not set
+     */
+    public String getEnvelopeFrom() {
+	return envelopeFrom;
+    }
+
+    /**
+     * Set notification options to be used if the server supports
+     * Delivery Status Notification
+     * (<A HREF="http://www.ietf.org/rfc/rfc1891.txt">RFC 1891</A>).
+     * Either <code>NOTIFY_NEVER</code> or some combination of
+     * <code>NOTIFY_SUCCESS</code>, <code>NOTIFY_FAILURE</code>, and
+     * <code>NOTIFY_DELAY</code>. <p>
+     *
+     * If set, overrides the <code>mail.smtp.dsn.notify</code> property.
+     *
+     * @param	options	notification options
+     */
+    public void setNotifyOptions(int options) {
+	if (options < -1 || options >= 8)
+	    throw new IllegalArgumentException("Bad return option");
+	notifyOptions = options;
+    }
+
+    /**
+     * Get notification options.  Returns zero if no options set.
+     *
+     * @return	notification options
+     */
+    public int getNotifyOptions() {
+	return notifyOptions;
+    }
+
+    /**
+     * Return notification options as an RFC 1891 string.
+     * Returns null if no options set.
+     */
+    String getDSNNotify() {
+	if (notifyOptions == 0)
+	    return null;
+	if (notifyOptions == NOTIFY_NEVER)
+	    return "NEVER";
+	StringBuilder sb = new StringBuilder();
+	if ((notifyOptions & NOTIFY_SUCCESS) != 0)
+	    sb.append("SUCCESS");
+	if ((notifyOptions & NOTIFY_FAILURE) != 0) {
+	    if (sb.length() != 0)
+		sb.append(',');
+	    sb.append("FAILURE");
+	}
+	if ((notifyOptions & NOTIFY_DELAY) != 0) {
+	    if (sb.length() != 0)
+		sb.append(',');
+	    sb.append("DELAY");
+	}
+	return sb.toString();
+    }
+
+    /**
+     * Set return option to be used if server supports
+     * Delivery Status Notification
+     * (<A HREF="http://www.ietf.org/rfc/rfc1891.txt">RFC 1891</A>).
+     * Either <code>RETURN_FULL</code> or <code>RETURN_HDRS</code>. <p>
+     *
+     * If set, overrides the <code>mail.smtp.dsn.ret</code> property.
+     *
+     * @param	option	return option
+     */
+    public void setReturnOption(int option) {
+	if (option < 0 || option > RETURN_HDRS)
+	    throw new IllegalArgumentException("Bad return option");
+	returnOption = option;
+    }
+
+    /**
+     * Return return option.  Returns zero if no option set.
+     *
+     * @return	return option
+     */
+    public int getReturnOption() {
+	return returnOption;
+    }
+
+    /**
+     * Return return option as an RFC 1891 string.
+     * Returns null if no option set.
+     */
+    String getDSNRet() {
+	return returnOptionString[returnOption];
+    }
+
+    /**
+     * If set to true, and the server supports the 8BITMIME extension, text
+     * parts of this message that use the "quoted-printable" or "base64"
+     * encodings are converted to use "8bit" encoding if they follow the
+     * RFC 2045 rules for 8bit text. <p>
+     *
+     * If true, overrides the <code>mail.smtp.allow8bitmime</code> property.
+     *
+     * @param	allow	allow 8-bit flag
+     */
+    public void setAllow8bitMIME(boolean allow) {
+	allow8bitMIME = allow;
+    }
+
+    /**
+     * Is use of the 8BITMIME extension is allowed?
+     *
+     * @return	allow 8-bit flag
+     */
+    public boolean getAllow8bitMIME() {
+	return allow8bitMIME;
+    }
+
+    /**
+     * If set to true, and this message has some valid and some invalid
+     * addresses, send the message anyway, reporting the partial failure with
+     * a SendFailedException.  If set to false (the default), the message is
+     * not sent to any of the recipients if there is an invalid recipient
+     * address. <p>
+     *
+     * If true, overrides the <code>mail.smtp.sendpartial</code> property.
+     *
+     * @param partial	send partial flag
+     */
+    public void setSendPartial(boolean partial) {
+	sendPartial = partial;
+    }
+
+    /**
+     * Send message if some addresses are invalid?
+     *
+     * @return	send partial flag
+     */
+    public boolean getSendPartial() {
+	return sendPartial;
+    }
+
+    /**
+     * Gets the submitter to be used for the RFC 2554 AUTH= value
+     * in the MAIL FROM command.
+     *
+     * @return	the name of the submitter.
+     */
+    public String getSubmitter() {
+	return submitter;
+    }
+
+    /**
+     * Sets the submitter to be used for the RFC 2554 AUTH= value
+     * in the MAIL FROM command.  Normally only used by a server
+     * that's relaying a message.  Clients will typically not
+     * set a submitter.  See
+     * <A HREF="http://www.ietf.org/rfc/rfc2554.txt">RFC 2554</A>
+     * for details.
+     *
+     * @param	submitter	the name of the submitter
+     */
+    public void setSubmitter(String submitter) {
+	this.submitter = submitter;
+    }
+
+    /**
+     * Gets the extension string to use with the MAIL command.
+     *
+     * @return	the extension string
+     *
+     * @since	JavaMail 1.3.2
+     */
+    public String getMailExtension() {
+	return extension;
+    }
+
+    /**
+     * Set the extension string to use with the MAIL command.
+     * The extension string can be used to specify standard SMTP
+     * service extensions as well as vendor-specific extensions.
+     * Typically the application should use the
+     * {@link com.sun.mail.smtp.SMTPTransport SMTPTransport}
+     * method {@link com.sun.mail.smtp.SMTPTransport#supportsExtension
+     * supportsExtension}
+     * to verify that the server supports the desired service extension.
+     * See <A HREF="http://www.ietf.org/rfc/rfc1869.txt">RFC 1869</A>
+     * and other RFCs that define specific extensions. <p>
+     *
+     * For example:
+     *
+     * <blockquote><pre>
+     * if (smtpTransport.supportsExtension("DELIVERBY"))
+     *    smtpMsg.setMailExtension("BY=60;R");
+     * </pre></blockquote>
+     *
+     * @param	extension	the extension string
+     * @since	JavaMail 1.3.2
+     */
+    public void setMailExtension(String extension) {
+	this.extension = extension;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPOutputStream.java b/mail/src/main/java/com/sun/mail/smtp/SMTPOutputStream.java
new file mode 100644
index 0000000..d2fd75b
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPOutputStream.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import com.sun.mail.util.CRLFOutputStream;
+
+/**
+ * In addition to converting lines into the canonical format,
+ * i.e., terminating lines with the CRLF sequence, escapes the "."
+ * by adding another "." to any "." that appears in the beginning
+ * of a line.  See RFC821 section 4.5.2.
+ * 
+ * @author Max Spivak
+ * @see CRLFOutputStream
+ */
+public class SMTPOutputStream extends CRLFOutputStream {
+    public SMTPOutputStream(OutputStream os) {
+	super(os);
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+	// if that last character was a newline, and the current
+	// character is ".", we always write out an extra ".".
+	if ((lastb == '\n' || lastb == '\r' || lastb == -1) && b == '.') {
+	    out.write('.');
+	}
+	
+	super.write(b);
+    }
+
+    /* 
+     * This method has been added to improve performance.
+     */
+    @Override
+    public void write(byte b[], int off, int len) throws IOException {
+	int lastc = (lastb == -1) ? '\n' : lastb;
+	int start = off;
+	
+	len += off;
+	for (int i = off; i < len; i++) {
+	    if ((lastc == '\n' || lastc == '\r') && b[i] == '.') {
+		super.write(b, start, i - start);
+		out.write('.');
+		start = i;
+	    }
+	    lastc = b[i];
+	}
+	if ((len - start) > 0)
+	    super.write(b, start, len - start);
+    }
+
+    /**
+     * Override flush method in FilterOutputStream.
+     *
+     * The MimeMessage writeTo method flushes its buffer at the end,
+     * but we don't want to flush data out to the socket until we've
+     * also written the terminating "\r\n.\r\n".
+     *
+     * We buffer nothing so there's nothing to flush.  We depend
+     * on the fact that CRLFOutputStream also buffers nothing.
+     * SMTPTransport will manually flush the socket before reading
+     * the response.
+     */
+    @Override
+    public void flush() {
+	// do nothing
+    }
+
+    /**
+     * Ensure we're at the beginning of a line.
+     * Write CRLF if not.
+     *
+     * @exception	IOException	if the write fails
+     */
+    public void ensureAtBOL() throws IOException {
+	if (!atBOL)
+	    super.writeln();
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPProvider.java b/mail/src/main/java/com/sun/mail/smtp/SMTPProvider.java
new file mode 100644
index 0000000..3b5c1e9
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.Provider;
+
+/**
+ * The SMTP protocol provider.
+ */
+public class SMTPProvider extends Provider {
+    public SMTPProvider() {
+	super(Provider.Type.TRANSPORT, "smtp", SMTPTransport.class.getName(),
+	    "Oracle", null);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPSSLProvider.java b/mail/src/main/java/com/sun/mail/smtp/SMTPSSLProvider.java
new file mode 100644
index 0000000..40a2acd
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPSSLProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.Provider;
+
+/**
+ * The SMTP SSL protocol provider.
+ */
+public class SMTPSSLProvider extends Provider {
+    public SMTPSSLProvider() {
+	super(Provider.Type.TRANSPORT, "smtps",
+	    SMTPSSLTransport.class.getName(), "Oracle", null);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPSSLTransport.java b/mail/src/main/java/com/sun/mail/smtp/SMTPSSLTransport.java
new file mode 100644
index 0000000..f3a7300
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPSSLTransport.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.*;
+
+/**
+ * This class implements the Transport abstract class using SMTP
+ * over SSL for message submission and transport.
+ *
+ * @author Bill Shannon
+ */
+
+public class SMTPSSLTransport extends SMTPTransport {
+
+    /**
+     * Constructor.
+     *
+     * @param	session	the Session
+     * @param	urlname	the URLName of this transport
+     */
+    public SMTPSSLTransport(Session session, URLName urlname) {
+	super(session, urlname, "smtps", true);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPSaslAuthenticator.java b/mail/src/main/java/com/sun/mail/smtp/SMTPSaslAuthenticator.java
new file mode 100644
index 0000000..70545a5
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPSaslAuthenticator.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import java.util.*;
+import java.util.logging.Level;
+import javax.security.sasl.*;
+import javax.security.auth.callback.*;
+import javax.mail.MessagingException;
+
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.BASE64DecoderStream;
+
+/**
+ * This class contains a single method that does authentication using
+ * SASL.  This is in a separate class so that it can be compiled with
+ * J2SE 1.5.  Eventually it should be merged into SMTPTransport.java.
+ */
+
+public class SMTPSaslAuthenticator implements SaslAuthenticator {
+
+    private SMTPTransport pr;
+    private String name;
+    private Properties props;
+    private MailLogger logger;
+    private String host;
+
+    /*
+     * This is a hack to initialize the OAUTH SASL provider just before,
+     * and only if, we might need it.  This avoids the need for the user 
+     * to initialize it explicitly, or manually configure the security
+     * providers file.
+     */
+    static {
+	try {
+	    com.sun.mail.auth.OAuth2SaslClientFactory.init();
+	} catch (Throwable t) { }
+    }
+
+    public SMTPSaslAuthenticator(SMTPTransport pr, String name,
+		Properties props, MailLogger logger, String host) {
+	this.pr = pr;
+	this.name = name;
+	this.props = props;
+	this.logger = logger;
+	this.host = host;
+    }
+
+    @Override
+    public boolean authenticate(String[] mechs, final String realm,
+				final String authzid, final String u,
+				final String p) throws MessagingException {
+
+	boolean done = false;
+	if (logger.isLoggable(Level.FINE)) {
+	    logger.fine("SASL Mechanisms:");
+	    for (int i = 0; i < mechs.length; i++)
+		logger.fine(" " + mechs[i]);
+	    logger.fine("");
+	}
+
+	SaslClient sc;
+	CallbackHandler cbh = new CallbackHandler() {
+	    @Override
+	    public void handle(Callback[] callbacks) {
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("SASL callback length: " + callbacks.length);
+		for (int i = 0; i < callbacks.length; i++) {
+		    if (logger.isLoggable(Level.FINE))
+			logger.fine("SASL callback " + i + ": " + callbacks[i]);
+		    if (callbacks[i] instanceof NameCallback) {
+			NameCallback ncb = (NameCallback)callbacks[i];
+			ncb.setName(u);
+		    } else if (callbacks[i] instanceof PasswordCallback) {
+			PasswordCallback pcb = (PasswordCallback)callbacks[i];
+			pcb.setPassword(p.toCharArray());
+		    } else if (callbacks[i] instanceof RealmCallback) {
+			RealmCallback rcb = (RealmCallback)callbacks[i];
+			rcb.setText(realm != null ?
+				    realm : rcb.getDefaultText());
+		    } else if (callbacks[i] instanceof RealmChoiceCallback) {
+			RealmChoiceCallback rcb =
+			    (RealmChoiceCallback)callbacks[i];
+			if (realm == null)
+			    rcb.setSelectedIndex(rcb.getDefaultChoice());
+			else {
+			    // need to find specified realm in list
+			    String[] choices = rcb.getChoices();
+			    for (int k = 0; k < choices.length; k++) {
+				if (choices[k].equals(realm)) {
+				    rcb.setSelectedIndex(k);
+				    break;
+				}
+			    }
+			}
+		    }
+		}
+	    }
+	};
+
+	try {
+	    @SuppressWarnings("unchecked")
+	    Map<String, ?> propsMap = (Map) props;
+	    sc = Sasl.createSaslClient(mechs, authzid, name, host,
+					propsMap, cbh);
+	} catch (SaslException sex) {
+	    logger.log(Level.FINE, "Failed to create SASL client", sex);
+	    throw new UnsupportedOperationException(sex.getMessage(), sex);
+	}
+	if (sc == null) {
+	    logger.fine("No SASL support");
+	    throw new UnsupportedOperationException("No SASL support");
+	}
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("SASL client " + sc.getMechanismName());
+
+	int resp;
+	try {
+	    String mech = sc.getMechanismName();
+	    String ir = null;
+	    if (sc.hasInitialResponse()) {
+		byte[] ba = sc.evaluateChallenge(new byte[0]);
+		if (ba.length > 0) {
+		    ba = BASE64EncoderStream.encode(ba);
+		    ir = ASCIIUtility.toString(ba, 0, ba.length);
+		} else
+		    ir = "=";
+	    }
+	    if (ir != null)
+		resp = pr.simpleCommand("AUTH " + mech + " " + ir);
+	    else
+		resp = pr.simpleCommand("AUTH " + mech);
+
+	    /*
+	     * A 530 response indicates that the server wants us to
+	     * issue a STARTTLS command first.  Do that and try again.
+	     */
+	    if (resp == 530) {
+		pr.startTLS();
+		if (ir != null)
+		    resp = pr.simpleCommand("AUTH " + mech + " " + ir);
+		else
+		    resp = pr.simpleCommand("AUTH " + mech);
+	    }
+
+	    if (resp == 235)
+		return true;	// success already!
+
+	    if (resp != 334)
+		return false;
+	} catch (Exception ex) {
+	    logger.log(Level.FINE, "SASL AUTHENTICATE Exception", ex);
+	    return false;
+	}
+
+	while (!done) { // loop till we are done
+	    try {
+	    	if (resp == 334) {
+		    byte[] ba = null;
+		    if (!sc.isComplete()) {
+			ba = ASCIIUtility.getBytes(responseText(pr));
+			if (ba.length > 0)
+			    ba = BASE64DecoderStream.decode(ba);
+			if (logger.isLoggable(Level.FINE))
+			    logger.fine("SASL challenge: " +
+				ASCIIUtility.toString(ba, 0, ba.length) + " :");
+			ba = sc.evaluateChallenge(ba);
+		    }
+		    if (ba == null) {
+			logger.fine("SASL: no response");
+			resp = pr.simpleCommand("");
+		    } else {
+			if (logger.isLoggable(Level.FINE))
+			    logger.fine("SASL response: " +
+				ASCIIUtility.toString(ba, 0, ba.length) + " :");
+			ba = BASE64EncoderStream.encode(ba);
+			resp = pr.simpleCommand(ba);
+		    }
+		} else
+		    done = true;
+	    } catch (Exception ioex) {
+		logger.log(Level.FINE, "SASL Exception", ioex);
+		done = true;
+		// XXX - ultimately return true???
+	    }
+	}
+	if (resp != 235)
+	    return false;
+
+	if (sc.isComplete() /*&& res.status == SUCCESS*/) {
+	    String qop = (String)sc.getNegotiatedProperty(Sasl.QOP);
+	    if (qop != null && (qop.equalsIgnoreCase("auth-int") ||
+				qop.equalsIgnoreCase("auth-conf"))) {
+		// XXX - NOT SUPPORTED!!!
+		logger.fine(
+		    "SASL Mechanism requires integrity or confidentiality");
+		return false;
+	    }
+	}
+
+	return true;
+    }
+
+    private static final String responseText(SMTPTransport pr) {
+	String resp = pr.getLastServerResponse().trim();
+	if (resp.length() > 4)
+	    return resp.substring(4);
+	else
+	    return "";
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPSendFailedException.java b/mail/src/main/java/com/sun/mail/smtp/SMTPSendFailedException.java
new file mode 100644
index 0000000..d8cf3e6
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPSendFailedException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.Address;
+import javax.mail.SendFailedException;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * This exception is thrown when the message cannot be sent. <p>
+ * 
+ * This exception will usually appear first in a chained list of exceptions,
+ * followed by SMTPAddressFailedExceptions and/or
+ * SMTPAddressSucceededExceptions, * one per address.
+ * This exception corresponds to one of the SMTP commands used to
+ * send a message, such as the MAIL, DATA, and "end of data" commands,
+ * but not including the RCPT command.
+ *
+ * @since JavaMail 1.3.2
+ */
+
+public class SMTPSendFailedException extends SendFailedException {
+    protected InternetAddress addr;	// address that failed
+    protected String cmd;		// command issued to server
+    protected int rc;			// return code from SMTP server
+
+    private static final long serialVersionUID = 8049122628728932894L;
+
+    /**
+     * Constructs an SMTPSendFailedException with the specified 
+     * address, return code, and error string.
+     *
+     * @param cmd	the command that was sent to the SMTP server
+     * @param rc	the SMTP return code indicating the failure
+     * @param err	the error string from the SMTP server
+     * @param ex	a chained exception
+     * @param vs	the valid addresses the message was sent to
+     * @param vus	the valid addresses the message was not sent to
+     * @param inv	the invalid addresses
+     */
+    public SMTPSendFailedException(String cmd, int rc, String err, Exception ex,
+				Address[] vs, Address[] vus, Address[] inv) {
+	super(err, ex, vs, vus, inv);
+	this.cmd = cmd;
+	this.rc = rc;
+    }
+
+    /**
+     * Return the command that failed.
+     *
+     * @return	the command
+     */
+    public String getCommand() {
+	return cmd;
+    }
+
+    /**
+     * Return the return code from the SMTP server that indicates the
+     * reason for the failure.  See
+     * <A HREF="http://www.ietf.org/rfc/rfc821.txt">RFC 821</A>
+     * for interpretation of the return code.
+     *
+     * @return	the return code
+     */
+    public int getReturnCode() {
+	return rc;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPSenderFailedException.java b/mail/src/main/java/com/sun/mail/smtp/SMTPSenderFailedException.java
new file mode 100644
index 0000000..3139c79
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPSenderFailedException.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.SendFailedException;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * This exception is thrown when the message cannot be sent. <p>
+ * 
+ * The exception includes the sender's address, which the mail server
+ * rejected.
+ *
+ * @since JavaMail 1.4.4
+ */
+
+public class SMTPSenderFailedException extends SendFailedException {
+    protected InternetAddress addr;	// address that failed
+    protected String cmd;		// command issued to server
+    protected int rc;			// return code from SMTP server
+
+    private static final long serialVersionUID = 514540454964476947L;
+
+    /**
+     * Constructs an SMTPSenderFailedException with the specified 
+     * address, return code, and error string.
+     *
+     * @param addr	the address that failed
+     * @param cmd	the command that was sent to the SMTP server
+     * @param rc	the SMTP return code indicating the failure
+     * @param err	the error string from the SMTP server
+     */
+    public SMTPSenderFailedException(InternetAddress addr, String cmd, int rc,
+				String err) {
+	super(err);
+	this.addr = addr;
+	this.cmd = cmd;
+	this.rc = rc;
+    }
+
+    /**
+     * Return the address that failed.
+     *
+     * @return	the address
+     */
+    public InternetAddress getAddress() {
+	return addr;
+    }
+
+    /**
+     * Return the command that failed.
+     *
+     * @return	the command
+     */
+    public String getCommand() {
+	return cmd;
+    }
+
+
+    /**
+     * Return the return code from the SMTP server that indicates the
+     * reason for the failure.  See
+     * <A HREF="http://www.ietf.org/rfc/rfc821.txt">RFC 821</A>
+     * for interpretation of the return code.
+     *
+     * @return	the return code
+     */
+    public int getReturnCode() {
+	return rc;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java b/mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java
new file mode 100644
index 0000000..6b2acfb
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SMTPTransport.java
@@ -0,0 +1,2791 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.logging.Level;
+import java.lang.reflect.*;
+import java.nio.charset.StandardCharsets;
+import javax.net.ssl.SSLSocket;
+
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.MailLogger;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.SocketFetcher;
+import com.sun.mail.util.MailConnectException;
+import com.sun.mail.util.SocketConnectException;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.TraceInputStream;
+import com.sun.mail.util.TraceOutputStream;
+import com.sun.mail.auth.Ntlm;
+
+/**
+ * This class implements the Transport abstract class using SMTP for
+ * message submission and transport. <p>
+ *
+ * See the <a href="package-summary.html">com.sun.mail.smtp</a> package
+ * documentation for further information on the SMTP protocol provider. <p>
+ *
+ * This class includes many protected methods that allow a subclass to
+ * extend this class and add support for non-standard SMTP commands.
+ * The {@link #issueCommand} and {@link #sendCommand} methods can be
+ * used to send simple SMTP commands.  Other methods such as the
+ * {@link #mailFrom} and {@link #data} methods can be overridden to
+ * insert new commands before or after the corresponding SMTP commands.
+ * For example, a subclass could do this to send the XACT command
+ * before sending the DATA command:
+ * <pre>
+ *	protected OutputStream data() throws MessagingException {
+ *	    if (supportsExtension("XACCOUNTING"))
+ *	        issueCommand("XACT", 25);
+ *	    return super.data();
+ *	}
+ * </pre>
+ *
+ * @author Max Spivak
+ * @author Bill Shannon
+ * @author Dean Gibson (DIGEST-MD5 authentication)
+ * @author Lu\u00EDs Serralheiro (NTLM authentication)
+ *
+ * @see javax.mail.event.ConnectionEvent
+ * @see javax.mail.event.TransportEvent
+ */
+
+public class SMTPTransport extends Transport {
+
+    private String name = "smtp";	// Name of this protocol
+    private int defaultPort = 25;	// default SMTP port
+    private boolean isSSL = false;	// use SSL?
+    private String host;		// host we're connected to
+
+    // Following fields valid only during the sendMessage method.
+    private MimeMessage message;	// Message to be sent
+    private Address[] addresses;	// Addresses to which to send the msg
+    // Valid sent, valid unsent and invalid addresses
+    private Address[] validSentAddr, validUnsentAddr, invalidAddr;
+    // Did we send the message even though some addresses were invalid?
+    private boolean sendPartiallyFailed = false;
+    // If so, here's an exception we need to throw
+    private MessagingException exception;
+    // stream where message data is written
+    private SMTPOutputStream dataStream;
+
+    // Map of SMTP service extensions supported by server, if EHLO used.
+    private Hashtable<String, String> extMap;
+
+    private Map<String, Authenticator> authenticators
+	    = new HashMap<>();
+    private String defaultAuthenticationMechanisms;	// set in constructor
+
+    private boolean quitWait = false;	// true if we should wait
+
+    private String saslRealm = UNKNOWN;
+    private String authorizationID = UNKNOWN;
+    private boolean enableSASL = false;	// enable SASL authentication
+    private boolean useCanonicalHostName = false; // use canonical host name?
+    private String[] saslMechanisms = UNKNOWN_SA;
+
+    private String ntlmDomain = UNKNOWN; // for ntlm authentication
+
+    private boolean reportSuccess;	// throw an exception even on success
+    private boolean useStartTLS;	// use STARTTLS command
+    private boolean requireStartTLS;	// require STARTTLS command
+    private boolean useRset;		// use RSET instead of NOOP
+    private boolean noopStrict = true;	// NOOP must return 250 for success
+
+    private MailLogger logger;		// debug logger
+    private MailLogger traceLogger;	// protocol trace logger
+    private String localHostName;	// our own host name
+    private String lastServerResponse;	// last SMTP response
+    private int lastReturnCode;		// last SMTP return code
+    private boolean notificationDone;	// only notify once per send
+
+    private SaslAuthenticator saslAuthenticator; // if SASL is being used
+
+    private boolean noauthdebug = true;	// hide auth info in debug output
+    private boolean debugusername;	// include username in debug output?
+    private boolean debugpassword;	// include password in debug output?
+    private boolean allowutf8;		// allow UTF-8 usernames and passwords?
+    private int chunkSize;		// chunk size if CHUNKING supported
+
+    /** Headers that should not be included when sending */
+    private static final String[] ignoreList = { "Bcc", "Content-Length" };
+    private static final byte[] CRLF = { (byte)'\r', (byte)'\n' };
+    private static final String UNKNOWN = "UNKNOWN";	// place holder
+    private static final String[] UNKNOWN_SA = new String[0]; // place holder
+
+    /**
+     * Constructor that takes a Session object and a URLName
+     * that represents a specific SMTP server.
+     *
+     * @param	session	the Session
+     * @param	urlname	the URLName of this transport
+     */
+    public SMTPTransport(Session session, URLName urlname) {
+	this(session, urlname, "smtp", false);
+    }
+
+    /**
+     * Constructor used by this class and by SMTPSSLTransport subclass.
+     *
+     * @param	session	the Session
+     * @param	urlname	the URLName of this transport
+     * @param	name	the protocol name of this transport
+     * @param	isSSL	use SSL to connect?
+     */
+    protected SMTPTransport(Session session, URLName urlname,
+				String name, boolean isSSL) {
+	super(session, urlname);
+	Properties props = session.getProperties();
+
+	logger = new MailLogger(this.getClass(), "DEBUG SMTP",
+				session.getDebug(), session.getDebugOut());
+	traceLogger = logger.getSubLogger("protocol", null);
+	noauthdebug = !PropUtil.getBooleanProperty(props,
+			    "mail.debug.auth", false);
+	debugusername = PropUtil.getBooleanProperty(props,
+			"mail.debug.auth.username", true);
+	debugpassword = PropUtil.getBooleanProperty(props,
+			"mail.debug.auth.password", false);
+	if (urlname != null)
+	    name = urlname.getProtocol();
+	this.name = name;
+	if (!isSSL)
+	    isSSL = PropUtil.getBooleanProperty(props,
+				"mail." + name + ".ssl.enable", false);
+	if (isSSL)
+	    this.defaultPort = 465;
+	else
+	    this.defaultPort = 25;
+	this.isSSL = isSSL;
+
+	// setting mail.smtp.quitwait to false causes us to not wait for the
+	// response from the QUIT command
+	quitWait = PropUtil.getBooleanProperty(props,
+				"mail." + name + ".quitwait", true);
+
+	// mail.smtp.reportsuccess causes us to throw an exception on success
+	reportSuccess = PropUtil.getBooleanProperty(props,
+				"mail." + name + ".reportsuccess", false);
+
+	// mail.smtp.starttls.enable enables use of STARTTLS command
+	useStartTLS = PropUtil.getBooleanProperty(props,
+				"mail." + name + ".starttls.enable", false);
+
+	// mail.smtp.starttls.required requires use of STARTTLS command
+	requireStartTLS = PropUtil.getBooleanProperty(props,
+				"mail." + name + ".starttls.required", false);
+
+	// mail.smtp.userset causes us to use RSET instead of NOOP
+	// for isConnected
+	useRset = PropUtil.getBooleanProperty(props,
+				"mail." + name + ".userset", false);
+
+	// mail.smtp.noop.strict requires 250 response to indicate success
+	noopStrict = PropUtil.getBooleanProperty(props,
+				"mail." + name + ".noop.strict", true);
+
+	// check if SASL is enabled
+	enableSASL = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".sasl.enable", false);
+	if (enableSASL)
+	    logger.config("enable SASL");
+	useCanonicalHostName = PropUtil.getBooleanProperty(props,
+	    "mail." + name + ".sasl.usecanonicalhostname", false);
+	if (useCanonicalHostName)
+	    logger.config("use canonical host name");
+
+	allowutf8 = PropUtil.getBooleanProperty(props,
+	    "mail.mime.allowutf8", false);
+	if (allowutf8)
+	    logger.config("allow UTF-8");
+
+	chunkSize = PropUtil.getIntProperty(props,
+	    "mail." + name + ".chunksize", -1);
+	if (chunkSize > 0 && logger.isLoggable(Level.CONFIG))
+	    logger.config("chunk size " + chunkSize);
+
+	// created here, because they're inner classes that reference "this"
+	Authenticator[] a = new Authenticator[] {
+	    new LoginAuthenticator(),
+	    new PlainAuthenticator(),
+	    new DigestMD5Authenticator(),
+	    new NtlmAuthenticator(),
+	    new OAuth2Authenticator()
+	};
+	StringBuilder sb = new StringBuilder();
+	for (int i = 0; i < a.length; i++) {
+	    authenticators.put(a[i].getMechanism(), a[i]);
+	    sb.append(a[i].getMechanism()).append(' ');
+	}
+	defaultAuthenticationMechanisms = sb.toString();
+    }
+
+    /**
+     * Get the name of the local host, for use in the EHLO and HELO commands.
+     * The property mail.smtp.localhost overrides mail.smtp.localaddress,
+     * which overrides what InetAddress would tell us.
+     *
+     * @return	the local host name
+     */
+    public synchronized String getLocalHost() {
+	// get our hostname and cache it for future use
+	if (localHostName == null || localHostName.length() <= 0)
+	    localHostName =
+		    session.getProperty("mail." + name + ".localhost");
+	if (localHostName == null || localHostName.length() <= 0)
+	    localHostName =
+		    session.getProperty("mail." + name + ".localaddress");
+	try {
+	    if (localHostName == null || localHostName.length() <= 0) {
+		InetAddress localHost = InetAddress.getLocalHost();
+		localHostName = localHost.getCanonicalHostName();
+		// if we can't get our name, use local address literal
+		if (localHostName == null)
+		    // XXX - not correct for IPv6
+		    localHostName = "[" + localHost.getHostAddress() + "]";
+	    }
+	} catch (UnknownHostException uhex) {
+	}
+
+	// last chance, try to get our address from our socket
+	if (localHostName == null || localHostName.length() <= 0) {
+	    if (serverSocket != null && serverSocket.isBound()) {
+		InetAddress localHost = serverSocket.getLocalAddress();
+		localHostName = localHost.getCanonicalHostName();
+		// if we can't get our name, use local address literal
+		if (localHostName == null)
+		    // XXX - not correct for IPv6
+		    localHostName = "[" + localHost.getHostAddress() + "]";
+	    }
+	}
+	return localHostName;
+    }
+
+    /**
+     * Set the name of the local host, for use in the EHLO and HELO commands.
+     *
+     * @param	localhost	the local host name
+     * @since JavaMail 1.3.1
+     */
+    public synchronized void setLocalHost(String localhost) {
+	localHostName = localhost;
+    }
+
+    /**
+     * Start the SMTP protocol on the given socket, which was already
+     * connected by the caller.  Useful for implementing the SMTP ATRN
+     * command (RFC 2645) where an existing connection is used when
+     * the server reverses roles and becomes the client.
+     *
+     * @param	socket	the already connected socket
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.3.3
+     */
+    public synchronized void connect(Socket socket) throws MessagingException {
+	serverSocket = socket;
+	super.connect();
+    }
+
+    /**
+     * Gets the authorization ID to be used for authentication.
+     *
+     * @return	the authorization ID to use for authentication.
+     *
+     * @since JavaMail 1.4.4
+     */
+    public synchronized String getAuthorizationId() {
+	if (authorizationID == UNKNOWN) {
+	    authorizationID =
+		session.getProperty("mail." + name + ".sasl.authorizationid");
+	}
+	return authorizationID;
+    }
+
+    /**
+     * Sets the authorization ID to be used for authentication.
+     *
+     * @param	authzid		the authorization ID to use for
+     *				authentication.
+     *
+     * @since JavaMail 1.4.4
+     */
+    public synchronized void setAuthorizationID(String authzid) {
+	this.authorizationID = authzid;
+    }
+
+    /**
+     * Is SASL authentication enabled?
+     *
+     * @return	true if SASL authentication is enabled
+     *
+     * @since JavaMail 1.4.4
+     */
+    public synchronized boolean getSASLEnabled() {
+	return enableSASL;
+    }
+
+    /**
+     * Set whether SASL authentication is enabled.
+     *
+     * @param	enableSASL	should we enable SASL authentication?
+     *
+     * @since JavaMail 1.4.4
+     */
+    public synchronized void setSASLEnabled(boolean enableSASL) {
+	this.enableSASL = enableSASL;
+    }
+
+    /**
+     * Gets the SASL realm to be used for DIGEST-MD5 authentication.
+     *
+     * @return	the name of the realm to use for SASL authentication.
+     *
+     * @since JavaMail 1.3.1
+     */
+    public synchronized String getSASLRealm() {
+	if (saslRealm == UNKNOWN) {
+	    saslRealm = session.getProperty("mail." + name + ".sasl.realm");
+	    if (saslRealm == null)	// try old name
+		saslRealm = session.getProperty("mail." + name + ".saslrealm");
+	}
+	return saslRealm;
+    }
+
+    /**
+     * Sets the SASL realm to be used for DIGEST-MD5 authentication.
+     *
+     * @param	saslRealm	the name of the realm to use for
+     *				SASL authentication.
+     *
+     * @since JavaMail 1.3.1
+     */
+    public synchronized void setSASLRealm(String saslRealm) {
+	this.saslRealm = saslRealm;
+    }
+
+    /**
+     * Should SASL use the canonical host name?
+     *
+     * @return	true if SASL should use the canonical host name
+     *
+     * @since JavaMail 1.5.2
+     */
+    public synchronized boolean getUseCanonicalHostName() {
+	return useCanonicalHostName;
+    }
+
+    /**
+     * Set whether SASL should use the canonical host name.
+     *
+     * @param	useCanonicalHostName	should SASL use the canonical host name?
+     *
+     * @since JavaMail 1.5.2
+     */
+    public synchronized void setUseCanonicalHostName(
+						boolean useCanonicalHostName) {
+	this.useCanonicalHostName = useCanonicalHostName;
+    }
+
+    /**
+     * Get the list of SASL mechanisms to consider if SASL authentication
+     * is enabled.  If the list is empty or null, all available SASL mechanisms
+     * are considered.
+     *
+     * @return	the array of SASL mechanisms to consider
+     *
+     * @since JavaMail 1.4.4
+     */
+    public synchronized String[] getSASLMechanisms() {
+	if (saslMechanisms == UNKNOWN_SA) {
+	    List<String> v = new ArrayList<>(5);
+	    String s = session.getProperty("mail." + name + ".sasl.mechanisms");
+	    if (s != null && s.length() > 0) {
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("SASL mechanisms allowed: " + s);
+		StringTokenizer st = new StringTokenizer(s, " ,");
+		while (st.hasMoreTokens()) {
+		    String m = st.nextToken();
+		    if (m.length() > 0)
+			v.add(m);
+		}
+	    }
+	    saslMechanisms = new String[v.size()];
+	    v.toArray(saslMechanisms);
+	}
+	if (saslMechanisms == null)
+	    return null;
+	return saslMechanisms.clone();
+    }
+
+    /**
+     * Set the list of SASL mechanisms to consider if SASL authentication
+     * is enabled.  If the list is empty or null, all available SASL mechanisms
+     * are considered.
+     *
+     * @param	mechanisms	the array of SASL mechanisms to consider
+     *
+     * @since JavaMail 1.4.4
+     */
+    public synchronized void setSASLMechanisms(String[] mechanisms) {
+	if (mechanisms != null)
+	    mechanisms = mechanisms.clone();
+	this.saslMechanisms = mechanisms;
+    }
+
+    /**
+     * Gets the NTLM domain to be used for NTLM authentication.
+     *
+     * @return	the name of the domain to use for NTLM authentication.
+     *
+     * @since JavaMail 1.4.3
+     */
+    public synchronized String getNTLMDomain() {
+	if (ntlmDomain == UNKNOWN) {
+	    ntlmDomain =
+		session.getProperty("mail." + name + ".auth.ntlm.domain");
+	}
+	return ntlmDomain;
+    }
+
+    /**
+     * Sets the NTLM domain to be used for NTLM authentication.
+     *
+     * @param	ntlmDomain	the name of the domain to use for
+     *				NTLM authentication.
+     *
+     * @since JavaMail 1.4.3
+     */
+    public synchronized void setNTLMDomain(String ntlmDomain) {
+	this.ntlmDomain = ntlmDomain;
+    }
+
+    /**
+     * Should we report even successful sends by throwing an exception?
+     * If so, a <code>SendFailedException</code> will always be thrown and
+     * an {@link com.sun.mail.smtp.SMTPAddressSucceededException
+     * SMTPAddressSucceededException} will be included in the exception
+     * chain for each successful address, along with the usual
+     * {@link com.sun.mail.smtp.SMTPAddressFailedException
+     * SMTPAddressFailedException} for each unsuccessful address.
+     *
+     * @return	true if an exception will be thrown on successful sends.
+     *
+     * @since JavaMail 1.3.2
+     */
+    public synchronized boolean getReportSuccess() {
+	return reportSuccess;
+    }
+
+    /**
+     * Set whether successful sends should be reported by throwing
+     * an exception.
+     *
+     * @param	reportSuccess	should we throw an exception on success?
+     *
+     * @since JavaMail 1.3.2
+     */
+    public synchronized void setReportSuccess(boolean reportSuccess) {
+	this.reportSuccess = reportSuccess;
+    }
+
+    /**
+     * Should we use the STARTTLS command to secure the connection
+     * if the server supports it?
+     *
+     * @return	true if the STARTTLS command will be used
+     *
+     * @since JavaMail 1.3.2
+     */
+    public synchronized boolean getStartTLS() {
+	return useStartTLS;
+    }
+
+    /**
+     * Set whether the STARTTLS command should be used.
+     *
+     * @param	useStartTLS	should we use the STARTTLS command?
+     *
+     * @since JavaMail 1.3.2
+     */
+    public synchronized void setStartTLS(boolean useStartTLS) {
+	this.useStartTLS = useStartTLS;
+    }
+
+    /**
+     * Should we require the STARTTLS command to secure the connection?
+     *
+     * @return	true if the STARTTLS command will be required
+     *
+     * @since JavaMail 1.4.2
+     */
+    public synchronized boolean getRequireStartTLS() {
+	return requireStartTLS;
+    }
+
+    /**
+     * Set whether the STARTTLS command should be required.
+     *
+     * @param	requireStartTLS	should we require the STARTTLS command?
+     *
+     * @since JavaMail 1.4.2
+     */
+    public synchronized void setRequireStartTLS(boolean requireStartTLS) {
+	this.requireStartTLS = requireStartTLS;
+    }
+
+    /**
+     * Is this Transport using SSL to connect to the server?
+     *
+     * @return	true if using SSL
+     * @since	JavaMail 1.4.6
+     */
+    public synchronized boolean isSSL() {
+	return serverSocket instanceof SSLSocket;
+    }
+
+    /**
+     * Should we use the RSET command instead of the NOOP command
+     * in the @{link #isConnected isConnected} method?
+     *
+     * @return	true if RSET will be used
+     *
+     * @since JavaMail 1.4
+     */
+    public synchronized boolean getUseRset() {
+	return useRset;
+    }
+
+    /**
+     * Set whether the RSET command should be used instead of the
+     * NOOP command in the @{link #isConnected isConnected} method.
+     *
+     * @param	useRset	should we use the RSET command?
+     *
+     * @since JavaMail 1.4
+     */
+    public synchronized void setUseRset(boolean useRset) {
+	this.useRset = useRset;
+    }
+
+    /**
+     * Is the NOOP command required to return a response code
+     * of 250 to indicate success?
+     *
+     * @return	true if NOOP must return 250
+     *
+     * @since JavaMail 1.4.3
+     */
+    public synchronized boolean getNoopStrict() {
+	return noopStrict;
+    }
+
+    /**
+     * Set whether the NOOP command is required to return a response code
+     * of 250 to indicate success.
+     *
+     * @param	noopStrict is NOOP required to return 250?
+     *
+     * @since JavaMail 1.4.3
+     */
+    public synchronized void setNoopStrict(boolean noopStrict) {
+	this.noopStrict = noopStrict;
+    }
+
+    /**
+     * Return the last response we got from the server.
+     * A failed send is often followed by an RSET command,
+     * but the response from the RSET command is not saved.
+     * Instead, this returns the response from the command
+     * before the RSET command.
+     *
+     * @return	last response from server
+     *
+     * @since JavaMail 1.3.2
+     */
+    public synchronized String getLastServerResponse() {
+	return lastServerResponse;
+    }
+
+    /**
+     * Return the return code from the last response we got from the server.
+     *
+     * @return	return code from last response from server
+     *
+     * @since JavaMail 1.4.1
+     */
+    public synchronized int getLastReturnCode() {
+	return lastReturnCode;
+    }
+
+    /**
+     * Performs the actual protocol-specific connection attempt.
+     * Will attempt to connect to "localhost" if the host was null. <p>
+     *
+     * Unless mail.smtp.ehlo is set to false, we'll try to identify
+     * ourselves using the ESMTP command EHLO.
+     *
+     * If mail.smtp.auth is set to true, we insist on having a username
+     * and password, and will try to authenticate ourselves if the server
+     * supports the AUTH extension (RFC 2554).
+     *
+     * @param	host		  the name of the host to connect to
+     * @param	port		  the port to use (-1 means use default port)
+     * @param	user		  the name of the user to login as
+     * @param	password	  the user's password
+     * @return	true if connection successful, false if authentication failed
+     * @exception MessagingException	for non-authentication failures
+     */
+    @Override
+    protected synchronized boolean protocolConnect(String host, int port,
+				String user, String password)
+				throws MessagingException {
+	Properties props = session.getProperties();
+
+	// setting mail.smtp.auth to true enables attempts to use AUTH
+	boolean useAuth = PropUtil.getBooleanProperty(props,
+					"mail." + name + ".auth", false);
+
+	/*
+	 * If mail.smtp.auth is set, make sure we have a valid username
+	 * and password, even if we might not end up using it (e.g.,
+	 * because the server doesn't support ESMTP or doesn't support
+	 * the AUTH extension).
+	 */
+	if (useAuth && (user == null || password == null)) {
+	    if (logger.isLoggable(Level.FINE)) {
+		logger.fine("need username and password for authentication");
+		logger.fine("protocolConnect returning false" +
+				", host=" + host +
+				", user=" + traceUser(user) +
+				", password=" + tracePassword(password));
+	    }
+	    return false;
+	}
+
+	// setting mail.smtp.ehlo to false disables attempts to use EHLO
+	boolean useEhlo =  PropUtil.getBooleanProperty(props,
+					"mail." + name + ".ehlo", true);
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("useEhlo " + useEhlo + ", useAuth " + useAuth);
+
+	/*
+	 * If port is not specified, set it to value of mail.smtp.port
+         * property if it exists, otherwise default to 25.
+	 */
+        if (port == -1)
+	    port = PropUtil.getIntProperty(props,
+					"mail." + name + ".port", -1);
+        if (port == -1)
+	    port = defaultPort;
+
+	if (host == null || host.length() == 0)
+	    host = "localhost";
+
+	/*
+	 * If anything goes wrong, we need to be sure
+	 * to close the connection.
+	 */
+	boolean connected = false;
+	try {
+
+	    if (serverSocket != null)
+		openServer();	// only happens from connect(socket)
+	    else
+		openServer(host, port);
+
+	    boolean succeed = false;
+	    if (useEhlo)
+		succeed = ehlo(getLocalHost());
+	    if (!succeed)
+		helo(getLocalHost());
+
+	    if (useStartTLS || requireStartTLS) {
+		if (serverSocket instanceof SSLSocket) {
+		    logger.fine("STARTTLS requested but already using SSL");
+		} else if (supportsExtension("STARTTLS")) {
+		    startTLS();
+		    /*
+		     * Have to issue another EHLO to update list of extensions
+		     * supported, especially authentication mechanisms.
+		     * Don't know if this could ever fail, but we ignore
+		     * failure.
+		     */
+		    ehlo(getLocalHost());
+		} else if (requireStartTLS) {
+		    logger.fine("STARTTLS required but not supported");
+		    throw new MessagingException(
+			"STARTTLS is required but " +
+			"host does not support STARTTLS");
+		}
+	    }
+
+	    if (allowutf8 && !supportsExtension("SMTPUTF8"))
+		logger.log(Level.INFO, "mail.mime.allowutf8 set " +
+			    "but server doesn't advertise SMTPUTF8 support");
+
+	    if ((useAuth || (user != null && password != null)) &&
+		  (supportsExtension("AUTH") ||
+		   supportsExtension("AUTH=LOGIN"))) {
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("protocolConnect login" +
+				", host=" + host +
+				", user=" + traceUser(user) +
+				", password=" + tracePassword(password));
+		connected = authenticate(user, password);
+		return connected;
+	    }
+
+	    // we connected correctly
+	    connected = true;
+	    return true;
+
+	} finally {
+	    // if we didn't connect successfully,
+	    // make sure the connection is closed
+	    if (!connected) {
+		try {
+		    closeConnection();
+		} catch (MessagingException mex) {
+		    // ignore it
+		}
+	    }
+	}
+    }
+
+    /**
+     * Authenticate to the server.
+     */
+    private boolean authenticate(String user, String passwd)
+				throws MessagingException {
+	// setting mail.smtp.auth.mechanisms controls which mechanisms will
+	// be used, and in what order they'll be considered.  only the first
+	// match is used.
+	String mechs = session.getProperty("mail." + name + ".auth.mechanisms");
+	if (mechs == null)
+	    mechs = defaultAuthenticationMechanisms;
+
+	String authzid = getAuthorizationId();
+	if (authzid == null)
+	    authzid = user;
+	if (enableSASL) {
+	    logger.fine("Authenticate with SASL");
+	    try {
+		if (sasllogin(getSASLMechanisms(), getSASLRealm(), authzid,
+				user, passwd)) {
+		    return true;	// success
+		} else {
+		    logger.fine("SASL authentication failed");
+		    return false;
+		}
+	    } catch (UnsupportedOperationException ex) {
+		logger.log(Level.FINE, "SASL support failed", ex);
+		// if the SASL support fails, fall back to non-SASL
+	    }
+	}
+
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("Attempt to authenticate using mechanisms: " + mechs);
+
+	/*
+	 * Loop through the list of mechanisms supplied by the user
+	 * (or defaulted) and try each in turn.  If the server supports
+	 * the mechanism and we have an authenticator for the mechanism,
+	 * and it hasn't been disabled, use it.
+	 */
+	StringTokenizer st = new StringTokenizer(mechs);
+	while (st.hasMoreTokens()) {
+	    String m = st.nextToken();
+	    m = m.toUpperCase(Locale.ENGLISH);
+	    Authenticator a = authenticators.get(m);
+	    if (a == null) {
+		logger.log(Level.FINE, "no authenticator for mechanism {0}", m);
+		continue;
+	    }
+
+	    if (!supportsAuthentication(m)) {
+		logger.log(Level.FINE, "mechanism {0} not supported by server",
+					m);
+		continue;
+	    }
+
+	    /*
+	     * If using the default mechanisms, check if this one is disabled.
+	     */
+	    if (mechs == defaultAuthenticationMechanisms) {
+		String dprop = "mail." + name + ".auth." +
+				    m.toLowerCase(Locale.ENGLISH) + ".disable";
+		boolean disabled = PropUtil.getBooleanProperty(
+						session.getProperties(),
+						dprop, !a.enabled());
+		if (disabled) {
+		    if (logger.isLoggable(Level.FINE))
+			logger.fine("mechanism " + m +
+					" disabled by property: " + dprop);
+		    continue;
+		}
+	    }
+
+	    // only the first supported and enabled mechanism is used
+	    logger.log(Level.FINE, "Using mechanism {0}", m);
+	    return a.authenticate(host, authzid, user, passwd);
+	}
+
+	// if no authentication mechanism found, fail
+	throw new AuthenticationFailedException(
+	    "No authentication mechanisms supported by both server and client");
+    }
+
+    /**
+     * Abstract base class for SMTP authentication mechanism implementations.
+     */
+    private abstract class Authenticator {
+	protected int resp;	// the response code, used by subclasses
+	private final String mech; // the mechanism name, set in the constructor
+	private final boolean enabled; // is this mechanism enabled by default?
+
+	Authenticator(String mech) {
+	    this(mech, true);
+	}
+
+	Authenticator(String mech, boolean enabled) {
+	    this.mech = mech.toUpperCase(Locale.ENGLISH);
+	    this.enabled = enabled;
+	}
+
+	String getMechanism() {
+	    return mech;
+	}
+
+	boolean enabled() {
+	    return enabled;
+	}
+
+	/**
+	 * Start the authentication handshake by issuing the AUTH command.
+	 * Delegate to the doAuth method to do the mechanism-specific
+	 * part of the handshake.
+	 */
+	boolean authenticate(String host, String authzid,
+			String user, String passwd) throws MessagingException {
+	    Throwable thrown = null;
+	    try {
+		// use "initial response" capability, if supported
+		String ir = getInitialResponse(host, authzid, user, passwd);
+		if (noauthdebug && isTracing()) {
+		    logger.fine("AUTH " + mech + " command trace suppressed");
+		    suspendTracing();
+		}
+		if (ir != null)
+		    resp = simpleCommand("AUTH " + mech + " " +
+					    (ir.length() == 0 ? "=" : ir));
+		else
+		    resp = simpleCommand("AUTH " + mech);
+
+		/*
+		 * A 530 response indicates that the server wants us to
+		 * issue a STARTTLS command first.  Do that and try again.
+		 */
+		if (resp == 530) {
+		    startTLS();
+		    if (ir != null)
+			resp = simpleCommand("AUTH " + mech + " " + ir);
+		    else
+			resp = simpleCommand("AUTH " + mech);
+		}
+		if (resp == 334)
+		    doAuth(host, authzid, user, passwd);
+	    } catch (IOException ex) {	// should never happen, ignore
+		logger.log(Level.FINE, "AUTH " + mech + " failed", ex);
+	    } catch (Throwable t) {	// crypto can't be initialized?
+		logger.log(Level.FINE, "AUTH " + mech + " failed", t);
+		thrown = t;
+	    } finally {
+		if (noauthdebug && isTracing())
+		    logger.fine("AUTH " + mech + " " +
+				    (resp == 235 ? "succeeded" : "failed"));
+		resumeTracing();
+		if (resp != 235) {
+		    closeConnection();
+		    if (thrown != null) {
+			if (thrown instanceof Error)
+			    throw (Error)thrown;
+			if (thrown instanceof Exception)
+			    throw new AuthenticationFailedException(
+					    getLastServerResponse(),
+					    (Exception)thrown);
+			assert false : "unknown Throwable";	// can't happen
+		    }
+		    throw new AuthenticationFailedException(
+					    getLastServerResponse());
+		}
+	    }
+	    return true;
+	}
+
+	/**
+	 * Provide the initial response to use in the AUTH command,
+	 * or null if not supported.  Subclasses that support the
+	 * initial response capability will override this method.
+	 */
+	String getInitialResponse(String host, String authzid, String user,
+		    String passwd) throws MessagingException, IOException {
+	    return null;
+	}
+
+	abstract void doAuth(String host, String authzid, String user,
+		    String passwd) throws MessagingException, IOException;
+    }
+
+    /**
+     * Perform the authentication handshake for LOGIN authentication.
+     */
+    private class LoginAuthenticator extends Authenticator {
+	LoginAuthenticator() {
+	    super("LOGIN");
+	}
+
+	@Override
+	void doAuth(String host, String authzid, String user, String passwd)
+				    throws MessagingException, IOException {
+	    // send username
+	    resp = simpleCommand(BASE64EncoderStream.encode(
+				user.getBytes(StandardCharsets.UTF_8)));
+	    if (resp == 334) {
+		// send passwd
+		resp = simpleCommand(BASE64EncoderStream.encode(
+				passwd.getBytes(StandardCharsets.UTF_8)));
+	    }
+	}
+    }
+
+    /**
+     * Perform the authentication handshake for PLAIN authentication.
+     */
+    private class PlainAuthenticator extends Authenticator {
+	PlainAuthenticator() {
+	    super("PLAIN");
+	}
+
+	@Override
+	String getInitialResponse(String host, String authzid, String user,
+			String passwd) throws MessagingException, IOException {
+	    // return "authzid<NUL>user<NUL>passwd"
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	    OutputStream b64os =
+			new BASE64EncoderStream(bos, Integer.MAX_VALUE);
+	    if (authzid != null)
+		b64os.write(authzid.getBytes(StandardCharsets.UTF_8));
+	    b64os.write(0);
+	    b64os.write(user.getBytes(StandardCharsets.UTF_8));
+	    b64os.write(0);
+	    b64os.write(passwd.getBytes(StandardCharsets.UTF_8));
+	    b64os.flush(); 	// complete the encoding
+
+	    return ASCIIUtility.toString(bos.toByteArray());
+	}
+
+	@Override
+	void doAuth(String host, String authzid, String user, String passwd)
+				    throws MessagingException, IOException {
+	    // should never get here
+	    throw new AuthenticationFailedException("PLAIN asked for more");
+	}
+    }
+
+    /**
+     * Perform the authentication handshake for DIGEST-MD5 authentication.
+     */
+    private class DigestMD5Authenticator extends Authenticator {
+	private DigestMD5 md5support;	// only create if needed
+
+	DigestMD5Authenticator() {
+	    super("DIGEST-MD5");
+	}
+
+	private synchronized DigestMD5 getMD5() {
+	    if (md5support == null)
+		md5support = new DigestMD5(logger);
+	    return md5support;
+	}
+
+	@Override
+	void doAuth(String host, String authzid, String user, String passwd)
+				    throws MessagingException, IOException {
+	    DigestMD5 md5 = getMD5();
+	    assert md5 != null;
+
+	    byte[] b = md5.authClient(host, user, passwd, getSASLRealm(),
+					getLastServerResponse());
+	    resp = simpleCommand(b);
+	    if (resp == 334) { // client authenticated by server
+		if (!md5.authServer(getLastServerResponse())) {
+		    // server NOT authenticated by client !!!
+		    resp = -1;
+		} else {
+		    // send null response
+		    resp = simpleCommand(new byte[0]);
+		}
+	    }
+	}
+    }
+
+    /**
+     * Perform the authentication handshake for NTLM authentication.
+     */
+    private class NtlmAuthenticator extends Authenticator {
+	private Ntlm ntlm;
+	private int flags;
+
+	NtlmAuthenticator() {
+	    super("NTLM");
+	}
+
+	@Override
+	String getInitialResponse(String host, String authzid, String user,
+		String passwd) throws MessagingException, IOException {
+	    ntlm = new Ntlm(getNTLMDomain(), getLocalHost(),
+				user, passwd, logger);
+
+	    flags = PropUtil.getIntProperty(
+		    session.getProperties(),
+		    "mail." + name + ".auth.ntlm.flags", 0);
+
+	    String type1 = ntlm.generateType1Msg(flags);
+	    return type1;
+	}
+
+	@Override
+	void doAuth(String host, String authzid, String user, String passwd)
+		throws MessagingException, IOException {
+	    assert ntlm != null;
+	    String type3 = ntlm.generateType3Msg(
+		    getLastServerResponse().substring(4).trim());
+
+	    resp = simpleCommand(type3);
+	}
+    }
+
+    /**
+     * Perform the authentication handshake for XOAUTH2 authentication.
+     */
+    private class OAuth2Authenticator extends Authenticator {
+
+	OAuth2Authenticator() {
+	    super("XOAUTH2", false);	// disabled by default
+	}
+
+	@Override
+	String getInitialResponse(String host, String authzid, String user,
+		String passwd) throws MessagingException, IOException {
+	    String resp = "user=" + user + "\001auth=Bearer " +
+			    passwd + "\001\001";
+	    byte[] b = BASE64EncoderStream.encode(
+					resp.getBytes(StandardCharsets.UTF_8));
+	    return ASCIIUtility.toString(b);
+	}
+
+	@Override
+	void doAuth(String host, String authzid, String user, String passwd)
+		throws MessagingException, IOException {
+	    // should never get here
+	    throw new AuthenticationFailedException("OAUTH2 asked for more");
+	}
+    }
+
+    /**
+     * SASL-based login.
+     *
+     * @param	allowed	the allowed SASL mechanisms
+     * @param	realm	the SASL realm
+     * @param	authzid	the authorization ID
+     * @param	u	the user name for authentication
+     * @param	p	the password for authentication
+     * @return		true for success
+     * @exception	MessagingException for failures
+     */
+    private boolean sasllogin(String[] allowed, String realm, String authzid,
+				String u, String p) throws MessagingException {
+	String serviceHost;
+	if (useCanonicalHostName)
+	    serviceHost = serverSocket.getInetAddress().getCanonicalHostName();
+	else
+	    serviceHost = host;
+	if (saslAuthenticator == null) {
+	    try {
+		Class<?> sac = Class.forName(
+		    "com.sun.mail.smtp.SMTPSaslAuthenticator");
+		Constructor<?> c = sac.getConstructor(new Class<?>[] {
+					SMTPTransport.class,
+					String.class,
+					Properties.class,
+					MailLogger.class,
+					String.class
+					});
+		saslAuthenticator = (SaslAuthenticator)c.newInstance(
+					new Object[] {
+					this,
+					name,
+					session.getProperties(),
+					logger,
+					serviceHost
+					});
+	    } catch (Exception ex) {
+		logger.log(Level.FINE, "Can't load SASL authenticator", ex);
+		// probably because we're running on a system without SASL
+		return false;	// not authenticated, try without SASL
+	    }
+	}
+
+	// were any allowed mechanisms specified?
+	List<String> v;
+	if (allowed != null && allowed.length > 0) {
+	    // remove anything not supported by the server
+	    v = new ArrayList<>(allowed.length);
+	    for (int i = 0; i < allowed.length; i++)
+		if (supportsAuthentication(allowed[i]))	// XXX - case must match
+		    v.add(allowed[i]);
+	} else {
+	    // everything is allowed
+	    v = new ArrayList<>();
+	    if (extMap != null) {
+		String a = extMap.get("AUTH");
+		if (a != null) {
+		    StringTokenizer st = new StringTokenizer(a);
+		    while (st.hasMoreTokens())
+			v.add(st.nextToken());
+		}
+	    }
+	}
+	String[] mechs = v.toArray(new String[v.size()]);
+	try {
+	    if (noauthdebug && isTracing()) {
+		logger.fine("SASL AUTH command trace suppressed");
+		suspendTracing();
+	    }
+	    return saslAuthenticator.authenticate(mechs, realm, authzid, u, p);
+	} finally {
+	    resumeTracing();
+	}
+    }
+
+    /**
+     * Send the Message to the specified list of addresses.<p>
+     *
+     * If all the <code>addresses</code> succeed the SMTP check
+     * using the <code>RCPT TO:</code> command, we attempt to send the message.
+     * A TransportEvent of type MESSAGE_DELIVERED is fired indicating the
+     * successful submission of a message to the SMTP host.<p>
+     *
+     * If some of the <code>addresses</code> fail the SMTP check,
+     * and the <code>mail.smtp.sendpartial</code> property is not set,
+     * sending is aborted. The TransportEvent of type MESSAGE_NOT_DELIVERED
+     * is fired containing the valid and invalid addresses. The
+     * SendFailedException is also thrown. <p>
+     *
+     * If some of the <code>addresses</code> fail the SMTP check,
+     * and the <code>mail.smtp.sendpartial</code> property is set to true,
+     * the message is sent. The TransportEvent of type
+     * MESSAGE_PARTIALLY_DELIVERED
+     * is fired containing the valid and invalid addresses. The
+     * SMTPSendFailedException is also thrown. <p>
+     *
+     * MessagingException is thrown if the message can't write out
+     * an RFC822-compliant stream using its <code>writeTo</code> method. <p>
+     *
+     * @param message	The MimeMessage to be sent
+     * @param addresses	List of addresses to send this message to
+     * @see 		javax.mail.event.TransportEvent
+     * @exception       SMTPSendFailedException if the send failed because of
+     *			an SMTP command error
+     * @exception       SendFailedException if the send failed because of
+     *			invalid addresses.
+     * @exception       MessagingException if the connection is dead
+     *                  or not in the connected state or if the message is
+     *                  not a MimeMessage.
+     */
+    @Override
+    public synchronized void sendMessage(Message message, Address[] addresses)
+		    throws MessagingException, SendFailedException {
+
+	sendMessageStart(message != null ? message.getSubject() : "");
+	checkConnected();
+
+	// check if the message is a valid MIME/RFC822 message and that
+	// it has all valid InternetAddresses; fail if not
+        if (!(message instanceof MimeMessage)) {
+	    logger.fine("Can only send RFC822 msgs");
+	    throw new MessagingException("SMTP can only send RFC822 messages");
+	}
+	for (int i = 0; i < addresses.length; i++) {
+	    if (!(addresses[i] instanceof InternetAddress)) {
+		throw new MessagingException(addresses[i] +
+					     " is not an InternetAddress");
+	    }
+	}
+	if (addresses.length == 0)
+	    throw new SendFailedException("No recipient addresses");
+
+	this.message = (MimeMessage)message;
+	this.addresses = addresses;
+	validUnsentAddr = addresses;	// until we know better
+	expandGroups();
+
+	boolean use8bit = false;
+	if (message instanceof SMTPMessage)
+	    use8bit = ((SMTPMessage)message).getAllow8bitMIME();
+	if (!use8bit)
+	    use8bit = PropUtil.getBooleanProperty(session.getProperties(),
+				"mail." + name + ".allow8bitmime", false);
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("use8bit " + use8bit);
+	if (use8bit && supportsExtension("8BITMIME")) {
+	    if (convertTo8Bit(this.message)) {
+		// in case we made any changes, save those changes
+		// XXX - this will change the Message-ID
+		try {
+		    this.message.saveChanges();
+		} catch (MessagingException mex) {
+		    // ignore it
+		}
+	    }
+	}
+
+	try {
+	    mailFrom();
+	    rcptTo();
+	    if (chunkSize > 0 && supportsExtension("CHUNKING")) {
+		/*
+		 * Use BDAT to send the data in chunks.
+		 * Note that even though the BDAT command is able to send
+		 * messages that contain binary data, we can't use it to
+		 * do that because a) we still need to canonicalize the
+		 * line terminators for text data, which we can't tell apart
+		 * from the message content, and b) the message content is
+		 * encoded before we even know that we can use BDAT.
+		 */
+		this.message.writeTo(bdat(), ignoreList);
+		finishBdat();
+	    } else {
+		this.message.writeTo(data(), ignoreList);
+		finishData();
+	    }
+	    if (sendPartiallyFailed) {
+		// throw the exception,
+		// fire TransportEvent.MESSAGE_PARTIALLY_DELIVERED event
+		logger.fine("Sending partially failed " +
+			"because of invalid destination addresses");
+		notifyTransportListeners(
+			TransportEvent.MESSAGE_PARTIALLY_DELIVERED,
+			validSentAddr, validUnsentAddr, invalidAddr,
+			this.message);
+
+		throw new SMTPSendFailedException(".", lastReturnCode,
+				lastServerResponse, exception,
+				validSentAddr, validUnsentAddr, invalidAddr);
+	    }
+	    logger.fine("message successfully delivered to mail server");
+	    notifyTransportListeners(TransportEvent.MESSAGE_DELIVERED,
+				     validSentAddr, validUnsentAddr,
+				     invalidAddr, this.message);
+	} catch (MessagingException mex) {
+	    logger.log(Level.FINE, "MessagingException while sending", mex);
+	    // the MessagingException might be wrapping an IOException
+	    if (mex.getNextException() instanceof IOException) {
+		// if we catch an IOException, it means that we want
+		// to drop the connection so that the message isn't sent
+		logger.fine("nested IOException, closing");
+		try {
+		    closeConnection();
+		} catch (MessagingException cex) { /* ignore it */ }
+	    }
+	    addressesFailed();
+	    notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
+				     validSentAddr, validUnsentAddr,
+				     invalidAddr, this.message);
+
+	    throw mex;
+	} catch (IOException ex) {
+	    logger.log(Level.FINE, "IOException while sending, closing", ex);
+	    // if we catch an IOException, it means that we want
+	    // to drop the connection so that the message isn't sent
+	    try {
+		closeConnection();
+	    } catch (MessagingException mex) { /* ignore it */ }
+	    addressesFailed();
+	    notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
+				     validSentAddr, validUnsentAddr,
+				     invalidAddr, this.message);
+
+	    throw new MessagingException("IOException while sending message",
+					 ex);
+	} finally {
+	    // no reason to keep this data around
+	    validSentAddr = validUnsentAddr = invalidAddr = null;
+	    this.addresses = null;
+	    this.message = null;
+	    this.exception = null;
+	    sendPartiallyFailed = false;
+	    notificationDone = false;	// reset for next send
+	}
+	sendMessageEnd();
+    }
+
+    /**
+     * The send failed, fix the address arrays to report the failure correctly.
+     */
+    private void addressesFailed() {
+	if (validSentAddr != null) {
+	    if (validUnsentAddr != null) {
+		Address newa[] =
+		    new Address[validSentAddr.length + validUnsentAddr.length];
+		System.arraycopy(validSentAddr, 0,
+			newa, 0, validSentAddr.length);
+		System.arraycopy(validUnsentAddr, 0,
+			newa, validSentAddr.length, validUnsentAddr.length);
+		validSentAddr = null;
+		validUnsentAddr = newa;
+	    } else {
+		validUnsentAddr = validSentAddr;
+		validSentAddr = null;
+	    }
+	}
+    }
+
+    /**
+     * Close the Transport and terminate the connection to the server.
+     */
+    @Override
+    public synchronized void close() throws MessagingException {
+	if (!super.isConnected()) // Already closed.
+	    return;
+	try {
+	    if (serverSocket != null) {
+		sendCommand("QUIT");
+		if (quitWait) {
+		    int resp = readServerResponse();
+		    if (resp != 221 && resp != -1 &&
+			    logger.isLoggable(Level.FINE))
+			logger.fine("QUIT failed with " + resp);
+		}
+	    }
+	} finally {
+	    closeConnection();
+	}
+    }
+
+    private void closeConnection() throws MessagingException {
+	try {
+	    if (serverSocket != null)
+		serverSocket.close();
+	} catch (IOException ioex) {	    // shouldn't happen
+	    throw new MessagingException("Server Close Failed", ioex);
+	} finally {
+	    serverSocket = null;
+	    serverOutput = null;
+	    serverInput = null;
+	    lineInputStream = null;
+	    if (super.isConnected())	// only notify if already connected
+		super.close();
+	}
+    }
+
+    /**
+     * Check whether the transport is connected. Override superclass
+     * method, to actually ping our server connection.
+     */
+    @Override
+    public synchronized boolean isConnected() {
+	if (!super.isConnected())
+	    // if we haven't been connected at all, don't bother with NOOP
+	    return false;
+
+	try {
+	    // sendmail may respond slowly to NOOP after many requests
+	    // so if mail.smtp.userset is set we use RSET instead of NOOP.
+	    if (useRset)
+		sendCommand("RSET");
+	    else
+		sendCommand("NOOP");
+	    int resp = readServerResponse();
+
+	    /*
+	     * NOOP should return 250 on success, however, SIMS 3.2 returns
+	     * 200, so we work around it.
+	     *
+	     * Hotmail didn't used to implement the NOOP command at all so
+	     * assume any kind of response means we're still connected.
+	     * That is, any response except 421, which means the server
+	     * is shutting down the connection.
+	     *
+	     * Some versions of Exchange return 451 instead of 421 when
+	     * timing out a connection.
+	     *
+	     * Argh!
+	     *
+	     * If mail.smtp.noop.strict is set to false, be tolerant of
+	     * servers that return the wrong response code for success.
+	     */
+	    if (resp >= 0 && (noopStrict ? resp == 250 : resp != 421)) {
+		return true;
+	    } else {
+		try {
+		    closeConnection();
+		} catch (MessagingException mex) {
+		    // ignore it
+		}
+		return false;
+	    }
+	} catch (Exception ex) {
+	    try {
+		closeConnection();
+	    } catch (MessagingException mex) {
+		// ignore it
+	    }
+	    return false;
+	}
+    }
+
+    /**
+     * Notify all TransportListeners.  Keep track of whether notification
+     * has been done so as to only notify once per send.
+     *
+     * @since	JavaMail 1.4.2
+     */
+    @Override
+    protected void notifyTransportListeners(int type, Address[] validSent,
+					    Address[] validUnsent,
+					    Address[] invalid, Message msg) {
+
+	if (!notificationDone) {
+	    super.notifyTransportListeners(type, validSent, validUnsent,
+		invalid, msg);
+	    notificationDone = true;
+	}
+    }
+
+    /**
+     * Expand any group addresses.
+     */
+    private void expandGroups() {
+	List<Address> groups = null;
+	for (int i = 0; i < addresses.length; i++) {
+	    InternetAddress a = (InternetAddress)addresses[i];
+	    if (a.isGroup()) {
+		if (groups == null) {
+		    // first group, catch up with where we are
+		    groups = new ArrayList<>();
+		    for (int k = 0; k < i; k++)
+			groups.add(addresses[k]);
+		}
+		// parse it and add each individual address
+		try {
+		    InternetAddress[] ia = a.getGroup(true);
+		    if (ia != null) {
+			for (int j = 0; j < ia.length; j++)
+			    groups.add(ia[j]);
+		    } else
+			groups.add(a);
+		} catch (ParseException pex) {
+		    // parse failed, add the whole thing
+		    groups.add(a);
+		}
+	    } else {
+		// if we've started accumulating a list, add this to it
+		if (groups != null)
+		    groups.add(a);
+	    }
+	}
+
+	// if we have a new list, convert it back to an array
+	if (groups != null) {
+	    InternetAddress[] newa = new InternetAddress[groups.size()];
+	    groups.toArray(newa);
+	    addresses = newa;
+	}
+    }
+
+    /**
+     * If the Part is a text part and has a Content-Transfer-Encoding
+     * of "quoted-printable" or "base64", and it obeys the rules for
+     * "8bit" encoding, change the encoding to "8bit".  If the part is
+     * a multipart, recursively process all its parts.
+     *
+     * @return	true	if any changes were made
+     *
+     * XXX - This is really quite a hack.
+     */
+    private boolean convertTo8Bit(MimePart part) {
+	boolean changed = false;
+	try {
+	    if (part.isMimeType("text/*")) {
+		String enc = part.getEncoding();
+		if (enc != null && (enc.equalsIgnoreCase("quoted-printable") ||
+		    enc.equalsIgnoreCase("base64"))) {
+		    InputStream is = null;
+		    try {
+			is = part.getInputStream();
+			if (is8Bit(is)) {
+			    /*
+			     * If the message was created using an InputStream
+			     * then we have to extract the content as an object
+			     * and set it back as an object so that the content
+			     * will be re-encoded.
+			     *
+			     * If the message was not created using an
+			     * InputStream, the following should have no effect.
+			     */
+			    part.setContent(part.getContent(),
+					    part.getContentType());
+			    part.setHeader("Content-Transfer-Encoding", "8bit");
+			    changed = true;
+			}
+		    } finally {
+			if (is != null) {
+			    try {
+				is.close();
+			    } catch (IOException ex2) {
+				// ignore it
+			    }
+			}
+		    }
+		}
+	    } else if (part.isMimeType("multipart/*")) {
+		MimeMultipart mp = (MimeMultipart)part.getContent();
+		int count = mp.getCount();
+		for (int i = 0; i < count; i++) {
+		    if (convertTo8Bit((MimePart)mp.getBodyPart(i)))
+			changed = true;
+		}
+	    }
+	} catch (IOException ioex) {
+	    // any exception causes us to give up
+	} catch (MessagingException mex) {
+	    // any exception causes us to give up
+	}
+	return changed;
+    }
+
+    /**
+     * Check whether the data in the given InputStream follows the
+     * rules for 8bit text.  Lines have to be 998 characters or less
+     * and no NULs are allowed.  CR and LF must occur in pairs but we
+     * don't check that because we assume this is text and we convert
+     * all CR/LF combinations into canonical CRLF later.
+     */
+    private boolean is8Bit(InputStream is) {
+	int b;
+	int linelen = 0;
+	boolean need8bit = false;
+	try {
+	    while ((b = is.read()) >= 0) {
+		b &= 0xff;
+		if (b == '\r' || b == '\n')
+		    linelen = 0;
+		else if (b == 0)
+		    return false;
+		else {
+		    linelen++;
+		    if (linelen > 998)	// 1000 - CRLF
+			return false;
+		}
+		if (b > 0x7f)
+		    need8bit = true;
+	    }
+	} catch (IOException ex) {
+	    return false;
+	}
+	if (need8bit)
+	    logger.fine("found an 8bit part");
+	return need8bit;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+	try {
+	    closeConnection();
+	} catch (MessagingException mex) {
+	    // ignore it
+	} finally {
+	    super.finalize();
+	}
+    }
+
+    ///////////////////// smtp stuff ///////////////////////
+    private BufferedInputStream serverInput;
+    private LineInputStream     lineInputStream;
+    private OutputStream        serverOutput;
+    private Socket              serverSocket;
+    private TraceInputStream	traceInput;
+    private TraceOutputStream	traceOutput;
+
+    /////// smtp protocol //////
+
+    /**
+     * Issue the <code>HELO</code> command.
+     *
+     * @param	domain	our domain
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    protected void helo(String domain) throws MessagingException {
+	if (domain != null)
+	    issueCommand("HELO " + domain, 250);
+	else
+	    issueCommand("HELO", 250);
+    }
+
+    /**
+     * Issue the <code>EHLO</code> command.
+     * Collect the returned list of service extensions.
+     *
+     * @param	domain	our domain
+     * @return		true if command succeeds
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    protected boolean ehlo(String domain) throws MessagingException {
+	String cmd;
+	if (domain != null)
+	    cmd = "EHLO " + domain;
+	else
+	    cmd = "EHLO";
+	sendCommand(cmd);
+	int resp = readServerResponse();
+	if (resp == 250) {
+	    // extract the supported service extensions
+	    BufferedReader rd =
+		new BufferedReader(new StringReader(lastServerResponse));
+	    String line;
+	    extMap = new Hashtable<>();
+	    try {
+		boolean first = true;
+		while ((line = rd.readLine()) != null) {
+		    if (first) {	// skip first line which is the greeting
+			first = false;
+			continue;
+		    }
+		    if (line.length() < 5)
+			continue;		// shouldn't happen
+		    line = line.substring(4);	// skip response code
+		    int i = line.indexOf(' ');
+		    String arg = "";
+		    if (i > 0) {
+			arg = line.substring(i + 1);
+			line = line.substring(0, i);
+		    }
+		    if (logger.isLoggable(Level.FINE))
+			logger.fine("Found extension \"" +
+					    line + "\", arg \"" + arg + "\"");
+		    extMap.put(line.toUpperCase(Locale.ENGLISH), arg);
+		}
+	    } catch (IOException ex) { }	// can't happen
+	}
+	return resp == 250;
+    }
+
+    /**
+     * Issue the <code>MAIL FROM:</code> command to start sending a message. <p>
+     *
+     * Gets the sender's address in the following order:
+     * <ol>
+     * <li>SMTPMessage.getEnvelopeFrom()</li>
+     * <li>mail.smtp.from property</li>
+     * <li>From: header in the message</li>
+     * <li>System username using the
+     * InternetAddress.getLocalAddress() method</li>
+     * </ol>
+     *
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    protected void mailFrom() throws MessagingException {
+	String from = null;
+	if (message instanceof SMTPMessage)
+	    from = ((SMTPMessage)message).getEnvelopeFrom();
+	if (from == null || from.length() <= 0)
+	    from = session.getProperty("mail." + name + ".from");
+	if (from == null || from.length() <= 0) {
+	    Address[] fa;
+	    Address me;
+	    if (message != null && (fa = message.getFrom()) != null &&
+		    fa.length > 0)
+		me = fa[0];
+	    else
+		me = InternetAddress.getLocalAddress(session);
+
+	    if (me != null)
+		from = ((InternetAddress)me).getAddress();
+	    else
+		throw new MessagingException(
+					"can't determine local email address");
+	}
+
+	String cmd = "MAIL FROM:" + normalizeAddress(from);
+
+	if (allowutf8 && supportsExtension("SMTPUTF8"))
+	    cmd += " SMTPUTF8";
+
+	// request delivery status notification?
+	if (supportsExtension("DSN")) {
+	    String ret = null;
+	    if (message instanceof SMTPMessage)
+		ret = ((SMTPMessage)message).getDSNRet();
+	    if (ret == null)
+		ret = session.getProperty("mail." + name + ".dsn.ret");
+	    // XXX - check for legal syntax?
+	    if (ret != null)
+		cmd += " RET=" + ret;
+	}
+
+	/*
+	 * If an RFC 2554 submitter has been specified, and the server
+	 * supports the AUTH extension, include the AUTH= element on
+	 * the MAIL FROM command.
+	 */
+	if (supportsExtension("AUTH")) {
+	    String submitter = null;
+	    if (message instanceof SMTPMessage)
+		submitter = ((SMTPMessage)message).getSubmitter();
+	    if (submitter == null)
+		submitter = session.getProperty("mail." + name + ".submitter");
+	    // XXX - check for legal syntax?
+	    if (submitter != null) {
+		try {
+		    String s = xtext(submitter,
+				    allowutf8 && supportsExtension("SMTPUTF8"));
+		    cmd += " AUTH=" + s;
+		} catch (IllegalArgumentException ex) {
+		    if (logger.isLoggable(Level.FINE))
+			logger.log(Level.FINE, "ignoring invalid submitter: " +
+			    submitter, ex);
+		}
+	    }
+	}
+
+	/*
+	 * Have any extensions to the MAIL command been specified?
+	 */
+	String ext = null;
+	if (message instanceof SMTPMessage)
+	    ext = ((SMTPMessage)message).getMailExtension();
+	if (ext == null)
+	    ext = session.getProperty("mail." + name + ".mailextension");
+	if (ext != null && ext.length() > 0)
+	    cmd += " " + ext;
+
+	try {
+	    issueSendCommand(cmd, 250);
+	} catch (SMTPSendFailedException ex) {
+	    int retCode = ex.getReturnCode();
+	    switch (retCode) {
+	    case 550: case 553: case 503: case 551: case 501:
+		// given address is invalid
+		try {
+		    ex.setNextException(new SMTPSenderFailedException(
+			new InternetAddress(from), cmd,
+			retCode, ex.getMessage()));
+		} catch (AddressException aex) {
+		    // oh well...
+		}
+		break;
+	    default:
+		break;
+	    }
+	    throw ex;
+	}
+    }
+
+    /**
+     * Sends each address to the SMTP host using the <code>RCPT TO:</code>
+     * command and copies the address either into
+     * the validSentAddr or invalidAddr arrays.
+     * Sets the <code>sendFailed</code>
+     * flag to true if any addresses failed.
+     *
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    /*
+     * success/failure/error possibilities from the RCPT command
+     * from rfc821, section 4.3
+     * S: 250, 251
+     * F: 550, 551, 552, 553, 450, 451, 452
+     * E: 500, 501, 503, 421
+     *
+     * and how we map the above error/failure conditions to valid/invalid
+     * address lists that are reported in the thrown exception:
+     * invalid addr: 550, 501, 503, 551, 553
+     * valid addr: 552 (quota), 450, 451, 452 (quota), 421 (srvr abort)
+     */
+    protected void rcptTo() throws MessagingException {
+	List<InternetAddress> valid = new ArrayList<>();
+	List<InternetAddress> validUnsent = new ArrayList<>();
+	List<InternetAddress> invalid = new ArrayList<>();
+	int retCode = -1;
+	MessagingException mex = null;
+	boolean sendFailed = false;
+	MessagingException sfex = null;
+	validSentAddr = validUnsentAddr = invalidAddr = null;
+	boolean sendPartial = false;
+	if (message instanceof SMTPMessage)
+	    sendPartial = ((SMTPMessage)message).getSendPartial();
+	if (!sendPartial)
+	    sendPartial = PropUtil.getBooleanProperty(session.getProperties(),
+					"mail." + name + ".sendpartial", false);
+	if (sendPartial)
+	    logger.fine("sendPartial set");
+
+	boolean dsn = false;
+	String notify = null;
+	if (supportsExtension("DSN")) {
+	    if (message instanceof SMTPMessage)
+		notify = ((SMTPMessage)message).getDSNNotify();
+	    if (notify == null)
+		notify = session.getProperty("mail." + name + ".dsn.notify");
+	    // XXX - check for legal syntax?
+	    if (notify != null)
+		dsn = true;
+	}
+
+	// try the addresses one at a time
+	for (int i = 0; i < addresses.length; i++) {
+
+	    sfex = null;
+	    InternetAddress ia = (InternetAddress)addresses[i];
+	    String cmd = "RCPT TO:" + normalizeAddress(ia.getAddress());
+	    if (dsn)
+		cmd += " NOTIFY=" + notify;
+	    // send the addresses to the SMTP server
+	    sendCommand(cmd);
+	    // check the server's response for address validity
+	    retCode = readServerResponse();
+	    switch (retCode) {
+	    case 250: case 251:
+		valid.add(ia);
+		if (!reportSuccess)
+		    break;
+
+		// user wants exception even when successful, including
+		// details of the return code
+
+		// create and chain the exception
+		sfex = new SMTPAddressSucceededException(ia, cmd, retCode,
+							lastServerResponse);
+		if (mex == null)
+		    mex = sfex;
+		else
+		    mex.setNextException(sfex);
+		break;
+
+	    case 550: case 553: case 503: case 551: case 501:
+		// given address is invalid
+		if (!sendPartial)
+		    sendFailed = true;
+		invalid.add(ia);
+		// create and chain the exception
+		sfex = new SMTPAddressFailedException(ia, cmd, retCode,
+							lastServerResponse);
+		if (mex == null)
+		    mex = sfex;
+		else
+		    mex.setNextException(sfex);
+		break;
+
+	    case 552: case 450: case 451: case 452:
+		// given address is valid
+		if (!sendPartial)
+		    sendFailed = true;
+		validUnsent.add(ia);
+		// create and chain the exception
+		sfex = new SMTPAddressFailedException(ia, cmd, retCode,
+							lastServerResponse);
+		if (mex == null)
+		    mex = sfex;
+		else
+		    mex.setNextException(sfex);
+		break;
+
+	    default:
+		// handle remaining 4xy & 5xy codes
+		if (retCode >= 400 && retCode <= 499) {
+		    // assume address is valid, although we don't really know
+		    validUnsent.add(ia);
+		} else if (retCode >= 500 && retCode <= 599) {
+		    // assume address is invalid, although we don't really know
+		    invalid.add(ia);
+		} else {
+		    // completely unexpected response, just give up
+		    if (logger.isLoggable(Level.FINE))
+			logger.fine("got response code " + retCode +
+			    ", with response: " + lastServerResponse);
+		    String _lsr = lastServerResponse; // else rset will nuke it
+		    int _lrc = lastReturnCode;
+		    if (serverSocket != null)	// hasn't already been closed
+			issueCommand("RSET", -1);
+		    lastServerResponse = _lsr;	// restore, for get
+		    lastReturnCode = _lrc;
+		    throw new SMTPAddressFailedException(ia, cmd, retCode,
+								_lsr);
+		}
+		if (!sendPartial)
+		    sendFailed = true;
+		// create and chain the exception
+		sfex = new SMTPAddressFailedException(ia, cmd, retCode,
+							lastServerResponse);
+		if (mex == null)
+		    mex = sfex;
+		else
+		    mex.setNextException(sfex);
+		break;
+	    }
+	}
+
+	// if we're willing to send to a partial list, and we found no
+	// valid addresses, that's complete failure
+	if (sendPartial && valid.size() == 0)
+	    sendFailed = true;
+
+	// copy the lists into appropriate arrays
+	if (sendFailed) {
+	    // copy invalid addrs
+	    invalidAddr = new Address[invalid.size()];
+	    invalid.toArray(invalidAddr);
+
+	    // copy all valid addresses to validUnsent, since something failed
+	    validUnsentAddr = new Address[valid.size() + validUnsent.size()];
+	    int i = 0;
+	    for (int j = 0; j < valid.size(); j++)
+		validUnsentAddr[i++] = (Address)valid.get(j);
+	    for (int j = 0; j < validUnsent.size(); j++)
+		validUnsentAddr[i++] = (Address)validUnsent.get(j);
+	} else if (reportSuccess || (sendPartial &&
+			(invalid.size() > 0 || validUnsent.size() > 0))) {
+	    // we'll go on to send the message, but after sending we'll
+	    // throw an exception with this exception nested
+	    sendPartiallyFailed = true;
+	    exception = mex;
+
+	    // copy invalid addrs
+	    invalidAddr = new Address[invalid.size()];
+	    invalid.toArray(invalidAddr);
+
+	    // copy valid unsent addresses to validUnsent
+	    validUnsentAddr = new Address[validUnsent.size()];
+	    validUnsent.toArray(validUnsentAddr);
+
+	    // copy valid addresses to validSent
+	    validSentAddr = new Address[valid.size()];
+	    valid.toArray(validSentAddr);
+	} else {        // all addresses pass
+	    validSentAddr = addresses;
+	}
+
+
+	// print out the debug info
+	if (logger.isLoggable(Level.FINE)) {
+	    if (validSentAddr != null && validSentAddr.length > 0) {
+		logger.fine("Verified Addresses");
+		for (int l = 0; l < validSentAddr.length; l++) {
+		    logger.fine("  " + validSentAddr[l]);
+		}
+	    }
+	    if (validUnsentAddr != null && validUnsentAddr.length > 0) {
+		logger.fine("Valid Unsent Addresses");
+		for (int j = 0; j < validUnsentAddr.length; j++) {
+		    logger.fine("  " + validUnsentAddr[j]);
+		}
+	    }
+	    if (invalidAddr != null && invalidAddr.length > 0) {
+		logger.fine("Invalid Addresses");
+		for (int k = 0; k < invalidAddr.length; k++) {
+		    logger.fine("  " + invalidAddr[k]);
+		}
+	    }
+	}
+
+	// throw the exception, fire TransportEvent.MESSAGE_NOT_DELIVERED event
+	if (sendFailed) {
+	    logger.fine(
+		"Sending failed because of invalid destination addresses");
+	    notifyTransportListeners(TransportEvent.MESSAGE_NOT_DELIVERED,
+				     validSentAddr, validUnsentAddr,
+				     invalidAddr, this.message);
+
+	    // reset the connection so more sends are allowed
+	    String lsr = lastServerResponse;	// save, for get
+	    int lrc = lastReturnCode;
+	    try {
+		if (serverSocket != null)
+		    issueCommand("RSET", -1);
+	    } catch (MessagingException ex) {
+		// if can't reset, best to close the connection
+		try {
+		    close();
+		} catch (MessagingException ex2) {
+		    // thrown by close()--ignore, will close() later anyway
+		    logger.log(Level.FINE, "close failed", ex2);
+		}
+	    } finally {
+		lastServerResponse = lsr;	// restore
+		lastReturnCode = lrc;
+	    }
+
+	    throw new SendFailedException("Invalid Addresses", mex,
+					  validSentAddr,
+					  validUnsentAddr, invalidAddr);
+	}
+    }
+
+    /**
+     * Send the <code>DATA</code> command to the SMTP host and return
+     * an OutputStream to which the data is to be written.
+     *
+     * @return		the stream to write to
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    protected OutputStream data() throws MessagingException {
+	assert Thread.holdsLock(this);
+	issueSendCommand("DATA", 354);
+	dataStream = new SMTPOutputStream(serverOutput);
+	return dataStream;
+    }
+
+    /**
+     * Terminate the sent data.
+     *
+     * @exception	IOException for I/O errors
+     * @exception	MessagingException for other failures
+     * @since JavaMail 1.4.1
+     */
+    protected void finishData() throws IOException, MessagingException {
+	assert Thread.holdsLock(this);
+	dataStream.ensureAtBOL();
+	issueSendCommand(".", 250);
+    }
+
+    /**
+     * Return a stream that will use the SMTP BDAT command to send data.
+     *
+     * @return		the stream to write to
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.6.0
+     */
+    protected OutputStream bdat() throws MessagingException {
+	assert Thread.holdsLock(this);
+	dataStream = new BDATOutputStream(serverOutput, chunkSize);
+	return dataStream;
+    }
+
+    /**
+     * Terminate the sent data.
+     *
+     * @exception	IOException for I/O errors
+     * @exception	MessagingException for other failures
+     * @since JavaMail 1.6.0
+     */
+    protected void finishBdat() throws IOException, MessagingException {
+	assert Thread.holdsLock(this);
+	dataStream.ensureAtBOL();
+	dataStream.close();	// doesn't close underlying socket
+    }
+
+    /**
+     * Issue the <code>STARTTLS</code> command and switch the socket to
+     * TLS mode if it succeeds.
+     *
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    protected void startTLS() throws MessagingException {
+	issueCommand("STARTTLS", 220);
+	// it worked, now switch the socket into TLS mode
+	try {
+	    serverSocket = SocketFetcher.startTLS(serverSocket, host,
+				session.getProperties(), "mail." + name);
+	    initStreams();
+	} catch (IOException ioex) {
+	    closeConnection();
+	    throw new MessagingException("Could not convert socket to TLS",
+								ioex);
+	}
+    }
+
+    /////// primitives ///////
+
+    /**
+     * Connect to host on port and start the SMTP protocol.
+     */
+    private void openServer(String host, int port)
+				throws MessagingException {
+
+        if (logger.isLoggable(Level.FINE))
+	    logger.fine("trying to connect to host \"" + host +
+				"\", port " + port + ", isSSL " + isSSL);
+
+	try {
+	    Properties props = session.getProperties();
+
+	    serverSocket = SocketFetcher.getSocket(host, port,
+		props, "mail." + name, isSSL);
+
+	    // socket factory may've chosen a different port,
+	    // update it for the debug messages that follow
+	    port = serverSocket.getPort();
+	    // save host name for startTLS
+	    this.host = host;
+
+	    initStreams();
+
+	    int r = -1;
+	    if ((r = readServerResponse()) != 220) {
+		serverSocket.close();
+		serverSocket = null;
+		serverOutput = null;
+		serverInput = null;
+		lineInputStream = null;
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("could not connect to host \"" +
+				    host + "\", port: " + port +
+				    ", response: " + r);
+		throw new MessagingException(
+			"Could not connect to SMTP host: " + host +
+				    ", port: " + port +
+				    ", response: " + r);
+	    } else {
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("connected to host \"" +
+				       host + "\", port: " + port);
+	    }
+	} catch (UnknownHostException uhex) {
+	    throw new MessagingException("Unknown SMTP host: " + host, uhex);
+	} catch (SocketConnectException scex) {
+	    throw new MailConnectException(scex);
+	} catch (IOException ioe) {
+	    throw new MessagingException("Could not connect to SMTP host: " +
+				    host + ", port: " + port, ioe);
+	}
+    }
+
+    /**
+     * Start the protocol to the server on serverSocket,
+     * assumed to be provided and connected by the caller.
+     */
+    private void openServer() throws MessagingException {
+	int port = -1;
+	host = "UNKNOWN";
+	try {
+	    port = serverSocket.getPort();
+	    host = serverSocket.getInetAddress().getHostName();
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("starting protocol to host \"" +
+					host + "\", port " + port);
+
+	    initStreams();
+
+	    int r = -1;
+	    if ((r = readServerResponse()) != 220) {
+		serverSocket.close();
+		serverSocket = null;
+		serverOutput = null;
+		serverInput = null;
+		lineInputStream = null;
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("got bad greeting from host \"" +
+				    host + "\", port: " + port +
+				    ", response: " + r);
+		throw new MessagingException(
+			"Got bad greeting from SMTP host: " + host +
+				    ", port: " + port +
+				    ", response: " + r);
+	    } else {
+		if (logger.isLoggable(Level.FINE))
+		    logger.fine("protocol started to host \"" +
+				       host + "\", port: " + port);
+	    }
+	} catch (IOException ioe) {
+	    throw new MessagingException(
+				    "Could not start protocol to SMTP host: " +
+				    host + ", port: " + port, ioe);
+	}
+    }
+
+
+    private void initStreams() throws IOException {
+	boolean quote = PropUtil.getBooleanProperty(session.getProperties(),
+					"mail.debug.quote", false);
+
+	traceInput =
+	    new TraceInputStream(serverSocket.getInputStream(), traceLogger);
+	traceInput.setQuote(quote);
+
+	traceOutput =
+	    new TraceOutputStream(serverSocket.getOutputStream(), traceLogger);
+	traceOutput.setQuote(quote);
+
+	serverOutput =
+	    new BufferedOutputStream(traceOutput);
+	serverInput =
+	    new BufferedInputStream(traceInput);
+	lineInputStream = new LineInputStream(serverInput);
+    }
+
+    /**
+     * Is protocol tracing enabled?
+     */
+    private boolean isTracing() {
+	return traceLogger.isLoggable(Level.FINEST);
+    }
+
+    /**
+     * Temporarily turn off protocol tracing, e.g., to prevent
+     * tracing the authentication sequence, including the password.
+     */
+    private void suspendTracing() {
+	if (traceLogger.isLoggable(Level.FINEST)) {
+	    traceInput.setTrace(false);
+	    traceOutput.setTrace(false);
+	}
+    }
+
+    /**
+     * Resume protocol tracing, if it was enabled to begin with.
+     */
+    private void resumeTracing() {
+	if (traceLogger.isLoggable(Level.FINEST)) {
+	    traceInput.setTrace(true);
+	    traceOutput.setTrace(true);
+	}
+    }
+
+    /**
+     * Send the command to the server.  If the expected response code
+     * is not received, throw a MessagingException.
+     *
+     * @param	cmd	the command to send
+     * @param	expect	the expected response code (-1 means don't care)
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    public synchronized void issueCommand(String cmd, int expect)
+				throws MessagingException {
+	sendCommand(cmd);
+
+	// if server responded with an unexpected return code,
+	// throw the exception, notifying the client of the response
+	int resp = readServerResponse();
+	if (expect != -1 && resp != expect)
+	    throw new MessagingException(lastServerResponse);
+    }
+
+    /**
+     * Issue a command that's part of sending a message.
+     */
+    private void issueSendCommand(String cmd, int expect)
+				throws MessagingException {
+	sendCommand(cmd);
+
+	// if server responded with an unexpected return code,
+	// throw the exception, notifying the client of the response
+	int ret;
+	if ((ret = readServerResponse()) != expect) {
+	    // assume message was not sent to anyone,
+	    // combine valid sent & unsent addresses
+	    int vsl = validSentAddr == null ? 0 : validSentAddr.length;
+	    int vul = validUnsentAddr == null ? 0 : validUnsentAddr.length;
+	    Address[] valid = new Address[vsl + vul];
+	    if (vsl > 0)
+		System.arraycopy(validSentAddr, 0, valid, 0, vsl);
+	    if (vul > 0)
+		System.arraycopy(validUnsentAddr, 0, valid, vsl, vul);
+	    validSentAddr = null;
+	    validUnsentAddr = valid;
+	    if (logger.isLoggable(Level.FINE))
+		logger.fine("got response code " + ret +
+		    ", with response: " + lastServerResponse);
+	    String _lsr = lastServerResponse; // else rset will nuke it
+	    int _lrc = lastReturnCode;
+	    if (serverSocket != null)	// hasn't already been closed
+		issueCommand("RSET", -1);
+	    lastServerResponse = _lsr;	// restore, for get
+	    lastReturnCode = _lrc;
+	    throw new SMTPSendFailedException(cmd, ret, lastServerResponse,
+			exception, validSentAddr, validUnsentAddr, invalidAddr);
+	}
+    }
+
+    /**
+     * Send the command to the server and return the response code
+     * from the server.
+     *
+     * @param	cmd	the command
+     * @return		the response code
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    public synchronized int simpleCommand(String cmd)
+				throws MessagingException {
+	sendCommand(cmd);
+	return readServerResponse();
+    }
+
+    /**
+     * Send the command to the server and return the response code
+     * from the server.
+     *
+     * @param	cmd	the command
+     * @return		the response code
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    protected int simpleCommand(byte[] cmd) throws MessagingException {
+	assert Thread.holdsLock(this);
+	sendCommand(cmd);
+	return readServerResponse();
+    }
+
+    /**
+     * Sends command <code>cmd</code> to the server terminating
+     * it with <code>CRLF</code>.
+     *
+     * @param	cmd	the command
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    protected void sendCommand(String cmd) throws MessagingException {
+	sendCommand(toBytes(cmd));
+    }
+
+    private void sendCommand(byte[] cmdBytes) throws MessagingException {
+	assert Thread.holdsLock(this);
+	//if (logger.isLoggable(Level.FINE))
+	    //logger.fine("SENT: " + new String(cmdBytes, 0));
+
+        try {
+	    serverOutput.write(cmdBytes);
+	    serverOutput.write(CRLF);
+	    serverOutput.flush();
+	} catch (IOException ex) {
+	    throw new MessagingException("Can't send command to SMTP host", ex);
+	}
+    }
+
+    /**
+     * Reads server reponse returning the <code>returnCode</code>
+     * as the number.  Returns -1 on failure. Sets
+     * <code>lastServerResponse</code> and <code>lastReturnCode</code>.
+     *
+     * @return		server response code
+     * @exception	MessagingException for failures
+     * @since JavaMail 1.4.1
+     */
+    protected int readServerResponse() throws MessagingException {
+	assert Thread.holdsLock(this);
+        String serverResponse = "";
+        int returnCode = 0;
+	StringBuilder buf = new StringBuilder(100);
+
+	// read the server response line(s) and add them to the buffer
+	// that stores the response
+        try {
+	    String line = null;
+
+	    do {
+		line = lineInputStream.readLine();
+		if (line == null) {
+		    serverResponse = buf.toString();
+		    if (serverResponse.length() == 0)
+			serverResponse = "[EOF]";
+		    lastServerResponse = serverResponse;
+		    lastReturnCode = -1;
+		    logger.log(Level.FINE, "EOF: {0}", serverResponse);
+		    return -1;
+		}
+		buf.append(line);
+		buf.append("\n");
+	    } while (isNotLastLine(line));
+
+            serverResponse = buf.toString();
+        } catch (IOException ioex) {
+	    logger.log(Level.FINE, "exception reading response", ioex);
+            //ioex.printStackTrace(out);
+	    lastServerResponse = "";
+	    lastReturnCode = 0;
+	    throw new MessagingException("Exception reading response", ioex);
+            //returnCode = -1;
+        }
+
+	// print debug info
+        //if (logger.isLoggable(Level.FINE))
+            //logger.fine("RCVD: " + serverResponse);
+
+	// parse out the return code
+        if (serverResponse.length() >= 3) {
+            try {
+                returnCode = Integer.parseInt(serverResponse.substring(0, 3));
+            } catch (NumberFormatException nfe) {
+		try {
+		    close();
+		} catch (MessagingException mex) {
+		    // thrown by close()--ignore, will close() later anyway
+		    logger.log(Level.FINE, "close failed", mex);
+		}
+		returnCode = -1;
+            } catch (StringIndexOutOfBoundsException ex) {
+		try {
+		    close();
+		} catch (MessagingException mex) {
+		    // thrown by close()--ignore, will close() later anyway
+		    logger.log(Level.FINE, "close failed", mex);
+		}
+                returnCode = -1;
+	    }
+	} else {
+	    returnCode = -1;
+	}
+	if (returnCode == -1)
+	    logger.log(Level.FINE, "bad server response: {0}", serverResponse);
+
+        lastServerResponse = serverResponse;
+	lastReturnCode = returnCode;
+        return returnCode;
+    }
+
+    /**
+     * Check if we're in the connected state.  Don't bother checking
+     * whether the server is still alive, that will be detected later.
+     *
+     * @exception	IllegalStateException	if not connected
+     *
+     * @since JavaMail 1.4.1
+     */
+    protected void checkConnected() {
+	if (!super.isConnected())
+	    throw new IllegalStateException("Not connected");
+    }
+
+    // tests if the <code>line</code> is an intermediate line according to SMTP
+    private boolean isNotLastLine(String line) {
+        return line != null && line.length() >= 4 && line.charAt(3) == '-';
+    }
+
+    // wraps an address in "<>"'s if necessary
+    private String normalizeAddress(String addr) {
+	if ((!addr.startsWith("<")) && (!addr.endsWith(">")))
+	    return "<" + addr + ">";
+	else
+	    return addr;
+    }
+
+    /**
+     * Return true if the SMTP server supports the specified service
+     * extension.  Extensions are reported as results of the EHLO
+     * command when connecting to the server. See
+     * <A HREF="http://www.ietf.org/rfc/rfc1869.txt">RFC 1869</A>
+     * and other RFCs that define specific extensions.
+     *
+     * @param	ext	the service extension name
+     * @return		true if the extension is supported
+     *
+     * @since JavaMail 1.3.2
+     */
+    public boolean supportsExtension(String ext) {
+	return extMap != null &&
+			extMap.get(ext.toUpperCase(Locale.ENGLISH)) != null;
+    }
+
+    /**
+     * Return the parameter the server provided for the specified
+     * service extension, or null if the extension isn't supported.
+     *
+     * @param	ext	the service extension name
+     * @return		the extension parameter
+     *
+     * @since JavaMail 1.3.2
+     */
+    public String getExtensionParameter(String ext) {
+	return extMap == null ? null :
+			extMap.get(ext.toUpperCase(Locale.ENGLISH));
+    }
+
+    /**
+     * Does the server we're connected to support the specified
+     * authentication mechanism?  Uses the extension information
+     * returned by the server from the EHLO command.
+     *
+     * @param	auth	the authentication mechanism
+     * @return		true if the authentication mechanism is supported
+     *
+     * @since JavaMail 1.4.1
+     */
+    protected boolean supportsAuthentication(String auth) {
+	assert Thread.holdsLock(this);
+	if (extMap == null)
+	    return false;
+	String a = extMap.get("AUTH");
+	if (a == null)
+	    return false;
+	StringTokenizer st = new StringTokenizer(a);
+	while (st.hasMoreTokens()) {
+	    String tok = st.nextToken();
+	    if (tok.equalsIgnoreCase(auth))
+		return true;
+	}
+	// hack for buggy servers that advertise capability incorrectly
+	if (auth.equalsIgnoreCase("LOGIN") && supportsExtension("AUTH=LOGIN")) {
+	    logger.fine("use AUTH=LOGIN hack");
+	    return true;
+	}
+	return false;
+    }
+
+    private static char[] hexchar = {
+	'0', '1', '2', '3', '4', '5', '6', '7',
+	'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    /**
+     * Convert a string to RFC 1891 xtext format.
+     *
+     * <pre>
+     *     xtext = *( xchar / hexchar )
+     *
+     *     xchar = any ASCII CHAR between "!" (33) and "~" (126) inclusive,
+     *          except for "+" and "=".
+     *
+     * ; "hexchar"s are intended to encode octets that cannot appear
+     * ; as ASCII characters within an esmtp-value.
+     *
+     *     hexchar = ASCII "+" immediately followed by two upper case
+     *          hexadecimal digits
+     * </pre>
+     *
+     * @param	s	the string to convert
+     * @return	the xtext format string
+     * @since JavaMail 1.4.1
+     */
+    // XXX - keeping this around only for compatibility
+    protected static String xtext(String s) {
+	return xtext(s, false);
+    }
+
+    /**
+     * Like xtext(s), but allow UTF-8 strings.
+     *
+     * @param	s	the string to convert
+     * @param	utf8	convert string to UTF-8 first?
+     * @return	the xtext format string
+     * @since JavaMail 1.6.0
+     */
+    protected static String xtext(String s, boolean utf8) {
+	StringBuilder sb = null;
+	byte[] bytes;
+	if (utf8)
+	    bytes = s.getBytes(StandardCharsets.UTF_8);
+	else
+	    bytes = ASCIIUtility.getBytes(s);
+	for (int i = 0; i < bytes.length; i++) {
+	    char c = (char)(((int)bytes[i])&0xff);
+	    if (!utf8 && c >= 128)	// not ASCII
+		throw new IllegalArgumentException(
+			    "Non-ASCII character in SMTP submitter: " + s);
+	    if (c < '!' || c > '~' || c == '+' || c == '=') {
+		// not printable ASCII
+		if (sb == null) {
+		    sb = new StringBuilder(s.length() + 4);
+		    sb.append(s.substring(0, i));
+		}
+		sb.append('+');
+		sb.append(hexchar[(((int)c)& 0xf0) >> 4]);
+		sb.append(hexchar[((int)c)& 0x0f]);
+	    } else {
+		if (sb != null)
+		    sb.append(c);
+	    }
+	}
+	return sb != null ? sb.toString() : s;
+    }
+
+    private String traceUser(String user) {
+	return debugusername ? user : "<user name suppressed>";
+    }
+
+    private String tracePassword(String password) {
+	return debugpassword ? password :
+				(password == null ? "<null>" : "<non-null>");
+    }
+
+    /**
+     * Convert the String to either ASCII or UTF-8 bytes
+     * depending on allowutf8.
+     */
+    private byte[] toBytes(String s) {
+	if (allowutf8)
+	    return s.getBytes(StandardCharsets.UTF_8);
+	else
+	    // don't use StandardCharsets.US_ASCII because it rejects non-ASCII
+	    return ASCIIUtility.getBytes(s);
+    }
+
+    /*
+     * Probe points for GlassFish monitoring.
+     */
+    private void sendMessageStart(String subject) { }
+    private void sendMessageEnd() { }
+
+
+    /**
+     * An SMTPOutputStream that wraps a ChunkedOutputStream.
+     */
+    private class BDATOutputStream extends SMTPOutputStream {
+
+	/**
+	 * Create a BDATOutputStream that wraps a ChunkedOutputStream
+	 * of the given size and built on top of the specified
+	 * underlying output stream.
+	 *
+	 * @param	out	the underlying output stream
+	 * @param	size	the chunk size
+	 */
+	public BDATOutputStream(OutputStream out, int size) {
+	    super(new ChunkedOutputStream(out, size));
+	}
+
+	/**
+	 * Close this output stream.
+	 *
+	 * @exception	IOException	for I/O errors
+	 */
+	@Override
+	public void close() throws IOException {
+	    out.close();
+	}
+    }
+
+    /**
+     * An OutputStream that buffers data in chunks and uses the
+     * RFC 3030 BDAT SMTP command to send each chunk.
+     */
+    private class ChunkedOutputStream extends OutputStream {
+	private final OutputStream out;
+	private final byte[] buf;
+	private int count = 0;
+
+	/**
+	 * Create a ChunkedOutputStream built on top of the specified
+	 * underlying output stream.
+	 *
+	 * @param	out	the underlying output stream
+	 * @param	size	the chunk size
+	 */
+	public ChunkedOutputStream(OutputStream out, int size) {
+	    this.out = out;
+	    buf = new byte[size];
+	}
+
+	/**
+	 * Writes the specified <code>byte</code> to this output stream.
+	 *
+	 * @param	b	the byte to write
+	 * @exception	IOException	for I/O errors
+	 */
+	@Override
+	public void write(int b) throws IOException {
+	    buf[count++] = (byte)b;
+	    if (count >= buf.length)
+		flush();
+	}
+		
+	/**
+	 * Writes len bytes to this output stream starting at off.
+	 *
+	 * @param	b	bytes to write
+	 * @param	off	offset in array
+	 * @param	len	number of bytes to write
+	 * @exception	IOException	for I/O errors
+	 */
+	@Override
+	public void write(byte b[], int off, int len) throws IOException {
+	    while (len > 0) {
+		int size = Math.min(buf.length - count, len);
+		if (size == buf.length) {
+		    // avoid the copy
+		    bdat(b, off, size, false);
+		} else {
+		    System.arraycopy(b, off, buf, count, size);
+		    count += size;
+		}
+		off += size;
+		len -= size;
+		if (count >= buf.length)
+		    flush();
+	    }
+	}
+
+	/**
+	 * Flush this output stream.
+	 *
+	 * @exception	IOException	for I/O errors
+	 */
+	@Override
+	public void flush() throws IOException {
+	    bdat(buf, 0, count, false);
+	    count = 0;
+	}
+
+	/**
+	 * Close this output stream.
+	 *
+	 * @exception	IOException	for I/O errors
+	 */
+	@Override
+	public void close() throws IOException {
+	    bdat(buf, 0, count, true);
+	    count = 0;
+	}
+
+	/**
+	 * Send the specified bytes using the BDAT command.
+	 */
+	private void bdat(byte[] b, int off, int len, boolean last)
+				throws IOException {
+	    if (len > 0 || last) {
+		try {
+		    if (last)
+			sendCommand("BDAT " + len + " LAST");
+		    else
+			sendCommand("BDAT " + len);
+		    out.write(b, off, len);
+		    out.flush();
+		    int ret = readServerResponse();
+		    if (ret != 250)
+			throw new IOException(lastServerResponse);
+		} catch (MessagingException mex) {
+		    throw new IOException("BDAT write exception", mex);
+		}
+	    }
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/SaslAuthenticator.java b/mail/src/main/java/com/sun/mail/smtp/SaslAuthenticator.java
new file mode 100644
index 0000000..2c2a645
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/SaslAuthenticator.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import javax.mail.MessagingException;
+
+/**
+ * Interface to make it easier to call SMTPSaslAuthenticator.
+ */
+
+public interface SaslAuthenticator {
+    public boolean authenticate(String[] mechs, String realm, String authzid,
+				String u, String p) throws MessagingException;
+
+}
diff --git a/mail/src/main/java/com/sun/mail/smtp/package.html b/mail/src/main/java/com/sun/mail/smtp/package.html
new file mode 100644
index 0000000..13f608e
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/smtp/package.html
@@ -0,0 +1,822 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.smtp package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+An SMTP protocol provider for the JavaMail API
+that provides access to an SMTP server.
+Refer to <A HREF="http://www.ietf.org/rfc/rfc821.txt" TARGET="_top">RFC 821</A>
+for more information.
+<P>
+When sending a message, detailed information on each address that
+fails is available in an
+{@link com.sun.mail.smtp.SMTPAddressFailedException SMTPAddressFailedException}
+chained off the top level
+{@link javax.mail.SendFailedException SendFailedException}
+that is thrown.
+In addition, if the <code>mail.smtp.reportsuccess</code> property
+is set, an
+{@link com.sun.mail.smtp.SMTPAddressSucceededException
+SMTPAddressSucceededException}
+will be included in the list for each address that is successful.
+Note that this will cause a top level
+{@link javax.mail.SendFailedException SendFailedException}
+to be thrown even though the send was successful.
+</P>
+<P>
+The SMTP provider also supports ESMTP
+(<A HREF="http://www.ietf.org/rfc/rfc1651.txt" TARGET="_top">RFC 1651</A>).
+It can optionally use SMTP Authentication 
+(<A HREF="http://www.ietf.org/rfc/rfc2554.txt" TARGET="_top">RFC 2554</A>)
+using the LOGIN, PLAIN, DIGEST-MD5, and NTLM mechanisms
+(<A HREF="http://www.ietf.org/rfc/rfc4616.txt" TARGET="_top">RFC 4616</A>
+and <A HREF="http://www.ietf.org/rfc/rfc2831.txt" TARGET="_top">RFC 2831</A>).
+</P>
+<P>
+To use SMTP authentication you'll need to set the <code>mail.smtp.auth</code>
+property (see below) or provide the SMTP Transport
+with a username and password when connecting to the SMTP server.  You
+can do this using one of the following approaches:
+</P>
+<UL>
+<LI>
+<P>
+Provide an Authenticator object when creating your mail Session
+and provide the username and password information during the
+Authenticator callback.
+</P>
+<P>
+Note that the <code>mail.smtp.user</code> property can be set to provide a
+default username for the callback, but the password will still need to be
+supplied explicitly.
+</P>
+<P>
+This approach allows you to use the static Transport <code>send</code> method
+to send messages.
+</P>
+</LI>
+<LI>
+<P>
+Call the Transport <code>connect</code> method explicitly with username and
+password arguments.
+</P>
+<P>
+This approach requires you to explicitly manage a Transport object
+and use the Transport <code>sendMessage</code> method to send the message.
+The transport.java demo program demonstrates how to manage a Transport
+object.  The following is roughly equivalent to the static
+Transport <code>send</code> method, but supplies the needed username and
+password:
+</P>
+<BLOCKQUOTE><PRE>
+Transport tr = session.getTransport("smtp");
+tr.connect(smtphost, username, password);
+msg.saveChanges();	// don't forget this
+tr.sendMessage(msg, msg.getAllRecipients());
+tr.close();
+</PRE></BLOCKQUOTE>
+</LI>
+</UL>
+<P>
+When using DIGEST-MD5 authentication,
+you'll also need to supply an appropriate realm;
+your mail server administrator can supply this information.
+You can set this using the <code>mail.smtp.sasl.realm</code> property,
+or the <code>setSASLRealm</code> method on <code>SMTPTransport</code>.
+</P>
+<P>
+The SMTP protocol provider can use SASL
+(<A HREF="http://www.ietf.org/rfc/rfc2222.txt" TARGET="_top">RFC 2222</A>)
+authentication mechanisms on systems that support the
+<CODE>javax.security.sasl</CODE> APIs, such as J2SE 5.0.
+In addition to the SASL mechanisms that are built into 
+the SASL implementation, users can also provide additional
+SASL mechanisms of their own design to support custom authentication
+schemes.  See the
+<A HREF="http://java.sun.com/j2se/1.5.0/docs/guide/security/sasl/sasl-refguide.html" TARGET="_top">
+Java SASL API Programming and Deployment Guide</A> for details.
+Note that the current implementation doesn't support SASL mechanisms
+that provide their own integrity or confidentiality layer.
+</P>
+<P>
+Support for OAuth 2.0 authentication via the
+<A HREF="https://developers.google.com/gmail/xoauth2_protocol" TARGET="_top">
+XOAUTH2 authentication mechanism</A> is provided either through the SASL
+support described above or as a built-in authentication mechanism in the
+SMTP provider.
+The OAuth 2.0 Access Token should be passed as the password for this mechanism.
+See <A HREF="https://java.net/projects/javamail/pages/OAuth2" TARGET="_top">
+OAuth2 Support</A> for details.
+</P>
+<P>
+SMTP can also optionally request Delivery Status Notifications
+(<A HREF="http://www.ietf.org/rfc/rfc1891.txt" TARGET="_top">RFC 1891</A>).
+The delivery status will typically be reported using
+a "multipart/report"
+(<A HREF="http://www.ietf.org/rfc/rfc1892.txt" TARGET="_top">RFC 1892</A>)
+message type with a "message/delivery-status"
+(<A HREF="http://www.ietf.org/rfc/rfc1894.txt" TARGET="_top">RFC 1894</A>)
+part.
+You can use the classes in the <code>com.sun.mail.dsn</code> package to
+handle these MIME types.
+Note that you'll need to include <code>dsn.jar</code> in your CLASSPATH
+as this support is not included in <code>mail.jar</code>.
+</P>
+<P>
+See below for the properties to enable these features.
+</P>
+<P>
+Note also that <strong>THERE IS NOT SUFFICIENT DOCUMENTATION HERE TO USE THESE
+FEATURES!!!</strong>  You will need to read the appropriate RFCs mentioned above
+to understand what these features do and how to use them.  Don't just
+start setting properties and then complain to us when it doesn't work
+like you expect it to work.  <strong>READ THE RFCs FIRST!!!</strong>
+</P>
+<P>
+The SMTP protocol provider supports the CHUNKING extension defined in
+<A HREF="http://www.ietf.org/rfc/rfc3030.txt" TARGET="_top">RFC 3030</A>.
+Set the <code>mail.smtp.chunksize</code> property to the desired chunk
+size in bytes.
+If the server supports the CHUNKING extension, the BDAT command will be
+used to send the message in chunksize pieces.  Note that no pipelining is
+done so this will be slower than sending the message in one piece.
+Note also that the BINARYMIME extension described in RFC 3030 is NOT supported.
+</P>
+<A NAME="properties"><STRONG>Properties</STRONG></A>
+<P>
+The SMTP protocol provider supports the following properties,
+which may be set in the JavaMail <code>Session</code> object.
+The properties are always set as strings; the Type column describes
+how the string is interpreted.  For example, use
+</P>
+<PRE>
+	props.put("mail.smtp.port", "888");
+</PRE>
+<P>
+to set the <CODE>mail.smtp.port</CODE> property, which is of type int.
+</P>
+<P>
+Note that if you're using the "smtps" protocol to access SMTP over SSL,
+all the properties would be named "mail.smtps.*".
+</P>
+<TABLE BORDER SUMMARY="SMTP properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.user">mail.smtp.user</A></TD>
+<TD>String</TD>
+<TD>Default user name for SMTP.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.host">mail.smtp.host</A></TD>
+<TD>String</TD>
+<TD>The SMTP server to connect to.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.port">mail.smtp.port</A></TD>
+<TD>int</TD>
+<TD>The SMTP server port to connect to, if the connect() method doesn't
+explicitly specify one. Defaults to 25.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.connectiontimeout">mail.smtp.connectiontimeout</A></TD>
+<TD>int</TD>
+<TD>Socket connection timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.timeout">mail.smtp.timeout</A></TD>
+<TD>int</TD>
+<TD>Socket read timeout value in milliseconds.
+This timeout is implemented by java.net.Socket.
+Default is infinite timeout.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.writetimeout">mail.smtp.writetimeout</A></TD>
+<TD>int</TD>
+<TD>Socket write timeout value in milliseconds.
+This timeout is implemented by using a
+java.util.concurrent.ScheduledExecutorService per connection
+that schedules a thread to close the socket if the timeout expires.
+Thus, the overhead of using this timeout is one thread per connection.
+Default is infinite timeout.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.from">mail.smtp.from</A></TD>
+<TD>String</TD>
+<TD>
+Email address to use for SMTP MAIL command.  This sets the envelope
+return address.  Defaults to msg.getFrom() or
+InternetAddress.getLocalAddress().  NOTE: mail.smtp.user was previously
+used for this.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.localhost">mail.smtp.localhost</A></TD>
+<TD>String</TD>
+<TD>
+Local host name used in the SMTP HELO or EHLO command.
+Defaults to <code>InetAddress.getLocalHost().getHostName()</code>.
+Should not normally need to
+be set if your JDK and your name service are configured properly.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.localaddress">mail.smtp.localaddress</A></TD>
+<TD>String</TD>
+<TD>
+Local address (host name) to bind to when creating the SMTP socket.
+Defaults to the address picked by the Socket class.
+Should not normally need to be set, but useful with multi-homed hosts
+where it's important to pick a particular local address to bind to.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.localport">mail.smtp.localport</A></TD>
+<TD>int</TD>
+<TD>
+Local port number to bind to when creating the SMTP socket.
+Defaults to the port number picked by the Socket class.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.ehlo">mail.smtp.ehlo</A></TD>
+<TD>boolean</TD>
+<TD>
+If false, do not attempt to sign on with the EHLO command.  Defaults to
+true.  Normally failure of the EHLO command will fallback to the HELO
+command; this property exists only for servers that don't fail EHLO
+properly or don't implement EHLO properly.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.auth">mail.smtp.auth</A></TD>
+<TD>boolean</TD>
+<TD>If true, attempt to authenticate the user using the AUTH command.
+Defaults to false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.auth.mechanisms">mail.smtp.auth.mechanisms</A></TD>
+<TD>String</TD>
+<TD>
+If set, lists the authentication mechanisms to consider, and the order
+in which to consider them.  Only mechanisms supported by the server and
+supported by the current implementation will be used.
+The default is <code>"LOGIN PLAIN DIGEST-MD5 NTLM"</code>, which includes all
+the authentication mechanisms supported by the current implementation
+except XOAUTH2.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.auth.login.disable">mail.smtp.auth.login.disable</A></TD>
+<TD>boolean</TD>
+<TD>If true, prevents use of the <code>AUTH LOGIN</code> command.
+Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.auth.plain.disable">mail.smtp.auth.plain.disable</A></TD>
+<TD>boolean</TD>
+<TD>If true, prevents use of the <code>AUTH PLAIN</code> command.
+Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.auth.digest-md5.disable">mail.smtp.auth.digest-md5.disable</A></TD>
+<TD>boolean</TD>
+<TD>If true, prevents use of the <code>AUTH DIGEST-MD5</code> command.
+Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.auth.ntlm.disable">mail.smtp.auth.ntlm.disable</A></TD>
+<TD>boolean</TD>
+<TD>If true, prevents use of the <code>AUTH NTLM</code> command.
+Default is false.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.auth.ntlm.domain">mail.smtp.auth.ntlm.domain</A></TD>
+<TD>String</TD>
+<TD>
+The NTLM authentication domain.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.auth.ntlm.flags">mail.smtp.auth.ntlm.flags</A></TD>
+<TD>int</TD>
+<TD>
+NTLM protocol-specific flags.
+See <A HREF="http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags" TARGET="_top">
+http://curl.haxx.se/rfc/ntlm.html#theNtlmFlags</A> for details.
+</TD>
+</TR>
+
+<!--
+<TR>
+<TD><A NAME="mail.smtp.auth.ntlm.unicode">mail.smtp.auth.ntlm.unicode</A></TD>
+<TD>boolean</TD>
+<TD>
+Set this to "true" if the username or password may use
+Unicode UTF-8 encoded characters.  Default is "true".
+Currently has no effect.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.auth.ntlm.lmcompat">mail.smtp.auth.ntlm.lmcompat</A></TD>
+<TD>int</TD>
+<TD>
+Sets the LM compatibility level, as described here:
+<A HREF="http://curl.haxx.se/rfc/ntlm.html#ntlmVersion2" TARGET="_top">
+http://curl.haxx.se/rfc/ntlm.html#ntlmVersion2</A>
+Defaults to "3".  Currently not used.
+</TD>
+</TR>
+-->
+
+<TR>
+<TD><A NAME="mail.smtp.auth.xoauth2.disable">mail.smtp.auth.xoauth2.disable</A></TD>
+<TD>boolean</TD>
+<TD>If true, prevents use of the <code>AUTHENTICATE XOAUTH2</code> command.
+Because the OAuth 2.0 protocol requires a special access token instead of
+a password, this mechanism is disabled by default.  Enable it by explicitly
+setting this property to "false" or by setting the "mail.smtp.auth.mechanisms"
+property to "XOAUTH2".</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.submitter">mail.smtp.submitter</A></TD>
+<TD>String</TD>
+<TD>The submitter to use in the AUTH tag in the MAIL FROM command.
+Typically used by a mail relay to pass along information about the
+original submitter of the message.
+See also the {@link com.sun.mail.smtp.SMTPMessage#setSubmitter setSubmitter}
+method of {@link com.sun.mail.smtp.SMTPMessage SMTPMessage}.
+Mail clients typically do not use this.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.dsn.notify">mail.smtp.dsn.notify</A></TD>
+<TD>String</TD>
+<TD>The NOTIFY option to the RCPT command.  Either NEVER, or some
+combination of SUCCESS, FAILURE, and DELAY (separated by commas).</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.dsn.ret">mail.smtp.dsn.ret</A></TD>
+<TD>String</TD>
+<TD>The RET option to the MAIL command.  Either FULL or HDRS.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.allow8bitmime">mail.smtp.allow8bitmime</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, and the server supports the 8BITMIME extension, text
+parts of messages that use the "quoted-printable" or "base64" encodings
+are converted to use "8bit" encoding if they follow the RFC2045 rules
+for 8bit text.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.sendpartial">mail.smtp.sendpartial</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, and a message has some valid and some invalid
+addresses, send the message anyway, reporting the partial failure with
+a SendFailedException.  If set to false (the default), the message is
+not sent to any of the recipients if there is an invalid recipient
+address.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.sasl.enable">mail.smtp.sasl.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, attempt to use the javax.security.sasl package to
+choose an authentication mechanism for login.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.sasl.mechanisms">mail.smtp.sasl.mechanisms</A></TD>
+<TD>String</TD>
+<TD>
+A space or comma separated list of SASL mechanism names to try
+to use.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.sasl.authorizationid">mail.smtp.sasl.authorizationid</A></TD>
+<TD>String</TD>
+<TD>
+The authorization ID to use in the SASL authentication.
+If not set, the authentication ID (user name) is used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.sasl.realm">mail.smtp.sasl.realm</A></TD>
+<TD>String</TD>
+<TD>The realm to use with DIGEST-MD5 authentication.</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.sasl.usecanonicalhostname">mail.smtp.sasl.usecanonicalhostname</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, the canonical host name returned by
+{@link java.net.InetAddress#getCanonicalHostName InetAddress.getCanonicalHostName}
+is passed to the SASL mechanism, instead of the host name used to connect.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.quitwait">mail.smtp.quitwait</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to false, the QUIT command is sent
+and the connection is immediately closed.
+If set to true (the default), causes the transport to wait
+for the response to the QUIT command.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.reportsuccess">mail.smtp.reportsuccess</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, causes the transport to include an
+{@link com.sun.mail.smtp.SMTPAddressSucceededException
+SMTPAddressSucceededException}
+for each address that is successful.
+Note also that this will cause a
+{@link javax.mail.SendFailedException SendFailedException}
+to be thrown from the
+{@link com.sun.mail.smtp.SMTPTransport#sendMessage sendMessage}
+method of
+{@link com.sun.mail.smtp.SMTPTransport SMTPTransport}
+even if all addresses were correct and the message was sent
+successfully.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.socketFactory">mail.smtp.socketFactory</A></TD>
+<TD>SocketFactory</TD>
+<TD>
+If set to a class that implements the
+<code>javax.net.SocketFactory</code> interface, this class
+will be used to create SMTP sockets.  Note that this is an
+instance of a class, not a name, and must be set using the
+<code>put</code> method, not the <code>setProperty</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.socketFactory.class">mail.smtp.socketFactory.class</A></TD>
+<TD>String</TD>
+<TD>
+If set, specifies the name of a class that implements the
+<code>javax.net.SocketFactory</code> interface.  This class
+will be used to create SMTP sockets.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.socketFactory.fallback">mail.smtp.socketFactory.fallback</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, failure to create a socket using the specified
+socket factory class will cause the socket to be created using
+the <code>java.net.Socket</code> class.
+Defaults to true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.socketFactory.port">mail.smtp.socketFactory.port</A></TD>
+<TD>int</TD>
+<TD>
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.ssl.enable">mail.smtp.ssl.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, use SSL to connect and use the SSL port by default.
+Defaults to false for the "smtp" protocol and true for the "smtps" protocol.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.ssl.checkserveridentity">mail.smtp.ssl.checkserveridentity</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, check the server identity as specified by
+<A HREF="http://www.ietf.org/rfc/rfc2595.txt" TARGET="_top">RFC 2595</A>.
+These additional checks based on the content of the server's certificate
+are intended to prevent man-in-the-middle attacks.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.ssl.trust">mail.smtp.ssl.trust</A></TD>
+<TD>String</TD>
+<TD>
+If set, and a socket factory hasn't been specified, enables use of a
+{@link com.sun.mail.util.MailSSLSocketFactory MailSSLSocketFactory}.
+If set to "*", all hosts are trusted.
+If set to a whitespace separated list of hosts, those hosts are trusted.
+Otherwise, trust depends on the certificate the server presents.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.ssl.socketFactory">mail.smtp.ssl.socketFactory</A></TD>
+<TD>SSLSocketFactory</TD>
+<TD>
+If set to a class that extends the
+<code>javax.net.ssl.SSLSocketFactory</code> class, this class
+will be used to create SMTP SSL sockets.  Note that this is an
+instance of a class, not a name, and must be set using the
+<code>put</code> method, not the <code>setProperty</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.ssl.socketFactory.class">mail.smtp.ssl.socketFactory.class</A></TD>
+<TD>String</TD>
+<TD>
+If set, specifies the name of a class that extends the
+<code>javax.net.ssl.SSLSocketFactory</code> class.  This class
+will be used to create SMTP SSL sockets.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.ssl.socketFactory.port">mail.smtp.ssl.socketFactory.port</A></TD>
+<TD>int</TD>
+<TD>
+Specifies the port to connect to when using the specified socket
+factory.
+If not set, the default port will be used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.ssl.protocols">mail.smtp.ssl.protocols</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the SSL protocols that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the <code>javax.net.ssl.SSLSocket.setEnabledProtocols</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.ssl.ciphersuites">mail.smtp.ssl.ciphersuites</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the SSL cipher suites that will be enabled for SSL connections.
+The property value is a whitespace separated list of tokens acceptable
+to the <code>javax.net.ssl.SSLSocket.setEnabledCipherSuites</code> method.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.starttls.enable">mail.smtp.starttls.enable</A></TD>
+<TD>boolean</TD>
+<TD>
+If true, enables the use of the <code>STARTTLS</code> command (if
+supported by the server) to switch the connection to a TLS-protected
+connection before issuing any login commands.
+If the server does not support STARTTLS, the connection continues without
+the use of TLS; see the
+<A HREF="#mail.smtp.starttls.required"><code>mail.smtp.starttls.required</code></A>
+property to fail if STARTTLS isn't supported.
+Note that an appropriate trust store must configured so that the client
+will trust the server's certificate.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.starttls.required">mail.smtp.starttls.required</A></TD>
+<TD>boolean</TD>
+<TD>
+If true, requires the use of the <code>STARTTLS</code> command.
+If the server doesn't support the STARTTLS command, or the command
+fails, the connect method will fail.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.proxy.host">mail.smtp.proxy.host</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the host name of an HTTP web proxy server that will be used for
+connections to the mail server.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.proxy.port">mail.smtp.proxy.port</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the port number for the HTTP web proxy server.
+Defaults to port 80.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.proxy.user">mail.smtp.proxy.user</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the user name to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.proxy.password">mail.smtp.proxy.password</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the password to use to authenticate with the HTTP web proxy server.
+By default, no authentication is done.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.socks.host">mail.smtp.socks.host</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the host name of a SOCKS5 proxy server that will be used for
+connections to the mail server.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.socks.port">mail.smtp.socks.port</A></TD>
+<TD>string</TD>
+<TD>
+Specifies the port number for the SOCKS5 proxy server.
+This should only need to be used if the proxy server is not using
+the standard port number of 1080.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.mailextension">mail.smtp.mailextension</A></TD>
+<TD>String</TD>
+<TD>
+Extension string to append to the MAIL command.
+The extension string can be used to specify standard SMTP
+service extensions as well as vendor-specific extensions.
+Typically the application should use the
+{@link com.sun.mail.smtp.SMTPTransport SMTPTransport}
+method {@link com.sun.mail.smtp.SMTPTransport#supportsExtension
+supportsExtension}
+to verify that the server supports the desired service extension.
+See <A HREF="http://www.ietf.org/rfc/rfc1869.txt" TARGET="_top">RFC 1869</A>
+and other RFCs that define specific extensions.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.userset">mail.smtp.userset</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true, use the RSET command instead of the NOOP command
+in the {@link javax.mail.Transport#isConnected isConnected} method.
+In some cases sendmail will respond slowly after many NOOP commands;
+use of RSET avoids this sendmail issue.
+Defaults to false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.smtp.noop.strict">mail.smtp.noop.strict</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to true (the default), insist on a 250 response code from the NOOP
+command to indicate success.  The NOOP command is used by the
+{@link javax.mail.Transport#isConnected isConnected} method to determine
+if the connection is still alive.
+Some older servers return the wrong response code on success, some
+servers don't implement the NOOP command at all and so always return
+a failure code.  Set this property to false to handle servers
+that are broken in this way.
+Normally, when a server times out a connection, it will send a 421
+response code, which the client will see as the response to the next
+command it issues.
+Some servers send the wrong failure response code when timing out a
+connection.
+Do not set this property to false when dealing with servers that are
+broken in this way.
+</TD>
+</TR>
+
+</TABLE>
+<P>
+In general, applications should not need to use the classes in this
+package directly.  Instead, they should use the APIs defined by
+<code>javax.mail</code> package (and subpackages).  Applications should
+never construct instances of <code>SMTPTransport</code> directly.
+Instead, they should use the
+<code>Session</code> method <code>getTransport</code> to acquire an
+appropriate <code>Transport</code> object.
+</P>
+<P>
+In addition to printing debugging output as controlled by the
+{@link javax.mail.Session Session} configuration,
+the com.sun.mail.smtp provider logs the same information using
+{@link java.util.logging.Logger} as described in the following table:
+</P>
+<TABLE BORDER SUMMARY="SMTP Loggers">
+<TR>
+<TH>Logger Name</TH>
+<TH>Logging Level</TH>
+<TH>Purpose</TH>
+</TR>
+
+<TR>
+<TD>com.sun.mail.smtp</TD>
+<TD>CONFIG</TD>
+<TD>Configuration of the SMTPTransport</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.smtp</TD>
+<TD>FINE</TD>
+<TD>General debugging output</TD>
+</TR>
+
+<TR>
+<TD>com.sun.mail.smtp.protocol</TD>
+<TD>FINEST</TD>
+<TD>Complete protocol trace</TD>
+</TR>
+</TABLE>
+<P>
+<strong>WARNING:</strong> The APIs unique to this package should be
+considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/com/sun/mail/util/ASCIIUtility.java b/mail/src/main/java/com/sun/mail/util/ASCIIUtility.java
new file mode 100644
index 0000000..4ab7a55
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/ASCIIUtility.java
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+
+public class ASCIIUtility {
+
+    // Private constructor so that this class is not instantiated
+    private ASCIIUtility() { }
+	
+    /**
+     * Convert the bytes within the specified range of the given byte 
+     * array into a signed integer in the given radix . The range extends 
+     * from <code>start</code> till, but not including <code>end</code>. <p>
+     *
+     * Based on java.lang.Integer.parseInt()
+     *
+     * @param	b	the bytes
+     * @param	start	the first byte offset
+     * @param	end	the last byte offset
+     * @param	radix	the radix
+     * @return		the integer value
+     * @exception	NumberFormatException	for conversion errors
+     */
+    public static int parseInt(byte[] b, int start, int end, int radix)
+		throws NumberFormatException {
+	if (b == null)
+	    throw new NumberFormatException("null");
+	
+	int result = 0;
+	boolean negative = false;
+	int i = start;
+	int limit;
+	int multmin;
+	int digit;
+
+	if (end > start) {
+	    if (b[i] == '-') {
+		negative = true;
+		limit = Integer.MIN_VALUE;
+		i++;
+	    } else {
+		limit = -Integer.MAX_VALUE;
+	    }
+	    multmin = limit / radix;
+	    if (i < end) {
+		digit = Character.digit((char)b[i++], radix);
+		if (digit < 0) {
+		    throw new NumberFormatException(
+			"illegal number: " + toString(b, start, end)
+			);
+		} else {
+		    result = -digit;
+		}
+	    }
+	    while (i < end) {
+		// Accumulating negatively avoids surprises near MAX_VALUE
+		digit = Character.digit((char)b[i++], radix);
+		if (digit < 0) {
+		    throw new NumberFormatException("illegal number");
+		}
+		if (result < multmin) {
+		    throw new NumberFormatException("illegal number");
+		}
+		result *= radix;
+		if (result < limit + digit) {
+		    throw new NumberFormatException("illegal number");
+		}
+		result -= digit;
+	    }
+	} else {
+	    throw new NumberFormatException("illegal number");
+	}
+	if (negative) {
+	    if (i > start + 1) {
+		return result;
+	    } else {	/* Only got "-" */
+		throw new NumberFormatException("illegal number");
+	    }
+	} else {
+	    return -result;
+	}
+    }
+
+    /**
+     * Convert the bytes within the specified range of the given byte 
+     * array into a signed integer . The range extends from 
+     * <code>start</code> till, but not including <code>end</code>.
+     *
+     * @param	b	the bytes
+     * @param	start	the first byte offset
+     * @param	end	the last byte offset
+     * @return		the integer value
+     * @exception	NumberFormatException	for conversion errors
+     */
+    public static int parseInt(byte[] b, int start, int end)
+		throws NumberFormatException {
+	return parseInt(b, start, end, 10);
+    }
+
+    /**
+     * Convert the bytes within the specified range of the given byte 
+     * array into a signed long in the given radix . The range extends 
+     * from <code>start</code> till, but not including <code>end</code>. <p>
+     *
+     * Based on java.lang.Long.parseLong()
+     *
+     * @param	b	the bytes
+     * @param	start	the first byte offset
+     * @param	end	the last byte offset
+     * @param	radix	the radix
+     * @return		the long value
+     * @exception	NumberFormatException	for conversion errors
+     */
+    public static long parseLong(byte[] b, int start, int end, int radix)
+		throws NumberFormatException {
+	if (b == null)
+	    throw new NumberFormatException("null");
+	
+	long result = 0;
+	boolean negative = false;
+	int i = start;
+	long limit;
+	long multmin;
+	int digit;
+
+	if (end > start) {
+	    if (b[i] == '-') {
+		negative = true;
+		limit = Long.MIN_VALUE;
+		i++;
+	    } else {
+		limit = -Long.MAX_VALUE;
+	    }
+	    multmin = limit / radix;
+	    if (i < end) {
+		digit = Character.digit((char)b[i++], radix);
+		if (digit < 0) {
+		    throw new NumberFormatException(
+			"illegal number: " + toString(b, start, end)
+			);
+		} else {
+		    result = -digit;
+		}
+	    }
+	    while (i < end) {
+		// Accumulating negatively avoids surprises near MAX_VALUE
+		digit = Character.digit((char)b[i++], radix);
+		if (digit < 0) {
+		    throw new NumberFormatException("illegal number");
+		}
+		if (result < multmin) {
+		    throw new NumberFormatException("illegal number");
+		}
+		result *= radix;
+		if (result < limit + digit) {
+		    throw new NumberFormatException("illegal number");
+		}
+		result -= digit;
+	    }
+	} else {
+	    throw new NumberFormatException("illegal number");
+	}
+	if (negative) {
+	    if (i > start + 1) {
+		return result;
+	    } else {	/* Only got "-" */
+		throw new NumberFormatException("illegal number");
+	    }
+	} else {
+	    return -result;
+	}
+    }
+
+    /**
+     * Convert the bytes within the specified range of the given byte 
+     * array into a signed long . The range extends from 
+     * <code>start</code> till, but not including <code>end</code>. <p>
+     *
+     * @param	b	the bytes
+     * @param	start	the first byte offset
+     * @param	end	the last byte offset
+     * @return		the long value
+     * @exception	NumberFormatException	for conversion errors
+     */
+    public static long parseLong(byte[] b, int start, int end)
+		throws NumberFormatException {
+	return parseLong(b, start, end, 10);
+    }
+
+    /**
+     * Convert the bytes within the specified range of the given byte 
+     * array into a String. The range extends from <code>start</code>
+     * till, but not including <code>end</code>.
+     *
+     * @param	b	the bytes
+     * @param	start	the first byte offset
+     * @param	end	the last byte offset
+     * @return		the String
+     */
+    public static String toString(byte[] b, int start, int end) {
+	int size = end - start;
+	char[] theChars = new char[size];
+
+	for (int i = 0, j = start; i < size; )
+	    theChars[i++] = (char)(b[j++]&0xff);
+	
+	return new String(theChars);
+    }
+
+    /**
+     * Convert the bytes into a String.
+     *
+     * @param	b	the bytes
+     * @return		the String
+     * @since	JavaMail 1.4.4
+     */
+    public static String toString(byte[] b) {
+	return toString(b, 0, b.length);
+    }
+
+    public static String toString(ByteArrayInputStream is) {
+	int size = is.available();
+	char[] theChars = new char[size];
+	byte[] bytes    = new byte[size];
+
+	is.read(bytes, 0, size);
+	for (int i = 0; i < size;)
+	    theChars[i] = (char)(bytes[i++]&0xff);
+	
+	return new String(theChars);
+    }
+
+
+    public static byte[] getBytes(String s) {
+	char [] chars= s.toCharArray();
+	int size = chars.length;
+	byte[] bytes = new byte[size];
+    	
+	for (int i = 0; i < size;)
+	    bytes[i] = (byte) chars[i++];
+	return bytes;
+    }
+
+    public static byte[] getBytes(InputStream is) throws IOException {
+
+	int len;
+	int size = 1024;
+	byte [] buf;
+
+
+	if (is instanceof ByteArrayInputStream) {
+	    size = is.available();
+	    buf = new byte[size];
+	    len = is.read(buf, 0, size);
+	}
+	else {
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	    buf = new byte[size];
+	    while ((len = is.read(buf, 0, size)) != -1)
+		bos.write(buf, 0, len);
+	    buf = bos.toByteArray();
+	}
+	return buf;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/BASE64DecoderStream.java b/mail/src/main/java/com/sun/mail/util/BASE64DecoderStream.java
new file mode 100644
index 0000000..11a8ede
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/BASE64DecoderStream.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a BASE64 Decoder. It is implemented as
+ * a FilterInputStream, so one can just wrap this class around
+ * any input stream and read bytes from this filter. The decoding
+ * is done as the bytes are read out.
+ * 
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class BASE64DecoderStream extends FilterInputStream {
+    // buffer of decoded bytes for single byte reads
+    private byte[] buffer = new byte[3];
+    private int bufsize = 0;	// size of the cache
+    private int index = 0;	// index into the cache
+
+    // buffer for almost 8K of typical 76 chars + CRLF lines,
+    // used by getByte method.  this buffer contains encoded bytes.
+    private byte[] input_buffer = new byte[78*105];
+    private int input_pos = 0;
+    private int input_len = 0;;
+
+    private boolean ignoreErrors = false;
+
+    /** 
+     * Create a BASE64 decoder that decodes the specified input stream.
+     * The System property <code>mail.mime.base64.ignoreerrors</code>
+     * controls whether errors in the encoded data cause an exception
+     * or are ignored.  The default is false (errors cause exception).
+     *
+     * @param in	the input stream
+     */
+    public BASE64DecoderStream(InputStream in) {
+	super(in);
+	// default to false
+	ignoreErrors = PropUtil.getBooleanSystemProperty(
+	    "mail.mime.base64.ignoreerrors", false);
+    }
+
+    /** 
+     * Create a BASE64 decoder that decodes the specified input stream.
+     *
+     * @param in	the input stream
+     * @param ignoreErrors	ignore errors in encoded data?
+     */
+    public BASE64DecoderStream(InputStream in, boolean ignoreErrors) {
+	super(in);
+	this.ignoreErrors = ignoreErrors;
+    }
+
+    /**
+     * Read the next decoded byte from this input stream. The byte
+     * is returned as an <code>int</code> in the range <code>0</code> 
+     * to <code>255</code>. If no byte is available because the end of 
+     * the stream has been reached, the value <code>-1</code> is returned.
+     * This method blocks until input data is available, the end of the 
+     * stream is detected, or an exception is thrown.
+     *
+     * @return     next byte of data, or <code>-1</code> if the end of the
+     *             stream is reached.
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#in
+     */
+    @Override
+    public int read() throws IOException {
+	if (index >= bufsize) {
+	    bufsize = decode(buffer, 0, buffer.length);
+	    if (bufsize <= 0) // buffer is empty
+		return -1;
+	    index = 0; // reset index into buffer
+	}
+	return buffer[index++] & 0xff; // Zero off the MSB
+    }
+
+    /**
+     * Reads up to <code>len</code> decoded bytes of data from this input stream
+     * into an array of bytes. This method blocks until some input is
+     * available.
+     * <p>
+     *
+     * @param      buf   the buffer into which the data is read.
+     * @param      off   the start offset of the data.
+     * @param      len   the maximum number of bytes read.
+     * @return     the total number of bytes read into the buffer, or
+     *             <code>-1</code> if there is no more data because the end of
+     *             the stream has been reached.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public int read(byte[] buf, int off, int len) throws IOException {
+	// empty out single byte read buffer
+	int off0 = off;
+	while (index < bufsize && len > 0) {
+	    buf[off++] = buffer[index++];
+	    len--;
+	}
+	if (index >= bufsize)
+	    bufsize = index = 0;
+
+	int bsize = (len / 3) * 3;	// round down to multiple of 3 bytes
+	if (bsize > 0) {
+	    int size = decode(buf, off, bsize);
+	    off += size;
+	    len -= size;
+
+	    if (size != bsize) {	// hit EOF?
+		if (off == off0)	// haven't returned any data
+		    return -1;
+		else			// returned some data before hitting EOF
+		    return off - off0;
+	    }
+	}
+
+	// finish up with a partial read if necessary
+	for (; len > 0; len--) {
+	    int c = read();
+	    if (c == -1)	// EOF
+		break;
+	    buf[off++] = (byte)c;
+	}
+
+	if (off == off0)	// haven't returned any data
+	    return -1;
+	else			// returned some data before hitting EOF
+	    return off - off0;
+    }
+
+    /**
+     * Skips over and discards n bytes of data from this stream.
+     */
+    @Override
+    public long skip(long n) throws IOException {
+	long skipped = 0;
+	while (n-- > 0 && read() >= 0)
+	    skipped++;
+	return skipped;
+    }
+
+    /**
+     * Tests if this input stream supports marks. Currently this class
+     * does not support marks
+     */
+    @Override
+    public boolean markSupported() {
+	return false; // Maybe later ..
+    }
+
+    /**
+     * Returns the number of bytes that can be read from this input
+     * stream without blocking. However, this figure is only
+     * a close approximation in case the original encoded stream
+     * contains embedded CRLFs; since the CRLFs are discarded, not decoded
+     */ 
+    @Override
+    public int available() throws IOException {
+	 // This is only an estimate, since in.available()
+	 // might include CRLFs too ..
+	 return ((in.available() * 3)/4 + (bufsize-index));
+    }
+
+    /**
+     * This character array provides the character to value map
+     * based on RFC1521.
+     */  
+    private final static char pem_array[] = {
+	'A','B','C','D','E','F','G','H', // 0
+	'I','J','K','L','M','N','O','P', // 1
+	'Q','R','S','T','U','V','W','X', // 2
+	'Y','Z','a','b','c','d','e','f', // 3
+	'g','h','i','j','k','l','m','n', // 4
+	'o','p','q','r','s','t','u','v', // 5
+	'w','x','y','z','0','1','2','3', // 6
+	'4','5','6','7','8','9','+','/'  // 7
+    };
+
+    private final static byte pem_convert_array[] = new byte[256];
+
+    static {
+	for (int i = 0; i < 255; i++)
+	    pem_convert_array[i] = -1;
+	for (int i = 0; i < pem_array.length; i++)
+	    pem_convert_array[pem_array[i]] = (byte)i;
+    }
+
+    /**
+     * The decoder algorithm.  Most of the complexity here is dealing
+     * with error cases.  Returns the number of bytes decoded, which
+     * may be zero.  Decoding is done by filling an int with 4 6-bit
+     * values by shifting them in from the bottom and then extracting
+     * 3 8-bit bytes from the int by shifting them out from the bottom.
+     *
+     * @param	outbuf	the buffer into which to put the decoded bytes
+     * @param	pos	position in the buffer to start filling
+     * @param	len	the number of bytes to fill
+     * @return		the number of bytes filled, always a multiple
+     *			of three, and may be zero
+     * @exception	IOException	if the data is incorrectly formatted
+     */
+    private int decode(byte[] outbuf, int pos, int len) throws IOException {
+	int pos0 = pos;
+	while (len >= 3) {
+	    /*
+	     * We need 4 valid base64 characters before we start decoding.
+	     * We skip anything that's not a valid base64 character (usually
+	     * just CRLF).
+	     */
+	    int got = 0;
+	    int val = 0;
+	    while (got < 4) {
+		int i = getByte();
+		if (i == -1 || i == -2) {
+		    boolean atEOF;
+		    if (i == -1) {
+			if (got == 0)
+			    return pos - pos0;
+			if (!ignoreErrors)
+			    throw new DecodingException(
+				"BASE64Decoder: Error in encoded stream: " +
+				"needed 4 valid base64 characters " +
+				"but only got " + got + " before EOF" +
+				recentChars());
+			atEOF = true;	// don't read any more
+		    } else {	// i == -2
+			// found a padding character, we're at EOF
+			// XXX - should do something to make EOF "sticky"
+			if (got < 2 && !ignoreErrors)
+			    throw new DecodingException(
+				"BASE64Decoder: Error in encoded stream: " +
+				"needed at least 2 valid base64 characters," +
+				" but only got " + got +
+				" before padding character (=)" +
+				recentChars());
+
+			// didn't get any characters before padding character?
+			if (got == 0)
+			    return pos - pos0;
+			atEOF = false;	// need to keep reading
+		    }
+
+		    // pad partial result with zeroes
+
+		    // how many bytes will we produce on output?
+		    // (got always < 4, so size always < 3)
+		    int size = got - 1;
+		    if (size == 0)
+			size = 1;
+
+		    // handle the one padding character we've seen
+		    got++;
+		    val <<= 6;
+
+		    while (got < 4) {
+			if (!atEOF) {
+			    // consume the rest of the padding characters,
+			    // filling with zeroes
+			    i = getByte();
+			    if (i == -1) {
+				if (!ignoreErrors)
+				    throw new DecodingException(
+					"BASE64Decoder: Error in encoded " +
+					"stream: hit EOF while looking for " +
+					"padding characters (=)" +
+					recentChars());
+			    } else if (i != -2) {
+				if (!ignoreErrors)
+				    throw new DecodingException(
+					"BASE64Decoder: Error in encoded " +
+					"stream: found valid base64 " +
+					"character after a padding character " +
+					"(=)" + recentChars());
+			    }
+			}
+			val <<= 6;
+			got++;
+		    }
+
+		    // now pull out however many valid bytes we got
+		    val >>= 8;		// always skip first one
+		    if (size == 2)
+			outbuf[pos + 1] = (byte)(val & 0xff);
+		    val >>= 8;
+		    outbuf[pos] = (byte)(val & 0xff);
+		    // len -= size;	// not needed, return below
+		    pos += size;
+		    return pos - pos0;
+		} else {
+		    // got a valid byte
+		    val <<= 6;
+		    got++;
+		    val |= i;
+		}
+	    }
+
+	    // read 4 valid characters, now extract 3 bytes
+	    outbuf[pos + 2] = (byte)(val & 0xff);
+	    val >>= 8;
+	    outbuf[pos + 1] = (byte)(val & 0xff);
+	    val >>= 8;
+	    outbuf[pos] = (byte)(val & 0xff);
+	    len -= 3;
+	    pos += 3;
+	}
+	return pos - pos0;
+    }
+
+    /**
+     * Read the next valid byte from the input stream.
+     * Buffer lots of data from underlying stream in input_buffer,
+     * for efficiency.
+     *
+     * @return	the next byte, -1 on EOF, or -2 if next byte is '='
+     *		(padding at end of encoded data)
+     */
+    private int getByte() throws IOException {
+	int c;
+	do {
+	    if (input_pos >= input_len) {
+		try {
+		    input_len = in.read(input_buffer);
+		} catch (EOFException ex) {
+		    return -1;
+		}
+		if (input_len <= 0)
+		    return -1;
+		input_pos = 0;
+	    }
+	    // get the next byte in the buffer
+	    c = input_buffer[input_pos++] & 0xff;
+	    // is it a padding byte?
+	    if (c == '=')
+		return -2;
+	    // no, convert it
+	    c = pem_convert_array[c];
+	    // loop until we get a legitimate byte
+	} while (c == -1);
+	return c;
+    }
+
+    /**
+     * Return the most recent characters, for use in an error message.
+     */
+    private String recentChars() {
+	// reach into the input buffer and extract up to 10
+	// recent characters, to help in debugging.
+	String errstr = "";
+	int nc = input_pos > 10 ? 10 : input_pos;
+	if (nc > 0) {
+	    errstr += ", the " + nc +
+			    " most recent characters were: \"";
+	    for (int k = input_pos - nc; k < input_pos; k++) {
+		char c = (char)(input_buffer[k] & 0xff);
+		switch (c) {
+		case '\r':	errstr += "\\r"; break;
+		case '\n':	errstr += "\\n"; break;
+		case '\t':	errstr += "\\t"; break;
+		default:
+		    if (c >= ' ' && c < 0177)
+			errstr += c;
+		    else
+			errstr += ("\\" + (int)c);
+		}
+	    }
+	    errstr += "\"";
+	}
+	return errstr;
+    }
+
+    /**
+     * Base64 decode a byte array.  No line breaks are allowed.
+     * This method is suitable for short strings, such as those
+     * in the IMAP AUTHENTICATE protocol, but not to decode the
+     * entire content of a MIME part.
+     *
+     * NOTE: inbuf may only contain valid base64 characters.
+     *       Whitespace is not ignored.
+     *
+     * @param	inbuf	the byte array
+     * @return		the decoded byte array
+     */
+    public static byte[] decode(byte[] inbuf) {
+	int size = (inbuf.length / 4) * 3;
+	if (size == 0)
+	    return inbuf;
+
+	if (inbuf[inbuf.length - 1] == '=') {
+	    size--;
+	    if (inbuf[inbuf.length - 2] == '=')
+		size--;
+	}
+	byte[] outbuf = new byte[size];
+
+	int inpos = 0, outpos = 0;
+	size = inbuf.length;
+	while (size > 0) {
+	    int val;
+	    int osize = 3;
+	    val = pem_convert_array[inbuf[inpos++] & 0xff];
+	    val <<= 6;
+	    val |= pem_convert_array[inbuf[inpos++] & 0xff];
+	    val <<= 6;
+	    if (inbuf[inpos] != '=') // End of this BASE64 encoding
+		val |= pem_convert_array[inbuf[inpos++] & 0xff];
+	    else
+		osize--;
+	    val <<= 6;
+	    if (inbuf[inpos] != '=') // End of this BASE64 encoding
+		val |= pem_convert_array[inbuf[inpos++] & 0xff];
+	    else
+		osize--;
+	    if (osize > 2)
+		outbuf[outpos + 2] = (byte)(val & 0xff);
+	    val >>= 8;
+	    if (osize > 1)
+		outbuf[outpos + 1] = (byte)(val & 0xff);
+	    val >>= 8;
+	    outbuf[outpos] = (byte)(val & 0xff);
+	    outpos += osize;
+	    size -= 4;
+	}
+	return outbuf;
+    }
+
+    /*** begin TEST program ***
+    public static void main(String argv[]) throws Exception {
+    	FileInputStream infile = new FileInputStream(argv[0]);
+	BASE64DecoderStream decoder = new BASE64DecoderStream(infile);
+	int c;
+
+	while ((c = decoder.read()) != -1)
+	    System.out.print((char)c);
+	System.out.flush();
+    }
+    *** end TEST program ***/
+}
diff --git a/mail/src/main/java/com/sun/mail/util/BASE64EncoderStream.java b/mail/src/main/java/com/sun/mail/util/BASE64EncoderStream.java
new file mode 100644
index 0000000..036b534
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/BASE64EncoderStream.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a BASE64 encoder.  It is implemented as
+ * a FilterOutputStream, so one can just wrap this class around
+ * any output stream and write bytes into this filter.  The encoding
+ * is done as the bytes are written out.
+ * 
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class BASE64EncoderStream extends FilterOutputStream {
+    private byte[] buffer; 	// cache of bytes that are yet to be encoded
+    private int bufsize = 0;	// size of the cache
+    private byte[] outbuf; 	// line size output buffer
+    private int count = 0; 	// number of bytes that have been output
+    private int bytesPerLine;	// number of bytes per line
+    private int lineLimit;	// number of input bytes to output bytesPerLine
+    private boolean noCRLF = false;
+
+    private static byte[] newline = new byte[] { '\r', '\n' };
+
+    /**
+     * Create a BASE64 encoder that encodes the specified output stream.
+     *
+     * @param out        the output stream
+     * @param bytesPerLine  number of bytes per line. The encoder inserts
+     * 			a CRLF sequence after the specified number of bytes,
+     *			unless bytesPerLine is Integer.MAX_VALUE, in which
+     *			case no CRLF is inserted.  bytesPerLine is rounded
+     *			down to a multiple of 4.
+     */
+    public BASE64EncoderStream(OutputStream out, int bytesPerLine) {
+	super(out);
+	buffer = new byte[3];
+	if (bytesPerLine == Integer.MAX_VALUE || bytesPerLine < 4) {
+	    noCRLF = true;
+	    bytesPerLine = 76;
+	}
+	bytesPerLine = (bytesPerLine / 4) * 4;	// Rounded down to 4n
+	this.bytesPerLine = bytesPerLine;	// save it
+        lineLimit = bytesPerLine / 4 * 3;
+
+	if (noCRLF) {
+	    outbuf = new byte[bytesPerLine];
+	} else {
+	    outbuf = new byte[bytesPerLine + 2];
+	    outbuf[bytesPerLine] = (byte)'\r';
+	    outbuf[bytesPerLine + 1] = (byte)'\n';
+	}
+    }
+
+    /**
+     * Create a BASE64 encoder that encodes the specified input stream.
+     * Inserts the CRLF sequence after outputting 76 bytes.
+     *
+     * @param out        the output stream
+     */
+    public BASE64EncoderStream(OutputStream out) {
+	this(out, 76);	
+    }
+
+    /**
+     * Encodes <code>len</code> bytes from the specified
+     * <code>byte</code> array starting at offset <code>off</code> to
+     * this output stream.
+     *
+     * @param      b     the data.
+     * @param      off   the start offset in the data.
+     * @param      len   the number of bytes to write.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public synchronized void write(byte[] b, int off, int len)
+				throws IOException {
+	int end = off + len;
+
+	// finish off incomplete coding unit
+	while (bufsize != 0 && off < end)
+	    write(b[off++]);
+
+	// finish off line
+	int blen = ((bytesPerLine - count) / 4) * 3;
+	if (off + blen <= end) {
+	    // number of bytes that will be produced in outbuf
+	    int outlen = encodedSize(blen);
+	    if (!noCRLF) {
+		outbuf[outlen++] = (byte)'\r';
+		outbuf[outlen++] = (byte)'\n';
+	    }
+	    out.write(encode(b, off, blen, outbuf), 0, outlen);
+	    off += blen;
+	    count = 0;
+	}
+
+	// do bulk encoding a line at a time.
+	for (; off + lineLimit <= end; off += lineLimit)
+	    out.write(encode(b, off, lineLimit, outbuf));
+
+	// handle remaining partial line
+	if (off + 3 <= end) {
+	    blen = end - off;
+	    blen = (blen / 3) * 3;	// round down
+	    // number of bytes that will be produced in outbuf
+	    int outlen = encodedSize(blen);
+	    out.write(encode(b, off, blen, outbuf), 0, outlen);
+	    off += blen;
+	    count += outlen;
+	}
+
+	// start next coding unit
+	for (; off < end; off++)
+	    write(b[off]);
+    }
+
+    /**
+     * Encodes <code>b.length</code> bytes to this output stream.
+     *
+     * @param      b   the data to be written.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public void write(byte[] b) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    /**
+     * Encodes the specified <code>byte</code> to this output stream.
+     *
+     * @param      c   the <code>byte</code>.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public synchronized void write(int c) throws IOException {
+	buffer[bufsize++] = (byte)c;
+	if (bufsize == 3) { // Encoding unit = 3 bytes
+	    encode();
+	    bufsize = 0;
+	}
+    }
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes
+     * to be encoded out to the stream. 
+     *
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public synchronized void flush() throws IOException {
+	if (bufsize > 0) { // If there's unencoded characters in the buffer ..
+	    encode();      // .. encode them
+	    bufsize = 0;
+	}
+	out.flush();
+    }
+
+    /**
+     * Forces any buffered output bytes to be encoded out to the stream
+     * and closes this output stream
+     */
+    @Override
+    public synchronized void close() throws IOException {
+	flush();
+	if (count > 0 && !noCRLF) {
+	    out.write(newline);
+	    out.flush();
+	}
+	out.close();
+    }
+
+    /** This array maps the characters to their 6 bit values */
+    private final static char pem_array[] = {
+	'A','B','C','D','E','F','G','H', // 0
+	'I','J','K','L','M','N','O','P', // 1
+	'Q','R','S','T','U','V','W','X', // 2
+	'Y','Z','a','b','c','d','e','f', // 3
+	'g','h','i','j','k','l','m','n', // 4
+	'o','p','q','r','s','t','u','v', // 5
+	'w','x','y','z','0','1','2','3', // 6
+	'4','5','6','7','8','9','+','/'  // 7
+    };
+
+    /**
+     * Encode the data stored in <code>buffer</code>.
+     * Uses <code>outbuf</code> to store the encoded
+     * data before writing.
+     *
+     * @exception  IOException  if an I/O error occurs.
+     */
+    private void encode() throws IOException {
+	int osize = encodedSize(bufsize);
+	out.write(encode(buffer, 0, bufsize, outbuf), 0, osize);
+	// increment count
+	count += osize;
+	// If writing out this encoded unit caused overflow,
+	// start a new line.
+	if (count >= bytesPerLine) {
+	    if (!noCRLF)
+		out.write(newline);
+	    count = 0;
+	}
+    }
+
+    /**
+     * Base64 encode a byte array.  No line breaks are inserted.
+     * This method is suitable for short strings, such as those
+     * in the IMAP AUTHENTICATE protocol, but not to encode the
+     * entire content of a MIME part.
+     *
+     * @param	inbuf	the byte array
+     * @return		the encoded byte array
+     */
+    public static byte[] encode(byte[] inbuf) {
+	if (inbuf.length == 0)
+	    return inbuf;
+	return encode(inbuf, 0, inbuf.length, null);
+    }
+
+    /**
+     * Internal use only version of encode.  Allow specifying which
+     * part of the input buffer to encode.  If outbuf is non-null,
+     * it's used as is.  Otherwise, a new output buffer is allocated.
+     */
+    private static byte[] encode(byte[] inbuf, int off, int size,
+				byte[] outbuf) {
+	if (outbuf == null)
+	    outbuf = new byte[encodedSize(size)];
+	int inpos, outpos;
+	int val;
+	for (inpos = off, outpos = 0; size >= 3; size -= 3, outpos += 4) {
+	    val = inbuf[inpos++] & 0xff;
+	    val <<= 8;
+	    val |= inbuf[inpos++] & 0xff;
+	    val <<= 8;
+	    val |= inbuf[inpos++] & 0xff;
+	    outbuf[outpos+3] = (byte)pem_array[val & 0x3f];
+	    val >>= 6;
+	    outbuf[outpos+2] = (byte)pem_array[val & 0x3f];
+	    val >>= 6;
+	    outbuf[outpos+1] = (byte)pem_array[val & 0x3f];
+	    val >>= 6;
+	    outbuf[outpos+0] = (byte)pem_array[val & 0x3f];
+	}
+	// done with groups of three, finish up any odd bytes left
+	if (size == 1) {
+	    val = inbuf[inpos++] & 0xff;
+	    val <<= 4;
+	    outbuf[outpos+3] = (byte)'=';	// pad character;
+	    outbuf[outpos+2] = (byte)'=';	// pad character;
+	    outbuf[outpos+1] = (byte)pem_array[val & 0x3f];
+	    val >>= 6;
+	    outbuf[outpos+0] = (byte)pem_array[val & 0x3f];
+	} else if (size == 2) {
+	    val = inbuf[inpos++] & 0xff;
+	    val <<= 8;
+	    val |= inbuf[inpos++] & 0xff;
+	    val <<= 2;
+	    outbuf[outpos+3] = (byte)'=';	// pad character;
+	    outbuf[outpos+2] = (byte)pem_array[val & 0x3f];
+	    val >>= 6;
+	    outbuf[outpos+1] = (byte)pem_array[val & 0x3f];
+	    val >>= 6;
+	    outbuf[outpos+0] = (byte)pem_array[val & 0x3f];
+	}
+	return outbuf;
+    }
+
+    /**
+     * Return the corresponding encoded size for the given number
+     * of bytes, not including any CRLF.
+     */
+    private static int encodedSize(int size) {
+	return ((size + 2) / 3) * 4;
+    }
+
+    /*** begin TEST program
+    public static void main(String argv[]) throws Exception {
+	FileInputStream infile = new FileInputStream(argv[0]);
+	BASE64EncoderStream encoder = new BASE64EncoderStream(System.out);
+	int c;
+
+	while ((c = infile.read()) != -1)
+	    encoder.write(c);
+	encoder.close();
+    }
+    *** end TEST program **/
+}
diff --git a/mail/src/main/java/com/sun/mail/util/BEncoderStream.java b/mail/src/main/java/com/sun/mail/util/BEncoderStream.java
new file mode 100644
index 0000000..5a43dd6
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/BEncoderStream.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a 'B' Encoder as defined by RFC2047 for
+ * encoding MIME headers. It subclasses the BASE64EncoderStream
+ * class.
+ * 
+ * @author John Mani
+ */
+
+public class BEncoderStream extends BASE64EncoderStream {
+
+    /**
+     * Create a 'B' encoder that encodes the specified input stream.
+     * @param out        the output stream
+     */
+    public BEncoderStream(OutputStream out) {
+	super(out, Integer.MAX_VALUE); // MAX_VALUE is 2^31, should
+				       // suffice (!) to indicate that
+				       // CRLFs should not be inserted
+    }
+
+    /**
+     * Returns the length of the encoded version of this byte array.
+     *
+     * @param	b	the byte array
+     * @return		the length
+     */
+    public static int encodedLength(byte[] b) {
+        return ((b.length + 2)/3) * 4;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/CRLFOutputStream.java b/mail/src/main/java/com/sun/mail/util/CRLFOutputStream.java
new file mode 100644
index 0000000..7cca3d7
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/CRLFOutputStream.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+
+/**
+ * Convert lines into the canonical format, that is, terminate lines with the
+ * CRLF sequence.
+ * 
+ * @author John Mani
+ */
+public class CRLFOutputStream extends FilterOutputStream {
+    protected int lastb = -1;
+    protected boolean atBOL = true;	// at beginning of line?
+    private static final byte[] newline = { (byte)'\r', (byte)'\n' };
+
+    public CRLFOutputStream(OutputStream os) {
+	super(os);
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+	if (b == '\r') {
+	    writeln();
+	} else if (b == '\n') {
+	    if (lastb != '\r')
+		writeln();
+	} else {
+	    out.write(b);
+	    atBOL = false;
+	}
+	lastb = b;
+    }
+
+    @Override
+    public void write(byte b[]) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    @Override
+    public void write(byte b[], int off, int len) throws IOException {
+	int start = off;
+	
+	len += off;
+	for (int i = start; i < len ; i++) {
+	    if (b[i] == '\r') {
+		out.write(b, start, i - start);
+		writeln();
+		start = i + 1;
+	    } else if (b[i] == '\n') {
+		if (lastb != '\r') {
+		    out.write(b, start, i - start);
+		    writeln();
+		}
+		start = i + 1;
+	    }
+	    lastb = b[i];
+	}
+	if ((len - start) > 0) {
+	    out.write(b, start, len - start);
+	    atBOL = false;
+	}
+    }
+
+    /*
+     * Just write out a new line, something similar to out.println()
+     */
+    public void writeln() throws IOException {
+        out.write(newline);
+	atBOL = true;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/DecodingException.java b/mail/src/main/java/com/sun/mail/util/DecodingException.java
new file mode 100644
index 0000000..fddd84f
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/DecodingException.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+
+/**
+ * A special IOException that indicates a failure to decode data due
+ * to an error in the formatting of the data.  This allows applications
+ * to distinguish decoding errors from other I/O errors.
+ *
+ * @author Bill Shannon
+ */
+
+public class DecodingException extends IOException {
+
+    private static final long serialVersionUID = -6913647794421459390L;
+
+    /**
+     * Constructor.
+     *
+     * @param	s	the exception message
+     */
+    public DecodingException(String s) {
+	super(s);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/FolderClosedIOException.java b/mail/src/main/java/com/sun/mail/util/FolderClosedIOException.java
new file mode 100644
index 0000000..5a4ef10
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/FolderClosedIOException.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+import javax.mail.Folder;
+
+/**
+ * A variant of FolderClosedException that can be thrown from methods
+ * that only throw IOException.  The getContent method will catch this
+ * exception and translate it back to FolderClosedException.
+ *
+ * @author Bill Shannon
+ */
+
+public class FolderClosedIOException extends IOException {
+    transient private Folder folder;
+
+    private static final long serialVersionUID = 4281122580365555735L;
+    
+    /**
+     * Constructor
+     * @param folder	the Folder
+     */
+    public FolderClosedIOException(Folder folder) {
+	this(folder, null);
+    }
+
+    /**
+     * Constructor
+     * @param folder 	the Folder
+     * @param message	the detailed error message
+     */
+    public FolderClosedIOException(Folder folder, String message) {
+	super(message);
+	this.folder = folder;
+    }
+
+    /**
+     * Returns the dead Folder object
+     *
+     * @return	the dead Folder
+     */
+    public Folder getFolder() {
+	return folder;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/LineInputStream.java b/mail/src/main/java/com/sun/mail/util/LineInputStream.java
new file mode 100644
index 0000000..9cb7fb6
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/LineInputStream.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * This class is to support reading CRLF terminated lines that
+ * contain only US-ASCII characters from an input stream. Provides
+ * functionality that is similar to the deprecated 
+ * <code>DataInputStream.readLine()</code>. Expected use is to read
+ * lines as String objects from a RFC822 stream.
+ *
+ * It is implemented as a FilterInputStream, so one can just wrap 
+ * this class around any input stream and read bytes from this filter.
+ * 
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class LineInputStream extends FilterInputStream {
+
+    private boolean allowutf8;
+    private byte[] lineBuffer = null; // reusable byte buffer
+    private static int MAX_INCR = 1024*1024;	// 1MB
+
+    public LineInputStream(InputStream in) {
+	this(in, false);
+    }
+
+    /**
+     * @param	in	the InputStream
+     * @param	allowutf8	allow UTF-8 characters?
+     * @since	JavaMail 1.6
+     */
+    public LineInputStream(InputStream in, boolean allowutf8) {
+	super(in);
+	this.allowutf8 = allowutf8;
+    }
+
+    /**
+     * Read a line containing only ASCII characters from the input 
+     * stream. A line is terminated by a CR or NL or CR-NL sequence.
+     * A common error is a CR-CR-NL sequence, which will also terminate
+     * a line.
+     * The line terminator is not returned as part of the returned 
+     * String. Returns null if no data is available. <p>
+     *
+     * This class is similar to the deprecated 
+     * <code>DataInputStream.readLine()</code>
+     *
+     * @return		the line
+     * @exception	IOException	for I/O errors
+     */
+    @SuppressWarnings("deprecation")	// for old String constructor
+    public String readLine() throws IOException {
+	//InputStream in = this.in;
+	byte[] buf = lineBuffer;
+
+	if (buf == null)
+	    buf = lineBuffer = new byte[128];
+
+	int c1;
+	int room = buf.length;
+	int offset = 0;
+
+	while ((c1 = in.read()) != -1) {
+	    if (c1 == '\n') // Got NL, outa here.
+		break;
+	    else if (c1 == '\r') {
+		// Got CR, is the next char NL ?
+		boolean twoCRs = false;
+		if (in.markSupported())
+		    in.mark(2);
+		int c2 = in.read();
+		if (c2 == '\r') {		// discard extraneous CR
+		    twoCRs = true;
+		    c2 = in.read();
+		}
+		if (c2 != '\n') {
+		    /*
+		     * If the stream supports it (which we hope will always
+		     * be the case), reset to after the first CR.  Otherwise,
+		     * we wrap a PushbackInputStream around the stream so we
+		     * can unread the characters we don't need.  The only
+		     * problem with that is that the caller might stop
+		     * reading from this LineInputStream, throw it away,
+		     * and then start reading from the underlying stream.
+		     * If that happens, the pushed back characters will be
+		     * lost forever.
+		     */
+		    if (in.markSupported())
+			in.reset();
+		    else {
+			if (!(in instanceof PushbackInputStream))
+			    in /*= this.in*/ = new PushbackInputStream(in, 2);
+			if (c2 != -1)
+			    ((PushbackInputStream)in).unread(c2);
+			if (twoCRs)
+			    ((PushbackInputStream)in).unread('\r');
+		    }
+		}
+		break; // outa here.
+	    }
+
+	    // Not CR, NL or CR-NL ...
+	    // .. Insert the byte into our byte buffer
+	    if (--room < 0) { // No room, need to grow.
+		if (buf.length < MAX_INCR)
+		    buf = new byte[buf.length * 2];
+		else
+		    buf = new byte[buf.length + MAX_INCR];
+		room = buf.length - offset - 1;
+		System.arraycopy(lineBuffer, 0, buf, 0, offset);
+		lineBuffer = buf;
+	    }
+	    buf[offset++] = (byte)c1;
+	}
+
+	if ((c1 == -1) && (offset == 0))
+	    return null;
+
+	if (allowutf8)
+	    return new String(buf, 0, offset, StandardCharsets.UTF_8);
+	else
+	    return new String(buf, 0, 0, offset);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/LineOutputStream.java b/mail/src/main/java/com/sun/mail/util/LineOutputStream.java
new file mode 100644
index 0000000..d86f5ec
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/LineOutputStream.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * This class is to support writing out Strings as a sequence of bytes
+ * terminated by a CRLF sequence. The String must contain only US-ASCII
+ * characters.<p>
+ *
+ * The expected use is to write out RFC822 style headers to an output
+ * stream. <p>
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class LineOutputStream extends FilterOutputStream {
+    private boolean allowutf8;
+
+    private static byte[] newline;
+
+    static {
+	newline = new byte[2];
+	newline[0] = (byte)'\r';
+	newline[1] = (byte)'\n';
+    }
+
+    public LineOutputStream(OutputStream out) {
+	this(out, false);
+    }
+
+    /**
+     * @param	out	the OutputStream
+     * @param	allowutf8	allow UTF-8 characters?
+     * @since	JavaMail 1.6
+     */
+    public LineOutputStream(OutputStream out, boolean allowutf8) {
+	super(out);
+	this.allowutf8 = allowutf8;
+    }
+
+    public void writeln(String s) throws IOException {
+	byte[] bytes;
+	if (allowutf8)
+	    bytes = s.getBytes(StandardCharsets.UTF_8);
+	else
+	    bytes = ASCIIUtility.getBytes(s);
+	out.write(bytes);
+	out.write(newline);
+    }
+
+    public void writeln() throws IOException {
+	out.write(newline);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/LogOutputStream.java b/mail/src/main/java/com/sun/mail/util/LogOutputStream.java
new file mode 100644
index 0000000..c1c3153
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/LogOutputStream.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2008, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.logging.Level;
+
+/**
+ * Capture output lines and send them to the mail logger.
+ */
+public class LogOutputStream extends OutputStream {
+    protected MailLogger logger;
+    protected Level level;
+
+    private int lastb = -1;
+    private byte[] buf = new byte[80];
+    private int pos = 0;
+
+    /**
+     * Log to the specified logger.
+     *
+     * @param	logger	the MailLogger
+     */
+    public LogOutputStream(MailLogger logger) {
+	this.logger = logger;
+	this.level = Level.FINEST;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+	if (!logger.isLoggable(level))
+	    return;
+
+	if (b == '\r') {
+	    logBuf();
+	} else if (b == '\n') {
+	    if (lastb != '\r')
+		logBuf();
+	} else {
+	    expandCapacity(1);
+	    buf[pos++] = (byte)b;
+	}
+	lastb = b;
+    }
+
+    @Override
+    public void write(byte b[]) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    @Override
+    public void write(byte b[], int off, int len) throws IOException {
+	int start = off;
+	
+	if (!logger.isLoggable(level))
+	    return;
+	len += off;
+	for (int i = start; i < len ; i++) {
+	    if (b[i] == '\r') {
+		expandCapacity(i - start);
+		System.arraycopy(b, start, buf, pos, i - start);
+		pos += i - start;
+		logBuf();
+		start = i + 1;
+	    } else if (b[i] == '\n') {
+		if (lastb != '\r') {
+		    expandCapacity(i - start);
+		    System.arraycopy(b, start, buf, pos, i - start);
+		    pos += i - start;
+		    logBuf();
+		}
+		start = i + 1;
+	    }
+	    lastb = b[i];
+	}
+	if ((len - start) > 0) {
+	    expandCapacity(len - start);
+	    System.arraycopy(b, start, buf, pos, len - start);
+	    pos += len - start;
+	}
+    }
+
+    /**
+     * Log the specified message.
+     * Can be overridden by subclass to do different logging.
+     *
+     * @param	msg	the message to log
+     */
+    protected void log(String msg) {
+	logger.log(level, msg);
+    }
+
+    /**
+     * Convert the buffer to a string and log it.
+     */
+    private void logBuf() {
+	String msg = new String(buf, 0, pos);
+	pos = 0;
+	log(msg);
+    }
+
+    /**
+     * Ensure that the buffer can hold at least len bytes
+     * beyond the current position.
+     */
+    private void expandCapacity(int len) {
+	while (pos + len > buf.length) {
+	    byte[] nb = new byte[buf.length * 2];
+	    System.arraycopy(buf, 0, nb, 0, pos);
+	    buf = nb;
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/MailConnectException.java b/mail/src/main/java/com/sun/mail/util/MailConnectException.java
new file mode 100644
index 0000000..768e90f
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/MailConnectException.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import javax.mail.MessagingException;
+
+/**
+ * A MessagingException that indicates a socket connection attempt failed.
+ * Unlike java.net.ConnectException, it includes details of what we
+ * were trying to connect to.  The underlying exception is available
+ * as the "cause" of this exception.
+ *
+ * @see		java.net.ConnectException
+ * @author	Bill Shannon
+ * @since 	JavaMail 1.5.0
+ */
+
+public class MailConnectException extends MessagingException {
+    private String host;
+    private int port;
+    private int cto;
+
+    private static final long serialVersionUID = -3818807731125317729L;
+
+    /**
+     * Constructs a MailConnectException.
+     *
+     * @param	cex	the SocketConnectException with the details
+     */
+    public MailConnectException(SocketConnectException cex) {
+	super(
+	    "Couldn't connect to host, port: " +
+	    cex.getHost() + ", " + cex.getPort() +
+	    "; timeout " + cex.getConnectionTimeout() +
+	    (cex.getMessage() != null ? ("; " + cex.getMessage()) : ""));
+	// extract the details and save them here
+	this.host = cex.getHost();
+	this.port = cex.getPort();
+	this.cto = cex.getConnectionTimeout();
+	setNextException(cex.getException());
+    }
+
+    /**
+     * The host we were trying to connect to.
+     *
+     * @return	the host
+     */
+    public String getHost() {
+	return host;
+    }
+
+    /**
+     * The port we were trying to connect to.
+     *
+     * @return	the port
+     */
+    public int getPort() {
+	return port;
+    }
+
+    /**
+     * The timeout used for the connection attempt.
+     *
+     * @return	the connection timeout
+     */
+    public int getConnectionTimeout() {
+	return cto;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/MailLogger.java b/mail/src/main/java/com/sun/mail/util/MailLogger.java
new file mode 100644
index 0000000..29363ca
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/MailLogger.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.PrintStream;
+import java.text.MessageFormat;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.mail.Session;
+
+/**
+ * A simplified logger used by JavaMail to handle logging to a
+ * PrintStream and logging through a java.util.logging.Logger.
+ * If debug is set, messages are written to the PrintStream and
+ * prefixed by the specified prefix (which is not included in
+ * Logger messages).
+ * Messages are logged by the Logger based on the configuration
+ * of the logging system.
+ */
+
+/*
+ * It would be so much simpler to just subclass Logger and override
+ * the log(LogRecord) method, as the javadocs suggest, but that doesn't
+ * work because Logger makes the decision about whether to log the message
+ * or not before calling the log(LogRecord) method.  Instead, we just
+ * provide the few log methods we need here.
+ */
+
+public final class MailLogger {
+    /**
+     * For log messages.
+     */
+    private final Logger logger;
+    /**
+     * For debug output.
+     */
+    private final String prefix;
+    /**
+     * Produce debug output?
+     */
+    private final boolean debug;
+    /**
+     * Stream for debug output.
+     */
+    private final PrintStream out;
+
+    /**
+     * Construct a new MailLogger using the specified Logger name,
+     * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
+     *
+     * @param	name	the Logger name
+     * @param	prefix	the prefix for debug output, or null for none
+     * @param	debug	if true, write to PrintStream
+     * @param	out	the PrintStream to write to
+     */
+    public MailLogger(String name, String prefix, boolean debug,
+				PrintStream out) {
+	logger = Logger.getLogger(name);
+	this.prefix = prefix;
+	this.debug = debug;
+	this.out = out != null ? out : System.out;
+    }
+
+    /**
+     * Construct a new MailLogger using the specified class' package
+     * name as the Logger name,
+     * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
+     *
+     * @param	clazz	the Logger name is the package name of this class
+     * @param	prefix	the prefix for debug output, or null for none
+     * @param	debug	if true, write to PrintStream
+     * @param	out	the PrintStream to write to
+     */
+    public MailLogger(Class<?> clazz, String prefix, boolean debug,
+				PrintStream out) {
+	String name = packageOf(clazz);
+	logger = Logger.getLogger(name);
+	this.prefix = prefix;
+	this.debug = debug;
+	this.out = out != null ? out : System.out;
+    }
+
+    /**
+     * Construct a new MailLogger using the specified class' package
+     * name combined with the specified subname as the Logger name,
+     * debug prefix (e.g., "DEBUG"), debug flag, and PrintStream.
+     *
+     * @param	clazz	the Logger name is the package name of this class
+     * @param	subname	the Logger name relative to this Logger name
+     * @param	prefix	the prefix for debug output, or null for none
+     * @param	debug	if true, write to PrintStream
+     * @param	out	the PrintStream to write to
+     */
+    public MailLogger(Class<?> clazz, String subname, String prefix, boolean debug,
+				PrintStream out) {
+	String name = packageOf(clazz) + "." + subname;
+	logger = Logger.getLogger(name);
+	this.prefix = prefix;
+	this.debug = debug;
+	this.out = out != null ? out : System.out;
+    }
+
+    /**
+     * Construct a new MailLogger using the specified Logger name and
+     * debug prefix (e.g., "DEBUG").  Get the debug flag and PrintStream
+     * from the Session.
+     *
+     * @param	name	the Logger name
+     * @param	prefix	the prefix for debug output, or null for none
+     * @param	session	where to get the debug flag and PrintStream
+     */
+    @Deprecated
+    public MailLogger(String name, String prefix, Session session) {
+	this(name, prefix, session.getDebug(), session.getDebugOut());
+    }
+
+    /**
+     * Construct a new MailLogger using the specified class' package
+     * name as the Logger name and the specified
+     * debug prefix (e.g., "DEBUG").  Get the debug flag and PrintStream
+     * from the Session.
+     *
+     * @param	clazz	the Logger name is the package name of this class
+     * @param	prefix	the prefix for debug output, or null for none
+     * @param	session	where to get the debug flag and PrintStream
+     */
+    @Deprecated
+    public MailLogger(Class<?> clazz, String prefix, Session session) {
+	this(clazz, prefix, session.getDebug(), session.getDebugOut());
+    }
+
+    /**
+     * Create a MailLogger that uses a Logger with the specified name
+     * and prefix.  The new MailLogger uses the same debug flag and
+     * PrintStream as this MailLogger.
+     *
+     * @param	name	the Logger name
+     * @param	prefix	the prefix for debug output, or null for none
+     * @return a MailLogger for the given name and prefix.
+     */
+    public MailLogger getLogger(String name, String prefix) {
+	return new MailLogger(name, prefix, debug, out);
+    }
+
+    /**
+     * Create a MailLogger using the specified class' package
+     * name as the Logger name and the specified prefix.
+     * The new MailLogger uses the same debug flag and
+     * PrintStream as this MailLogger.
+     *
+     * @param	clazz	the Logger name is the package name of this class
+     * @param	prefix	the prefix for debug output, or null for none
+     * @return a MailLogger for the given name and prefix.
+     */
+    public MailLogger getLogger(Class<?> clazz, String prefix) {
+	return new MailLogger(clazz, prefix, debug, out);
+    }
+
+    /**
+     * Create a MailLogger that uses a Logger whose name is composed
+     * of this MailLogger's name plus the specified sub-name, separated
+     * by a dot.  The new MailLogger uses the new prefix for debug output.
+     * This is used primarily by the protocol trace code that wants a
+     * different prefix (none).
+     *
+     * @param	subname	the Logger name relative to this Logger name
+     * @param	prefix	the prefix for debug output, or null for none
+     * @return a MailLogger for the given name and prefix.
+     */
+    public MailLogger getSubLogger(String subname, String prefix) {
+	return new MailLogger(logger.getName() + "." + subname, prefix,
+				debug, out);
+    }
+
+    /**
+     * Create a MailLogger that uses a Logger whose name is composed
+     * of this MailLogger's name plus the specified sub-name, separated
+     * by a dot.  The new MailLogger uses the new prefix for debug output.
+     * This is used primarily by the protocol trace code that wants a
+     * different prefix (none).
+     *
+     * @param	subname	the Logger name relative to this Logger name
+     * @param	prefix	the prefix for debug output, or null for none
+     * @param	debug	the debug flag for the sub-logger
+     * @return a MailLogger for the given name and prefix.
+     */
+    public MailLogger getSubLogger(String subname, String prefix,
+				boolean debug) {
+	return new MailLogger(logger.getName() + "." + subname, prefix,
+				debug, out);
+    }
+
+    /**
+     * Log the message at the specified level.
+     * @param level the log level.
+     * @param msg the message.
+     */
+    public void log(Level level, String msg) {
+	ifDebugOut(msg);
+	if (logger.isLoggable(level)) {
+	    final StackTraceElement frame = inferCaller();
+	    logger.logp(level, frame.getClassName(), frame.getMethodName(), msg);
+	}
+    }
+
+    /**
+     * Log the message at the specified level.
+     * @param level the log level.
+     * @param msg the message.
+     * @param param1 the additional parameter.
+     */
+    public void log(Level level, String msg, Object param1) {
+	if (debug) {
+	    msg = MessageFormat.format(msg, new Object[] { param1 });
+	    debugOut(msg);
+	}
+	
+	if (logger.isLoggable(level)) {
+	    final StackTraceElement frame = inferCaller();
+	    logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, param1);
+	}
+    }
+
+    /**
+     * Log the message at the specified level.
+     * @param level the log level.
+     * @param msg the message.
+     * @param params the message parameters.
+     */
+    public void log(Level level, String msg, Object... params) {
+	if (debug) {
+	    msg = MessageFormat.format(msg, params);
+	    debugOut(msg);
+	}
+	
+	if (logger.isLoggable(level)) {
+	    final StackTraceElement frame = inferCaller();
+	    logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, params);
+	}
+    }
+
+    /**
+     * Log the message at the specified level using a format string.
+     * @param level the log level.
+     * @param msg the message format string.
+     * @param params the message parameters.
+     *
+     * @since	JavaMail 1.5.4
+     */
+    public void logf(Level level, String msg, Object... params) {
+	msg = String.format(msg, params);
+	ifDebugOut(msg);
+	logger.log(level, msg);
+    }
+
+    /**
+     * Log the message at the specified level.
+     * @param level the log level.
+     * @param msg the message.
+     * @param thrown the throwable to log.
+     */
+    public void log(Level level, String msg, Throwable thrown) {
+	if (debug) {
+	    if (thrown != null) {
+		debugOut(msg + ", THROW: ");
+		thrown.printStackTrace(out);
+	    } else {
+		debugOut(msg);
+	    }
+	}
+ 
+	if (logger.isLoggable(level)) {
+	    final StackTraceElement frame = inferCaller();
+	    logger.logp(level, frame.getClassName(), frame.getMethodName(), msg, thrown);
+	}
+    }
+
+    /**
+     * Log a message at the CONFIG level.
+     * @param msg the message.
+     */
+    public void config(String msg) {
+	log(Level.CONFIG, msg);
+    }
+
+    /**
+     * Log a message at the FINE level.
+     * @param msg the message.
+     */
+    public void fine(String msg) {
+	log(Level.FINE, msg);
+    }
+
+    /**
+     * Log a message at the FINER level.
+     * @param msg the message.
+     */
+    public void finer(String msg) {
+	log(Level.FINER, msg);
+    }
+
+    /**
+     * Log a message at the FINEST level.
+     * @param msg the message.
+     */
+    public void finest(String msg) {
+	log(Level.FINEST, msg);
+    }
+
+    /**
+     * If "debug" is set, or our embedded Logger is loggable at the
+     * given level, return true.
+     * @param level the log level.
+     * @return true if loggable.
+     */
+    public boolean isLoggable(Level level) {
+	return debug || logger.isLoggable(level);
+    }
+
+    /**
+     * Common code to conditionally log debug statements.
+     * @param msg the message to log.
+     */
+    private void ifDebugOut(String msg) {
+	if (debug)
+	    debugOut(msg);
+    }
+
+    /**
+     * Common formatting for debug output.
+     * @param msg the message to log.
+     */
+    private void debugOut(String msg) {
+	if (prefix != null)
+	    out.println(prefix + ": " + msg);
+	else
+	    out.println(msg);
+    }
+
+    /**
+     * Return the package name of the class.
+     * Sometimes there will be no Package object for the class,
+     * e.g., if the class loader hasn't created one (see Class.getPackage()).
+     * @param clazz the class source.
+     * @return the package name or an empty string.
+     */
+    private String packageOf(Class<?> clazz) {
+	Package p = clazz.getPackage();
+	if (p != null)
+	    return p.getName();		// hopefully the common case
+	String cname = clazz.getName();
+	int i = cname.lastIndexOf('.');
+	if (i > 0)
+	    return cname.substring(0, i);
+	// no package name, now what?
+	return "";
+    }
+
+    /**
+     * A disadvantage of not being able to use Logger directly in JavaMail
+     * code is that the "source class" information that Logger guesses will
+     * always refer to this class instead of our caller.  This method
+     * duplicates what Logger does to try to find *our* caller, so that
+     * Logger doesn't have to do it (and get the wrong answer), and because
+     * our caller is what's wanted.
+     * @return StackTraceElement that logged the message.  Treat as read-only.
+     */
+    private StackTraceElement inferCaller() {
+	// Get the stack trace.
+	StackTraceElement stack[] = (new Throwable()).getStackTrace();
+	// First, search back to a method in the Logger class.
+	int ix = 0;
+	while (ix < stack.length) {
+	    StackTraceElement frame = stack[ix];
+	    String cname = frame.getClassName();
+	    if (isLoggerImplFrame(cname)) {
+		break;
+	    }
+	    ix++;
+	}
+	// Now search for the first frame before the "Logger" class.
+	while (ix < stack.length) {
+	    StackTraceElement frame = stack[ix];
+	    String cname = frame.getClassName();
+	    if (!isLoggerImplFrame(cname)) {
+		// We've found the relevant frame.
+		return frame;
+	    }
+	    ix++;
+	}
+	// We haven't found a suitable frame, so just punt.  This is
+	// OK as we are only committed to making a "best effort" here.
+	return new StackTraceElement(MailLogger.class.getName(), "log",
+                             MailLogger.class.getName(), -1);
+    }
+    
+    /**
+     * Frames to ignore as part of the MailLogger to JUL bridge.
+     * @param cname the class name.
+     * @return true if the class name is part of the MailLogger bridge.
+     */
+    private boolean isLoggerImplFrame(String cname) {
+	return MailLogger.class.getName().equals(cname);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/MailSSLSocketFactory.java b/mail/src/main/java/com/sun/mail/util/MailSSLSocketFactory.java
new file mode 100644
index 0000000..b715f36
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/MailSSLSocketFactory.java
@@ -0,0 +1,356 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.security.cert.*;
+import java.util.*;
+
+import javax.net.ssl.*;
+
+/**
+ * An SSL socket factory that makes it easier to specify trust.
+ * This socket factory can be configured to trust all hosts or
+ * trust a specific set of hosts, in which case the server's
+ * certificate isn't verified.  Alternatively, a custom TrustManager
+ * can be supplied. <p>
+ *
+ * An instance of this factory can be set as the value of the
+ * <code>mail.&lt;protocol&gt;.ssl.socketFactory</code> property.
+ *
+ * @since	JavaMail 1.4.2
+ * @author	Stephan Sann
+ * @author	Bill Shannon
+ */
+public class MailSSLSocketFactory extends SSLSocketFactory {
+
+    /** Should all hosts be trusted? */
+    private boolean trustAllHosts;
+
+    /** String-array of trusted hosts */
+    private String[] trustedHosts = null;
+
+    /** Holds a SSLContext to get SSLSocketFactories from */
+    private SSLContext sslcontext;
+
+    /** Holds the KeyManager array to use */
+    private KeyManager[] keyManagers;
+
+    /** Holds the TrustManager array to use */
+    private TrustManager[] trustManagers;
+
+    /** Holds the SecureRandom to use */
+    private SecureRandom secureRandom;
+
+    /** Holds a SSLSocketFactory to pass all API-method-calls to */
+    private SSLSocketFactory adapteeFactory = null;
+
+    /**
+     * Initializes a new MailSSLSocketFactory.
+     * 
+     * @throws  GeneralSecurityException for security errors
+     */
+    public MailSSLSocketFactory() throws GeneralSecurityException {
+	this("TLS");
+    }
+
+    /**
+     * Initializes a new MailSSLSocketFactory with a given protocol.
+     * Normally the protocol will be specified as "TLS".
+     * 
+     * @param   protocol  The protocol to use
+     * @throws  NoSuchAlgorithmException if given protocol is not supported
+     * @throws  GeneralSecurityException for security errors
+     */
+    public MailSSLSocketFactory(String protocol)
+				throws GeneralSecurityException {
+
+	// By default we do NOT trust all hosts.
+	trustAllHosts = false;
+
+	// Get an instance of an SSLContext.
+	sslcontext = SSLContext.getInstance(protocol);
+
+	// Default properties to init the SSLContext
+	keyManagers = null;
+	trustManagers = new TrustManager[] { new MailTrustManager() };
+	secureRandom = null;
+
+	// Assemble a default SSLSocketFactory to delegate all API-calls to.
+	newAdapteeFactory();
+    }
+
+
+    /**
+     * Gets an SSLSocketFactory based on the given (or default)
+     * KeyManager array, TrustManager array and SecureRandom and
+     * sets it to the instance var adapteeFactory.
+     *
+     * @throws	KeyManagementException for key manager errors
+     */
+    private synchronized void newAdapteeFactory()
+				throws KeyManagementException {
+	sslcontext.init(keyManagers, trustManagers, secureRandom);
+
+	// Get SocketFactory and save it in our instance var
+	adapteeFactory = sslcontext.getSocketFactory();
+    }
+
+    /**
+     * @return the keyManagers
+     */
+    public synchronized KeyManager[] getKeyManagers() {
+	return keyManagers.clone();
+    }
+
+    /**
+     * @param keyManagers the keyManagers to set
+     * @throws  GeneralSecurityException for security errors
+     */
+    public synchronized void setKeyManagers(KeyManager[] keyManagers)
+				throws GeneralSecurityException  {
+	this.keyManagers = keyManagers.clone();
+	newAdapteeFactory();
+    }
+
+    /**
+     * @return the secureRandom
+     */
+    public synchronized SecureRandom getSecureRandom() {
+	return secureRandom;
+    }
+
+    /**
+     * @param secureRandom the secureRandom to set
+     * @throws  GeneralSecurityException for security errors
+     */
+    public synchronized void setSecureRandom(SecureRandom secureRandom)
+				throws GeneralSecurityException  {
+	this.secureRandom = secureRandom;
+	newAdapteeFactory();
+    }
+
+    /**
+     * @return the trustManagers
+     */
+    public synchronized TrustManager[] getTrustManagers() {
+	return trustManagers;
+    }
+
+    /**
+     * @param trustManagers the trustManagers to set
+     * @throws  GeneralSecurityException for security errors
+     */
+    public synchronized void setTrustManagers(TrustManager[] trustManagers)
+				throws GeneralSecurityException {
+	this.trustManagers = trustManagers;
+	newAdapteeFactory();
+    }
+
+    /**
+     * @return	true if all hosts should be trusted
+     */
+    public synchronized boolean isTrustAllHosts() {
+	return trustAllHosts;
+    }
+
+    /**
+     * @param	trustAllHosts should all hosts be trusted?
+     */
+    public synchronized void setTrustAllHosts(boolean trustAllHosts) {
+	this.trustAllHosts = trustAllHosts;
+    }
+    
+    /**
+     * @return	the trusted hosts
+     */
+    public synchronized String[] getTrustedHosts() {
+	if (trustedHosts == null)
+	    return null;
+	else
+	    return trustedHosts.clone();
+    }
+
+    /**
+     * @param	trustedHosts the hosts to trust
+     */
+    public synchronized void setTrustedHosts(String[] trustedHosts) {
+	if (trustedHosts == null)
+	    this.trustedHosts = null;
+	else
+	    this.trustedHosts = trustedHosts.clone();
+    }
+
+    /**
+     * After a successful conection to the server, this method is
+     * called to ensure that the server should be trusted.
+     * 
+     * @param	server		name of the server we connected to
+     * @param   sslSocket	SSLSocket connected to the server
+     * @return  true  if "trustAllHosts" is set to true OR the server
+     *		is contained in the "trustedHosts" array;
+     */
+    public synchronized boolean isServerTrusted(String server,
+				SSLSocket sslSocket) {
+
+	//System.out.println("DEBUG: isServerTrusted host " + server);
+
+	// If "trustAllHosts" is set to true, we return true
+	if (trustAllHosts)
+	    return true;
+
+	// If the socket host is contained in the "trustedHosts" array,
+	// we return true
+	if (trustedHosts != null)
+	    return Arrays.asList(trustedHosts).contains(server); // ignore case?
+
+	// If we get here, trust of the server was verified by the trust manager
+	return true;
+    }
+
+
+    // SocketFactory methods
+
+    /* (non-Javadoc)
+     * @see javax.net.ssl.SSLSocketFactory#createSocket(java.net.Socket,
+     *						java.lang.String, int, boolean)
+     */
+    @Override
+    public synchronized Socket createSocket(Socket socket, String s, int i,
+				boolean flag) throws IOException {
+	return adapteeFactory.createSocket(socket, s, i, flag);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.ssl.SSLSocketFactory#getDefaultCipherSuites()
+     */
+    @Override
+    public synchronized String[] getDefaultCipherSuites() {
+	return adapteeFactory.getDefaultCipherSuites();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.ssl.SSLSocketFactory#getSupportedCipherSuites()
+     */
+    @Override
+    public synchronized String[] getSupportedCipherSuites() {
+	return adapteeFactory.getSupportedCipherSuites();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket()
+     */
+    @Override
+    public synchronized Socket createSocket() throws IOException {
+	return adapteeFactory.createSocket();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int,
+     *						java.net.InetAddress, int)
+     */
+    @Override
+    public synchronized Socket createSocket(InetAddress inetaddress, int i,
+			InetAddress inetaddress1, int j) throws IOException {
+	return adapteeFactory.createSocket(inetaddress, i, inetaddress1, j);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int)
+     */
+    @Override
+    public synchronized Socket createSocket(InetAddress inetaddress, int i)
+				throws IOException {
+	return adapteeFactory.createSocket(inetaddress, i);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.lang.String, int,
+     *						java.net.InetAddress, int)
+     */
+    @Override
+    public synchronized Socket createSocket(String s, int i,
+				InetAddress inetaddress, int j)
+				throws IOException, UnknownHostException {
+	return adapteeFactory.createSocket(s, i, inetaddress, j);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.lang.String, int)
+     */
+    @Override
+    public synchronized Socket createSocket(String s, int i)
+				throws IOException, UnknownHostException {
+	return adapteeFactory.createSocket(s, i);
+    }
+
+
+    // inner classes
+
+    /**
+     * A default Trustmanager.
+     * 
+     * @author  Stephan Sann
+     */
+    private class MailTrustManager implements X509TrustManager {
+
+	/** A TrustManager to pass method calls to */
+	private X509TrustManager adapteeTrustManager = null;
+
+	/**
+	 * Initializes a new TrustManager instance.
+	 */
+	private MailTrustManager() throws GeneralSecurityException {
+	    TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+	    tmf.init((KeyStore)null);
+	    adapteeTrustManager = (X509TrustManager)tmf.getTrustManagers()[0];
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.net.ssl.X509TrustManager#checkClientTrusted(
+	 *		java.security.cert.X509Certificate[], java.lang.String)
+	 */
+	@Override
+	public void checkClientTrusted(X509Certificate[] certs, String authType)
+					throws CertificateException {
+	    if (!(isTrustAllHosts() || getTrustedHosts() != null))
+		adapteeTrustManager.checkClientTrusted(certs, authType);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.net.ssl.X509TrustManager#checkServerTrusted(
+	 *		java.security.cert.X509Certificate[], java.lang.String)
+	 */
+	@Override
+	public void checkServerTrusted(X509Certificate[] certs, String authType)
+					throws CertificateException {
+
+	    if (!(isTrustAllHosts() || getTrustedHosts() != null))
+		adapteeTrustManager.checkServerTrusted(certs, authType);
+	}
+
+	/* (non-Javadoc)
+	 * @see javax.net.ssl.X509TrustManager#getAcceptedIssuers()
+	 */
+	@Override
+	public X509Certificate[] getAcceptedIssuers() {
+	    return adapteeTrustManager.getAcceptedIssuers();
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/MessageRemovedIOException.java b/mail/src/main/java/com/sun/mail/util/MessageRemovedIOException.java
new file mode 100644
index 0000000..96840f7
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/MessageRemovedIOException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+
+/**
+ * A variant of MessageRemovedException that can be thrown from methods
+ * that only throw IOException.  The getContent method will catch this
+ * exception and translate it back to MessageRemovedException.
+ *
+ * @see	   javax.mail.Message#isExpunged()
+ * @see	   javax.mail.Message#getMessageNumber()
+ * @author Bill Shannon
+ */
+
+public class MessageRemovedIOException extends IOException {
+
+    private static final long serialVersionUID = 4280468026581616424L;
+
+    /**
+     * Constructs a MessageRemovedIOException with no detail message.
+     */
+    public MessageRemovedIOException() {
+	super();
+    }
+
+    /**
+     * Constructs a MessageRemovedIOException with the specified detail message.
+     * @param s		the detail message
+     */
+    public MessageRemovedIOException(String s) {
+	super(s);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/MimeUtil.java b/mail/src/main/java/com/sun/mail/util/MimeUtil.java
new file mode 100644
index 0000000..6944249
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/MimeUtil.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.lang.reflect.*;
+import java.security.*;
+
+import javax.mail.internet.MimePart;
+
+/**
+ * General MIME-related utility methods.
+ *
+ * @author	Bill Shannon
+ * @since	JavaMail 1.4.4
+ */
+public class MimeUtil {
+
+    private static final Method cleanContentType;
+
+    static {
+	Method meth = null;
+	try {
+	    String cth = System.getProperty("mail.mime.contenttypehandler");
+	    if (cth != null) {
+		ClassLoader cl = getContextClassLoader();
+		Class<?> clsHandler = null;
+		if (cl != null) {
+		    try {
+			clsHandler = Class.forName(cth, false, cl);
+		    } catch (ClassNotFoundException cex) { }
+		}
+		if (clsHandler == null)
+		    clsHandler = Class.forName(cth);
+		meth = clsHandler.getMethod("cleanContentType",
+			new Class<?>[] { MimePart.class, String.class });
+	    }
+	} catch (ClassNotFoundException ex) {
+	    // ignore it
+	} catch (NoSuchMethodException ex) {
+	    // ignore it
+	} catch (RuntimeException ex) {
+	    // ignore it
+	} finally {
+	    cleanContentType = meth;
+	}
+    }
+
+    // No one should instantiate this class.
+    private MimeUtil() {
+    }
+
+    /**
+     * If a Content-Type handler has been specified,
+     * call it to clean up the Content-Type value.
+     *
+     * @param	mp	the MimePart
+     * @param	contentType	the Content-Type value
+     * @return		the cleaned Content-Type value
+     */
+    public static String cleanContentType(MimePart mp, String contentType) {
+	if (cleanContentType != null) {
+	    try {
+		return (String)cleanContentType.invoke(null,
+					    new Object[] { mp, contentType });
+	    } catch (Exception ex) {
+		return contentType;
+	    }
+	} else
+	    return contentType;
+    }
+
+    /**
+     * Convenience method to get our context class loader.
+     * Assert any privileges we might have and then call the
+     * Thread.getContextClassLoader method.
+     */
+    private static ClassLoader getContextClassLoader() {
+	return
+	AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+	    @Override
+	    public ClassLoader run() {
+		ClassLoader cl = null;
+		try {
+		    cl = Thread.currentThread().getContextClassLoader();
+		} catch (SecurityException ex) { }
+		return cl;
+	    }
+	});
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/PropUtil.java b/mail/src/main/java/com/sun/mail/util/PropUtil.java
new file mode 100644
index 0000000..e1b793c
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/PropUtil.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.util.*;
+import javax.mail.Session;
+
+/**
+ * Utilities to make it easier to get property values.
+ * Properties can be strings or type-specific value objects.
+ *
+ * @author Bill Shannon
+ */
+public class PropUtil {
+
+    // No one should instantiate this class.
+    private PropUtil() {
+    }
+
+    /**
+     * Get an integer valued property.
+     *
+     * @param	props	the properties
+     * @param	name	the property name
+     * @param	def	default value if property not found
+     * @return		the property value
+     */
+    public static int getIntProperty(Properties props, String name, int def) {
+	return getInt(getProp(props, name), def);
+    }
+
+    /**
+     * Get a boolean valued property.
+     *
+     * @param	props	the properties
+     * @param	name	the property name
+     * @param	def	default value if property not found
+     * @return		the property value
+     */
+    public static boolean getBooleanProperty(Properties props,
+				String name, boolean def) {
+	return getBoolean(getProp(props, name), def);
+    }
+
+    /**
+     * Get an integer valued property.
+     *
+     * @param	session	the Session
+     * @param	name	the property name
+     * @param	def	default value if property not found
+     * @return		the property value
+     */
+    @Deprecated
+    public static int getIntSessionProperty(Session session,
+				String name, int def) {
+	return getInt(getProp(session.getProperties(), name), def);
+    }
+
+    /**
+     * Get a boolean valued property.
+     *
+     * @param	session	the Session
+     * @param	name	the property name
+     * @param	def	default value if property not found
+     * @return		the property value
+     */
+    @Deprecated
+    public static boolean getBooleanSessionProperty(Session session,
+				String name, boolean def) {
+	return getBoolean(getProp(session.getProperties(), name), def);
+    }
+
+    /**
+     * Get a boolean valued System property.
+     *
+     * @param	name	the property name
+     * @param	def	default value if property not found
+     * @return		the property value
+     */
+    public static boolean getBooleanSystemProperty(String name, boolean def) {
+	try {
+	    return getBoolean(getProp(System.getProperties(), name), def);
+	} catch (SecurityException sex) {
+	    // fall through...
+	}
+
+	/*
+	 * If we can't get the entire System Properties object because
+	 * of a SecurityException, just ask for the specific property.
+	 */
+	try {
+	    String value = System.getProperty(name);
+	    if (value == null)
+		return def;
+	    if (def)
+		return !value.equalsIgnoreCase("false");
+	    else
+		return value.equalsIgnoreCase("true");
+	} catch (SecurityException sex) {
+	    return def;
+	}
+    }
+
+    /**
+     * Get the value of the specified property.
+     * If the "get" method returns null, use the getProperty method,
+     * which might cascade to a default Properties object.
+     */
+    private static Object getProp(Properties props, String name) {
+	Object val = props.get(name);
+	if (val != null)
+	    return val;
+	else
+	    return props.getProperty(name);
+    }
+
+    /**
+     * Interpret the value object as an integer,
+     * returning def if unable.
+     */
+    private static int getInt(Object value, int def) {
+	if (value == null)
+	    return def;
+	if (value instanceof String) {
+	    try {
+		return Integer.parseInt((String)value);
+	    } catch (NumberFormatException nfex) { }
+	}
+	if (value instanceof Integer)
+	    return ((Integer)value).intValue();
+	return def;
+    }
+
+    /**
+     * Interpret the value object as a boolean,
+     * returning def if unable.
+     */
+    private static boolean getBoolean(Object value, boolean def) {
+	if (value == null)
+	    return def;
+	if (value instanceof String) {
+	    /*
+	     * If the default is true, only "false" turns it off.
+	     * If the default is false, only "true" turns it on.
+	     */
+	    if (def)
+		return !((String)value).equalsIgnoreCase("false");
+	    else
+		return ((String)value).equalsIgnoreCase("true");
+	}
+	if (value instanceof Boolean)
+	    return ((Boolean)value).booleanValue();
+	return def;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/QDecoderStream.java b/mail/src/main/java/com/sun/mail/util/QDecoderStream.java
new file mode 100644
index 0000000..19ea0c6
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/QDecoderStream.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a Q Decoder as defined in RFC 2047
+ * for decoding MIME headers. It subclasses the QPDecoderStream class.
+ * 
+ * @author John Mani
+ */
+
+public class QDecoderStream extends QPDecoderStream {
+
+    /**
+     * Create a Q-decoder that decodes the specified input stream.
+     * @param in        the input stream
+     */
+    public QDecoderStream(InputStream in) {
+	super(in);
+    }
+
+    /**
+     * Read the next decoded byte from this input stream. The byte
+     * is returned as an <code>int</code> in the range <code>0</code>
+     * to <code>255</code>. If no byte is available because the end of
+     * the stream has been reached, the value <code>-1</code> is returned.
+     * This method blocks until input data is available, the end of the
+     * stream is detected, or an exception is thrown.
+     *
+     * @return     the next byte of data, or <code>-1</code> if the end of the
+     *             stream is reached.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public int read() throws IOException {
+	int c = in.read();
+
+	if (c == '_') // Return '_' as ' '
+	    return ' ';
+	else if (c == '=') {
+	    // QP Encoded atom. Get the next two bytes ..
+	    ba[0] = (byte)in.read();
+	    ba[1] = (byte)in.read();
+	    // .. and decode them
+	    try {
+		return ASCIIUtility.parseInt(ba, 0, 2, 16);
+	    } catch (NumberFormatException nex) {
+		throw new DecodingException(
+			"QDecoder: Error in QP stream " + nex.getMessage());
+	    }
+	} else
+	    return c;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/QEncoderStream.java b/mail/src/main/java/com/sun/mail/util/QEncoderStream.java
new file mode 100644
index 0000000..659897a
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/QEncoderStream.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a Q Encoder as defined by RFC 2047 for 
+ * encoding MIME headers. It subclasses the QPEncoderStream class.
+ * 
+ * @author John Mani
+ */
+
+public class QEncoderStream extends QPEncoderStream {
+
+    private String specials;
+    private static String WORD_SPECIALS = "=_?\"#$%&'(),.:;<>@[\\]^`{|}~";
+    private static String TEXT_SPECIALS = "=_?";
+
+    /**
+     * Create a Q encoder that encodes the specified input stream
+     * @param out        the output stream
+     * @param encodingWord true if we are Q-encoding a word within a
+     *			phrase.
+     */
+    public QEncoderStream(OutputStream out, boolean encodingWord) {
+	super(out, Integer.MAX_VALUE); // MAX_VALUE is 2^31, should
+				       // suffice (!) to indicate that
+				       // CRLFs should not be inserted
+				       // when encoding rfc822 headers
+
+	// a RFC822 "word" token has more restrictions than a
+	// RFC822 "text" token.
+	specials = encodingWord ? WORD_SPECIALS : TEXT_SPECIALS;
+    }
+
+    /**
+     * Encodes the specified <code>byte</code> to this output stream.
+     * @param      c   the <code>byte</code>.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public void write(int c) throws IOException {
+	c = c & 0xff; // Turn off the MSB.
+	if (c == ' ')
+	    output('_', false);
+	else if (c < 040 || c >= 0177 || specials.indexOf(c) >= 0)
+	    // Encoding required. 
+	    output(c, true);
+	else // No encoding required
+	    output(c, false);
+    }
+
+    /**
+     * Returns the length of the encoded version of this byte array.
+     *
+     * @param	b	the byte array
+     * @param	encodingWord	true if encoding words, false if encoding text
+     * @return		the length
+     */
+    public static int encodedLength(byte[] b, boolean encodingWord) {
+	int len = 0;
+	String specials = encodingWord ? WORD_SPECIALS: TEXT_SPECIALS;
+	for (int i = 0; i < b.length; i++) {
+	    int c = b[i] & 0xff; // Mask off MSB
+	    if (c < 040 || c >= 0177 || specials.indexOf(c) >= 0)
+		// needs encoding
+		len += 3; // Q-encoding is 1 -> 3 conversion
+	    else
+		len++;
+	}
+	return len;
+    }
+
+    /**** begin TEST program ***
+    public static void main(String argv[]) throws Exception {
+        FileInputStream infile = new FileInputStream(argv[0]);
+        QEncoderStream encoder = new QEncoderStream(System.out);
+        int c;
+ 
+        while ((c = infile.read()) != -1)
+            encoder.write(c);
+        encoder.close();
+    }
+    *** end TEST program ***/
+}
diff --git a/mail/src/main/java/com/sun/mail/util/QPDecoderStream.java b/mail/src/main/java/com/sun/mail/util/QPDecoderStream.java
new file mode 100644
index 0000000..284c614
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/QPDecoderStream.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a QP Decoder. It is implemented as
+ * a FilterInputStream, so one can just wrap this class around
+ * any input stream and read bytes from this filter. The decoding
+ * is done as the bytes are read out.
+ * 
+ * @author John Mani
+ */
+
+public class QPDecoderStream extends FilterInputStream {
+    protected byte[] ba = new byte[2];
+    protected int spaces = 0;
+
+    /**
+     * Create a Quoted Printable decoder that decodes the specified 
+     * input stream.
+     * @param in        the input stream
+     */
+    public QPDecoderStream(InputStream in) {
+	super(new PushbackInputStream(in, 2)); // pushback of size=2
+    }
+
+    /**
+     * Read the next decoded byte from this input stream. The byte
+     * is returned as an <code>int</code> in the range <code>0</code>
+     * to <code>255</code>. If no byte is available because the end of
+     * the stream has been reached, the value <code>-1</code> is returned.
+     * This method blocks until input data is available, the end of the
+     * stream is detected, or an exception is thrown.
+     *
+     * @return     the next byte of data, or <code>-1</code> if the end of the
+     *             stream is reached.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public int read() throws IOException {
+	if (spaces > 0) {
+	    // We have cached space characters, return one
+	    spaces--;
+	    return ' ';
+	}
+	
+	int c = in.read();
+
+	if (c == ' ') { 
+	    // Got space, keep reading till we get a non-space char
+	    while ((c = in.read()) == ' ')
+		spaces++;
+
+	    if (c == '\r' || c == '\n' || c == -1)
+		// If the non-space char is CR/LF/EOF, the spaces we got
+	    	// so far is junk introduced during transport. Junk 'em.
+		spaces = 0;
+    	    else {
+		// The non-space char is NOT CR/LF, the spaces are valid.
+		((PushbackInputStream)in).unread(c);
+		c = ' ';
+	    }
+	    return c; // return either <SPACE> or <CR/LF>
+	}
+	else if (c == '=') {
+	    // QP Encoded atom. Decode the next two bytes
+	    int a = in.read();
+
+	    if (a == '\n') {
+		/* Hmm ... not really confirming QP encoding, but lets
+		 * allow this as a LF terminated encoded line .. and
+		 * consider this a soft linebreak and recurse to fetch 
+		 * the next char.
+		 */
+		return read();
+	    } else if (a == '\r') {
+		// Expecting LF. This forms a soft linebreak to be ignored.
+		int b = in.read();
+		if (b != '\n') 
+		    /* Not really confirming QP encoding, but
+		     * lets allow this as well.
+		     */
+		    ((PushbackInputStream)in).unread(b);
+		return read();
+	    } else if (a == -1) {
+	   	// Not valid QP encoding, but we be nice and tolerant here !
+		return -1;
+	    } else {
+		ba[0] = (byte)a;
+		ba[1] = (byte)in.read();
+		try {
+		    return ASCIIUtility.parseInt(ba, 0, 2, 16);
+		} catch (NumberFormatException nex) {
+		    /*
+		    System.err.println(
+		     	"Illegal characters in QP encoded stream: " + 
+		     	ASCIIUtility.toString(ba, 0, 2)
+		    );
+		    */
+
+		    ((PushbackInputStream)in).unread(ba);
+		    return c;
+		}
+	    }
+	}
+	return c;
+    }
+
+    /**
+     * Reads up to <code>len</code> decoded bytes of data from this input stream
+     * into an array of bytes. This method blocks until some input is
+     * available.
+     * <p>
+     *
+     * @param      buf   the buffer into which the data is read.
+     * @param      off   the start offset of the data.
+     * @param      len   the maximum number of bytes read.
+     * @return     the total number of bytes read into the buffer, or
+     *             <code>-1</code> if there is no more data because the end of
+     *             the stream has been reached.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public int read(byte[] buf, int off, int len) throws IOException {
+	int i, c;
+	for (i = 0; i < len; i++) {
+	    if ((c = read()) == -1) {
+		if (i == 0) // At end of stream, so we should
+		    i = -1; // return -1 , NOT 0.
+		break;
+	    }
+	    buf[off+i] = (byte)c;
+	}
+        return i;
+    }
+
+    /**
+     * Skips over and discards n bytes of data from this stream.
+     */
+    @Override
+    public long skip(long n) throws IOException {
+	long skipped = 0;
+	while (n-- > 0 && read() >= 0)
+	    skipped++;
+	return skipped;
+    }
+
+    /**
+     * Tests if this input stream supports marks. Currently this class
+     * does not support marks
+     */
+    @Override
+    public boolean markSupported() {
+	return false;
+    }
+
+    /**
+     * Returns the number of bytes that can be read from this input
+     * stream without blocking. The QP algorithm does not permit
+     * a priori knowledge of the number of bytes after decoding, so
+     * this method just invokes the <code>available</code> method
+     * of the original input stream.
+     */
+    @Override
+    public int available() throws IOException {
+	// This is bogus ! We don't really know how much
+	// bytes are available *after* decoding
+	return in.available();
+    }
+
+    /**** begin TEST program
+    public static void main(String argv[]) throws Exception {
+        FileInputStream infile = new FileInputStream(argv[0]);
+        QPDecoderStream decoder = new QPDecoderStream(infile);
+        int c;
+ 
+        while ((c = decoder.read()) != -1)
+            System.out.print((char)c);
+        System.out.println();
+    }
+    *** end TEST program ****/
+}
diff --git a/mail/src/main/java/com/sun/mail/util/QPEncoderStream.java b/mail/src/main/java/com/sun/mail/util/QPEncoderStream.java
new file mode 100644
index 0000000..d39102d
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/QPEncoderStream.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a Quoted Printable Encoder. It is implemented as
+ * a FilterOutputStream, so one can just wrap this class around
+ * any output stream and write bytes into this filter. The Encoding
+ * is done as the bytes are written out.
+ * 
+ * @author John Mani
+ */
+
+public class QPEncoderStream extends FilterOutputStream {
+    private int count = 0; 	// number of bytes that have been output
+    private int bytesPerLine;	// number of bytes per line
+    private boolean gotSpace = false;
+    private boolean gotCR = false;
+
+    /**
+     * Create a QP encoder that encodes the specified input stream
+     * @param out        the output stream
+     * @param bytesPerLine  the number of bytes per line. The encoder
+     *                   inserts a CRLF sequence after this many number
+     *                   of bytes.
+     */
+    public QPEncoderStream(OutputStream out, int bytesPerLine) {
+	super(out);
+	// Subtract 1 to account for the '=' in the soft-return 
+	// at the end of a line
+	this.bytesPerLine = bytesPerLine - 1;
+    }
+
+    /**
+     * Create a QP encoder that encodes the specified input stream.
+     * Inserts the CRLF sequence after outputting 76 bytes.
+     * @param out        the output stream
+     */
+    public QPEncoderStream(OutputStream out) {
+	this(out, 76);	
+    }
+
+    /**
+     * Encodes <code>len</code> bytes from the specified
+     * <code>byte</code> array starting at offset <code>off</code> to
+     * this output stream.
+     *
+     * @param      b     the data.
+     * @param      off   the start offset in the data.
+     * @param      len   the number of bytes to write.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+	for (int i = 0; i < len; i++)
+	    write(b[off + i]);
+    }
+
+    /**
+     * Encodes <code>b.length</code> bytes to this output stream.
+     * @param      b   the data to be written.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public void write(byte[] b) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    /**
+     * Encodes the specified <code>byte</code> to this output stream.
+     * @param      c   the <code>byte</code>.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public void write(int c) throws IOException {
+	c = c & 0xff; // Turn off the MSB.
+	if (gotSpace) { // previous character was <SPACE>
+	    if (c == '\r' || c == '\n')
+		// if CR/LF, we need to encode the <SPACE> char
+		output(' ', true);
+	    else // no encoding required, just output the char
+		output(' ', false);
+	    gotSpace = false;
+	}
+
+	if (c == '\r') {
+	    gotCR = true;
+	    outputCRLF();
+	} else {
+	    if (c == '\n') {
+		if (gotCR) 
+		    // This is a CRLF sequence, we already output the 
+		    // corresponding CRLF when we got the CR, so ignore this
+		    ;
+		else
+		    outputCRLF();
+	    } else if (c == ' ') {
+		gotSpace = true;
+	    } else if (c < 040 || c >= 0177 || c == '=')
+		// Encoding required. 
+		output(c, true);
+	    else // No encoding required
+		output(c, false);
+	    // whatever it was, it wasn't a CR
+	    gotCR = false;
+	}
+    }
+
+    /**
+     * Flushes this output stream and forces any buffered output bytes
+     * to be encoded out to the stream.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public void flush() throws IOException {
+	if (gotSpace) {
+	    output(' ', true);
+	    gotSpace = false;
+	}
+	out.flush();
+    }
+
+    /**
+     * Forces any buffered output bytes to be encoded out to the stream
+     * and closes this output stream.
+     *
+     * @exception	IOException	for I/O errors
+     */
+    @Override
+    public void close() throws IOException {
+	flush();
+	out.close();
+    }
+
+    private void outputCRLF() throws IOException {
+	out.write('\r');
+	out.write('\n');
+	count = 0;
+    }
+
+    // The encoding table
+    private final static char hex[] = {
+	'0','1', '2', '3', '4', '5', '6', '7',
+	'8','9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    protected void output(int c, boolean encode) throws IOException {
+	if (encode) {
+	    if ((count += 3) > bytesPerLine) {
+		out.write('=');
+	    	out.write('\r');
+	    	out.write('\n');
+		count = 3; // set the next line's length
+	    }
+	    out.write('=');
+	    out.write(hex[c >> 4]);
+	    out.write(hex[c & 0xf]);
+	} else {
+	    if (++count > bytesPerLine) {
+		out.write('=');
+	    	out.write('\r');
+	    	out.write('\n');
+		count = 1; // set the next line's length
+	    }
+	    out.write(c);
+	}
+    }
+
+    /**** begin TEST program ***
+    public static void main(String argv[]) throws Exception {
+        FileInputStream infile = new FileInputStream(argv[0]);
+        QPEncoderStream encoder = new QPEncoderStream(System.out);
+        int c;
+ 
+        while ((c = infile.read()) != -1)
+            encoder.write(c);
+        encoder.close();
+    }
+    *** end TEST program ***/
+}
diff --git a/mail/src/main/java/com/sun/mail/util/ReadableMime.java b/mail/src/main/java/com/sun/mail/util/ReadableMime.java
new file mode 100644
index 0000000..ecba7e2
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/ReadableMime.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.InputStream;
+
+import javax.mail.MessagingException;
+
+/**
+ * A Message or message Part whose data can be read as a MIME format
+ * stream.  Note that the MIME stream will include both the headers
+ * and the body of the message or part.  This should be the same data
+ * that is produced by the writeTo method, but in a readable form.
+ *
+ * @author	Bill Shannon
+ * @since	JavaMail 1.4.5
+ */
+public interface ReadableMime {
+    /**
+     * Return the MIME format stream corresponding to this message part.
+     *
+     * @return	the MIME format stream
+     * @exception	MessagingException for failures
+     */
+    public InputStream getMimeStream() throws MessagingException;
+}
diff --git a/mail/src/main/java/com/sun/mail/util/SharedByteArrayOutputStream.java b/mail/src/main/java/com/sun/mail/util/SharedByteArrayOutputStream.java
new file mode 100644
index 0000000..9fd617e
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/SharedByteArrayOutputStream.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+
+import javax.mail.util.SharedByteArrayInputStream;
+
+/**
+ * A ByteArrayOutputStream that allows us to share the byte array
+ * rather than copy it.  Eventually could replace this with something
+ * that doesn't require a single contiguous byte array.
+ *
+ * @author	Bill Shannon
+ * @since	JavaMail 1.4.5
+ */
+public class SharedByteArrayOutputStream extends ByteArrayOutputStream {
+    public SharedByteArrayOutputStream(int size) {
+	super(size);
+    }
+
+    public InputStream toStream() {
+	return new SharedByteArrayInputStream(buf, 0, count);
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/SocketConnectException.java b/mail/src/main/java/com/sun/mail/util/SocketConnectException.java
new file mode 100644
index 0000000..6b3c16e
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/SocketConnectException.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+
+/**
+ * An IOException that indicates a socket connection attempt failed.
+ * Unlike java.net.ConnectException, it includes details of what we
+ * were trying to connect to.
+ *
+ * @see		java.net.ConnectException
+ * @author	Bill Shannon
+ * @since 	JavaMail 1.5.0
+ */
+
+public class SocketConnectException extends IOException {
+    /**
+     * The socket host name.
+     */
+    private String host;
+    /**
+     * The socket port.
+     */
+    private int port;
+    /**
+     * The connection timeout.
+     */
+    private int cto;
+    /**
+     * The generated serial id.
+     */
+    private static final long serialVersionUID = 3997871560538755463L;
+
+    /**
+     * Constructs a SocketConnectException.
+     *
+     * @param	msg	error message detail
+     * @param	cause	the underlying exception that indicates the failure
+     * @param	host	the host we were trying to connect to
+     * @param	port	the port we were trying to connect to
+     * @param	cto	the timeout for the connection attempt
+     */
+    public SocketConnectException(String msg, Exception cause,
+				    String host, int port, int cto) {
+	super(msg);
+	initCause(cause);
+	this.host = host;
+	this.port = port;
+	this.cto = cto;
+    }
+
+    /**
+     * The exception that caused the failure.
+     *
+     * @return	the exception
+     */
+    public Exception getException() {
+	// the "cause" is always an Exception; see constructor above
+	Throwable t = getCause();
+	assert t == null || t instanceof Exception;
+	return (Exception) t;
+    }
+
+    /**
+     * The host we were trying to connect to.
+     *
+     * @return	the host
+     */
+    public String getHost() {
+	return host;
+    }
+
+    /**
+     * The port we were trying to connect to.
+     *
+     * @return	the port
+     */
+    public int getPort() {
+	return port;
+    }
+
+    /**
+     * The timeout used for the connection attempt.
+     *
+     * @return	the connection timeout
+     */
+    public int getConnectionTimeout() {
+	return cto;
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/SocketFetcher.java b/mail/src/main/java/com/sun/mail/util/SocketFetcher.java
new file mode 100644
index 0000000..d6af220
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/SocketFetcher.java
@@ -0,0 +1,895 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.security.*;
+import java.net.*;
+import java.io.*;
+import java.nio.channels.SocketChannel;
+import java.nio.charset.StandardCharsets;
+import java.lang.reflect.*;
+import java.util.*;
+import java.util.regex.*;
+import java.util.logging.Level;
+import java.security.cert.*;
+import javax.net.*;
+import javax.net.ssl.*;
+
+/**
+ * This class is used to get Sockets. Depending on the arguments passed
+ * it will either return a plain java.net.Socket or dynamically load
+ * the SocketFactory class specified in the classname param and return
+ * a socket created by that SocketFactory.
+ *
+ * @author Max Spivak
+ * @author Bill Shannon
+ */
+public class SocketFetcher {
+
+    private static MailLogger logger = new MailLogger(
+	SocketFetcher.class,
+	"socket",
+	"DEBUG SocketFetcher",
+	PropUtil.getBooleanSystemProperty("mail.socket.debug", false),
+	System.out);
+
+    // No one should instantiate this class.
+    private SocketFetcher() {
+    }
+
+    /**
+     * This method returns a Socket.  Properties control the use of
+     * socket factories and other socket characteristics.  The properties
+     * used are:
+     * <ul>
+     * <li> <i>prefix</i>.socketFactory
+     * <li> <i>prefix</i>.socketFactory.class
+     * <li> <i>prefix</i>.socketFactory.fallback
+     * <li> <i>prefix</i>.socketFactory.port
+     * <li> <i>prefix</i>.ssl.socketFactory
+     * <li> <i>prefix</i>.ssl.socketFactory.class
+     * <li> <i>prefix</i>.ssl.socketFactory.port
+     * <li> <i>prefix</i>.timeout
+     * <li> <i>prefix</i>.connectiontimeout
+     * <li> <i>prefix</i>.localaddress
+     * <li> <i>prefix</i>.localport
+     * <li> <i>prefix</i>.usesocketchannels
+     * </ul> <p>
+     * If we're making an SSL connection, the ssl.socketFactory
+     * properties are used first, if set. <p>
+     *
+     * If the socketFactory property is set, the value is an
+     * instance of a SocketFactory class, not a string.  The
+     * instance is used directly.  If the socketFactory property
+     * is not set, the socketFactory.class property is considered.
+     * (Note that the SocketFactory property must be set using the
+     * <code>put</code> method, not the <code>setProperty</code>
+     * method.) <p>
+     *
+     * If the socketFactory.class property isn't set, the socket
+     * returned is an instance of java.net.Socket connected to the
+     * given host and port. If the socketFactory.class property is set,
+     * it is expected to contain a fully qualified classname of a
+     * javax.net.SocketFactory subclass.  In this case, the class is
+     * dynamically instantiated and a socket created by that
+     * SocketFactory is returned. <p>
+     *
+     * If the socketFactory.fallback property is set to false, don't
+     * fall back to using regular sockets if the socket factory fails. <p>
+     *
+     * The socketFactory.port specifies a port to use when connecting
+     * through the socket factory.  If unset, the port argument will be
+     * used.  <p>
+     *
+     * If the connectiontimeout property is set, the timeout is passed
+     * to the socket connect method. <p>
+     *
+     * If the timeout property is set, it is used to set the socket timeout.
+     * <p>
+     *
+     * If the localaddress property is set, it's used as the local address
+     * to bind to.  If the localport property is also set, it's used as the
+     * local port number to bind to. <p>
+     *
+     * If the usesocketchannels property is set, and we create the Socket
+     * ourself, and the selection of other properties allows, create a
+     * SocketChannel and get the Socket from it.  This allows us to later
+     * retrieve the SocketChannel from the Socket and use it with Select.
+     *
+     * @param host The host to connect to
+     * @param port The port to connect to at the host
+     * @param props Properties object containing socket properties
+     * @param prefix Property name prefix, e.g., "mail.imap"
+     * @param useSSL use the SSL socket factory as the default
+     * @return		the Socket
+     * @exception	IOException	for I/O errors
+     */
+    public static Socket getSocket(String host, int port, Properties props,
+				String prefix, boolean useSSL)
+				throws IOException {
+
+	if (logger.isLoggable(Level.FINER))
+	    logger.finer("getSocket" + ", host " + host + ", port " + port +
+				", prefix " + prefix + ", useSSL " + useSSL);
+	if (prefix == null)
+	    prefix = "socket";
+	if (props == null)
+	    props = new Properties();	// empty
+	int cto = PropUtil.getIntProperty(props,
+					prefix + ".connectiontimeout", -1);
+	Socket socket = null;
+	String localaddrstr = props.getProperty(prefix + ".localaddress", null);
+	InetAddress localaddr = null;
+	if (localaddrstr != null)
+	    localaddr = InetAddress.getByName(localaddrstr);
+	int localport = PropUtil.getIntProperty(props,
+					prefix + ".localport", 0);
+
+	boolean fb = PropUtil.getBooleanProperty(props,
+				prefix + ".socketFactory.fallback", true);
+
+	int sfPort = -1;
+	String sfErr = "unknown socket factory";
+	int to = PropUtil.getIntProperty(props, prefix + ".timeout", -1);
+	try {
+	    /*
+	     * If using SSL, first look for SSL-specific class name or
+	     * factory instance.
+	     */
+	    SocketFactory sf = null;
+	    String sfPortName = null;
+	    if (useSSL) {
+		Object sfo = props.get(prefix + ".ssl.socketFactory");
+		if (sfo instanceof SocketFactory) {
+		    sf = (SocketFactory)sfo;
+		    sfErr = "SSL socket factory instance " + sf;
+		}
+		if (sf == null) {
+		    String sfClass =
+			props.getProperty(prefix + ".ssl.socketFactory.class");
+		    sf = getSocketFactory(sfClass);
+		    sfErr = "SSL socket factory class " + sfClass;
+		}
+		sfPortName = ".ssl.socketFactory.port";
+	    }
+
+	    if (sf == null) {
+		Object sfo = props.get(prefix + ".socketFactory");
+		if (sfo instanceof SocketFactory) {
+		    sf = (SocketFactory)sfo;
+		    sfErr = "socket factory instance " + sf;
+		}
+		if (sf == null) {
+		    String sfClass =
+			props.getProperty(prefix + ".socketFactory.class");
+		    sf = getSocketFactory(sfClass);
+		    sfErr = "socket factory class " + sfClass;
+		}
+		sfPortName = ".socketFactory.port";
+	    }
+
+	    // if we now have a socket factory, use it
+	    if (sf != null) {
+		sfPort = PropUtil.getIntProperty(props,
+						prefix + sfPortName, -1);
+
+		// if port passed in via property isn't valid, use param
+		if (sfPort == -1)
+		    sfPort = port;
+		socket = createSocket(localaddr, localport,
+		    host, sfPort, cto, to, props, prefix, sf, useSSL);
+	    }
+	} catch (SocketTimeoutException sex) {
+	    throw sex;
+	} catch (Exception ex) {
+	    if (!fb) {
+		if (ex instanceof InvocationTargetException) {
+		    Throwable t =
+		      ((InvocationTargetException)ex).getTargetException();
+		    if (t instanceof Exception)
+			ex = (Exception)t;
+		}
+		if (ex instanceof IOException)
+		    throw (IOException)ex;
+		throw new SocketConnectException("Using " + sfErr, ex,
+						host, sfPort, cto);
+	    }
+	}
+
+	if (socket == null) {
+	    socket = createSocket(localaddr, localport,
+		    host, port, cto, to, props, prefix, null, useSSL);
+
+	} else {
+	    if (to >= 0) {
+		if (logger.isLoggable(Level.FINEST))
+		    logger.finest("set socket read timeout " + to);
+		socket.setSoTimeout(to);
+	    }
+	}
+
+	return socket;
+    }
+
+    public static Socket getSocket(String host, int port, Properties props,
+				String prefix) throws IOException {
+	return getSocket(host, port, props, prefix, false);
+    }
+
+    /**
+     * Create a socket with the given local address and connected to
+     * the given host and port.  Use the specified connection timeout
+     * and read timeout.
+     * If a socket factory is specified, use it.  Otherwise, use the
+     * SSLSocketFactory if useSSL is true.
+     */
+    private static Socket createSocket(InetAddress localaddr, int localport,
+				String host, int port, int cto, int to,
+				Properties props, String prefix,
+				SocketFactory sf, boolean useSSL)
+				throws IOException {
+	Socket socket = null;
+
+	if (logger.isLoggable(Level.FINEST))
+	    logger.finest("create socket: prefix " + prefix +
+		", localaddr " + localaddr + ", localport " + localport +
+		", host " + host + ", port " + port +
+		", connection timeout " + cto + ", timeout " + to +
+		", socket factory " + sf + ", useSSL " + useSSL);
+
+	String proxyHost = props.getProperty(prefix + ".proxy.host", null);
+	String proxyUser = props.getProperty(prefix + ".proxy.user", null);
+	String proxyPassword = props.getProperty(prefix + ".proxy.password", null);
+	int proxyPort = 80;
+	String socksHost = null;
+	int socksPort = 1080;
+	String err = null;
+
+	if (proxyHost != null) {
+	    int i = proxyHost.indexOf(':');
+	    if (i >= 0) {
+		try {
+		    proxyPort = Integer.parseInt(proxyHost.substring(i + 1));
+		} catch (NumberFormatException ex) {
+		    // ignore it
+		}
+		proxyHost = proxyHost.substring(0, i);
+	    }
+	    proxyPort = PropUtil.getIntProperty(props,
+					prefix + ".proxy.port", proxyPort);
+	    err = "Using web proxy host, port: " + proxyHost + ", " + proxyPort;
+	    if (logger.isLoggable(Level.FINER)) {
+		logger.finer("web proxy host " + proxyHost + ", port " + proxyPort);
+		if (proxyUser != null)
+		    logger.finer("web proxy user " + proxyUser + ", password " +
+			(proxyPassword == null ? "<null>" : "<non-null>"));
+	    }
+	} else if ((socksHost =
+		    props.getProperty(prefix + ".socks.host", null)) != null) {
+	    int i = socksHost.indexOf(':');
+	    if (i >= 0) {
+		try {
+		    socksPort = Integer.parseInt(socksHost.substring(i + 1));
+		} catch (NumberFormatException ex) {
+		    // ignore it
+		}
+		socksHost = socksHost.substring(0, i);
+	    }
+	    socksPort = PropUtil.getIntProperty(props,
+					prefix + ".socks.port", socksPort);
+	    err = "Using SOCKS host, port: " + socksHost + ", " + socksPort;
+	    if (logger.isLoggable(Level.FINER))
+		logger.finer("socks host " + socksHost + ", port " + socksPort);
+	}
+
+	if (sf != null && !(sf instanceof SSLSocketFactory))
+	    socket = sf.createSocket();
+	if (socket == null) {
+	    if (socksHost != null) {
+		socket = new Socket(
+				new java.net.Proxy(java.net.Proxy.Type.SOCKS,
+				new InetSocketAddress(socksHost, socksPort)));
+	    } else if (PropUtil.getBooleanProperty(props,
+					prefix + ".usesocketchannels", false)) {
+		logger.finer("using SocketChannels");
+		socket = SocketChannel.open().socket();
+	    } else
+		socket = new Socket();
+	}
+	if (to >= 0) {
+	    if (logger.isLoggable(Level.FINEST))
+		logger.finest("set socket read timeout " + to);
+	    socket.setSoTimeout(to);
+	}
+	int writeTimeout = PropUtil.getIntProperty(props,
+						prefix + ".writetimeout", -1);
+	if (writeTimeout != -1) {	// wrap original
+	    if (logger.isLoggable(Level.FINEST))
+		logger.finest("set socket write timeout " + writeTimeout);
+	    socket = new WriteTimeoutSocket(socket, writeTimeout);
+	}
+	if (localaddr != null)
+	    socket.bind(new InetSocketAddress(localaddr, localport));
+	try {
+	    logger.finest("connecting...");
+	    if (proxyHost != null)
+		proxyConnect(socket, proxyHost, proxyPort,
+				proxyUser, proxyPassword, host, port, cto);
+	    else if (cto >= 0)
+		socket.connect(new InetSocketAddress(host, port), cto);
+	    else
+		socket.connect(new InetSocketAddress(host, port));
+	    logger.finest("success!");
+	} catch (IOException ex) {
+	    logger.log(Level.FINEST, "connection failed", ex);
+	    throw new SocketConnectException(err, ex, host, port, cto);
+	}
+
+	/*
+	 * If we want an SSL connection and we didn't get an SSLSocket,
+	 * wrap our plain Socket with an SSLSocket.
+	 */
+	if ((useSSL || sf instanceof SSLSocketFactory) &&
+		!(socket instanceof SSLSocket)) {
+	    String trusted;
+	    SSLSocketFactory ssf;
+	    if ((trusted = props.getProperty(prefix + ".ssl.trust")) != null) {
+		try {
+		    MailSSLSocketFactory msf = new MailSSLSocketFactory();
+		    if (trusted.equals("*"))
+			msf.setTrustAllHosts(true);
+		    else
+			msf.setTrustedHosts(trusted.split("\\s+"));
+		    ssf = msf;
+		} catch (GeneralSecurityException gex) {
+		    IOException ioex = new IOException(
+				    "Can't create MailSSLSocketFactory");
+		    ioex.initCause(gex);
+		    throw ioex;
+		}
+	    } else if (sf instanceof SSLSocketFactory)
+		ssf = (SSLSocketFactory)sf;
+	    else
+		ssf = (SSLSocketFactory)SSLSocketFactory.getDefault();
+	    socket = ssf.createSocket(socket, host, port, true);
+	    sf = ssf;
+	}
+
+	/*
+	 * No matter how we created the socket, if it turns out to be an
+	 * SSLSocket, configure it.
+	 */
+	configureSSLSocket(socket, host, props, prefix, sf);
+
+	return socket;
+    }
+
+    /**
+     * Return a socket factory of the specified class.
+     */
+    private static SocketFactory getSocketFactory(String sfClass)
+				throws ClassNotFoundException,
+				    NoSuchMethodException,
+				    IllegalAccessException,
+				    InvocationTargetException {
+	if (sfClass == null || sfClass.length() == 0)
+	    return null;
+
+	// dynamically load the class
+
+	ClassLoader cl = getContextClassLoader();
+	Class<?> clsSockFact = null;
+	if (cl != null) {
+	    try {
+		clsSockFact = Class.forName(sfClass, false, cl);
+	    } catch (ClassNotFoundException cex) { }
+	}
+	if (clsSockFact == null)
+	    clsSockFact = Class.forName(sfClass);
+	// get & invoke the getDefault() method
+	Method mthGetDefault = clsSockFact.getMethod("getDefault",
+						     new Class<?>[]{});
+	SocketFactory sf = (SocketFactory)
+	    mthGetDefault.invoke(new Object(), new Object[]{});
+	return sf;
+    }
+
+    /**
+     * Start TLS on an existing socket.
+     * Supports the "STARTTLS" command in many protocols.
+     * This version for compatibility with possible third party code
+     * that might've used this API even though it shouldn't.
+     *
+     * @param	socket	the existing socket
+     * @return		the wrapped Socket
+     * @exception	IOException	for I/O errors
+     * @deprecated
+     */
+    @Deprecated
+    public static Socket startTLS(Socket socket) throws IOException {
+	return startTLS(socket, new Properties(), "socket");
+    }
+
+    /**
+     * Start TLS on an existing socket.
+     * Supports the "STARTTLS" command in many protocols.
+     * This version for compatibility with possible third party code
+     * that might've used this API even though it shouldn't.
+     *
+     * @param	socket	the existing socket
+     * @param	props	the properties
+     * @param	prefix	the property prefix
+     * @return		the wrapped Socket
+     * @exception	IOException	for I/O errors
+     * @deprecated
+     */
+    @Deprecated
+    public static Socket startTLS(Socket socket, Properties props,
+				String prefix) throws IOException {
+	InetAddress a = socket.getInetAddress();
+	String host = a.getHostName();
+	return startTLS(socket, host, props, prefix);
+    }
+
+    /**
+     * Start TLS on an existing socket.
+     * Supports the "STARTTLS" command in many protocols.
+     *
+     * @param	socket	the existing socket
+     * @param	host	the host the socket is connected to
+     * @param	props	the properties
+     * @param	prefix	the property prefix
+     * @return		the wrapped Socket
+     * @exception	IOException	for I/O errors
+     */
+    public static Socket startTLS(Socket socket, String host, Properties props,
+				String prefix) throws IOException {
+	int port = socket.getPort();
+	if (logger.isLoggable(Level.FINER))
+	    logger.finer("startTLS host " + host + ", port " + port);
+
+	String sfErr = "unknown socket factory";
+	try {
+	    SSLSocketFactory ssf = null;
+	    SocketFactory sf = null;
+
+	    // first, look for an SSL socket factory
+	    Object sfo = props.get(prefix + ".ssl.socketFactory");
+	    if (sfo instanceof SocketFactory) {
+		sf = (SocketFactory)sfo;
+		sfErr = "SSL socket factory instance " + sf;
+	    }
+	    if (sf == null) {
+		String sfClass =
+		    props.getProperty(prefix + ".ssl.socketFactory.class");
+		sf = getSocketFactory(sfClass);
+		sfErr = "SSL socket factory class " + sfClass;
+	    }
+	    if (sf != null && sf instanceof SSLSocketFactory)
+		ssf = (SSLSocketFactory)sf;
+
+	    // next, look for a regular socket factory that happens to be
+	    // an SSL socket factory
+	    if (ssf == null) {
+		sfo = props.get(prefix + ".socketFactory");
+		if (sfo instanceof SocketFactory) {
+		    sf = (SocketFactory)sfo;
+		    sfErr = "socket factory instance " + sf;
+		}
+		if (sf == null) {
+		    String sfClass =
+			props.getProperty(prefix + ".socketFactory.class");
+		    sf = getSocketFactory(sfClass);
+		    sfErr = "socket factory class " + sfClass;
+		}
+		if (sf != null && sf instanceof SSLSocketFactory)
+		    ssf = (SSLSocketFactory)sf;
+	    }
+
+	    // finally, use the default SSL socket factory
+	    if (ssf == null) {
+		String trusted;
+		if ((trusted = props.getProperty(prefix + ".ssl.trust")) !=
+			null) {
+		    try {
+			MailSSLSocketFactory msf = new MailSSLSocketFactory();
+			if (trusted.equals("*"))
+			    msf.setTrustAllHosts(true);
+			else
+			    msf.setTrustedHosts(trusted.split("\\s+"));
+			ssf = msf;
+			sfErr = "mail SSL socket factory";
+		    } catch (GeneralSecurityException gex) {
+			IOException ioex = new IOException(
+					"Can't create MailSSLSocketFactory");
+			ioex.initCause(gex);
+			throw ioex;
+		    }
+		} else {
+		    ssf = (SSLSocketFactory)SSLSocketFactory.getDefault();
+		    sfErr = "default SSL socket factory";
+		}
+	    }
+
+	    socket = ssf.createSocket(socket, host, port, true);
+	    configureSSLSocket(socket, host, props, prefix, ssf);
+	} catch (Exception ex) {
+	    if (ex instanceof InvocationTargetException) {
+		Throwable t =
+		  ((InvocationTargetException)ex).getTargetException();
+		if (t instanceof Exception)
+		    ex = (Exception)t;
+	    }
+	    if (ex instanceof IOException)
+		throw (IOException)ex;
+	    // wrap anything else before sending it on
+	    IOException ioex = new IOException(
+				"Exception in startTLS using " + sfErr +
+				": host, port: " +
+				host + ", " + port +
+				"; Exception: " + ex);
+	    ioex.initCause(ex);
+	    throw ioex;
+	}
+	return socket;
+    }
+
+    /**
+     * Configure the SSL options for the socket (if it's an SSL socket),
+     * based on the mail.<protocol>.ssl.protocols and
+     * mail.<protocol>.ssl.ciphersuites properties.
+     * Check the identity of the server as specified by the
+     * mail.<protocol>.ssl.checkserveridentity property.
+     */
+    private static void configureSSLSocket(Socket socket, String host,
+			Properties props, String prefix, SocketFactory sf)
+			throws IOException {
+	if (!(socket instanceof SSLSocket))
+	    return;
+	SSLSocket sslsocket = (SSLSocket)socket;
+
+	String protocols = props.getProperty(prefix + ".ssl.protocols", null);
+	if (protocols != null)
+	    sslsocket.setEnabledProtocols(stringArray(protocols));
+	else {
+	    /*
+	     * The UW IMAP server insists on at least the TLSv1
+	     * protocol for STARTTLS, and won't accept the old SSLv2
+	     * or SSLv3 protocols.  Here we enable only the non-SSL
+	     * protocols.  XXX - this should probably be parameterized.
+	     */
+	    String[] prots = sslsocket.getEnabledProtocols();
+	    if (logger.isLoggable(Level.FINER))
+		logger.finer("SSL enabled protocols before " +
+		    Arrays.asList(prots));
+	    List<String> eprots = new ArrayList<>();
+	    for (int i = 0; i < prots.length; i++) {
+		if (prots[i] != null && !prots[i].startsWith("SSL"))
+		    eprots.add(prots[i]);
+	    }
+	    sslsocket.setEnabledProtocols(
+				eprots.toArray(new String[eprots.size()]));
+	}
+	String ciphers = props.getProperty(prefix + ".ssl.ciphersuites", null);
+	if (ciphers != null)
+	    sslsocket.setEnabledCipherSuites(stringArray(ciphers));
+	if (logger.isLoggable(Level.FINER)) {
+	    logger.finer("SSL enabled protocols after " +
+		Arrays.asList(sslsocket.getEnabledProtocols()));
+	    logger.finer("SSL enabled ciphers after " +
+		Arrays.asList(sslsocket.getEnabledCipherSuites()));
+	}
+
+	/*
+	 * Force the handshake to be done now so that we can report any
+	 * errors (e.g., certificate errors) to the caller of the startTLS
+	 * method.
+	 */
+	sslsocket.startHandshake();
+
+	/*
+	 * Check server identity and trust.
+	 */
+	boolean idCheck = PropUtil.getBooleanProperty(props,
+			    prefix + ".ssl.checkserveridentity", false);
+	if (idCheck)
+	    checkServerIdentity(host, sslsocket);
+	if (sf instanceof MailSSLSocketFactory) {
+	    MailSSLSocketFactory msf = (MailSSLSocketFactory)sf;
+	    if (!msf.isServerTrusted(host, sslsocket)) {
+		throw cleanupAndThrow(sslsocket,
+			new IOException("Server is not trusted: " + host));
+	    }
+	}
+    }
+
+    private static IOException cleanupAndThrow(Socket socket, IOException ife) {
+	try {
+	    socket.close();
+	} catch (Throwable thr) {
+	    if (isRecoverable(thr)) {
+		ife.addSuppressed(thr);
+	    } else {
+		thr.addSuppressed(ife);
+		if (thr instanceof Error) {
+		    throw (Error) thr;
+		}
+		if (thr instanceof RuntimeException) {
+		    throw (RuntimeException) thr;
+		}
+		throw new RuntimeException("unexpected exception", thr);
+	    }
+	}
+	return ife;
+    }
+
+    private static boolean isRecoverable(Throwable t) {
+	return (t instanceof Exception) || (t instanceof LinkageError);
+    }
+
+    /**
+     * Check the server from the Socket connection against the server name(s)
+     * as expressed in the server certificate (RFC 2595 check).
+     *
+     * @param	server		name of the server expected
+     * @param   sslSocket	SSLSocket connected to the server
+     * @exception	IOException	if we can't verify identity of server
+     */
+    private static void checkServerIdentity(String server, SSLSocket sslSocket)
+				throws IOException {
+
+	// Check against the server name(s) as expressed in server certificate
+	try {
+	    java.security.cert.Certificate[] certChain =
+		      sslSocket.getSession().getPeerCertificates();
+	    if (certChain != null && certChain.length > 0 &&
+		    certChain[0] instanceof X509Certificate &&
+		    matchCert(server, (X509Certificate)certChain[0]))
+		return;
+	} catch (SSLPeerUnverifiedException e) {
+	    sslSocket.close();
+	    IOException ioex = new IOException(
+		"Can't verify identity of server: " + server);
+	    ioex.initCause(e);
+	    throw ioex;
+	}
+
+	// If we get here, there is nothing to consider the server as trusted.
+	sslSocket.close();
+	throw new IOException("Can't verify identity of server: " + server);
+    }
+
+    /**
+     * Do any of the names in the cert match the server name?
+     *
+     * @param	server	name of the server expected
+     * @param   cert	X509Certificate to get the subject's name from
+     * @return  true if it matches
+     */
+    private static boolean matchCert(String server, X509Certificate cert) {
+	if (logger.isLoggable(Level.FINER))
+	    logger.finer("matchCert server " +
+		server + ", cert " + cert);
+
+	/*
+	 * First, try to use sun.security.util.HostnameChecker,
+	 * which exists in Sun's JDK starting with 1.4.1.
+	 * We use reflection to access it in case it's not available
+	 * in the JDK we're running on.
+	 */
+	try {
+	    Class<?> hnc = Class.forName("sun.security.util.HostnameChecker");
+	    // invoke HostnameChecker.getInstance(HostnameChecker.TYPE_LDAP)
+	    // HostnameChecker.TYPE_LDAP == 2
+	    // LDAP requires the same regex handling as we need
+	    Method getInstance = hnc.getMethod("getInstance",
+					new Class<?>[] { byte.class });
+	    Object hostnameChecker = getInstance.invoke(new Object(),
+					new Object[] { Byte.valueOf((byte)2) });
+
+	    // invoke hostnameChecker.match( server, cert)
+	    if (logger.isLoggable(Level.FINER))
+		logger.finer("using sun.security.util.HostnameChecker");
+	    Method match = hnc.getMethod("match",
+			new Class<?>[] { String.class, X509Certificate.class });
+	    try {
+		match.invoke(hostnameChecker, new Object[] { server, cert });
+		return true;
+	    } catch (InvocationTargetException cex) {
+		logger.log(Level.FINER, "HostnameChecker FAIL", cex);
+		return false;
+	    }
+	} catch (Exception ex) {
+	    logger.log(Level.FINER, "NO sun.security.util.HostnameChecker", ex);
+	    // ignore failure and continue below
+	}
+
+	/*
+	 * Lacking HostnameChecker, we implement a crude version of
+	 * the same checks ourselves.
+	 */
+	try {
+	    /*
+	     * Check each of the subjectAltNames.
+	     * XXX - only checks DNS names, should also handle
+	     * case where server name is a literal IP address
+	     */
+	    Collection<List<?>> names = cert.getSubjectAlternativeNames();
+	    if (names != null) {
+		boolean foundName = false;
+		for (Iterator<List<?>> it = names.iterator(); it.hasNext(); ) {
+		    List<?> nameEnt = it.next();
+		    Integer type = (Integer)nameEnt.get(0);
+		    if (type.intValue() == 2) {	// 2 == dNSName
+			foundName = true;
+			String name = (String)nameEnt.get(1);
+			if (logger.isLoggable(Level.FINER))
+			    logger.finer("found name: " + name);
+			if (matchServer(server, name))
+			    return true;
+		    }
+		}
+		if (foundName)	// found a name, but no match
+		    return false;
+	    }
+	} catch (CertificateParsingException ex) {
+	    // ignore it
+	}
+
+	// XXX - following is a *very* crude parse of the name and ignores
+	//	 all sorts of important issues such as quoting
+	Pattern p = Pattern.compile("CN=([^,]*)");
+	Matcher m = p.matcher(cert.getSubjectX500Principal().getName());
+	if (m.find() && matchServer(server, m.group(1).trim()))
+	    return true;
+
+	return false;
+    }
+
+    /**
+     * Does the server we're expecting to connect to match the
+     * given name from the server's certificate?
+     *
+     * @param	server		name of the server expected
+     * @param	name		name from the server's certificate
+     */
+    private static boolean matchServer(String server, String name) {
+	if (logger.isLoggable(Level.FINER))
+	    logger.finer("match server " + server + " with " + name);
+	if (name.startsWith("*.")) {
+	    // match "foo.example.com" with "*.example.com"
+	    String tail = name.substring(2);
+	    if (tail.length() == 0)
+		return false;
+	    int off = server.length() - tail.length();
+	    if (off < 1)
+		return false;
+	    // if tail matches and is preceeded by "."
+	    return server.charAt(off - 1) == '.' &&
+		    server.regionMatches(true, off, tail, 0, tail.length());
+	} else
+	    return server.equalsIgnoreCase(name);
+    }
+
+    /**
+     * Use the HTTP CONNECT protocol to connect to a
+     * site through an HTTP proxy server. <p>
+     *
+     * Protocol is roughly:
+     * <pre>
+     * CONNECT <host>:<port> HTTP/1.1
+     * Host: <host>:<port>
+     * <blank line>
+     *
+     * HTTP/1.1 200 Connect established
+     * <headers>
+     * <blank line>
+     * </pre>
+     */
+    private static void proxyConnect(Socket socket,
+				String proxyHost, int proxyPort,
+				String proxyUser, String proxyPassword,
+				String host, int port, int cto)
+				throws IOException {
+	if (logger.isLoggable(Level.FINE))
+	    logger.fine("connecting through proxy " +
+			proxyHost + ":" + proxyPort + " to " +
+			host + ":" + port);
+	if (cto >= 0)
+	    socket.connect(new InetSocketAddress(proxyHost, proxyPort), cto);
+	else
+	    socket.connect(new InetSocketAddress(proxyHost, proxyPort));
+	PrintStream os = new PrintStream(socket.getOutputStream(), false,
+					    StandardCharsets.UTF_8.name());
+	StringBuilder requestBuilder = new StringBuilder();
+	requestBuilder.append("CONNECT ").append(host).append(":").append(port).
+			append(" HTTP/1.1\r\n");
+	requestBuilder.append("Host: ").append(host).append(":").append(port).
+			append("\r\n");
+	if (proxyUser != null && proxyPassword != null) {
+	    byte[] upbytes = (proxyUser + ':' + proxyPassword).
+				getBytes(StandardCharsets.UTF_8);
+	    String proxyHeaderValue = new String(
+		BASE64EncoderStream.encode(upbytes),
+		StandardCharsets.US_ASCII);
+	    requestBuilder.append("Proxy-Authorization: Basic ").
+				append(proxyHeaderValue).append("\r\n");
+	}
+	requestBuilder.append("Proxy-Connection: keep-alive\r\n\r\n");
+	os.print(requestBuilder.toString());
+	os.flush();
+	BufferedReader r = new BufferedReader(new InputStreamReader(
+			    socket.getInputStream(), StandardCharsets.UTF_8));
+	String line;
+	boolean first = true;
+	while ((line = r.readLine()) != null) {
+	    if (line.length() == 0)
+		break;
+	    logger.finest(line);
+	    if (first) {
+		StringTokenizer st = new StringTokenizer(line);
+		String http = st.nextToken();
+		String code = st.nextToken();
+		if (!code.equals("200")) {
+		    try {
+			socket.close();
+		    } catch (IOException ioex) {
+			// ignored
+		    }
+		    ConnectException ex = new ConnectException(
+			"connection through proxy " +
+			proxyHost + ":" + proxyPort + " to " +
+			host + ":" + port + " failed: " + line);
+		    logger.log(Level.FINE, "connect failed", ex);
+		    throw ex;
+		}
+		first = false;
+	    }
+	}
+    }
+
+    /**
+     * Parse a string into whitespace separated tokens
+     * and return the tokens in an array.
+     */
+    private static String[] stringArray(String s) {
+	StringTokenizer st = new StringTokenizer(s);
+	List<String> tokens = new ArrayList<>();
+	while (st.hasMoreTokens())
+	    tokens.add(st.nextToken());
+	return tokens.toArray(new String[tokens.size()]);
+    }
+
+    /**
+     * Convenience method to get our context class loader.
+     * Assert any privileges we might have and then call the
+     * Thread.getContextClassLoader method.
+     */
+    private static ClassLoader getContextClassLoader() {
+	return
+	AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
+	    @Override
+	    public ClassLoader run() {
+		ClassLoader cl = null;
+		try {
+		    cl = Thread.currentThread().getContextClassLoader();
+		} catch (SecurityException ex) { }
+		return cl;
+	    }
+	});
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/TraceInputStream.java b/mail/src/main/java/com/sun/mail/util/TraceInputStream.java
new file mode 100644
index 0000000..fceb7ac
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/TraceInputStream.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.util.logging.Level;
+
+/**
+ * This class is a FilterInputStream that writes the bytes
+ * being read from the given input stream into the given output
+ * stream. This class is typically used to provide a trace of
+ * the data that is being retrieved from an input stream.
+ *
+ * @author John Mani
+ */
+
+public class TraceInputStream extends FilterInputStream {
+    private boolean trace = false;
+    private boolean quote = false;
+    private OutputStream traceOut;
+
+    /**
+     * Creates an input stream filter built on top of the specified
+     * input stream.
+     *   
+     * @param   in   the underlying input stream.
+     * @param   logger	log trace here
+     */
+    public TraceInputStream(InputStream in, MailLogger logger) {
+	super(in);
+	this.trace = logger.isLoggable(Level.FINEST);
+	this.traceOut = new LogOutputStream(logger);
+    }
+
+    /**
+     * Creates an input stream filter built on top of the specified
+     * input stream.
+     *   
+     * @param   in   the underlying input stream.
+     * @param	traceOut	the trace stream.
+     */
+    public TraceInputStream(InputStream in, OutputStream traceOut) {
+	super(in);
+	this.traceOut = traceOut;
+    }
+
+    /**
+     * Set trace mode.
+     * @param	trace	the trace mode
+     */
+    public void setTrace(boolean trace) {
+	this.trace = trace;
+    }
+
+    /**
+     * Set quote mode.
+     * @param	quote	the quote mode
+     */
+    public void setQuote(boolean quote) {
+	this.quote = quote;
+    }
+
+    /**
+     * Reads the next byte of data from this input stream. Returns
+     * <code>-1</code> if no data is available. Writes out the read
+     * byte into the trace stream, if trace mode is <code>true</code>
+     */
+    @Override
+    public int read() throws IOException {
+	int b = in.read();
+	if (trace && b != -1) {
+	    if (quote)
+		writeByte(b);
+	    else
+		traceOut.write(b);
+	}
+	return b;
+    }
+
+    /**
+     * Reads up to <code>len</code> bytes of data from this input stream
+     * into an array of bytes. Returns <code>-1</code> if no more data
+     * is available. Writes out the read bytes into the trace stream, if 
+     * trace mode is <code>true</code>
+     */
+    @Override
+    public int read(byte b[], int off, int len) throws IOException {
+	int count = in.read(b, off, len);
+	if (trace && count != -1) {
+	    if (quote) {
+		for (int i = 0; i < count; i++)
+		    writeByte(b[off + i]);
+	    } else
+		traceOut.write(b, off, count);
+	}
+	return count;
+    }
+
+    /**
+     * Write a byte in a way that every byte value is printable ASCII.
+     */
+    private final void writeByte(int b) throws IOException {
+	b &= 0xff;
+	if (b > 0x7f) {
+	    traceOut.write('M');
+	    traceOut.write('-');
+	    b &= 0x7f;
+	}
+	if (b == '\r') {
+	    traceOut.write('\\');
+	    traceOut.write('r');
+	} else if (b == '\n') {
+	    traceOut.write('\\');
+	    traceOut.write('n');
+	    traceOut.write('\n');
+	} else if (b == '\t') {
+	    traceOut.write('\\');
+	    traceOut.write('t');
+	} else if (b < ' ') {
+	    traceOut.write('^');
+	    traceOut.write('@' + b);
+	} else {
+	    traceOut.write(b);
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/TraceOutputStream.java b/mail/src/main/java/com/sun/mail/util/TraceOutputStream.java
new file mode 100644
index 0000000..28f2820
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/TraceOutputStream.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.util.logging.Level;
+
+/**
+ * This class is a subclass of DataOutputStream that copies the
+ * data being written into the DataOutputStream into another output
+ * stream. This class is used here to provide a debug trace of the
+ * stuff thats being written out into the DataOutputStream.
+ *
+ * @author John Mani
+ */
+
+public class TraceOutputStream extends FilterOutputStream {
+    private boolean trace = false;
+    private boolean quote = false;
+    private OutputStream traceOut;
+
+    /**
+     * Creates an output stream filter built on top of the specified
+     * underlying output stream.
+     *
+     * @param   out   the underlying output stream.
+     * @param	logger	log trace here
+     */
+    public TraceOutputStream(OutputStream out, MailLogger logger) {
+	super(out);
+	this.trace = logger.isLoggable(Level.FINEST);
+	this.traceOut = new LogOutputStream(logger);;
+    }
+
+    /**
+     * Creates an output stream filter built on top of the specified
+     * underlying output stream.
+     *
+     * @param   out   the underlying output stream.
+     * @param	traceOut	the trace stream.
+     */
+    public TraceOutputStream(OutputStream out, OutputStream traceOut) {
+	super(out);
+	this.traceOut = traceOut;
+    }
+
+    /**
+     * Set the trace mode.
+     *
+     * @param	trace	the trace mode
+     */
+    public void setTrace(boolean trace) {
+	this.trace = trace;
+    }
+
+    /**
+     * Set quote mode.
+     * @param	quote	the quote mode
+     */
+    public void setQuote(boolean quote) {
+	this.quote = quote;
+    }
+
+    /**
+     * Writes the specified <code>byte</code> to this output stream.
+     * Writes out the byte into the trace stream if the trace mode
+     * is <code>true</code>
+     *
+     * @param	b	the byte to write
+     * @exception	IOException	for I/O errors
+     */
+    @Override
+    public void write(int b) throws IOException {
+	if (trace) {
+	    if (quote)
+		writeByte(b);
+	    else
+		traceOut.write(b);
+	}
+	out.write(b);
+    }
+	    
+    /**
+     * Writes <code>b.length</code> bytes to this output stream.
+     * Writes out the bytes into the trace stream if the trace
+     * mode is <code>true</code>
+     *
+     * @param	b	bytes to write
+     * @param	off	offset in array
+     * @param	len	number of bytes to write
+     * @exception	IOException	for I/O errors
+     */
+    @Override
+    public void write(byte b[], int off, int len) throws IOException {
+	if (trace) {
+	    if (quote) {
+		for (int i = 0; i < len; i++)
+		    writeByte(b[off + i]);
+	    } else
+		traceOut.write(b, off, len);
+	}
+	out.write(b, off, len);
+    }
+
+    /**
+     * Write a byte in a way that every byte value is printable ASCII.
+     */
+    private final void writeByte(int b) throws IOException {
+	b &= 0xff;
+	if (b > 0x7f) {
+	    traceOut.write('M');
+	    traceOut.write('-');
+	    b &= 0x7f;
+	}
+	if (b == '\r') {
+	    traceOut.write('\\');
+	    traceOut.write('r');
+	} else if (b == '\n') {
+	    traceOut.write('\\');
+	    traceOut.write('n');
+	    traceOut.write('\n');
+	} else if (b == '\t') {
+	    traceOut.write('\\');
+	    traceOut.write('t');
+	} else if (b < ' ') {
+	    traceOut.write('^');
+	    traceOut.write('@' + b);
+	} else {
+	    traceOut.write(b);
+	}
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/UUDecoderStream.java b/mail/src/main/java/com/sun/mail/util/UUDecoderStream.java
new file mode 100644
index 0000000..82f957c
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/UUDecoderStream.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a UUDecoder. It is implemented as
+ * a FilterInputStream, so one can just wrap this class around
+ * any input stream and read bytes from this filter. The decoding
+ * is done as the bytes are read out.
+ * 
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class UUDecoderStream extends FilterInputStream {
+    private String name;
+    private int mode;
+
+    private byte[] buffer = new byte[45]; // max decoded chars in a line = 45
+    private int bufsize = 0;	// size of the cache
+    private int index = 0;	// index into the cache
+    private boolean gotPrefix = false;
+    private boolean gotEnd = false;
+    private LineInputStream lin;
+    private boolean ignoreErrors;
+    private boolean ignoreMissingBeginEnd;
+    private String readAhead;
+
+    /**
+     * Create a UUdecoder that decodes the specified input stream.
+     * The System property <code>mail.mime.uudecode.ignoreerrors</code>
+     * controls whether errors in the encoded data cause an exception
+     * or are ignored.  The default is false (errors cause exception).
+     * The System property <code>mail.mime.uudecode.ignoremissingbeginend</code>
+     * controls whether a missing begin or end line cause an exception
+     * or are ignored.  The default is false (errors cause exception).
+     * @param in        the input stream
+     */
+    public UUDecoderStream(InputStream in) {
+	super(in);
+	lin = new LineInputStream(in);
+	// default to false
+	ignoreErrors = PropUtil.getBooleanSystemProperty(
+	    "mail.mime.uudecode.ignoreerrors", false);
+	// default to false
+	ignoreMissingBeginEnd = PropUtil.getBooleanSystemProperty(
+	    "mail.mime.uudecode.ignoremissingbeginend", false);
+    }
+
+    /**
+     * Create a UUdecoder that decodes the specified input stream.
+     * @param in        	the input stream
+     * @param ignoreErrors	ignore errors?
+     * @param ignoreMissingBeginEnd	ignore missing begin or end?
+     */
+    public UUDecoderStream(InputStream in, boolean ignoreErrors,
+				boolean ignoreMissingBeginEnd) {
+	super(in);
+	lin = new LineInputStream(in);
+	this.ignoreErrors = ignoreErrors;
+	this.ignoreMissingBeginEnd = ignoreMissingBeginEnd;
+    }
+
+    /**
+     * Read the next decoded byte from this input stream. The byte
+     * is returned as an <code>int</code> in the range <code>0</code>
+     * to <code>255</code>. If no byte is available because the end of
+     * the stream has been reached, the value <code>-1</code> is returned.
+     * This method blocks until input data is available, the end of the
+     * stream is detected, or an exception is thrown.
+     *
+     * @return     next byte of data, or <code>-1</code> if the end of 
+     *             stream is reached.
+     * @exception  IOException  if an I/O error occurs.
+     * @see        java.io.FilterInputStream#in
+     */
+    @Override
+    public int read() throws IOException {
+	if (index >= bufsize) {
+	    readPrefix();
+	    if (!decode())
+		return -1;
+	    index = 0; // reset index into buffer
+	}
+	return buffer[index++] & 0xff; // return lower byte
+    }
+
+    @Override
+    public int read(byte[] buf, int off, int len) throws IOException {
+	int i, c;
+	for (i = 0; i < len; i++) {
+	    if ((c = read()) == -1) {
+		if (i == 0) // At end of stream, so we should
+		    i = -1; // return -1, NOT 0.
+		break;
+	    }
+	    buf[off+i] = (byte)c;
+	}
+	return i;
+    }
+
+    @Override
+    public boolean markSupported() {
+	return false;
+    }
+
+    @Override
+    public int available() throws IOException {
+	 // This is only an estimate, since in.available()
+	 // might include CRLFs too ..
+	 return ((in.available() * 3)/4 + (bufsize-index));
+    }
+
+    /**
+     * Get the "name" field from the prefix. This is meant to
+     * be the pathname of the decoded file
+     *
+     * @return     name of decoded file
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public String getName() throws IOException {
+	readPrefix();
+	return name;
+    }
+
+    /**
+     * Get the "mode" field from the prefix. This is the permission
+     * mode of the source file.
+     *
+     * @return     permission mode of source file
+     * @exception  IOException  if an I/O error occurs.
+     */
+    public int getMode() throws IOException {
+	readPrefix();
+	return mode;
+    }
+
+    /**
+     * UUencoded streams start off with the line:
+     *  "begin <mode> <filename>"
+     * Search for this prefix and gobble it up.
+     */
+    private void readPrefix() throws IOException {
+	if (gotPrefix) // got the prefix
+	    return;
+
+	mode = 0666;		// defaults, overridden below
+	name = "encoder.buf";	// same default used by encoder
+	String line;
+	for (;;) {
+	    // read till we get the prefix: "begin MODE FILENAME"
+	    line = lin.readLine(); // NOTE: readLine consumes CRLF pairs too
+	    if (line == null) {
+		if (!ignoreMissingBeginEnd)
+		    throw new DecodingException("UUDecoder: Missing begin");
+		// at EOF, fake it
+		gotPrefix = true;
+		gotEnd = true;
+		break;
+	    }
+	    if (line.regionMatches(false, 0, "begin", 0, 5)) {
+		try {
+		    mode = Integer.parseInt(line.substring(6,9));
+		} catch (NumberFormatException ex) {
+		    if (!ignoreErrors)
+			throw new DecodingException(
+				"UUDecoder: Error in mode: " + ex.toString());
+		}
+		if (line.length() > 10) {
+		    name = line.substring(10);
+		} else {
+		    if (!ignoreErrors)
+			throw new DecodingException(
+				"UUDecoder: Missing name: " + line);
+		}
+		gotPrefix = true;
+		break;
+	    } else if (ignoreMissingBeginEnd && line.length() != 0) {
+		int count = line.charAt(0);
+		count = (count - ' ') & 0x3f;
+		int need = ((count * 8)+5)/6;
+		if (need == 0 || line.length() >= need + 1) {
+		    /*
+		     * Looks like a legitimate encoded line.
+		     * Pretend we saw the "begin" line and
+		     * save this line for later processing in
+		     * decode().
+		     */
+		    readAhead = line;
+		    gotPrefix = true;	// fake it
+		    break;
+		}
+	    }
+	}
+    }
+
+    private boolean decode() throws IOException {
+
+	if (gotEnd)
+	    return false;
+	bufsize = 0;
+	int count = 0;
+	String line;
+	for (;;) {
+	    /*
+	     * If we ignored a missing "begin", the first line
+	     * will be saved in readAhead.
+	     */
+	    if (readAhead != null) {
+		line = readAhead;
+		readAhead = null;
+	    } else
+		line = lin.readLine();
+
+	    /*
+	     * Improperly encoded data sometimes omits the zero length
+	     * line that starts with a space character, we detect the
+	     * following "end" line here.
+	     */
+	    if (line == null) {
+		if (!ignoreMissingBeginEnd)
+		    throw new DecodingException(
+					"UUDecoder: Missing end at EOF");
+		gotEnd = true;
+		return false;
+	    }
+	    if (line.equals("end")) {
+		gotEnd = true;
+		return false;
+	    }
+	    if (line.length() == 0)
+		continue;
+	    count = line.charAt(0);
+	    if (count < ' ') {
+		if (!ignoreErrors)
+		    throw new DecodingException(
+					"UUDecoder: Buffer format error");
+		continue;
+	    }
+
+	    /*
+	     * The first character in a line is the number of original (not
+	     *  the encoded atoms) characters in the line. Note that all the
+	     *  code below has to handle the <SPACE> character that indicates
+	     *  end of encoded stream.
+	     */
+	    count = (count - ' ') & 0x3f;
+
+	    if (count == 0) {
+		line = lin.readLine();
+		if (line == null || !line.equals("end")) {
+		    if (!ignoreMissingBeginEnd)
+			throw new DecodingException(
+				"UUDecoder: Missing End after count 0 line");
+		}
+		gotEnd = true;
+		return false;
+	    }
+
+	    int need = ((count * 8)+5)/6;
+//System.out.println("count " + count + ", need " + need + ", len " + line.length());
+	    if (line.length() < need + 1) {
+		if (!ignoreErrors)
+		    throw new DecodingException(
+					"UUDecoder: Short buffer error");
+		continue;
+	    }
+
+	    // got a line we're committed to, break out and decode it
+	    break;
+	}
+	    
+	int i = 1;
+	byte a, b;
+	/*
+	 * A correct uuencoder always encodes 3 characters at a time, even
+	 * if there aren't 3 characters left.  But since some people out
+	 * there have broken uuencoders we handle the case where they
+	 * don't include these "unnecessary" characters.
+	 */
+	while (bufsize < count) {
+	    // continue decoding until we get 'count' decoded chars
+	    a = (byte)((line.charAt(i++) - ' ') & 0x3f);
+	    b = (byte)((line.charAt(i++) - ' ') & 0x3f);
+	    buffer[bufsize++] = (byte)(((a << 2) & 0xfc) | ((b >>> 4) & 3));
+
+	    if (bufsize < count) {
+		a = b;
+		b = (byte)((line.charAt(i++) - ' ') & 0x3f);
+		buffer[bufsize++] =
+				(byte)(((a << 4) & 0xf0) | ((b >>> 2) & 0xf));
+	    }
+
+	    if (bufsize < count) {
+		a = b;
+		b = (byte)((line.charAt(i++) - ' ') & 0x3f);
+		buffer[bufsize++] = (byte)(((a << 6) & 0xc0) | (b & 0x3f));
+	    }
+	}
+	return true;
+    }
+
+    /*** begin TEST program *****
+    public static void main(String argv[]) throws Exception {
+    	FileInputStream infile = new FileInputStream(argv[0]);
+	UUDecoderStream decoder = new UUDecoderStream(infile);
+	int c;
+
+	try {
+	    while ((c = decoder.read()) != -1)
+		System.out.write(c);
+	    System.out.flush();
+	} catch (Exception e) {
+	    e.printStackTrace();
+	}
+    }
+    **** end TEST program ****/
+}
diff --git a/mail/src/main/java/com/sun/mail/util/UUEncoderStream.java b/mail/src/main/java/com/sun/mail/util/UUEncoderStream.java
new file mode 100644
index 0000000..183cc34
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/UUEncoderStream.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+
+/**
+ * This class implements a UUEncoder. It is implemented as
+ * a FilterOutputStream, so one can just wrap this class around
+ * any output stream and write bytes into this filter. The Encoding
+ * is done as the bytes are written out.
+ * 
+ * @author John Mani
+ */
+
+public class UUEncoderStream extends FilterOutputStream {
+    private byte[] buffer; 	// cache of bytes that are yet to be encoded
+    private int bufsize = 0;	// size of the cache
+    private boolean wrotePrefix = false;
+    private boolean wroteSuffix = false;
+
+    private String name; 	// name of file
+    private int mode;		// permissions mode
+
+    /**
+     * Create a UUencoder that encodes the specified input stream
+     * @param out        the output stream
+     */
+    public UUEncoderStream(OutputStream out) {
+	this(out, "encoder.buf", 0644);
+    }
+
+    /**
+     * Create a UUencoder that encodes the specified input stream
+     * @param out        the output stream
+     * @param name	 Specifies a name for the encoded buffer
+     */
+    public UUEncoderStream(OutputStream out, String name) {
+	this(out, name, 0644);	
+    }
+
+    /**
+     * Create a UUencoder that encodes the specified input stream
+     * @param out        the output stream
+     * @param name       Specifies a name for the encoded buffer
+     * @param mode	 Specifies permission mode for the encoded buffer
+     */
+    public UUEncoderStream(OutputStream out, String name, int mode) {
+	super(out);
+	this.name = name;
+	this.mode = mode;
+	buffer = new byte[45];
+    }
+
+    /**
+     * Set up the buffer name and permission mode.
+     * This method has any effect only if it is invoked before
+     * you start writing into the output stream
+     *
+     * @param	name	the buffer name
+     * @param	mode	the permission mode
+     */
+    public void setNameMode(String name, int mode) {
+	this.name = name;
+	this.mode = mode;
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+	for (int i = 0; i < len; i++)
+	    write(b[off + i]);
+    }
+
+    @Override
+    public void write(byte[] data) throws IOException {
+	write(data, 0, data.length);
+    }
+
+    @Override
+    public void write(int c) throws IOException {
+	/* buffer up characters till we get a line's worth, then encode
+	 * and write them out. Max number of characters allowed per 
+	 * line is 45.
+	 */
+	buffer[bufsize++] = (byte)c;
+	if (bufsize == 45) {
+	    writePrefix();
+	    encode();
+	    bufsize = 0;
+	}
+    }
+
+    @Override
+    public void flush() throws IOException {
+	if (bufsize > 0) { // If there's unencoded characters in the buffer
+	    writePrefix();
+	    encode();      // .. encode them
+	    bufsize = 0;
+	}
+	writeSuffix();
+	out.flush();
+    }
+
+    @Override
+    public void close() throws IOException {
+	flush();
+	out.close();
+    }
+
+    /**
+     * Write out the prefix: "begin <mode> <name>"
+     */
+    private void writePrefix() throws IOException {
+	if (!wrotePrefix) {
+	    // name should be ASCII, but who knows...
+	    PrintStream ps = new PrintStream(out, false, "utf-8");
+	    ps.format("begin %o %s%n", mode, name);
+	    ps.flush();
+	    wrotePrefix = true;
+	}
+    }
+
+    /**
+     * Write a single line containing space and the suffix line
+     * containing the single word "end" (terminated by a newline)
+     */
+    private void writeSuffix() throws IOException {
+	if (!wroteSuffix) {
+	    PrintStream ps = new PrintStream(out, false, "us-ascii");
+	    ps.println(" \nend");
+	    ps.flush();
+	    wroteSuffix = true;
+	}
+    }
+
+    /**
+     * Encode a line. 
+     * Start off with the character count, followed by the encoded atoms
+     * and terminate with LF. (or is it CRLF or the local line-terminator ?)
+     * Take three bytes and encodes them into 4 characters
+     * If bufsize if not a multiple of 3, the remaining bytes are filled 
+     * with '1'. This insures that the last line won't end in spaces 
+     * and potentiallly be truncated.
+     */
+    private void encode() throws IOException {
+	byte a, b, c;
+	int c1, c2, c3, c4;
+	int i = 0;
+
+	// Start off with the count of characters in the line
+	out.write((bufsize & 0x3f) + ' ');
+
+	while (i < bufsize) {
+	    a = buffer[i++];
+	    if (i < bufsize) {
+		b = buffer[i++];
+		if (i < bufsize)
+		    c = buffer[i++];
+		else // default c to 1
+		    c = 1;
+	    }
+	    else { // default b & c to 1
+		b = 1;
+		c = 1;
+	    }
+
+	    c1 = (a >>> 2) & 0x3f;
+	    c2 = ((a << 4) & 0x30) | ((b >>> 4) & 0xf);
+	    c3 = ((b << 2) & 0x3c) | ((c >>> 6) & 0x3);
+	    c4 = c & 0x3f;
+	    out.write(c1 + ' ');
+	    out.write(c2 + ' ');
+	    out.write(c3 + ' ');
+	    out.write(c4 + ' ');
+	}
+	// Terminate with LF. (should it be CRLF or local line-terminator ?)
+	out.write('\n');
+    }
+
+    /**** begin TEST program *****
+    public static void main(String argv[]) throws Exception {
+	FileInputStream infile = new FileInputStream(argv[0]);
+	UUEncoderStream encoder = new UUEncoderStream(System.out);
+	int c;
+
+	while ((c = infile.read()) != -1)
+	    encoder.write(c);
+	encoder.close();
+    }
+    **** end TEST program *****/
+}
diff --git a/mail/src/main/java/com/sun/mail/util/WriteTimeoutSocket.java b/mail/src/main/java/com/sun/mail/util/WriteTimeoutSocket.java
new file mode 100644
index 0000000..5cd01b2
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/WriteTimeoutSocket.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.net.*;
+import java.util.concurrent.*;
+import java.util.Collections;
+import java.util.Set;
+import java.nio.channels.SocketChannel;
+import java.lang.reflect.*;
+
+/**
+ * A special Socket that uses a ScheduledExecutorService to
+ * implement timeouts for writes.  The write timeout is specified
+ * (in milliseconds) when the WriteTimeoutSocket is created.
+ *
+ * @author	Bill Shannon
+ */
+public class WriteTimeoutSocket extends Socket {
+
+    // delegate all operations to this socket
+    private final Socket socket;
+    // to schedule task to cancel write after timeout
+    private final ScheduledExecutorService ses;
+    // the timeout, in milliseconds
+    private final int timeout;
+
+    public WriteTimeoutSocket(Socket socket, int timeout) throws IOException {
+	this.socket = socket;
+	// XXX - could share executor with all instances?
+        this.ses = Executors.newScheduledThreadPool(1);
+	this.timeout = timeout;
+    }
+
+    public WriteTimeoutSocket(int timeout) throws IOException {
+	this(new Socket(), timeout);
+    }
+
+    public WriteTimeoutSocket(InetAddress address, int port, int timeout)
+				throws IOException {
+	this(timeout);
+	socket.connect(new InetSocketAddress(address, port));
+    }
+
+    public WriteTimeoutSocket(InetAddress address, int port,
+			InetAddress localAddress, int localPort, int timeout)
+			throws IOException {
+	this(timeout);
+	socket.bind(new InetSocketAddress(localAddress, localPort));
+	socket.connect(new InetSocketAddress(address, port));
+    }
+
+    public WriteTimeoutSocket(String host, int port, int timeout)
+				throws IOException {
+	this(timeout);
+	socket.connect(new InetSocketAddress(host, port));
+    }
+
+    public WriteTimeoutSocket(String host, int port,
+			InetAddress localAddress, int localPort, int timeout)
+			throws IOException {
+	this(timeout);
+	socket.bind(new InetSocketAddress(localAddress, localPort));
+	socket.connect(new InetSocketAddress(host, port));
+    }
+
+    // override all Socket methods and delegate to underlying Socket
+
+    @Override
+    public void connect(SocketAddress remote) throws IOException {
+        socket.connect(remote, 0);
+    }
+
+    @Override
+    public void connect(SocketAddress remote, int timeout) throws IOException {
+	socket.connect(remote, timeout);
+    }
+
+    @Override
+    public void bind(SocketAddress local) throws IOException {
+	socket.bind(local);
+    }
+
+    @Override
+    public SocketAddress getRemoteSocketAddress() {
+	return socket.getRemoteSocketAddress();
+    }
+
+    @Override
+    public SocketAddress getLocalSocketAddress() {
+	return socket.getLocalSocketAddress();
+    }
+
+    @Override
+    public void setPerformancePreferences(int connectionTime, int latency,
+                                          int bandwidth) {
+        socket.setPerformancePreferences(connectionTime, latency, bandwidth);
+    }
+
+    @Override
+    public SocketChannel getChannel() {
+	return socket.getChannel();
+    }
+
+    @Override
+    public InetAddress getInetAddress() {
+	return socket.getInetAddress();
+    }
+
+    @Override
+    public InetAddress getLocalAddress() {
+	return socket.getLocalAddress();
+    }
+
+    @Override
+    public int getPort() {
+	return socket.getPort();
+    }
+
+    @Override
+    public int getLocalPort() {
+	return socket.getLocalPort();
+    }
+
+    @Override
+    public InputStream getInputStream() throws IOException {
+	return socket.getInputStream();
+    }
+
+    @Override
+    public synchronized OutputStream getOutputStream() throws IOException {
+	// wrap the returned stream to implement write timeout
+        return new TimeoutOutputStream(socket.getOutputStream(), ses, timeout);
+    }
+
+    @Override
+    public void setTcpNoDelay(boolean on) throws SocketException {
+        socket.setTcpNoDelay(on);
+    }
+
+    @Override
+    public boolean getTcpNoDelay() throws SocketException {
+        return socket.getTcpNoDelay();
+    }
+
+    @Override
+    public void setSoLinger(boolean on, int linger) throws SocketException {
+        socket.setSoLinger(on, linger);
+    }
+
+    @Override
+    public int getSoLinger() throws SocketException {
+        return socket.getSoLinger();
+    }
+
+    @Override
+    public void sendUrgentData(int data) throws IOException {
+        socket.sendUrgentData(data);
+    }
+
+    @Override
+    public void setOOBInline(boolean on) throws SocketException {
+        socket.setOOBInline(on);
+    }
+
+    @Override
+    public boolean getOOBInline() throws SocketException {
+        return socket.getOOBInline();
+    }
+
+    @Override
+    public void setSoTimeout(int timeout) throws SocketException {
+	socket.setSoTimeout(timeout);
+    }
+
+    @Override
+    public int getSoTimeout() throws SocketException {
+        return socket.getSoTimeout();
+    }
+
+    @Override
+    public void setSendBufferSize(int size) throws SocketException {
+        socket.setSendBufferSize(size);
+    }
+
+    @Override
+    public int getSendBufferSize() throws SocketException {
+        return socket.getSendBufferSize();
+    }
+
+    @Override
+    public void setReceiveBufferSize(int size) throws SocketException {
+        socket.setReceiveBufferSize(size);
+    }
+
+    @Override
+    public int getReceiveBufferSize() throws SocketException {
+        return socket.getReceiveBufferSize();
+    }
+
+    @Override
+    public void setKeepAlive(boolean on) throws SocketException {
+        socket.setKeepAlive(on);
+    }
+
+    @Override
+    public boolean getKeepAlive() throws SocketException {
+        return socket.getKeepAlive();
+    }
+
+    @Override
+    public void setTrafficClass(int tc) throws SocketException {
+        socket.setTrafficClass(tc);
+    }
+
+    @Override
+    public int getTrafficClass() throws SocketException {
+        return socket.getTrafficClass();
+    }
+
+    @Override
+    public void setReuseAddress(boolean on) throws SocketException {
+        socket.setReuseAddress(on);
+    }
+
+    @Override
+    public boolean getReuseAddress() throws SocketException {
+        return socket.getReuseAddress();
+    }
+
+    @Override
+    public void close() throws IOException {
+	try {
+	    socket.close();
+	} finally {
+	    ses.shutdownNow();
+	}
+    }
+
+    @Override
+    public void shutdownInput() throws IOException {
+	socket.shutdownInput();
+    }
+
+    @Override
+    public void shutdownOutput() throws IOException {
+	socket.shutdownOutput();
+    }
+
+    @Override
+    public String toString() {
+	return socket.toString();
+    }
+
+    @Override
+    public boolean isConnected() {
+        return socket.isConnected();
+    }
+
+    @Override
+    public boolean isBound() {
+        return socket.isBound();
+    }
+
+    @Override
+    public boolean isClosed() {
+        return socket.isClosed();
+    }
+
+    @Override
+    public boolean isInputShutdown() {
+        return socket.isInputShutdown();
+    }
+
+    @Override
+    public boolean isOutputShutdown() {
+        return socket.isOutputShutdown();
+    }
+
+    /*
+     * The following three methods were added to java.net.Socket in Java SE 9.
+     * Since they're not supported on Android, and since we know that we
+     * never use them in JavaMail, we just stub them out here.
+     */
+    //@Override
+    public <T> Socket setOption(SocketOption<T> so, T val) throws IOException {
+	// socket.setOption(so, val);
+	// return this;
+	throw new UnsupportedOperationException("WriteTimeoutSocket.setOption");
+    }
+
+    //@Override
+    public <T> T getOption(SocketOption<T> so) throws IOException {
+	// return socket.getOption(so);
+	throw new UnsupportedOperationException("WriteTimeoutSocket.getOption");
+    }
+
+    //@Override
+    public Set<SocketOption<?>> supportedOptions() {
+	// return socket.supportedOptions();
+	return Collections.emptySet();
+    }
+
+    /**
+     * KLUDGE for Android, which has this illegal non-Java Compatible method.
+     *
+     * @return	the FileDescriptor object
+     */
+    public FileDescriptor getFileDescriptor$() {
+	try {
+	    Method m = Socket.class.getDeclaredMethod("getFileDescriptor$");
+	    return (FileDescriptor)m.invoke(socket);
+	} catch (Exception ex) {
+	    return null;
+	}
+    }
+}
+
+
+/**
+ * An OutputStream that wraps the Socket's OutputStream and uses
+ * the ScheduledExecutorService to schedule a task to close the
+ * socket (aborting the write) if the timeout expires.
+ */
+class TimeoutOutputStream extends OutputStream {
+    private final OutputStream os;
+    private final ScheduledExecutorService ses;
+    private final Callable<Object> timeoutTask;
+    private final int timeout;
+    private byte[] b1;
+
+    public TimeoutOutputStream(OutputStream os0, ScheduledExecutorService ses,
+				int timeout) throws IOException {
+	this.os = os0;
+	this.ses = ses;
+	this.timeout = timeout;
+	timeoutTask = new Callable<Object>() {
+	    @Override
+	    public Object call() throws Exception {
+		os.close();	// close the stream to abort the write
+		return null;
+	    }
+	};
+    }
+
+    @Override
+    public synchronized void write(int b) throws IOException {
+	if (b1 == null)
+	    b1 = new byte[1];
+	b1[0] = (byte)b;
+	this.write(b1);
+    }
+
+    @Override
+    public synchronized void write(byte[] bs, int off, int len)
+				throws IOException {
+	if ((off < 0) || (off > bs.length) || (len < 0) ||
+	    ((off + len) > bs.length) || ((off + len) < 0)) {
+	    throw new IndexOutOfBoundsException();
+	} else if (len == 0) {
+	    return;
+	}
+
+	// Implement timeout with a scheduled task
+	ScheduledFuture<Object> sf = null;
+	try {
+	    try {
+		if (timeout > 0)
+		    sf = ses.schedule(timeoutTask,
+					timeout, TimeUnit.MILLISECONDS);
+	    } catch (RejectedExecutionException ex) {
+		// ignore it; Executor was shut down by another thread,
+		// the following write should fail with IOException
+	    }
+	    os.write(bs, off, len);
+	} finally {
+	    if (sf != null)
+		sf.cancel(true);
+	}
+    }
+
+    @Override
+    public void close() throws IOException {
+	os.close();
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/logging/CollectorFormatter.java b/mail/src/main/java/com/sun/mail/util/logging/CollectorFormatter.java
new file mode 100644
index 0000000..3b67ba1
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/logging/CollectorFormatter.java
@@ -0,0 +1,601 @@
+/*

+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2013, 2018 Jason Mehrens. All rights reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0, which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * This Source Code may also be made available under the following Secondary

+ * Licenses when the conditions for such availability set forth in the

+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,

+ * version 2 with the GNU Classpath Exception, which is available at

+ * https://www.gnu.org/software/classpath/license.html.

+ *

+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

+ */

+package com.sun.mail.util.logging;

+

+import static com.sun.mail.util.logging.LogManagerProperties.fromLogManager;

+import java.lang.reflect.UndeclaredThrowableException;

+import java.text.MessageFormat;

+import java.util.Comparator;

+import java.util.Locale;

+import java.util.ResourceBundle;

+import java.util.logging.Formatter;

+import java.util.logging.Handler;

+import java.util.logging.LogRecord;

+

+/**

+ * A LogRecord formatter that takes a sequence of LogRecords and combines them

+ * into a single summary result. Formating of the head, LogRecord, and tail are

+ * delegated to the wrapped formatter.

+ *

+ * <p>

+ * By default each <tt>CollectorFormatter</tt> is initialized using the

+ * following LogManager configuration properties where

+ * <tt>&lt;formatter-name&gt;</tt> refers to the fully qualified class name or

+ * the fully qualified derived class name of the formatter.  If properties are

+ * not defined, or contain invalid values, then the specified default values are

+ * used.

+ * <ul>

+ * <li>&lt;formatter-name&gt;.comparator name of a

+ * {@linkplain java.util.Comparator} class used to choose the collected

+ * <tt>LogRecord</tt>. If a comparator is specified then the max

+ * <tt>LogRecord</tt> is chosen. If comparator is set to the string literal

+ * null, then the last record is chosen. (defaults to

+ * {@linkplain SeverityComparator})

+ *

+ * <li>&lt;formatter-name&gt;.comparator.reverse a boolean

+ * <tt>true</tt> to collect the min <tt>LogRecord</tt> or <tt>false</tt> to

+ * collect the max <tt>LogRecord</tt>. (defaults to <tt>false</tt>)

+ *

+ * <li>&lt;formatter-name&gt;.format the

+ * {@linkplain java.text.MessageFormat MessageFormat} string used to format the

+ * collected summary statistics. The arguments are explained in detail in the

+ * {@linkplain #getTail(java.util.logging.Handler) getTail} documentation.

+ * (defaults to <tt>{0}{1}{2}{4,choice,-1#|0#|0&lt;... {4,number,integer}

+ * more}\n</tt>)

+ *

+ * <li>&lt;formatter-name&gt;.formatter name of a <tt>Formatter</tt> class used

+ * to format the collected LogRecord. (defaults to {@linkplain CompactFormatter})

+ *

+ * </ul>

+ *

+ * @author Jason Mehrens

+ * @since JavaMail 1.5.2

+ */

+public class CollectorFormatter extends Formatter {

+    /**

+     * Avoid depending on JMX runtime bean to get the start time.

+     */

+    private static final long INIT_TIME = System.currentTimeMillis();

+    /**

+     * The message format string used as the formatted output.

+     */

+    private final String fmt;

+    /**

+     * The formatter used to format the chosen log record.

+     */

+    private final Formatter formatter;

+    /**

+     * The comparator used to pick the log record to format.

+     */

+    private final Comparator<? super LogRecord> comparator;

+    /**

+     * The last accepted record. Synchronized access is preferred over volatile

+     * for this class.

+     */

+    private LogRecord last;

+    /**

+     * The number of log records that have been formatted.

+     */

+    private long count;

+    /**

+     * The number of log produced containing at least one log record.

+     * Only incremented when this formatter is reset.

+     */

+    private long generation = 1L;

+    /**

+     * The number of log records that have been formatted with a thrown object.

+     */

+    private long thrown;

+    /**

+     * The eldest log record time or eldest time possible for this instance.

+     */

+    private long minMillis = INIT_TIME;

+    /**

+     * The newest log record time.

+     */

+    private long maxMillis = Long.MIN_VALUE;

+

+    /**

+     * Creates the formatter using the LogManager defaults.

+     *

+     * @throws SecurityException if a security manager exists and the caller

+     * does not have <tt>LoggingPermission("control")</tt>.

+     * @throws UndeclaredThrowableException if there are problems loading from

+     * the LogManager.

+     */

+    public CollectorFormatter() {

+        final String p = getClass().getName();

+        this.fmt = initFormat(p);

+        this.formatter = initFormatter(p);

+        this.comparator = initComparator(p);

+    }

+

+    /**

+     * Creates the formatter using the given format.

+     *

+     * @param format the message format or null to use the LogManager default.

+     * @throws SecurityException if a security manager exists and the caller

+     * does not have <tt>LoggingPermission("control")</tt>.

+     * @throws UndeclaredThrowableException if there are problems loading from

+     * the LogManager.

+     */

+    public CollectorFormatter(String format) {

+        final String p = getClass().getName();

+        this.fmt = format == null ? initFormat(p) : format;

+        this.formatter = initFormatter(p);

+        this.comparator = initComparator(p);

+    }

+

+    /**

+     * Creates the formatter using the given values.

+     *

+     * @param format the format string or null to use the LogManager default.

+     * @param f the formatter used on the collected log record or null to

+     * specify no formatter.

+     * @param c the comparator used to determine which log record to format or

+     * null to specify no comparator.

+     * @throws SecurityException if a security manager exists and the caller

+     * does not have <tt>LoggingPermission("control")</tt>.

+     * @throws UndeclaredThrowableException if there are problems loading from

+     * the LogManager.

+     */

+    public CollectorFormatter(String format, Formatter f,

+            Comparator<? super LogRecord> c) {

+        final String p = getClass().getName();

+        this.fmt = format == null ? initFormat(p) : format;

+        this.formatter = f;

+        this.comparator = c;

+    }

+

+    /**

+     * Accumulates log records which will be used to produce the final output.

+     * The output is generated using the {@link #getTail} method which also

+     * resets this formatter back to its original state.

+     *

+     * @param record the record to store.

+     * @return an empty string.

+     * @throws NullPointerException if the given record is null.

+     */

+    @Override

+    public String format(final LogRecord record) {

+        if (record == null) {

+            throw new NullPointerException();

+        }

+

+        boolean accepted;

+        do {

+            final LogRecord peek = peek();

+            //The self compare of the first record acts like a type check.

+            LogRecord update = apply(peek != null ? peek : record, record);

+            if (peek != update) { //Not identical.

+                update.getSourceMethodName(); //Infer caller, null check.

+                accepted = acceptAndUpdate(peek, update);

+            } else {

+                accepted = accept(peek, record);

+            }

+        } while (!accepted);

+        return "";

+    }

+

+    /**

+     * Formats the collected LogRecord and summary statistics. The collected

+     * results are reset after calling this method. The

+     * {@linkplain java.text.MessageFormat java.text} argument indexes are assigned

+     * to the following properties:

+     *

+     * <ol start='0'>

+     * <li>{@code head} the

+     * {@linkplain Formatter#getHead(java.util.logging.Handler) head} string

+     * returned from the target formatter and

+     * {@linkplain #finish(java.lang.String) finished} by this formatter.

+     * <li>{@code formatted} the current log record

+     * {@linkplain Formatter#format(java.util.logging.LogRecord) formatted} by

+     * the target formatter and {@linkplain #finish(java.lang.String) finished}

+     * by this formatter.  If the formatter is null then record is formatted by

+     * this {@linkplain #formatMessage(java.util.logging.LogRecord) formatter}.

+     * <li>{@code tail} the

+     * {@linkplain Formatter#getTail(java.util.logging.Handler) tail} string

+     * returned from the target formatter and

+     * {@linkplain #finish(java.lang.String) finished} by this formatter.

+     * <li>{@code count} the total number of log records

+     * {@linkplain #format consumed} by this formatter.

+     * <li>{@code remaining} the count minus one.

+     * <li>{@code thrown} the total number of log records

+     * {@linkplain #format consumed} by this formatter with an assigned

+     * {@linkplain java.util.logging.LogRecord#getThrown throwable}.

+     * <li>{@code normal messages} the count minus the thrown.

+     * <li>{@code minMillis} the eldest log record

+     * {@linkplain java.util.logging.LogRecord#getMillis event time}

+     * {@linkplain #format consumed} by this formatter. If the count is zero

+     * then this is set to the previous max or approximate start time if there

+     * was no previous max. By default this parameter is defined as a number.

+     * The format type and format style rules from the

+     * {@linkplain java.text.MessageFormat} should be used to convert this from

+     * milliseconds to a date or time.

+     * <li>{@code maxMillis} the most recent log record

+     * {@linkplain java.util.logging.LogRecord#getMillis event time}

+     * {@linkplain #format consumed} by this formatter. If the count is zero

+     * then this is set to the {@linkplain System#currentTimeMillis() current time}.

+     * By default this parameter is defined as a number. The format type and

+     * format style rules from the {@linkplain java.text.MessageFormat} should be

+     * used to convert this from milliseconds to a date or time.

+     * <li>{@code elapsed} the elapsed time in milliseconds between the

+     * {@code maxMillis} and {@code minMillis}.

+     * <li>{@code startTime} the approximate start time in milliseconds.  By

+     * default this parameter is defined as a number. The format type and format

+     * style rules from the {@linkplain java.text.MessageFormat} should be used to

+     * convert this from milliseconds to a date or time.

+     * <li>{@code currentTime} the

+     * {@linkplain System#currentTimeMillis() current time} in milliseconds.  By

+     * default this parameter is defined as a number. The format type and format

+     * style rules from the {@linkplain java.text.MessageFormat} should be used to

+     * convert this from milliseconds to a date or time.

+     * <li>{@code uptime} the elapsed time in milliseconds between the

+     * {@code currentTime} and {@code startTime}.

+     * <li>{@code generation} the number times this method produced output with

+     * at least one {@linkplain #format consumed} log record.  This can be used

+     * to track the number of complete reports this formatter has produced.

+     * </ol>

+     *

+     * <p>

+     * Some example formats:<br>

+     * <ul>

+     * <li>{@code com.sun.mail.util.logging.CollectorFormatter.format={0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n}

+     * <p>

+     * This prints the head ({@code {0}}), format ({@code {1}}), and tail

+     * ({@code {2}}) from the target formatter followed by the number of

+     * remaining ({@code {4}}) log records consumed by this formatter if there

+     * are any remaining records.

+     * <pre>

+     * Encoding failed.|NullPointerException: null String.getBytes(:913)... 3 more

+     * </pre>

+     * <li>{@code com.sun.mail.util.logging.CollectorFormatter.format=These {3} messages occurred between\n{7,date,EEE, MMM dd HH:mm:ss:S ZZZ yyyy} and {8,time,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}\n}

+     * <p>

+     * This prints the count ({@code {3}}) followed by the date and time of the

+     * eldest log record ({@code {7}}) and the date and time of the most recent

+     * log record ({@code {8}}).

+     * <pre>

+     * These 292 messages occurred between

+     * Tue, Jul 21 14:11:42:449 -0500 2009 and Fri, Nov 20 07:29:24:0 -0600 2009

+     * </pre>

+     * <li>{@code com.sun.mail.util.logging.CollectorFormatter.format=These {3} messages occurred between {9,choice,86400000#{7,date} {7,time} and {8,time}|86400000<{7,date} and {8,date}}\n}

+     * <p>

+     * This prints the count ({@code {3}}) and then chooses the format based on

+     * the elapsed time ({@code {9}}). If the elapsed time is less than one day

+     * then the eldest log record ({@code {7}}) date and time is formatted

+     * followed by just the time of the most recent log record ({@code {8}}.

+     * Otherwise, the just the date of the eldest log record ({@code {7}}) and

+     * just the date of most recent log record ({@code {8}} is formatted.

+     * <pre>

+     * These 73 messages occurred between Jul 21, 2009 2:11:42 PM and 2:13:32 PM

+     *

+     * These 116 messages occurred between Jul 21, 2009 and Aug 20, 2009

+     * </pre>

+     * <li>{@code com.sun.mail.util.logging.CollectorFormatter.format={13} alert reports since {10,date}.\n}

+     * <p>

+     * This prints the generation ({@code {13}}) followed by the start time

+     * ({@code {10}}) formatted as a date.

+     * <pre>

+     * 4,320 alert reports since Jul 21, 2012.

+     * </pre>

+     * </ul>

+     *

+     * @param h the handler or null.

+     * @return the output string.

+     */

+    @Override

+    public String getTail(final Handler h) {

+        super.getTail(h);  //Be forward compatible with super.getHead.

+        return formatRecord(h, true);

+    }

+

+    /**

+     * Formats the collected LogRecord and summary statistics. The LogRecord and

+     * summary statistics are not changed by calling this method.

+     *

+     * @return the current record formatted or the default toString.

+     * @see #getTail(java.util.logging.Handler)

+     */

+    @Override

+    public String toString() {

+        String result;

+        try {

+            result = formatRecord((Handler) null, false);

+        } catch (final RuntimeException ignore) {

+            result = super.toString();

+        }

+        return result;

+    }

+

+    /**

+     * Used to choose the collected LogRecord. This implementation returns the

+     * greater of two LogRecords.

+     *

+     * @param t the current record.

+     * @param u the record that could replace the current.

+     * @return the greater of the given log records.

+     * @throws NullPointerException may occur if either record is null.

+     */

+    protected LogRecord apply(final LogRecord t, final LogRecord u) {

+        if (t == null || u == null) {

+            throw new NullPointerException();

+        }

+

+        if (comparator != null) {

+            return comparator.compare(t, u) >= 0 ? t : u;

+        } else {

+            return u;

+        }

+    }

+

+    /**

+     * Updates the summary statistics only if the expected record matches the

+     * last record.  The update record is not stored.

+     *

+     * @param e the LogRecord that is expected.

+     * @param u the LogRecord used to collect statistics.

+     * @return true if the last record was the expected record.

+     * @throws NullPointerException if the update record is null.

+     */

+    private synchronized boolean accept(final LogRecord e, final LogRecord u) {

+        /**

+         * LogRecord methods must be called before the check of the last stored

+         * record to guard against subclasses of LogRecord that might attempt to

+         * reset the state by triggering a call to getTail.

+         */

+        final long millis = u.getMillis(); //Null check.

+        final Throwable ex = u.getThrown();

+        if (last == e) {  //Only if the exact same reference.

+            if (++count != 1L) {

+                minMillis = Math.min(minMillis, millis);

+            } else { //Show single records as instant and not a time period.

+                minMillis = millis;

+            }

+            maxMillis = Math.max(maxMillis, millis);

+

+            if (ex != null) {

+                ++thrown;

+            }

+            return true;

+        } else {

+            return false;

+        }

+    }

+

+    /**

+     * Resets all of the collected summary statistics including the LogRecord.

+     * @param min the current min milliseconds.

+     */

+    private synchronized void reset(final long min) {

+        if (last != null) {

+            last = null;

+            ++generation;

+        }

+

+        count = 0L;

+        thrown = 0L;

+        minMillis = min;

+        maxMillis = Long.MIN_VALUE;

+    }

+

+    /**

+     * Formats the given record with the head and tail.

+     *

+     * @param h the Handler or null.

+     * @param reset true if the summary statistics and LogRecord should be reset

+     * back to initial values.

+     * @return the formatted string.

+     * @see #getTail(java.util.logging.Handler)

+     */

+    private String formatRecord(final Handler h, final boolean reset) {

+        final LogRecord record;

+        final long c;

+        final long t;

+        final long g;

+        long msl;

+        long msh;

+        long now;

+        synchronized (this) {

+            record = last;

+            c = count;

+            g = generation;

+            t = thrown;

+            msl = minMillis;

+            msh = maxMillis;

+            now = System.currentTimeMillis();

+            if (c == 0L) {

+                msh = now;

+            }

+

+            if (reset) { //BUG ID 6351685

+                reset(msh);

+            }

+        }

+

+        final String head;

+        final String msg;

+        final String tail;

+        final Formatter f = this.formatter;

+        if (f != null) {

+            synchronized (f) {

+                head = f.getHead(h);

+                msg = record != null ? f.format(record) : "";

+                tail = f.getTail(h);

+            }

+        } else {

+            head = "";

+            msg = record != null ? formatMessage(record) : "";

+            tail = "";

+        }

+

+        Locale l = null;

+        if (record != null) {

+            ResourceBundle rb = record.getResourceBundle();

+            l = rb == null ? null : rb.getLocale();

+        }

+

+        final MessageFormat mf;

+        if (l == null) { //BUG ID 8039165

+            mf = new MessageFormat(fmt);

+        } else {

+            mf = new MessageFormat(fmt, l);

+        }

+

+        /**

+         * These arguments are described in the getTail documentation.

+         */

+        return mf.format(new Object[]{finish(head), finish(msg), finish(tail),

+            c, (c - 1L), t, (c - t), msl, msh, (msh - msl), INIT_TIME, now,

+            (now - INIT_TIME), g});

+    }

+

+    /**

+     * Applied to the head, format, and tail returned by the target formatter.

+     * This implementation trims all input strings.

+     *

+     * @param s the string to transform.

+     * @return the transformed string.

+     * @throws NullPointerException if the given string is null.

+     */

+    protected String finish(String s) {

+        return s.trim();

+    }

+

+    /**

+     * Peek at the current log record.

+     *

+     * @return null or the current log record.

+     */

+    private synchronized LogRecord peek() {

+        return this.last;

+    }

+

+    /**

+     * Updates the summary statistics and stores given LogRecord if the expected

+     * record matches the current record.

+     *

+     * @param e the expected record.

+     * @param u the update record.

+     * @return true if the update was performed.

+     * @throws NullPointerException if the update record is null.

+     */

+    private synchronized boolean acceptAndUpdate(LogRecord e, LogRecord u) {

+        if (accept(e, u)) {

+            this.last = u;

+            return true;

+        } else {

+            return false;

+        }

+    }

+

+    /**

+     * Gets the message format string from the LogManager or creates the default

+     * message format string.

+     *

+     * @param p the class name prefix.

+     * @return the format string.

+     * @throws NullPointerException if the given argument is null.

+     */

+    private String initFormat(final String p) {

+        String v = fromLogManager(p.concat(".format"));

+        if (v == null || v.length() == 0) {

+            v = "{0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n";

+        }

+        return v;

+    }

+

+    /**

+     * Gets and creates the formatter from the LogManager or creates the default

+     * formatter.

+     *

+     * @param p the class name prefix.

+     * @return the formatter.

+     * @throws NullPointerException if the given argument is null.

+     * @throws UndeclaredThrowableException if the formatter can not be created.

+     */

+    private Formatter initFormatter(final String p) {

+        Formatter f;

+        String v = fromLogManager(p.concat(".formatter"));

+        if (v != null && v.length() != 0) {

+            if (!"null".equalsIgnoreCase(v)) {

+                try {

+                    f = LogManagerProperties.newFormatter(v);

+                } catch (final RuntimeException re) {

+                    throw re;

+                } catch (final Exception e) {

+                    throw new UndeclaredThrowableException(e);

+                }

+            } else {

+                f = null;

+            }

+        } else {

+            //Don't force the byte code verifier to load the formatter.

+            f = Formatter.class.cast(new CompactFormatter());

+        }

+        return f;

+    }

+

+    /**

+     * Gets and creates the comparator from the LogManager or returns the

+     * default comparator.

+     *

+     * @param p the class name prefix.

+     * @return the comparator or null.

+     * @throws IllegalArgumentException if it was specified that the comparator

+     * should be reversed but no initial comparator was specified.

+     * @throws NullPointerException if the given argument is null.

+     * @throws UndeclaredThrowableException if the comparator can not be

+     * created.

+     */

+    @SuppressWarnings("unchecked")

+    private Comparator<? super LogRecord> initComparator(final String p) {

+        Comparator<? super LogRecord> c;

+        final String name = fromLogManager(p.concat(".comparator"));

+        final String reverse = fromLogManager(p.concat(".comparator.reverse"));

+        try {

+            if (name != null && name.length() != 0) {

+                if (!"null".equalsIgnoreCase(name)) {

+                    c = LogManagerProperties.newComparator(name);

+                    if (Boolean.parseBoolean(reverse)) {

+                        assert c != null;

+                        c = LogManagerProperties.reverseOrder(c);

+                    }

+                } else {

+                    if (reverse != null) {

+                        throw new IllegalArgumentException(

+                                "No comparator to reverse.");

+                    } else {

+                        c = null; //No ordering.

+                    }

+                }

+            } else {

+                if (reverse != null) {

+                    throw new IllegalArgumentException(

+                            "No comparator to reverse.");

+                } else {

+                    //Don't force the byte code verifier to load the comparator.

+                    c = Comparator.class.cast(SeverityComparator.getInstance());

+                }

+            }

+        } catch (final RuntimeException re) {

+            throw re; //Avoid catch all.

+        } catch (final Exception e) {

+            throw new UndeclaredThrowableException(e);

+        }

+        return c;

+    }

+}

diff --git a/mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java b/mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
new file mode 100644
index 0000000..f359f18
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/logging/CompactFormatter.java
@@ -0,0 +1,865 @@
+/*

+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2013, 2018 Jason Mehrens. All rights reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0, which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * This Source Code may also be made available under the following Secondary

+ * Licenses when the conditions for such availability set forth in the

+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,

+ * version 2 with the GNU Classpath Exception, which is available at

+ * https://www.gnu.org/software/classpath/license.html.

+ *

+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

+ */

+package com.sun.mail.util.logging;

+

+import java.util.*;

+import java.util.logging.LogRecord;

+

+/**

+ * A plain text formatter that can produce fixed width output. By default this

+ * formatter will produce output no greater than 160 characters wide plus the

+ * separator and newline characters. Only specified fields support an

+ * {@linkplain #toAlternate(java.lang.String) alternate} fixed width format.

+ * <p>

+ * By default each <tt>CompactFormatter</tt> is initialized using the following

+ * LogManager configuration properties where

+ * <tt>&lt;formatter-name&gt;</tt> refers to the fully qualified class name or

+ * the fully qualified derived class name of the formatter. If properties are

+ * not defined, or contain invalid values, then the specified default values are

+ * used.

+ * <ul>

+ * <li>&lt;formatter-name&gt;.format - the {@linkplain java.util.Formatter

+ *     format} string used to transform the output. The format string can be

+ * used to fix the output size. (defaults to <tt>%7$#.160s%n</tt>)</li>

+ * </ul>

+ *

+ * @author Jason Mehrens

+ * @since JavaMail 1.5.2

+ */

+public class CompactFormatter extends java.util.logging.Formatter {

+

+    /**

+     * Load any declared classes to workaround GLASSFISH-21258.

+     */

+    static {

+        loadDeclaredClasses();

+    }

+

+    /**

+     * Used to load declared classes encase class loader doesn't allow loading

+     * during JVM termination. This method is used with unit testing.

+     *

+     * @return an array of classes never null.

+     */

+    private static Class<?>[] loadDeclaredClasses() {

+        return new Class<?>[]{Alternate.class};

+    }

+

+    /**

+     * Holds the java.util.Formatter pattern.

+     */

+    private final String fmt;

+

+    /**

+     * Creates an instance with a default format pattern.

+     */

+    public CompactFormatter() {

+        String p = getClass().getName();

+        this.fmt = initFormat(p);

+    }

+

+    /**

+     * Creates an instance with the given format pattern.

+     *

+     * @param format the {@linkplain java.util.Formatter pattern} or null to use

+     * the LogManager default. The arguments are described in the

+     * {@linkplain #format(java.util.logging.LogRecord) format} method.

+     */

+    public CompactFormatter(final String format) {

+        String p = getClass().getName();

+        this.fmt = format == null ? initFormat(p) : format;

+    }

+

+    /**

+     * Format the given log record and returns the formatted string. The

+     * {@linkplain java.util.Formatter#format(java.lang.String, java.lang.Object...)

+     * java.util} argument indexes are assigned to the following properties:

+     *

+     * <ol start='0'>

+     * <li>{@code format} - the {@linkplain java.util.Formatter

+     *     java.util.Formatter} format string specified in the

+     * &lt;formatter-name&gt;.format property or the format that was given when

+     * this formatter was created.</li>

+     * <li>{@code date} - if the log record supports nanoseconds then a

+     * ZonedDateTime object representing the event time of the log record in the

+     * system time zone. Otherwise, a {@linkplain Date} object representing

+     * {@linkplain LogRecord#getMillis event time} of the log record.</li>

+     * <li>{@code source} - a string representing the caller, if available;

+     * otherwise, the logger's name.</li>

+     * <li>{@code logger} - the logger's

+     * {@linkplain Class#getSimpleName() simple}

+     * {@linkplain LogRecord#getLoggerName() name}.</li>

+     * <li>{@code level} - the

+     * {@linkplain java.util.logging.Level#getLocalizedName log level}.</li>

+     * <li>{@code message} - the formatted log message returned from the

+     * {@linkplain #formatMessage(LogRecord)} method.</li>

+     * <li>{@code thrown} - a string representing the

+     * {@linkplain LogRecord#getThrown throwable} associated with the log record

+     * and a relevant stack trace element if available; otherwise, an empty

+     * string is used.</li>

+     * <li>{@code message|thrown} The message and the thrown properties joined

+     * as one parameter. This parameter supports

+     * {@linkplain #toAlternate(java.lang.String) alternate} form.</li>

+     * <li>{@code thrown|message} The thrown and message properties joined as

+     * one parameter. This parameter supports

+     * {@linkplain #toAlternate(java.lang.String) alternate} form.</li>

+     * <li>{@code sequence} the

+     * {@linkplain LogRecord#getSequenceNumber() sequence number} if the given

+     * log record.</li>

+     * <li>{@code thread id} the {@linkplain LogRecord#getThreadID() thread id}

+     * of the given log record. By default this is formatted as a {@code long}

+     * by an unsigned conversion.</li>

+     * <li>{@code error} the throwable

+     * {@linkplain Class#getSimpleName() simple class name} and

+     * {@linkplain #formatError(LogRecord) error message} without any stack

+     * trace.</li>

+     * <li>{@code message|error} The message and error properties joined as one

+     * parameter. This parameter supports

+     * {@linkplain #toAlternate(java.lang.String) alternate} form.</li>

+     * <li>{@code error|message} The error and message properties joined as one

+     * parameter. This parameter supports

+     * {@linkplain #toAlternate(java.lang.String) alternate} form.</li>

+     * <li>{@code backtrace} only the

+     * {@linkplain #formatBackTrace(LogRecord) stack trace} of the given

+     * throwable.</li>

+     * <li>{@code bundlename} the resource bundle

+     * {@linkplain LogRecord#getResourceBundleName() name} of the given log

+     * record.</li>

+     * <li>{@code key} the {@linkplain LogRecord#getMessage() raw message}

+     * before localization or formatting.</li>

+     * </ol>

+     *

+     * <p>

+     * Some example formats:<br>

+     * <ul>

+     * <li>{@code com.sun.mail.util.logging.CompactFormatter.format=%7$#.160s%n}

+     * <p>

+     * This prints only 160 characters of the message|thrown ({@code 7$}) using

+     * the {@linkplain #toAlternate(java.lang.String) alternate} form. The

+     * separator is not included as part of the total width.

+     * <pre>

+     * Encoding failed.|NullPointerException: null String.getBytes(:913)

+     * </pre>

+     *

+     * <li>{@code com.sun.mail.util.logging.CompactFormatter.format=%7$#.20s%n}

+     * <p>

+     * This prints only 20 characters of the message|thrown ({@code 7$}) using

+     * the {@linkplain #toAlternate(java.lang.String) alternate} form. This will

+     * perform a weighted truncation of both the message and thrown properties

+     * of the log record. The separator is not included as part of the total

+     * width.

+     * <pre>

+     * Encoding|NullPointerE

+     * </pre>

+     *

+     * <li>{@code com.sun.mail.util.logging.CompactFormatter.format=%1$tc %2$s%n%4$s: %5$s%6$s%n}

+     * <p>

+     * This prints the timestamp ({@code 1$}) and the source ({@code 2$}) on the

+     * first line. The second line is the log level ({@code 4$}), log message

+     * ({@code 5$}), and the throwable with a relevant stack trace element

+     * ({@code 6$}) if one is available.

+     * <pre>

+     * Fri Nov 20 07:29:24 CST 2009 MyClass fatal

+     * SEVERE: Encoding failed.NullPointerException: null String.getBytes(:913)

+     * </pre>

+     *

+     * <li>{@code com.sun.mail.util.logging.CompactFormatter.format=%4$s: %12$#.160s%n}

+     * <p>

+     * This prints the log level ({@code 4$}) and only 160 characters of the

+     * message|error ({@code 12$}) using the alternate form.

+     * <pre>

+     * SEVERE: Unable to send notification.|SocketException: Permission denied: connect

+     * </pre>

+     *

+     * <li>{@code com.sun.mail.util.logging.CompactFormatter.format=[%9$d][%1$tT][%10$d][%2$s] %5$s%n%6$s%n}

+     * <p>

+     * This prints the sequence ({@code 9$}), event time ({@code 1$}) as 24 hour

+     * time, thread id ({@code 10$}), source ({@code 2$}), log message

+     * ({@code 5$}), and the throwable with back trace ({@code 6$}).

+     * <pre>

+     * [125][14:11:42][38][MyClass fatal] Unable to send notification.

+     * SocketException: Permission denied: connect SMTPTransport.openServer(:1949)

+     * </pre>

+     *

+     * </ul>

+     *

+     * @param record the log record to format.

+     * @return the formatted record.

+     * @throws NullPointerException if the given record is null.

+     */

+    @Override

+    public String format(final LogRecord record) {

+        //LogRecord is mutable so define local vars.

+        ResourceBundle rb = record.getResourceBundle();

+        Locale l = rb == null ? null : rb.getLocale();

+

+        String msg = formatMessage(record);

+        String thrown = formatThrown(record);

+        String err = formatError(record);

+        Object[] params = {

+            formatZonedDateTime(record),

+            formatSource(record),

+            formatLoggerName(record),

+            formatLevel(record),

+            msg,

+            thrown,

+            new Alternate(msg, thrown),

+            new Alternate(thrown, msg),

+            record.getSequenceNumber(),

+            formatThreadID(record),

+            err,

+            new Alternate(msg, err),

+            new Alternate(err, msg),

+            formatBackTrace(record),

+            record.getResourceBundleName(),

+            record.getMessage()};

+

+        if (l == null) { //BUG ID 6282094

+            return String.format(fmt, params);

+        } else {

+            return String.format(l, fmt, params);

+        }

+    }

+

+    /**

+     * Formats message for the log record. This method removes any fully

+     * qualified throwable class names from the message.

+     *

+     * @param record the log record.

+     * @return the formatted message string.

+     */

+    @Override

+    public String formatMessage(final LogRecord record) {

+        String msg = super.formatMessage(record);

+        msg = replaceClassName(msg, record.getThrown());

+        msg = replaceClassName(msg, record.getParameters());

+        return msg;

+    }

+

+    /**

+     * Formats the message from the thrown property of the log record. This

+     * method replaces fully qualified throwable class names from the message

+     * cause chain with simple class names.

+     *

+     * @param t the throwable to format or null.

+     * @return the empty string if null was given or the formatted message

+     * string from the throwable which may be null.

+     */

+    public String formatMessage(final Throwable t) {

+        String r;

+        if (t != null) {

+            final Throwable apply = apply(t);

+            final String m = apply.getLocalizedMessage();

+            final String s = apply.toString();

+            final String sn = simpleClassName(apply.getClass());

+            if (!isNullOrSpaces(m)) {

+                if (s.contains(m)) {

+                    if (s.startsWith(apply.getClass().getName())

+                            || s.startsWith(sn)) {

+                        r = replaceClassName(m, t);

+                    } else {

+                        r = replaceClassName(simpleClassName(s), t);

+                    }

+                } else {

+                    r = replaceClassName(simpleClassName(s) + ": " + m, t);

+                }

+            } else {

+                r = replaceClassName(simpleClassName(s), t);

+            }

+

+            if (!r.contains(sn)) {

+                r = sn + ": " + r;

+            }

+        } else {

+            r = "";

+        }

+        return r;

+    }

+

+    /**

+     * Formats the level property of the given log record.

+     *

+     * @param record the record.

+     * @return the formatted logger name.

+     * @throws NullPointerException if the given record is null.

+     */

+    public String formatLevel(final LogRecord record) {

+        return record.getLevel().getLocalizedName();

+    }

+

+    /**

+     * Formats the source from the given log record.

+     *

+     * @param record the record.

+     * @return the formatted source of the log record.

+     * @throws NullPointerException if the given record is null.

+     */

+    public String formatSource(final LogRecord record) {

+        String source = record.getSourceClassName();

+        if (source != null) {

+            if (record.getSourceMethodName() != null) {

+                source = simpleClassName(source) + " "

+                        + record.getSourceMethodName();

+            } else {

+                source = simpleClassName(source);

+            }

+        } else {

+            source = simpleClassName(record.getLoggerName());

+        }

+        return source;

+    }

+

+    /**

+     * Formats the logger name property of the given log record.

+     *

+     * @param record the record.

+     * @return the formatted logger name.

+     * @throws NullPointerException if the given record is null.

+     */

+    public String formatLoggerName(final LogRecord record) {

+        return simpleClassName(record.getLoggerName());

+    }

+

+    /**

+     * Formats the thread id property of the given log record. By default this

+     * is formatted as a {@code long} by an unsigned conversion.

+     *

+     * @param record the record.

+     * @return the formatted thread id as a number.

+     * @throws NullPointerException if the given record is null.

+     * @since JavaMail 1.5.4

+     */

+    public Number formatThreadID(final LogRecord record) {

+        /**

+         * Thread.getID is defined as long and LogRecord.getThreadID is defined

+         * as int. Convert to unsigned as a means to better map the two types of

+         * thread identifiers.

+         */

+        return (((long) record.getThreadID()) & 0xffffffffL);

+    }

+

+    /**

+     * Formats the thrown property of a LogRecord. The returned string will

+     * contain a throwable message with a back trace.

+     *

+     * @param record the record.

+     * @return empty string if nothing was thrown or formatted string.

+     * @throws NullPointerException if the given record is null.

+     * @see #apply(java.lang.Throwable)

+     * @see #formatBackTrace(java.util.logging.LogRecord)

+     */

+    public String formatThrown(final LogRecord record) {

+        String msg;

+        final Throwable t = record.getThrown();

+        if (t != null) {

+            String site = formatBackTrace(record);

+            msg = formatMessage(t) + (isNullOrSpaces(site) ? "" : ' ' + site);

+        } else {

+            msg = "";

+        }

+        return msg;

+    }

+

+    /**

+     * Formats the thrown property of a LogRecord as an error message. The

+     * returned string will not contain a back trace.

+     *

+     * @param record the record.

+     * @return empty string if nothing was thrown or formatted string.

+     * @throws NullPointerException if the given record is null.

+     * @see Throwable#toString()

+     * @see #apply(java.lang.Throwable)

+     * @see #formatMessage(java.lang.Throwable)

+     * @since JavaMail 1.5.4

+     */

+    public String formatError(final LogRecord record) {

+        return formatMessage(record.getThrown());

+    }

+

+    /**

+     * Formats the back trace for the given log record.

+     *

+     * @param record the log record to format.

+     * @return the formatted back trace.

+     * @throws NullPointerException if the given record is null.

+     * @see #apply(java.lang.Throwable)

+     * @see #formatThrown(java.util.logging.LogRecord)

+     * @see #ignore(java.lang.StackTraceElement)

+     */

+    public String formatBackTrace(final LogRecord record) {

+        String site = "";

+        final Throwable t = record.getThrown();

+        if (t != null) {

+            final Throwable root = apply(t);

+            StackTraceElement[] trace = root.getStackTrace();

+            site = findAndFormat(trace);

+            if (isNullOrSpaces(site)) {

+                int limit = 0;

+                for (Throwable c = t; c != null; c = c.getCause()) {

+                    StackTraceElement[] ste = c.getStackTrace();

+                    site = findAndFormat(ste);

+                    if (!isNullOrSpaces(site)) {

+                        break;

+                    } else {

+                        if (trace.length == 0) {

+                           trace = ste;

+                        }

+                    }

+

+                    //Deal with excessive cause chains

+                    //and cyclic throwables.

+                    if (++limit == (1 << 16)) {

+                        break; //Give up.

+                    }

+                }

+

+                //Punt.

+                if (isNullOrSpaces(site) && trace.length != 0) {

+                    site = formatStackTraceElement(trace[0]);

+                }

+            }

+        }

+        return site;

+    }

+

+    /**

+     * Finds and formats the first stack frame of interest.

+     *

+     * @param trace the fill stack to examine.

+     * @return a String that best describes the call site.

+     * @throws NullPointerException if stack trace element array is null.

+     */

+    private String findAndFormat(final StackTraceElement[] trace) {

+        String site = "";

+        for (StackTraceElement s : trace) {

+            if (!ignore(s)) {

+                site = formatStackTraceElement(s);

+                break;

+            }

+        }

+

+        //Check if all code was compiled with no debugging info.

+        if (isNullOrSpaces(site)) {

+            for (StackTraceElement s : trace) {

+                if (!defaultIgnore(s)) {

+                    site = formatStackTraceElement(s);

+                    break;

+                }

+            }

+        }

+        return site;

+    }

+

+    /**

+     * Formats a stack trace element into a simple call site.

+     *

+     * @param s the stack trace element to format.

+     * @return the formatted stack trace element.

+     * @throws NullPointerException if stack trace element is null.

+     * @see #formatThrown(java.util.logging.LogRecord)

+     */

+    private String formatStackTraceElement(final StackTraceElement s) {

+        String v = simpleClassName(s.getClassName());

+        String result;

+        if (v != null) {

+            result = s.toString().replace(s.getClassName(), v);

+        } else {

+            result = s.toString();

+        }

+

+        //If the class name contains the simple file name then remove file name.

+        v = simpleFileName(s.getFileName());

+        if (v != null && result.startsWith(v)) {

+            result = result.replace(s.getFileName(), "");

+        }

+        return result;

+    }

+

+    /**

+     * Chooses a single throwable from the cause chain that will be formatted.

+     * This implementation chooses the throwable that best describes the chain.

+     * Subclasses can override this method to choose an alternate throwable for

+     * formatting.

+     *

+     * @param t the throwable from the log record.

+     * @return the chosen throwable or null only if the given argument is null.

+     * @see #formatThrown(java.util.logging.LogRecord)

+     */

+    protected Throwable apply(final Throwable t) {

+        return SeverityComparator.getInstance().apply(t);

+    }

+

+    /**

+     * Determines if a stack frame should be ignored as the cause of an error.

+     *

+     * @param s the stack trace element.

+     * @return true if this frame should be ignored.

+     * @see #formatThrown(java.util.logging.LogRecord)

+     */

+    protected boolean ignore(final StackTraceElement s) {

+        return isUnknown(s) || defaultIgnore(s);

+    }

+

+    /**

+     * Defines the alternate format. This implementation removes all control

+     * characters from the given string.

+     *

+     * @param s any string or null.

+     * @return null if the argument was null otherwise, an alternate string.

+     */

+    protected String toAlternate(final String s) {

+        return s != null ? s.replaceAll("[\\x00-\\x1F\\x7F]+", "") : null;

+    }

+

+    /**

+     * Gets the zoned date time from the given log record.

+     *

+     * @param record the current log record.

+     * @return a zoned date time or a legacy date object.

+     * @throws NullPointerException if the given record is null.

+     * @since JavaMail 1.5.6

+     */

+    private Comparable<?> formatZonedDateTime(final LogRecord record) {

+        Comparable<?> zdt = LogManagerProperties.getZonedDateTime(record);

+        if (zdt == null) {

+            zdt = new java.util.Date(record.getMillis());

+        }

+        return zdt;

+    }

+

+    /**

+     * Determines if a stack frame should be ignored as the cause of an error.

+     * This does not check for unknown line numbers because code can be compiled

+     * without debugging info.

+     *

+     * @param s the stack trace element.

+     * @return true if this frame should be ignored.

+     */

+    private boolean defaultIgnore(final StackTraceElement s) {

+        return isSynthetic(s) || isStaticUtility(s) || isReflection(s);

+    }

+

+    /**

+     * Determines if a stack frame is for a static utility class.

+     *

+     * @param s the stack trace element.

+     * @return true if this frame should be ignored.

+     */

+    private boolean isStaticUtility(final StackTraceElement s) {

+        try {

+            return LogManagerProperties.isStaticUtilityClass(s.getClassName());

+        } catch (RuntimeException ignore) {

+        } catch (Exception | LinkageError ignore) {

+        }

+        final String cn = s.getClassName();

+        return (cn.endsWith("s") && !cn.endsWith("es"))

+                || cn.contains("Util") || cn.endsWith("Throwables");

+    }

+

+    /**

+     * Determines if a stack trace element is for a synthetic method.

+     *

+     * @param s the stack trace element.

+     * @return true if synthetic.

+     * @throws NullPointerException if stack trace element is null.

+     */

+    private boolean isSynthetic(final StackTraceElement s) {

+        return s.getMethodName().indexOf('$') > -1;

+    }

+

+    /**

+     * Determines if a stack trace element has an unknown line number or a

+     * native line number.

+     *

+     * @param s the stack trace element.

+     * @return true if the line number is unknown.

+     * @throws NullPointerException if stack trace element is null.

+     */

+    private boolean isUnknown(final StackTraceElement s) {

+        return s.getLineNumber() < 0;

+    }

+

+    /**

+     * Determines if a stack trace element represents a reflection frame.

+     *

+     * @param s the stack trace element.

+     * @return true if the line number is unknown.

+     * @throws NullPointerException if stack trace element is null.

+     */

+    private boolean isReflection(final StackTraceElement s) {

+        try {

+            return LogManagerProperties.isReflectionClass(s.getClassName());

+        } catch (RuntimeException ignore) {

+        } catch (Exception | LinkageError ignore) {

+        }

+        return s.getClassName().startsWith("java.lang.reflect.")

+                || s.getClassName().startsWith("sun.reflect.");

+    }

+

+    /**

+     * Creates the format pattern for this formatter.

+     *

+     * @param p the class name prefix.

+     * @return the java.util.Formatter format string.

+     * @throws NullPointerException if the given class name is null.

+     */

+    private String initFormat(final String p) {

+        String v = LogManagerProperties.fromLogManager(p.concat(".format"));

+        if (isNullOrSpaces(v)) {

+            v = "%7$#.160s%n"; //160 chars split between message and thrown.

+        }

+        return v;

+    }

+

+    /**

+     * Searches the given message for all instances fully qualified class name

+     * with simple class name based off of the types contained in the given

+     * parameter array.

+     *

+     * @param msg the message.

+     * @param t the throwable cause chain to search or null.

+     * @return the modified message string.

+     */

+    private static String replaceClassName(String msg, Throwable t) {

+        if (!isNullOrSpaces(msg)) {

+            int limit = 0;

+            for (Throwable c = t; c != null; c = c.getCause()) {

+                final Class<?> k = c.getClass();

+                msg = msg.replace(k.getName(), simpleClassName(k));

+

+                //Deal with excessive cause chains and cyclic throwables.

+                if (++limit == (1 << 16)) {

+                    break; //Give up.

+                }

+            }

+        }

+        return msg;

+    }

+

+    /**

+     * Searches the given message for all instances fully qualified class name

+     * with simple class name based off of the types contained in the given

+     * parameter array.

+     *

+     * @param msg the message or null.

+     * @param p the parameter array or null.

+     * @return the modified message string.

+     */

+    private static String replaceClassName(String msg, Object[] p) {

+        if (!isNullOrSpaces(msg) && p != null) {

+            for (Object o : p) {

+                if (o != null) {

+                    final Class<?> k = o.getClass();

+                    msg = msg.replace(k.getName(), simpleClassName(k));

+                }

+            }

+        }

+        return msg;

+    }

+

+    /**

+     * Gets the simple class name from the given class. This is a workaround for

+     * BUG ID JDK-8057919.

+     *

+     * @param k the class object.

+     * @return the simple class name or null.

+     * @since JavaMail 1.5.3

+     */

+    private static String simpleClassName(final Class<?> k) {

+        try {

+            return k.getSimpleName();

+        } catch (final InternalError JDK8057919) {

+        }

+        return simpleClassName(k.getName());

+    }

+

+    /**

+     * Converts a fully qualified class name to a simple class name. If the

+     * leading part of the given string is not a legal class name then the given

+     * string is returned.

+     *

+     * @param name the fully qualified class name prefix or null.

+     * @return the simple class name or given input.

+     */

+    private static String simpleClassName(String name) {

+        if (name != null) {

+            int cursor = 0;

+            int sign = -1;

+            int dot = -1;

+            for (int c, prev = dot; cursor < name.length();

+                    cursor += Character.charCount(c)) {

+                c = name.codePointAt(cursor);

+                if (!Character.isJavaIdentifierPart(c)) {

+                    if (c == ((int) '.')) {

+                        if ((dot + 1) != cursor && (dot + 1) != sign) {

+                            prev = dot;

+                            dot = cursor;

+                        } else {

+                            return name;

+                        }

+                    } else {

+                        if ((dot + 1) == cursor) {

+                            dot = prev;

+                        }

+                        break;

+                    }

+                } else {

+                    if (c == ((int) '$')) {

+                        sign = cursor;

+                    }

+                }

+            }

+

+            if (dot > -1 && ++dot < cursor && ++sign < cursor) {

+                name = name.substring(sign > dot ? sign : dot);

+            }

+        }

+        return name;

+    }

+

+    /**

+     * Converts a file name with an extension to a file name without an

+     * extension.

+     *

+     * @param name the full file name or null.

+     * @return the simple file name or null.

+     */

+    private static String simpleFileName(String name) {

+        if (name != null) {

+            final int index = name.lastIndexOf('.');

+            name = index > -1 ? name.substring(0, index) : name;

+        }

+        return name;

+    }

+

+    /**

+     * Determines is the given string is null or spaces.

+     *

+     * @param s the string or null.

+     * @return true if null or spaces.

+     */

+    private static boolean isNullOrSpaces(final String s) {

+        return s == null || s.trim().length() == 0;

+    }

+

+    /**

+     * Used to format two arguments as fixed length message.

+     */

+    private class Alternate implements java.util.Formattable {

+

+        /**

+         * The left side of the output.

+         */

+        private final String left;

+        /**

+         * The right side of the output.

+         */

+        private final String right;

+

+        /**

+         * Creates an alternate output.

+         *

+         * @param left the left side or null.

+         * @param right the right side or null.

+         */

+        Alternate(final String left, final String right) {

+            this.left = String.valueOf(left);

+            this.right = String.valueOf(right);

+        }

+

+        @SuppressWarnings("override") //JDK-6954234

+        public void formatTo(java.util.Formatter formatter, int flags,

+                int width, int precision) {

+

+            String l = left;

+            String r = right;

+            if ((flags & java.util.FormattableFlags.UPPERCASE)

+                    == java.util.FormattableFlags.UPPERCASE) {

+                l = l.toUpperCase(formatter.locale());

+                r = r.toUpperCase(formatter.locale());

+            }

+

+            if ((flags & java.util.FormattableFlags.ALTERNATE)

+                    == java.util.FormattableFlags.ALTERNATE) {

+                l = toAlternate(l);

+                r = toAlternate(r);

+            }

+

+            if (precision <= 0) {

+                precision = Integer.MAX_VALUE;

+            }

+

+            int fence = Math.min(l.length(), precision);

+            if (fence > (precision >> 1)) {

+                fence = Math.max(fence - r.length(), fence >> 1);

+            }

+

+            if (fence > 0) {

+                if (fence > l.length()

+                        && Character.isHighSurrogate(l.charAt(fence - 1))) {

+                    --fence;

+                }

+                l = l.substring(0, fence);

+            }

+            r = r.substring(0, Math.min(precision - fence, r.length()));

+

+            if (width > 0) {

+                final int half = width >> 1;

+                if (l.length() < half) {

+                    l = pad(flags, l, half);

+                }

+

+                if (r.length() < half) {

+                    r = pad(flags, r, half);

+                }

+            }

+

+            Object[] empty = Collections.emptySet().toArray();

+            formatter.format(l, empty);

+            if (l.length() != 0 && r.length() != 0) {

+                formatter.format("|", empty);

+            }

+            formatter.format(r, empty);

+        }

+

+        /**

+         * Pad the given input string.

+         *

+         * @param flags the formatter flags.

+         * @param s the string to pad.

+         * @param length the final string length.

+         * @return the padded string.

+         */

+        private String pad(int flags, String s, int length) {

+            final int padding = length - s.length();

+            final StringBuilder b = new StringBuilder(length);

+            if ((flags & java.util.FormattableFlags.LEFT_JUSTIFY)

+                    == java.util.FormattableFlags.LEFT_JUSTIFY) {

+                for (int i = 0; i < padding; ++i) {

+                    b.append('\u0020');

+                }

+                b.append(s);

+            } else {

+                b.append(s);

+                for (int i = 0; i < padding; ++i) {

+                    b.append('\u0020');

+                }

+            }

+            return b.toString();

+        }

+    }

+}

diff --git a/mail/src/main/java/com/sun/mail/util/logging/DurationFilter.java b/mail/src/main/java/com/sun/mail/util/logging/DurationFilter.java
new file mode 100644
index 0000000..d868229
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/logging/DurationFilter.java
@@ -0,0 +1,444 @@
+/*

+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2015, 2018 Jason Mehrens. All rights reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0, which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * This Source Code may also be made available under the following Secondary

+ * Licenses when the conditions for such availability set forth in the

+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,

+ * version 2 with the GNU Classpath Exception, which is available at

+ * https://www.gnu.org/software/classpath/license.html.

+ *

+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

+ */

+package com.sun.mail.util.logging;

+

+import static com.sun.mail.util.logging.LogManagerProperties.fromLogManager;

+import java.util.logging.*;

+

+/**

+ * A filter used to limit log records based on a maximum generation rate.

+ *

+ * The duration specified is used to compute the record rate and the amount of

+ * time the filter will reject records once the rate has been exceeded. Once the

+ * rate is exceeded records are not allowed until the duration has elapsed.

+ *

+ * <p>

+ * By default each {@code DurationFilter} is initialized using the following

+ * LogManager configuration properties where {@code <filter-name>} refers to the

+ * fully qualified class name of the handler. If properties are not defined, or

+ * contain invalid values, then the specified default values are used.

+ *

+ * <ul>

+ * <li>{@literal <filter-name>}.records the max number of records per duration.

+ * A numeric long integer or a multiplication expression can be used as the

+ * value. (defaults to {@code 1000})

+ *

+ * <li>{@literal <filter-name>}.duration the number of milliseconds to suppress

+ * log records from being published. This is also used as duration to determine

+ * the log record rate. A numeric long integer or a multiplication expression

+ * can be used as the value. If the {@code java.time} package is available then

+ * an ISO-8601 duration format of {@code PnDTnHnMn.nS} can be used as the value.

+ * The suffixes of "D", "H", "M" and "S" are for days, hours, minutes and

+ * seconds. The suffixes must occur in order. The seconds can be specified with

+ * a fractional component to declare milliseconds. (defaults to {@code PT15M})

+ * </ul>

+ *

+ * <p>

+ * For example, the settings to limit {@code MailHandler} with a default

+ * capacity to only send a maximum of two email messages every six minutes would

+ * be as follows:

+ * <pre>

+ * {@code

+ *  com.sun.mail.util.logging.MailHandler.filter = com.sun.mail.util.logging.DurationFilter

+ *  com.sun.mail.util.logging.MailHandler.capacity = 1000

+ *  com.sun.mail.util.logging.DurationFilter.records = 2L * 1000L

+ *  com.sun.mail.util.logging.DurationFilter.duration = PT6M

+ * }

+ * </pre>

+ *

+ *

+ * @author Jason Mehrens

+ * @since JavaMail 1.5.5

+ */

+public class DurationFilter implements Filter {

+

+    /**

+     * The number of expected records per duration.

+     */

+    private final long records;

+    /**

+     * The duration in milliseconds used to determine the rate. The duration is

+     * also used as the amount of time that the filter will not allow records

+     * when saturated.

+     */

+    private final long duration;

+    /**

+     * The number of records seen for the current duration. This value negative

+     * if saturated. Zero is considered saturated but is reserved for recording

+     * the first duration.

+     */

+    private long count;

+    /**

+     * The most recent record time seen for the current duration.

+     */

+    private long peak;

+    /**

+     * The start time for the current duration.

+     */

+    private long start;

+

+    /**

+     * Creates the filter using the default properties.

+     */

+    public DurationFilter() {

+        this.records = checkRecords(initLong(".records"));

+        this.duration = checkDuration(initLong(".duration"));

+    }

+

+    /**

+     * Creates the filter using the given properties. Default values are used if

+     * any of the given values are outside the allowed range.

+     *

+     * @param records the number of records per duration.

+     * @param duration the number of milliseconds to suppress log records from

+     * being published.

+     */

+    public DurationFilter(final long records, final long duration) {

+        this.records = checkRecords(records);

+        this.duration = checkDuration(duration);

+    }

+

+    /**

+     * Determines if this filter is equal to another filter.

+     *

+     * @param obj the given object.

+     * @return true if equal otherwise false.

+     */

+    @Override

+    public boolean equals(final Object obj) {

+        if (this == obj) { //Avoid locks and deal with rapid state changes.

+            return true;

+        }

+

+        if (obj == null || getClass() != obj.getClass()) {

+            return false;

+        }

+

+        final DurationFilter other = (DurationFilter) obj;

+        if (this.records != other.records) {

+            return false;

+        }

+

+        if (this.duration != other.duration) {

+            return false;

+        }

+

+        final long c;

+        final long p;

+        final long s;

+        synchronized (this) {

+            c = this.count;

+            p = this.peak;

+            s = this.start;

+        }

+

+        synchronized (other) {

+            if (c != other.count || p != other.peak || s != other.start) {

+                return false;

+            }

+        }

+        return true;

+    }

+

+    /**

+     * Determines if this filter is able to accept the maximum number of log

+     * records for this instant in time. The result is a best-effort estimate

+     * and should be considered out of date as soon as it is produced. This

+     * method is designed for use in monitoring the state of this filter.

+     *

+     * @return true if the filter is idle; false otherwise.

+     */

+    public boolean isIdle() {

+        return test(0L, System.currentTimeMillis());

+    }

+

+    /**

+     * Returns a hash code value for this filter.

+     *

+     * @return hash code for this filter.

+     */

+    @Override

+    public int hashCode() {

+        int hash = 3;

+        hash = 89 * hash + (int) (this.records ^ (this.records >>> 32));

+        hash = 89 * hash + (int) (this.duration ^ (this.duration >>> 32));

+        return hash;

+    }

+

+    /**

+     * Check if the given log record should be published. This method will

+     * modify the internal state of this filter.

+     *

+     * @param record the log record to check.

+     * @return true if allowed; false otherwise.

+     * @throws NullPointerException if given record is null.

+     */

+    @SuppressWarnings("override") //JDK-6954234

+    public boolean isLoggable(final LogRecord record) {

+        return accept(record.getMillis());

+    }

+

+    /**

+     * Determines if this filter will accept log records for this instant in

+     * time. The result is a best-effort estimate and should be considered out

+     * of date as soon as it is produced. This method is designed for use in

+     * monitoring the state of this filter.

+     *

+     * @return true if the filter is not saturated; false otherwise.

+     */

+    public boolean isLoggable() {

+        return test(records, System.currentTimeMillis());

+    }

+

+    /**

+     * Returns a string representation of this filter. The result is a

+     * best-effort estimate and should be considered out of date as soon as it

+     * is produced.

+     *

+     * @return a string representation of this filter.

+     */

+    @Override

+    public String toString() {

+        boolean idle;

+        boolean loggable;

+        synchronized (this) {

+            final long millis = System.currentTimeMillis();

+            idle = test(0L, millis);

+            loggable = test(records, millis);

+        }

+

+        return getClass().getName() + "{records=" + records

+                + ", duration=" + duration

+                + ", idle=" + idle

+                + ", loggable=" + loggable + '}';

+    }

+

+    /**

+     * Creates a copy of this filter that retains the filter settings but does

+     * not include the current filter state. The newly create clone acts as if

+     * it has never seen any records.

+     *

+     * @return a copy of this filter.

+     * @throws CloneNotSupportedException if this filter is not allowed to be

+     * cloned.

+     */

+    @Override

+    protected DurationFilter clone() throws CloneNotSupportedException {

+        final DurationFilter clone = (DurationFilter) super.clone();

+        clone.count = 0L; //Reset the filter state.

+        clone.peak = 0L;

+        clone.start = 0L;

+        return clone;

+    }

+

+    /**

+     * Checks if this filter is not saturated or bellow a maximum rate.

+     *

+     * @param limit the number of records allowed to be under the rate.

+     * @param millis the current time in milliseconds.

+     * @return true if not saturated or bellow the rate.

+     */

+    private boolean test(final long limit, final long millis) {

+        assert limit >= 0L : limit;

+        final long c;

+        final long s;

+        synchronized (this) {

+            c = count;

+            s = start;

+        }

+

+        if (c > 0L) { //If not saturated.

+            if ((millis - s) >= duration || c < limit) {

+                return true;

+            }

+        } else {  //Subtraction is used to deal with numeric overflow.

+            if ((millis - s) >= 0L || c == 0L) {

+                return true;

+            }

+        }

+        return false;

+    }

+

+    /**

+     * Determines if the record is loggable by time.

+     *

+     * @param millis the log record milliseconds.

+     * @return true if accepted false otherwise.

+     */

+    private synchronized boolean accept(final long millis) {

+        //Subtraction is used to deal with numeric overflow of millis.

+        boolean allow;

+        if (count > 0L) { //If not saturated.

+            if ((millis - peak) > 0L) {

+                peak = millis; //Record the new peak.

+            }

+

+            //Under the rate if the count has not been reached.

+            if (count != records) {

+                ++count;

+                allow = true;

+            } else {

+                if ((peak - start) >= duration) {

+                    count = 1L;  //Start a new duration.

+                    start = peak;

+                    allow = true;

+                } else {

+                    count = -1L; //Saturate for the duration.

+                    start = peak + duration;

+                    allow = false;

+                }

+            }

+        } else {

+            //If the saturation period has expired or this is the first record

+            //then start a new duration and allow records.

+            if ((millis - start) >= 0L || count == 0L) {

+                count = 1L;

+                start = millis;

+                peak = millis;

+                allow = true;

+            } else {

+                allow = false; //Remain in a saturated state.

+            }

+        }

+        return allow;

+    }

+

+    /**

+     * Reads a long value or multiplication expression from the LogManager. If

+     * the value can not be parsed or is not defined then Long.MIN_VALUE is

+     * returned.

+     *

+     * @param suffix a dot character followed by the key name.

+     * @return a long value or Long.MIN_VALUE if unable to parse or undefined.

+     * @throws NullPointerException if suffix is null.

+     */

+    private long initLong(final String suffix) {

+        long result = 0L;

+        final String p = getClass().getName();

+        String value = fromLogManager(p.concat(suffix));

+        if (value != null && value.length() != 0) {

+            value = value.trim();

+            if (isTimeEntry(suffix, value)) {

+                try {

+                    result = LogManagerProperties.parseDurationToMillis(value);

+                } catch (final RuntimeException ignore) {

+                } catch (final Exception ignore) {

+                } catch (final LinkageError ignore) {

+                }

+            }

+

+            if (result == 0L) { //Zero is invalid.

+                try {

+                    result = 1L;

+                    for (String s : tokenizeLongs(value)) {

+                        if (s.endsWith("L") || s.endsWith("l")) {

+                            s = s.substring(0, s.length() - 1);

+                        }

+                        result = multiplyExact(result, Long.parseLong(s));

+                    }

+                } catch (final RuntimeException ignore) {

+                    result = Long.MIN_VALUE;

+                }

+            }

+        } else {

+            result = Long.MIN_VALUE;

+        }

+        return result;

+    }

+

+    /**

+     * Determines if the given suffix can be a time unit and the value is

+     * encoded as an ISO ISO-8601 duration format.

+     *

+     * @param suffix the suffix property.

+     * @param value the value of the property.

+     * @return true if the entry is a time entry.

+     * @throws IndexOutOfBoundsException if value is empty.

+     * @throws NullPointerException if either argument is null.

+     */

+    private boolean isTimeEntry(final String suffix, final String value) {

+        return (value.charAt(0) == 'P' || value.charAt(0) == 'p')

+                && suffix.equals(".duration");

+    }

+

+    /**

+     * Parse any long value or multiplication expressions into tokens.

+     *

+     * @param value the expression or value.

+     * @return an array of long tokens, never empty.

+     * @throws NullPointerException if the given value is null.

+     * @throws NumberFormatException if the expression is invalid.

+     */

+    private static String[] tokenizeLongs(final String value) {

+        String[] e;

+        final int i = value.indexOf('*');

+        if (i > -1 && (e = value.split("\\s*\\*\\s*")).length != 0) {

+            if (i == 0 || value.charAt(value.length() - 1) == '*') {

+                throw new NumberFormatException(value);

+            }

+

+            if (e.length == 1) {

+                throw new NumberFormatException(e[0]);

+            }

+        } else {

+            e = new String[]{value};

+        }

+        return e;

+    }

+

+    /**

+     * Multiply and check for overflow. This can be replaced with

+     * {@code java.lang.Math.multiplyExact} when JavaMail requires JDK 8.

+     *

+     * @param x the first value.

+     * @param y the second value.

+     * @return x times y.

+     * @throws ArithmeticException if overflow is detected.

+     */

+    private static long multiplyExact(final long x, final long y) {

+        long r = x * y;

+        if (((Math.abs(x) | Math.abs(y)) >>> 31L != 0L)) {

+            if (((y != 0L) && (r / y != x))

+                    || (x == Long.MIN_VALUE && y == -1L)) {

+                throw new ArithmeticException();

+            }

+        }

+        return r;

+    }

+

+    /**

+     * Converts record count to a valid record count. If the value is out of

+     * bounds then the default record count is used.

+     *

+     * @param records the record count.

+     * @return a valid number of record count.

+     */

+    private static long checkRecords(final long records) {

+        return records > 0L ? records : 1000L;

+    }

+

+    /**

+     * Converts the duration to a valid duration. If the value is out of bounds

+     * then the default duration is used.

+     *

+     * @param duration the duration to check.

+     * @return a valid duration.

+     */

+    private static long checkDuration(final long duration) {

+        return duration > 0L ? duration : 15L * 60L * 1000L;

+    }

+}

diff --git a/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java b/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
new file mode 100644
index 0000000..b20a8f2
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/logging/LogManagerProperties.java
@@ -0,0 +1,1090 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2018 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package com.sun.mail.util.logging;
+
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import java.util.logging.*;
+import java.util.logging.Formatter;
+
+/**
+ * An adapter class to allow the Mail API to access the LogManager properties.
+ * The LogManager properties are treated as the root of all properties. First,
+ * the parent properties are searched. If no value is found, then, the
+ * LogManager is searched with prefix value. If not found, then, just the key
+ * itself is searched in the LogManager. If a value is found in the LogManager
+ * it is then copied to this properties object with no key prefix. If no value
+ * is found in the LogManager or the parent properties, then this properties
+ * object is searched only by passing the key value.
+ *
+ * <p>
+ * This class also emulates the LogManager functions for creating new objects
+ * from string class names. This is to support initial setup of objects such as
+ * log filters, formatters, error managers, etc.
+ *
+ * <p>
+ * This class should never be exposed outside of this package. Keep this class
+ * package private (default access).
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.4.3
+ */
+final class LogManagerProperties extends Properties {
+
+    /**
+     * Generated serial id.
+     */
+    private static final long serialVersionUID = -2239983349056806252L;
+    /**
+     * Holds the method used to get the LogRecord instant if running on JDK 9 or
+     * later.
+     */
+    private static final Method LR_GET_INSTANT;
+
+    /**
+     * Holds the method used to get the default time zone if running on JDK 9 or
+     * later.
+     */
+    private static final Method ZI_SYSTEM_DEFAULT;
+
+    /**
+     * Holds the method used to convert and instant to a zoned date time if
+     * running on JDK 9 later.
+     */
+    private static final Method ZDT_OF_INSTANT;
+
+    static {
+        Method lrgi = null;
+        Method zisd = null;
+        Method zdtoi = null;
+        try {
+            lrgi = LogRecord.class.getMethod("getInstant");
+            assert Comparable.class
+                    .isAssignableFrom(lrgi.getReturnType()) : lrgi;
+            zisd = findClass("java.time.ZoneId")
+                    .getMethod("systemDefault");
+            if (!Modifier.isStatic(zisd.getModifiers())) {
+                throw new NoSuchMethodException(zisd.toString());
+            }
+
+            zdtoi = findClass("java.time.ZonedDateTime")
+                    .getMethod("ofInstant", findClass("java.time.Instant"),
+                            findClass("java.time.ZoneId"));
+            if (!Modifier.isStatic(zdtoi.getModifiers())) {
+                throw new NoSuchMethodException(zdtoi.toString());
+            }
+
+            if (!Comparable.class.isAssignableFrom(zdtoi.getReturnType())) {
+                throw new NoSuchMethodException(zdtoi.toString());
+            }
+        } catch (final RuntimeException ignore) {
+        } catch (final Exception ignore) { //No need for specific catch.
+        } catch (final LinkageError ignore) {
+        } finally {
+            if (lrgi == null || zisd == null || zdtoi == null) {
+                lrgi = null; //If any are null then clear all.
+                zisd = null;
+                zdtoi = null;
+            }
+        }
+
+        LR_GET_INSTANT = lrgi;
+        ZI_SYSTEM_DEFAULT = zisd;
+        ZDT_OF_INSTANT = zdtoi;
+    }
+    /**
+     * Caches the read only reflection class names string array. Declared
+     * volatile for safe publishing only. The VO_VOLATILE_REFERENCE_TO_ARRAY
+     * warning is a false positive.
+     */
+    @SuppressWarnings("VolatileArrayField")
+    private static volatile String[] REFLECT_NAMES;
+    /**
+     * Caches the LogManager or Properties so we only read the configuration
+     * once.
+     */
+    private static final Object LOG_MANAGER = loadLogManager();
+
+    /**
+     * Get the LogManager or loads a Properties object to use as the LogManager.
+     *
+     * @return the LogManager or a loaded Properties object.
+     * @since JavaMail 1.5.3
+     */
+    private static Object loadLogManager() {
+        Object m;
+        try {
+            m = LogManager.getLogManager();
+        } catch (final LinkageError restricted) {
+            m = readConfiguration();
+        } catch (final RuntimeException unexpected) {
+            m = readConfiguration();
+        }
+        return m;
+    }
+
+    /**
+     * Create a properties object from the default logging configuration file.
+     * Since the LogManager is not available in restricted environments, only
+     * the default configuration is applicable.
+     *
+     * @return a properties object loaded with the default configuration.
+     * @since JavaMail 1.5.3
+     */
+    private static Properties readConfiguration() {
+        /**
+         * Load the properties file so the default settings are available when
+         * user code creates a logging object. The class loader for the
+         * restricted LogManager can't access these classes to attach them to a
+         * logger or handler on startup. Creating logging objects at this point
+         * is both useless and risky.
+         */
+        final Properties props = new Properties();
+        try {
+            String n = System.getProperty("java.util.logging.config.file");
+            if (n != null) {
+                final File f = new File(n).getCanonicalFile();
+                final InputStream in = new FileInputStream(f);
+                try {
+                    props.load(in);
+                } finally {
+                    in.close();
+                }
+            }
+        } catch (final RuntimeException permissionsOrMalformed) {
+        } catch (final Exception ioe) {
+        } catch (final LinkageError unexpected) {
+        }
+        return props;
+    }
+
+    /**
+     * Gets LogManger property for the running JVM. If the LogManager doesn't
+     * exist then the default LogManger properties are used.
+     *
+     * @param name the property name.
+     * @return the LogManager.
+     * @throws NullPointerException if the given name is null.
+     * @since JavaMail 1.5.3
+     */
+    static String fromLogManager(final String name) {
+        if (name == null) {
+            throw new NullPointerException();
+        }
+
+        final Object m = LOG_MANAGER;
+        try {
+            if (m instanceof Properties) {
+                return ((Properties) m).getProperty(name);
+            }
+        } catch (final RuntimeException unexpected) {
+        }
+
+        if (m != null) {
+            try {
+                if (m instanceof LogManager) {
+                    return ((LogManager) m).getProperty(name);
+                }
+            } catch (final LinkageError restricted) {
+            } catch (final RuntimeException unexpected) {
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check that the current context is trusted to modify the logging
+     * configuration. This requires LoggingPermission("control").
+     * @throws SecurityException  if a security manager exists and the caller
+     * does not have {@code LoggingPermission("control")}.
+     * @since JavaMail 1.5.3
+     */
+    static void checkLogManagerAccess() {
+        boolean checked = false;
+        final Object m = LOG_MANAGER;
+        if (m != null) {
+            try {
+                if (m instanceof LogManager) {
+                    checked = true;
+                    ((LogManager) m).checkAccess();
+                }
+            } catch (final SecurityException notAllowed) {
+                if (checked) {
+                    throw notAllowed;
+                }
+            } catch (final LinkageError restricted) {
+            } catch (final RuntimeException unexpected) {
+            }
+        }
+
+        if (!checked) {
+            checkLoggingAccess();
+        }
+    }
+
+    /**
+     * Check that the current context is trusted to modify the logging
+     * configuration when the LogManager is not present. This requires
+     * LoggingPermission("control").
+     * @throws SecurityException  if a security manager exists and the caller
+     * does not have {@code LoggingPermission("control")}.
+     * @since JavaMail 1.5.3
+     */
+    private static void checkLoggingAccess() {
+        /**
+         * Some environments selectively enforce logging permissions by allowing
+         * access to loggers but not allowing access to handlers. This is an
+         * indirect way of checking for LoggingPermission when the LogManager is
+         * not present. The root logger will lazy create handlers so the global
+         * logger is used instead as it is a known named logger with well
+         * defined behavior. If the global logger is a subclass then fallback to
+         * using the SecurityManager.
+         */
+        boolean checked = false;
+        final Logger global = Logger.getLogger("global");
+        try {
+            if (Logger.class == global.getClass()) {
+                global.removeHandler((Handler) null);
+                checked = true;
+            }
+        } catch (final NullPointerException unexpected) {
+        }
+
+        if (!checked) {
+            final SecurityManager sm = System.getSecurityManager();
+            if (sm != null) {
+                sm.checkPermission(new LoggingPermission("control", null));
+            }
+        }
+    }
+
+    /**
+     * Determines if access to the {@code java.util.logging.LogManager} class is
+     * restricted by the class loader.
+     *
+     * @return true if a LogManager is present.
+     * @since JavaMail 1.5.3
+     */
+    static boolean hasLogManager() {
+        final Object m = LOG_MANAGER;
+        return m != null && !(m instanceof Properties);
+    }
+
+    /**
+     * Gets the ZonedDateTime from the given log record.
+     *
+     * @param record used to generate the zoned date time.
+     * @return null if LogRecord doesn't support nanoseconds otherwise a new
+     * zoned date time is returned.
+     * @throws NullPointerException if record is null.
+     * @since JavaMail 1.5.6
+     */
+    @SuppressWarnings("UseSpecificCatch")
+    static Comparable<?> getZonedDateTime(LogRecord record) {
+        if (record == null) {
+           throw new NullPointerException();
+        }
+        final Method m = ZDT_OF_INSTANT;
+        if (m != null) {
+            try {
+                return (Comparable<?>) m.invoke((Object) null,
+                        LR_GET_INSTANT.invoke(record),
+                        ZI_SYSTEM_DEFAULT.invoke((Object) null));
+            } catch (final RuntimeException ignore) {
+                assert LR_GET_INSTANT != null
+                        && ZI_SYSTEM_DEFAULT != null : ignore;
+            } catch (final InvocationTargetException ite) {
+                final Throwable cause = ite.getCause();
+                if (cause instanceof Error) {
+                    throw (Error) cause;
+                } else if (cause instanceof RuntimeException) {
+                    throw (RuntimeException) cause;
+                } else { //Should never happen.
+                    throw new UndeclaredThrowableException(ite);
+                }
+            } catch (final Exception ignore) {
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Gets the local host name from the given service.
+     *
+     * @param s the service to examine.
+     * @return the local host name or null.
+     * @throws IllegalAccessException if the method is inaccessible.
+     * @throws InvocationTargetException if the method throws an exception.
+     * @throws LinkageError if the linkage fails.
+     * @throws NullPointerException if the given service is null.
+     * @throws ExceptionInInitializerError if the static initializer fails.
+     * @throws Exception if there is a problem.
+     * @throws NoSuchMethodException if the given service does not have a method
+     * to get the local host name as a string.
+     * @throws SecurityException if unable to inspect properties of object.
+     * @since JavaMail 1.5.3
+     */
+    static String getLocalHost(final Object s) throws Exception {
+        try {
+            final Method m = s.getClass().getMethod("getLocalHost");
+            if (!Modifier.isStatic(m.getModifiers())
+                    && m.getReturnType() == String.class) {
+                return (String) m.invoke(s);
+            } else {
+                throw new NoSuchMethodException(m.toString());
+            }
+        } catch (final ExceptionInInitializerError EIIE) {
+            throw wrapOrThrow(EIIE);
+        } catch (final InvocationTargetException ite) {
+            throw paramOrError(ite);
+        }
+    }
+
+    /**
+     * Used to parse an ISO-8601 duration format of {@code PnDTnHnMn.nS}.
+     *
+     * @param value an ISO-8601 duration character sequence.
+     * @return the number of milliseconds parsed from the duration.
+     * @throws ClassNotFoundException if the java.time classes are not present.
+     * @throws IllegalAccessException if the method is inaccessible.
+     * @throws InvocationTargetException if the method throws an exception.
+     * @throws LinkageError if the linkage fails.
+     * @throws NullPointerException if the given duration is null.
+     * @throws ExceptionInInitializerError if the static initializer fails.
+     * @throws Exception if there is a problem.
+     * @throws NoSuchMethodException if the correct time methods are missing.
+     * @throws SecurityException if reflective access to the java.time classes
+     * are not allowed.
+     * @since JavaMail 1.5.5
+     */
+    static long parseDurationToMillis(final CharSequence value) throws Exception {
+        try {
+            final Class<?> k = findClass("java.time.Duration");
+            final Method parse = k.getMethod("parse", CharSequence.class);
+            if (!k.isAssignableFrom(parse.getReturnType())
+                    || !Modifier.isStatic(parse.getModifiers())) {
+               throw new NoSuchMethodException(parse.toString());
+            }
+
+            final Method toMillis = k.getMethod("toMillis");
+            if (!Long.TYPE.isAssignableFrom(toMillis.getReturnType())
+                    || Modifier.isStatic(toMillis.getModifiers())) {
+                throw new NoSuchMethodException(toMillis.toString());
+            }
+            return (Long) toMillis.invoke(parse.invoke(null, value));
+        } catch (final ExceptionInInitializerError EIIE) {
+            throw wrapOrThrow(EIIE);
+        } catch (final InvocationTargetException ite) {
+            throw paramOrError(ite);
+        }
+    }
+
+    /**
+     * Converts a locale to a language tag.
+     *
+     * @param locale the locale to convert.
+     * @return the language tag.
+     * @throws NullPointerException if the given locale is null.
+     * @since JavaMail 1.4.5
+     */
+    static String toLanguageTag(final Locale locale) {
+        final String l = locale.getLanguage();
+        final String c = locale.getCountry();
+        final String v = locale.getVariant();
+        final char[] b = new char[l.length() + c.length() + v.length() + 2];
+        int count = l.length();
+        l.getChars(0, count, b, 0);
+        if (c.length() != 0 || (l.length() != 0 && v.length() != 0)) {
+            b[count] = '-';
+            ++count; //be nice to the client compiler.
+            c.getChars(0, c.length(), b, count);
+            count += c.length();
+        }
+
+        if (v.length() != 0 && (l.length() != 0 || c.length() != 0)) {
+            b[count] = '-';
+            ++count; //be nice to the client compiler.
+            v.getChars(0, v.length(), b, count);
+            count += v.length();
+        }
+        return String.valueOf(b, 0, count);
+    }
+
+    /**
+     * Creates a new filter from the given class name.
+     *
+     * @param name the fully qualified class name.
+     * @return a new filter.
+     * @throws ClassCastException if class name does not match the type.
+     * @throws ClassNotFoundException if the class name was not found.
+     * @throws IllegalAccessException if the constructor is inaccessible.
+     * @throws InstantiationException if the given class name is abstract.
+     * @throws InvocationTargetException if the constructor throws an exception.
+     * @throws LinkageError if the linkage fails.
+     * @throws ExceptionInInitializerError if the static initializer fails.
+     * @throws Exception to match the error method of the ErrorManager.
+     * @throws NoSuchMethodException if the class name does not have a no
+     * argument constructor.
+     * @since JavaMail 1.4.5
+     */
+    static Filter newFilter(String name) throws Exception {
+        return newObjectFrom(name, Filter.class);
+    }
+
+    /**
+     * Creates a new formatter from the given class name.
+     *
+     * @param name the fully qualified class name.
+     * @return a new formatter.
+     * @throws ClassCastException if class name does not match the type.
+     * @throws ClassNotFoundException if the class name was not found.
+     * @throws IllegalAccessException if the constructor is inaccessible.
+     * @throws InstantiationException if the given class name is abstract.
+     * @throws InvocationTargetException if the constructor throws an exception.
+     * @throws LinkageError if the linkage fails.
+     * @throws ExceptionInInitializerError if the static initializer fails.
+     * @throws Exception to match the error method of the ErrorManager.
+     * @throws NoSuchMethodException if the class name does not have a no
+     * argument constructor.
+     * @since JavaMail 1.4.5
+     */
+    static Formatter newFormatter(String name) throws Exception {
+        return newObjectFrom(name, Formatter.class);
+    }
+
+    /**
+     * Creates a new log record comparator from the given class name.
+     *
+     * @param name the fully qualified class name.
+     * @return a new comparator.
+     * @throws ClassCastException if class name does not match the type.
+     * @throws ClassNotFoundException if the class name was not found.
+     * @throws IllegalAccessException if the constructor is inaccessible.
+     * @throws InstantiationException if the given class name is abstract.
+     * @throws InvocationTargetException if the constructor throws an exception.
+     * @throws LinkageError if the linkage fails.
+     * @throws ExceptionInInitializerError if the static initializer fails.
+     * @throws Exception to match the error method of the ErrorManager.
+     * @throws NoSuchMethodException if the class name does not have a no
+     * argument constructor.
+     * @since JavaMail 1.4.5
+     * @see java.util.logging.LogRecord
+     */
+    @SuppressWarnings("unchecked")
+    static Comparator<? super LogRecord> newComparator(String name) throws Exception {
+        return newObjectFrom(name, Comparator.class);
+    }
+
+    /**
+     * Returns a comparator that imposes the reverse ordering of the specified
+     * {@link Comparator}. If the given comparator declares a public
+     * reverseOrder method that method is called first and the return value is
+     * used. If that method is not declared or the caller does not have access
+     * then a comparator wrapping the given comparator is returned.
+     *
+     * @param <T> the element type to be compared
+     * @param c a comparator whose ordering is to be reversed by the returned
+     * comparator
+     * @return A comparator that imposes the reverse ordering of the specified
+     * comparator.
+     * @throws NullPointerException if the given comparator is null.
+     * @since JavaMail 1.5.0
+     */
+    @SuppressWarnings({"unchecked", "ThrowableResultIgnored"})
+    static <T> Comparator<T> reverseOrder(final Comparator<T> c) {
+        if (c == null) {
+            throw new NullPointerException();
+        }
+
+        Comparator<T> reverse = null;
+        //Comparator in Java 1.8 has 'reversed' as a default method.
+        //This code calls that method first to allow custom
+        //code to define what reverse order means.
+        try {
+            //assert Modifier.isPublic(c.getClass().getModifiers()) :
+            //        Modifier.toString(c.getClass().getModifiers());
+            final Method m = c.getClass().getMethod("reversed");
+            if (!Modifier.isStatic(m.getModifiers())
+                    && Comparator.class.isAssignableFrom(m.getReturnType())) {
+                try {
+                    reverse = (Comparator<T>) m.invoke(c);
+                } catch (final ExceptionInInitializerError eiie) {
+                    throw wrapOrThrow(eiie);
+                }
+            }
+        } catch (final NoSuchMethodException ignore) {
+        } catch (final IllegalAccessException ignore) {
+        } catch (final RuntimeException ignore) {
+        } catch (final InvocationTargetException ite) {
+            paramOrError(ite); //Ignore invocation bugs (returned values).
+        }
+
+        if (reverse == null) {
+            reverse = Collections.reverseOrder(c);
+        }
+        return reverse;
+    }
+
+    /**
+     * Creates a new error manager from the given class name.
+     *
+     * @param name the fully qualified class name.
+     * @return a new error manager.
+     * @throws ClassCastException if class name does not match the type.
+     * @throws ClassNotFoundException if the class name was not found.
+     * @throws IllegalAccessException if the constructor is inaccessible.
+     * @throws InstantiationException if the given class name is abstract.
+     * @throws InvocationTargetException if the constructor throws an exception.
+     * @throws LinkageError if the linkage fails.
+     * @throws ExceptionInInitializerError if the static initializer fails.
+     * @throws Exception to match the error method of the ErrorManager.
+     * @throws NoSuchMethodException if the class name does not have a no
+     * argument constructor.
+     * @since JavaMail 1.4.5
+     */
+    static ErrorManager newErrorManager(String name) throws Exception {
+        return newObjectFrom(name, ErrorManager.class);
+    }
+
+    /**
+     * Determines if the given class name identifies a utility class.
+     *
+     * @param name the fully qualified class name.
+     * @return true if the given class name
+     * @throws ClassNotFoundException if the class name was not found.
+     * @throws IllegalAccessException if the constructor is inaccessible.
+     * @throws LinkageError if the linkage fails.
+     * @throws ExceptionInInitializerError if the static initializer fails.
+     * @throws Exception to match the error method of the ErrorManager.
+     * @throws SecurityException if unable to inspect properties of class.
+     * @since JavaMail 1.5.2
+     */
+    static boolean isStaticUtilityClass(String name) throws Exception {
+        final Class<?> c = findClass(name);
+        final Class<?> obj = Object.class;
+        Method[] methods;
+        boolean util;
+        if (c != obj && (methods = c.getMethods()).length != 0) {
+            util = true;
+            for (Method m : methods) {
+                if (m.getDeclaringClass() != obj
+                        && !Modifier.isStatic(m.getModifiers())) {
+                    util = false;
+                    break;
+                }
+            }
+        } else {
+            util = false;
+        }
+        return util;
+    }
+
+    /**
+     * Determines if the given class name is a reflection class name responsible
+     * for invoking methods and or constructors.
+     *
+     * @param name the fully qualified class name.
+     * @return true if the given class name
+     * @throws ClassNotFoundException if the class name was not found.
+     * @throws IllegalAccessException if the constructor is inaccessible.
+     * @throws LinkageError if the linkage fails.
+     * @throws ExceptionInInitializerError if the static initializer fails.
+     * @throws Exception to match the error method of the ErrorManager.
+     * @throws SecurityException if unable to inspect properties of class.
+     * @since JavaMail 1.5.2
+     */
+    static boolean isReflectionClass(String name) throws Exception {
+        String[] names = REFLECT_NAMES;
+        if (names == null) { //Benign data race.
+            REFLECT_NAMES = names = reflectionClassNames();
+        }
+
+        for (String rf : names) { //The set of names is small.
+            if (name.equals(rf)) {
+                return true;
+            }
+        }
+
+        findClass(name); //Fail late instead of normal return.
+        return false;
+    }
+
+    /**
+     * Determines all of the reflection class names used to invoke methods.
+     *
+     * This method performs indirect and direct calls on a throwable to capture
+     * the standard class names and the implementation class names.
+     *
+     * @return a string array containing the fully qualified class names.
+     * @throws Exception if there is a problem.
+     */
+    private static String[] reflectionClassNames() throws Exception {
+        final Class<?> thisClass = LogManagerProperties.class;
+        assert Modifier.isFinal(thisClass.getModifiers()) : thisClass;
+        try {
+            final HashSet<String> traces = new HashSet<>();
+            Throwable t = Throwable.class.getConstructor().newInstance();
+            for (StackTraceElement ste : t.getStackTrace()) {
+                if (!thisClass.getName().equals(ste.getClassName())) {
+                    traces.add(ste.getClassName());
+                } else {
+                    break;
+                }
+            }
+
+            Throwable.class.getMethod("fillInStackTrace").invoke(t);
+            for (StackTraceElement ste : t.getStackTrace()) {
+                if (!thisClass.getName().equals(ste.getClassName())) {
+                    traces.add(ste.getClassName());
+                } else {
+                    break;
+                }
+            }
+            return traces.toArray(new String[traces.size()]);
+        } catch (final InvocationTargetException ITE) {
+            throw paramOrError(ITE);
+        }
+    }
+
+    /**
+     * Creates a new object from the given class name.
+     *
+     * @param <T> The generic class type.
+     * @param name the fully qualified class name.
+     * @param type the assignable type for the given name.
+     * @return a new object assignable to the given type.
+     * @throws ClassCastException if class name does not match the type.
+     * @throws ClassNotFoundException if the class name was not found.
+     * @throws IllegalAccessException if the constructor is inaccessible.
+     * @throws InstantiationException if the given class name is abstract.
+     * @throws InvocationTargetException if the constructor throws an exception.
+     * @throws LinkageError if the linkage fails.
+     * @throws ExceptionInInitializerError if the static initializer fails.
+     * @throws Exception to match the error method of the ErrorManager.
+     * @throws NoSuchMethodException if the class name does not have a no
+     * argument constructor.
+     * @since JavaMail 1.4.5
+     */
+    static <T> T newObjectFrom(String name, Class<T> type) throws Exception {
+        try {
+            final Class<?> clazz = LogManagerProperties.findClass(name);
+            //This check avoids additional side effects when the name parameter
+            //is a literal name and not a class name.
+            if (type.isAssignableFrom(clazz)) {
+                try {
+                    return type.cast(clazz.getConstructor().newInstance());
+                } catch (final InvocationTargetException ITE) {
+                    throw paramOrError(ITE);
+                }
+            } else {
+                throw new ClassCastException(clazz.getName()
+                        + " cannot be cast to " + type.getName());
+            }
+        } catch (final NoClassDefFoundError NCDFE) {
+            //No class def found can occur on filesystems that are
+            //case insensitive (BUG ID 6196068).  In some cases, we allow class
+            //names or literal names, this code guards against the case where a
+            //literal name happens to match a class name in a different case.
+            //This is also a nice way to adapt this error for the error manager.
+            throw new ClassNotFoundException(NCDFE.toString(), NCDFE);
+        } catch (final ExceptionInInitializerError EIIE) {
+            throw wrapOrThrow(EIIE);
+        }
+    }
+
+    /**
+     * Returns the given exception or throws the escaping cause.
+     *
+     * @param ite any invocation target.
+     * @return the exception.
+     * @throws VirtualMachineError if present as cause.
+     * @throws ThreadDeath if present as cause.
+     * @since JavaMail 1.4.5
+     */
+    private static Exception paramOrError(InvocationTargetException ite) {
+        final Throwable cause = ite.getCause();
+        if (cause != null) {
+            //Bitwise inclusive OR produces tighter bytecode for instanceof
+            //and matches with multicatch syntax.
+            if (cause instanceof VirtualMachineError
+                    | cause instanceof ThreadDeath) {
+                throw (Error) cause;
+            }
+        }
+        return ite;
+    }
+
+    /**
+     * Throws the given error if the cause is an error otherwise the given error
+     * is wrapped.
+     *
+     * @param eiie the error.
+     * @return an InvocationTargetException.
+     * @since JavaMail 1.5.0
+     */
+    private static InvocationTargetException wrapOrThrow(
+            ExceptionInInitializerError eiie) {
+        //This linkage error will escape the constructor new instance call.
+        //If the cause is an error, rethrow to skip any error manager.
+        if (eiie.getCause() instanceof Error) {
+            throw eiie;
+        } else {
+            //Considered a bug in the code, wrap the error so it can be
+            //reported to the error manager.
+            return new InvocationTargetException(eiie);
+        }
+    }
+
+    /**
+     * This code is modified from the LogManager, which explictly states
+     * searching the system class loader first, then the context class loader.
+     * There is resistance (compatibility) to change this behavior to simply
+     * searching the context class loader.
+     *
+     * @param name full class name
+     * @return the class.
+     * @throws LinkageError if the linkage fails.
+     * @throws ClassNotFoundException if the class name was not found.
+     * @throws ExceptionInInitializerError if static initializer fails.
+     */
+    private static Class<?> findClass(String name) throws ClassNotFoundException {
+        ClassLoader[] loaders = getClassLoaders();
+        assert loaders.length == 2 : loaders.length;
+        Class<?> clazz;
+        if (loaders[0] != null) {
+            try {
+                clazz = Class.forName(name, false, loaders[0]);
+            } catch (ClassNotFoundException tryContext) {
+                clazz = tryLoad(name, loaders[1]);
+            }
+        } else {
+            clazz = tryLoad(name, loaders[1]);
+        }
+        return clazz;
+    }
+
+    /**
+     * Loads a class using the given loader or the class loader of this class.
+     *
+     * @param name the class name.
+     * @param l any class loader or null.
+     * @return the raw class.
+     * @throws ClassNotFoundException if not found.
+     */
+    private static Class<?> tryLoad(String name, ClassLoader l) throws ClassNotFoundException {
+        if (l != null) {
+            return Class.forName(name, false, l);
+        } else {
+            return Class.forName(name);
+        }
+    }
+
+    /**
+     * Gets the class loaders using elevated privileges.
+     *
+     * @return any array of class loaders. Indexes may be null.
+     */
+    private static ClassLoader[] getClassLoaders() {
+        return AccessController.doPrivileged(new PrivilegedAction<ClassLoader[]>() {
+
+            @SuppressWarnings("override") //JDK-6954234
+            public ClassLoader[] run() {
+                final ClassLoader[] loaders = new ClassLoader[2];
+                try {
+                    loaders[0] = ClassLoader.getSystemClassLoader();
+                } catch (SecurityException ignore) {
+                    loaders[0] = null;
+                }
+
+                try {
+                    loaders[1] = Thread.currentThread().getContextClassLoader();
+                } catch (SecurityException ignore) {
+                    loaders[1] = null;
+                }
+                return loaders;
+            }
+        });
+    }
+    /**
+     * The namespace prefix to search LogManager and defaults.
+     */
+    private final String prefix;
+
+    /**
+     * Creates a log manager properties object.
+     *
+     * @param parent the parent properties.
+     * @param prefix the namespace prefix.
+     * @throws NullPointerException if <tt>prefix</tt> or <tt>parent</tt> is
+     * <tt>null</tt>.
+     */
+    LogManagerProperties(final Properties parent, final String prefix) {
+        super(parent);
+        if (parent == null || prefix == null) {
+            throw new NullPointerException();
+        }
+        this.prefix = prefix;
+    }
+
+    /**
+     * Returns a properties object that contains a snapshot of the current
+     * state. This method violates the clone contract so that no instances of
+     * LogManagerProperties is exported for public use.
+     *
+     * @return the snapshot.
+     * @since JavaMail 1.4.4
+     */
+    @Override
+    @SuppressWarnings("CloneDoesntCallSuperClone")
+    public synchronized Object clone() {
+        return exportCopy(defaults);
+    }
+
+    /**
+     * Searches defaults, then searches the log manager if available or the
+     * system properties by the prefix property, and then by the key itself.
+     *
+     * @param key a non null key.
+     * @return the value for that key.
+     */
+    @Override
+    public synchronized String getProperty(final String key) {
+        String value = defaults.getProperty(key);
+        if (value == null) {
+            if (key.length() > 0) {
+                value = fromLogManager(prefix + '.' + key);
+            }
+
+            if (value == null) {
+                value = fromLogManager(key);
+            }
+
+            /**
+             * Copy the log manager properties as we read them. If a value is no
+             * longer present in the LogManager read it from here. The reason
+             * this works is because LogManager.reset() closes all attached
+             * handlers therefore, stale values only exist in closed handlers.
+             */
+            if (value != null) {
+                super.put(key, value);
+            } else {
+                Object v = super.get(key); //defaults are not used.
+                value = v instanceof String ? (String) v : null;
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Calls getProperty directly. If getProperty returns null the default value
+     * is returned.
+     *
+     * @param key a key to search for.
+     * @param def the default value to use if not found.
+     * @return the value for the key.
+     * @since JavaMail 1.4.4
+     */
+    @Override
+    public String getProperty(final String key, final String def) {
+        final String value = this.getProperty(key);
+        return value == null ? def : value;
+    }
+
+    /**
+     * Required to work with PropUtil. Calls getProperty directly if the given
+     * key is a string. Otherwise, performs a get operation on the defaults
+     * followed by the normal hash table get.
+     *
+     * @param key any key.
+     * @return the value for the key or null.
+     * @since JavaMail 1.4.5
+     */
+    @Override
+    public synchronized Object get(final Object key) {
+        Object value;
+        if (key instanceof String) {
+            value = getProperty((String) key);
+        } else {
+            value = null;
+        }
+
+        //Search for non-string value.
+        if (value == null) {
+            value = defaults.get(key);
+            if (value == null && !defaults.containsKey(key)) {
+                value = super.get(key);
+            }
+        }
+        return value;
+    }
+
+    /**
+     * Required to work with PropUtil. An updated copy of the key is fetched
+     * from the log manager if the key doesn't exist in this properties.
+     *
+     * @param key any key.
+     * @return the value for the key or the default value for the key.
+     * @since JavaMail 1.4.5
+     */
+    @Override
+    public synchronized Object put(final Object key, final Object value) {
+        if (key instanceof String && value instanceof String) {
+            final Object def = preWrite(key);
+            final Object man = super.put(key, value);
+            return man == null ? def : man;
+        } else {
+            return super.put(key, value);
+        }
+    }
+
+    /**
+     * Calls the put method directly.
+     *
+     * @param key any key.
+     * @return the value for the key or the default value for the key.
+     * @since JavaMail 1.4.5
+     */
+    @Override
+    public Object setProperty(String key, String value) {
+        return this.put(key, value);
+    }
+
+    /**
+     * Required to work with PropUtil. An updated copy of the key is fetched
+     * from the log manager prior to returning.
+     *
+     * @param key any key.
+     * @return the value for the key or null.
+     * @since JavaMail 1.4.5
+     */
+    @Override
+    public synchronized boolean containsKey(final Object key) {
+        boolean found = key instanceof String
+                && getProperty((String) key) != null;
+        if (!found) {
+            found = defaults.containsKey(key) || super.containsKey(key);
+        }
+        return found;
+    }
+
+    /**
+     * Required to work with PropUtil. An updated copy of the key is fetched
+     * from the log manager if the key doesn't exist in this properties.
+     *
+     * @param key any key.
+     * @return the value for the key or the default value for the key.
+     * @since JavaMail 1.4.5
+     */
+    @Override
+    public synchronized Object remove(final Object key) {
+        final Object def = preWrite(key);
+        final Object man = super.remove(key);
+        return man == null ? def : man;
+    }
+
+    /**
+     * It is assumed that this method will never be called. No way to get the
+     * property names from LogManager.
+     *
+     * @return the property names
+     */
+    @Override
+    public Enumeration<?> propertyNames() {
+        assert false;
+        return super.propertyNames();
+    }
+
+    /**
+     * It is assumed that this method will never be called. The prefix value is
+     * not used for the equals method.
+     *
+     * @param o any object or null.
+     * @return true if equal, otherwise false.
+     */
+    @Override
+    public boolean equals(final Object o) {
+        if (o == null) {
+            return false;
+        }
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof Properties == false) {
+            return false;
+        }
+        assert false : prefix;
+        return super.equals(o);
+    }
+
+    /**
+     * It is assumed that this method will never be called. See equals.
+     *
+     * @return the hash code.
+     */
+    @Override
+    public int hashCode() {
+        assert false : prefix.hashCode();
+        return super.hashCode();
+    }
+
+    /**
+     * Called before a write operation of a key. Caches a key read from the log
+     * manager in this properties object. The key is only cached if it is an
+     * instance of a String and this properties doesn't contain a copy of the
+     * key.
+     *
+     * @param key the key to search.
+     * @return the default value for the key.
+     */
+    private Object preWrite(final Object key) {
+        assert Thread.holdsLock(this);
+        return get(key);
+    }
+
+    /**
+     * Creates a public snapshot of this properties object using the given
+     * parent properties.
+     *
+     * @param parent the defaults to use with the snapshot.
+     * @return the safe snapshot.
+     */
+    private Properties exportCopy(final Properties parent) {
+        Thread.holdsLock(this);
+        final Properties child = new Properties(parent);
+        child.putAll(this);
+        return child;
+    }
+
+    /**
+     * It is assumed that this method will never be called. We return a safe
+     * copy for export to avoid locking this properties object or the defaults
+     * during write.
+     *
+     * @return the parent properties.
+     * @throws ObjectStreamException if there is a problem.
+     */
+    private synchronized Object writeReplace() throws ObjectStreamException {
+        assert false;
+        return exportCopy((Properties) defaults.clone());
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java b/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
new file mode 100644
index 0000000..891e921
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/logging/MailHandler.java
@@ -0,0 +1,4396 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2018 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util.logging;
+
+import static com.sun.mail.util.logging.LogManagerProperties.fromLogManager;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.net.InetAddress;
+import java.net.URLConnection;
+import java.net.UnknownHostException;
+import java.nio.charset.Charset;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.*;
+import java.util.logging.*;
+import java.util.logging.Formatter;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.util.ByteArrayDataSource;
+
+/**
+ * <tt>Handler</tt> that formats log records as an email message.
+ *
+ * <p>
+ * This <tt>Handler</tt> will store a fixed number of log records used to
+ * generate a single email message.  When the internal buffer reaches capacity,
+ * all log records are formatted and placed in an email which is sent to an
+ * email server.  The code to manually setup this handler can be as simple as
+ * the following:
+ *
+ * <pre>
+ *      Properties props = new Properties();
+ *      props.put("mail.smtp.host", "my-mail-server");
+ *      props.put("mail.to", "me@example.com");
+ *      props.put("verify", "local");
+ *      MailHandler h = new MailHandler(props);
+ *      h.setLevel(Level.WARNING);
+ * </pre>
+ *
+ * <p>
+ * <b>Configuration:</b>
+ * The LogManager should define at least one or more recipient addresses and a
+ * mail host for outgoing email.  The code to setup this handler via the
+ * logging properties can be as simple as the following:
+ *
+ * <pre>
+ *      #Default MailHandler settings.
+ *      com.sun.mail.util.logging.MailHandler.mail.smtp.host = my-mail-server
+ *      com.sun.mail.util.logging.MailHandler.mail.to = me@example.com
+ *      com.sun.mail.util.logging.MailHandler.level = WARNING
+ *      com.sun.mail.util.logging.MailHandler.verify = local
+ * </pre>
+ *
+ * For a custom handler, e.g. <tt>com.foo.MyHandler</tt>, the properties would
+ * be:
+ *
+ * <pre>
+ *      #Subclass com.foo.MyHandler settings.
+ *      com.foo.MyHandler.mail.smtp.host = my-mail-server
+ *      com.foo.MyHandler.mail.to = me@example.com
+ *      com.foo.MyHandler.level = WARNING
+ *      com.foo.MyHandler.verify = local
+ * </pre>
+ *
+ * All mail properties documented in the <tt>Java Mail API</tt> cascade to the
+ * LogManager by prefixing a key using the fully qualified class name of this
+ * <tt>MailHandler</tt> or the fully qualified derived class name dot mail
+ * property.  If the prefixed property is not found, then the mail property
+ * itself is searched in the LogManager. By default each <tt>MailHandler</tt> is
+ * initialized using the following LogManager configuration properties where
+ * <tt>&lt;handler-name&gt;</tt> refers to the fully qualified class name of the
+ * handler.  If properties are not defined, or contain invalid values, then the
+ * specified default values are used.
+ *
+ * <ul>
+ * <li>&lt;handler-name&gt;.attachment.filters a comma
+ * separated list of <tt>Filter</tt> class names used to create each attachment.
+ * The literal <tt>null</tt> is reserved for attachments that do not require
+ * filtering. (defaults to the
+ * {@linkplain java.util.logging.Handler#getFilter() body} filter)
+ *
+ * <li>&lt;handler-name&gt;.attachment.formatters a comma
+ * separated list of <tt>Formatter</tt> class names used to create each
+ * attachment. (default is no attachments)
+ *
+ * <li>&lt;handler-name&gt;.attachment.names a comma separated
+ * list of names or <tt>Formatter</tt> class names of each attachment.  All
+ * control characters are removed from the attachment names.
+ * (default is {@linkplain java.util.logging.Formatter#toString() toString}
+ * of the attachment formatter)
+ *
+ * <li>&lt;handler-name&gt;.authenticator name of an
+ * {@linkplain javax.mail.Authenticator} class used to provide login credentials
+ * to the email server or string literal that is the password used with the
+ * {@linkplain Authenticator#getDefaultUserName() default} user name.
+ * (default is <tt>null</tt>)
+ *
+ * <li>&lt;handler-name&gt;.capacity the max number of
+ * <tt>LogRecord</tt> objects include in each email message.
+ * (defaults to <tt>1000</tt>)
+ *
+ * <li>&lt;handler-name&gt;.comparator name of a
+ * {@linkplain java.util.Comparator} class used to sort the published
+ * <tt>LogRecord</tt> objects prior to all formatting.
+ * (defaults to <tt>null</tt> meaning records are unsorted).
+ *
+ * <li>&lt;handler-name&gt;.comparator.reverse a boolean
+ * <tt>true</tt> to reverse the order of the specified comparator or
+ * <tt>false</tt> to retain the original order. (defaults to <tt>false</tt>)
+ *
+ * <li>&lt;handler-name&gt;.encoding the name of the Java
+ * {@linkplain java.nio.charset.Charset#name() character set} to use for the
+ * email message. (defaults to <tt>null</tt>, the
+ * {@linkplain javax.mail.internet.MimeUtility#getDefaultJavaCharset() default}
+ * platform encoding).
+ *
+ * <li>&lt;handler-name&gt;.errorManager name of an
+ * <tt>ErrorManager</tt> class used to handle any configuration or mail
+ * transport problems. (defaults to <tt>java.util.logging.ErrorManager</tt>)
+ *
+ * <li>&lt;handler-name&gt;.filter name of a <tt>Filter</tt>
+ * class used for the body of the message. (defaults to <tt>null</tt>,
+ * allow all records)
+ *
+ * <li>&lt;handler-name&gt;.formatter name of a
+ * <tt>Formatter</tt> class used to format the body of this message.
+ * (defaults to <tt>java.util.logging.SimpleFormatter</tt>)
+ *
+ * <li>&lt;handler-name&gt;.level specifies the default level
+ * for this <tt>Handler</tt> (defaults to <tt>Level.WARNING</tt>).
+ *
+ * <li>&lt;handler-name&gt;.mail.bcc a comma separated list of
+ * addresses which will be blind carbon copied.  Typically, this is set to the
+ * recipients that may need to be privately notified of a log message or
+ * notified that a log message was sent to a third party such as a support team.
+ * The empty string can be used to specify no blind carbon copied address.
+ * (defaults to <tt>null</tt>, none)
+ *
+ * <li>&lt;handler-name&gt;.mail.cc a comma separated list of
+ * addresses which will be carbon copied.  Typically, this is set to the
+ * recipients that may need to be notified of a log message but, are not
+ * required to provide direct support.  The empty string can be used to specify
+ * no carbon copied address.  (defaults to <tt>null</tt>, none)
+ *
+ * <li>&lt;handler-name&gt;.mail.from a comma separated list of
+ * addresses which will be from addresses. Typically, this is set to the email
+ * address identifying the user running the application.  The empty string can
+ * be used to override the default behavior and specify no from address.
+ * (defaults to the {@linkplain javax.mail.Message#setFrom() local address})
+ *
+ * <li>&lt;handler-name&gt;.mail.host the host name or IP
+ * address of the email server. (defaults to <tt>null</tt>, use
+ * {@linkplain Transport#protocolConnect default}
+ * <tt>Java Mail</tt> behavior)
+ *
+ * <li>&lt;handler-name&gt;.mail.reply.to a comma separated
+ * list of addresses which will be reply-to addresses.  Typically, this is set
+ * to the recipients that provide support for the application itself.  The empty
+ * string can be used to specify no reply-to address.
+ * (defaults to <tt>null</tt>, none)
+ *
+ * <li>&lt;handler-name&gt;.mail.to a comma separated list of
+ * addresses which will be send-to addresses. Typically, this is set to the
+ * recipients that provide support for the application, system, and/or
+ * supporting infrastructure.  The empty string can be used to specify no
+ * send-to address which overrides the default behavior.  (defaults to
+ * {@linkplain javax.mail.internet.InternetAddress#getLocalAddress
+ * local address}.)
+ *
+ * <li>&lt;handler-name&gt;.mail.sender a single address
+ * identifying sender of the email; never equal to the from address.  Typically,
+ * this is set to the email address identifying the application itself.  The
+ * empty string can be used to specify no sender address.
+ * (defaults to <tt>null</tt>, none)
+ *
+ * <li>&lt;handler-name&gt;.subject the name of a
+ * <tt>Formatter</tt> class or string literal used to create the subject line.
+ * The empty string can be used to specify no subject.  All control characters
+ * are removed from the subject line. (defaults to {@linkplain
+ * com.sun.mail.util.logging.CollectorFormatter CollectorFormatter}.)
+ *
+ * <li>&lt;handler-name&gt;.pushFilter the name of a
+ * <tt>Filter</tt> class used to trigger an early push.
+ * (defaults to <tt>null</tt>, no early push)
+ *
+ * <li>&lt;handler-name&gt;.pushLevel the level which will
+ * trigger an early push. (defaults to <tt>Level.OFF</tt>, only push when full)
+ *
+ * <li>&lt;handler-name&gt;.verify <a name="verify">used</a> to
+ * verify the <tt>Handler</tt> configuration prior to a push.
+ * <ul>
+ *      <li>If the value is not set, equal to an empty string, or equal to the
+ *      literal <tt>null</tt> then no settings are verified prior to a push.
+ *      <li>If set to a value of <tt>limited</tt> then the <tt>Handler</tt> will
+ *      verify minimal local machine settings.
+ *      <li>If set to a value of <tt>local</tt> the <tt>Handler</tt> will verify
+ *      all of settings of the local machine.
+ *      <li>If set to a value of <tt>resolve</tt>, the <tt>Handler</tt> will
+ *      verify all local settings and try to resolve the remote host name with
+ *      the domain name server.
+ *      <li>If set to a value of <tt>login</tt>, the <tt>Handler</tt> will
+ *      verify all local settings and try to establish a connection with
+ *      the email server.
+ *      <li>If set to a value of <tt>remote</tt>, the <tt>Handler</tt> will
+ *      verify all local settings, try to establish a connection with the
+ *      email server, and try to verify the envelope of the email message.
+ * </ul>
+ * If this <tt>Handler</tt> is only implicitly closed by the
+ * <tt>LogManager</tt>, then verification should be turned on.
+ * (defaults to <tt>null</tt>, no verify).
+ * </ul>
+ *
+ * <p>
+ * <b>Normalization:</b>
+ * The error manager, filters, and formatters when loaded from the LogManager
+ * are converted into canonical form inside the MailHandler.  The pool of
+ * interned values is limited to each MailHandler object such that no two
+ * MailHandler objects created by the LogManager will be created sharing
+ * identical error managers, filters, or formatters.  If a filter or formatter
+ * should <b>not</b> be interned then it is recommended to retain the identity
+ * equals and identity hashCode methods as the implementation.  For a filter or
+ * formatter to be interned the class must implement the
+ * {@linkplain java.lang.Object#equals(java.lang.Object) equals}
+ * and {@linkplain java.lang.Object#hashCode() hashCode} methods.
+ * The recommended code to use for stateless filters and formatters is:
+ * <pre>
+ * public boolean equals(Object obj) {
+ *     return obj == null ? false : obj.getClass() == getClass();
+ * }
+ *
+ * public int hashCode() {
+ *     return 31 * getClass().hashCode();
+ * }
+ * </pre>
+ *
+ * <p>
+ * <b>Sorting:</b>
+ * All <tt>LogRecord</tt> objects are ordered prior to formatting if this
+ * <tt>Handler</tt> has a non null comparator.  Developers might be interested
+ * in sorting the formatted email by thread id, time, and sequence properties
+ * of a <tt>LogRecord</tt>.  Where as system administrators might be interested
+ * in sorting the formatted email by thrown, level, time, and sequence
+ * properties of a <tt>LogRecord</tt>.  If comparator for this handler is
+ * <tt>null</tt> then the order is unspecified.
+ *
+ * <p>
+ * <b>Formatting:</b>
+ * The main message body is formatted using the <tt>Formatter</tt> returned by
+ * <tt>getFormatter()</tt>.  Only records that pass the filter returned by
+ * <tt>getFilter()</tt> will be included in the message body.  The subject
+ * <tt>Formatter</tt> will see all <tt>LogRecord</tt> objects that were
+ * published regardless of the current <tt>Filter</tt>.  The MIME type of the
+ * message body can be {@linkplain FileTypeMap#setDefaultFileTypeMap overridden}
+ * by adding a MIME {@linkplain MimetypesFileTypeMap entry} using the simple
+ * class name of the body formatter as the file extension.  The MIME type of the
+ * attachments can be overridden by changing the attachment file name extension
+ * or by editing the default MIME entry for a specific file name extension.
+ *
+ * <p>
+ * <b>Attachments:</b>
+ * This <tt>Handler</tt> allows multiple attachments per each email message.
+ * The presence of an attachment formatter will change the content type of the
+ * email message to a multi-part message.  The attachment order maps directly to
+ * the array index order in this <tt>Handler</tt> with zero index being the
+ * first attachment.  The number of attachment formatters controls the number of
+ * attachments per email and the content type of each attachment.  The
+ * attachment filters determine if a <tt>LogRecord</tt> will be included in an
+ * attachment.  If an attachment filter is <tt>null</tt> then all records are
+ * included for that attachment.  Attachments without content will be omitted
+ * from email message.  The attachment name formatters create the file name for
+ * an attachment.  Custom attachment name formatters can be used to generate an
+ * attachment name based on the contents of the attachment.
+ *
+ * <p>
+ * <b>Push Level and Push Filter:</b>
+ * The push method, push level, and optional push filter can be used to
+ * conditionally trigger a push at or prior to full capacity.  When a push
+ * occurs, the current buffer is formatted into an email and is sent to the
+ * email server.  If the push method, push level, or push filter trigger a push
+ * then the outgoing email is flagged as high importance with urgent priority.
+ *
+ * <p>
+ * <b>Buffering:</b>
+ * Log records that are published are stored in an internal buffer.  When this
+ * buffer reaches capacity the existing records are formatted and sent in an
+ * email.  Any published records can be sent before reaching capacity by
+ * explictly calling the <tt>flush</tt>, <tt>push</tt>, or <tt>close</tt>
+ * methods.  If a circular buffer is required then this handler can be wrapped
+ * with a {@linkplain java.util.logging.MemoryHandler} typically with an
+ * equivalent capacity, level, and push level.
+ *
+ * <p>
+ * <b>Error Handling:</b>
+ * If the transport of an email message fails, the email is converted to
+ * a {@linkplain javax.mail.internet.MimeMessage#writeTo raw}
+ * {@linkplain java.io.ByteArrayOutputStream#toString(java.lang.String) string}
+ * and is then passed as the <tt>msg</tt> parameter to
+ * {@linkplain Handler#reportError reportError} along with the exception
+ * describing the cause of the failure.  This allows custom error managers to
+ * store, {@linkplain javax.mail.internet.MimeMessage#MimeMessage(
+ * javax.mail.Session, java.io.InputStream) reconstruct}, and resend the
+ * original MimeMessage.  The message parameter string is <b>not</b> a raw email
+ * if it starts with value returned from <tt>Level.SEVERE.getName()</tt>.
+ * Custom error managers can use the following test to determine if the
+ * <tt>msg</tt> parameter from this handler is a raw email:
+ *
+ * <pre>
+ * public void error(String msg, Exception ex, int code) {
+ *      if (msg == null || msg.length() == 0 || msg.startsWith(Level.SEVERE.getName())) {
+ *          super.error(msg, ex, code);
+ *      } else {
+ *          //The 'msg' parameter is a raw email.
+ *      }
+ * }
+ * </pre>
+ *
+ * @author Jason Mehrens
+ * @since JavaMail 1.4.3
+ */
+public class MailHandler extends Handler {
+    /**
+     * Use the emptyFilterArray method.
+     */
+    private static final Filter[] EMPTY_FILTERS = new Filter[0];
+    /**
+     * Use the emptyFormatterArray method.
+     */
+    private static final Formatter[] EMPTY_FORMATTERS = new Formatter[0];
+    /**
+     * Min byte size for header data.  Used for initial arrays sizing.
+     */
+    private static final int MIN_HEADER_SIZE = 1024;
+    /**
+     * Cache the off value.
+     */
+    private static final int offValue = Level.OFF.intValue();
+    /**
+     * The action to set the context class loader for use with the JavaMail API.
+     * Load and pin this before it is loaded in the close method. The field is
+     * declared as java.security.PrivilegedAction so
+     * WebappClassLoader.clearReferencesStaticFinal() method will ignore this
+     * field.
+     */
+    private static final PrivilegedAction<Object> MAILHANDLER_LOADER
+            = new GetAndSetContext(MailHandler.class);
+    /**
+     * A thread local mutex used to prevent logging loops.  This code has to be
+     * prepared to deal with unexpected null values since the
+     * WebappClassLoader.clearReferencesThreadLocals() and
+     * InnocuousThread.eraseThreadLocals() can remove thread local values.
+     * The MUTEX has 5 states:
+     * 1. A null value meaning default state of not publishing.
+     * 2. MUTEX_PUBLISH on first entry of a push or publish.
+     * 3. The index of the first filter to accept a log record.
+     * 4. MUTEX_REPORT when cycle of records is detected.
+     * 5. MUTEXT_LINKAGE when a linkage error is reported.
+     */
+    private static final ThreadLocal<Integer> MUTEX = new ThreadLocal<>();
+    /**
+     * The marker object used to report a publishing state.
+     * This must be less than the body filter index (-1).
+     */
+    private static final Integer MUTEX_PUBLISH = -2;
+    /**
+     * The used for the error reporting state.
+     * This must be less than the PUBLISH state.
+     */
+    private static final Integer MUTEX_REPORT = -4;
+    /**
+     * The used for linkage error reporting.
+     * This must be less than the REPORT state.
+     */
+    private static final Integer MUTEX_LINKAGE = -8;
+    /**
+     * Used to turn off security checks.
+     */
+    private volatile boolean sealed;
+    /**
+     * Determines if we are inside of a push.
+     * Makes the handler properties read-only during a push.
+     */
+    private boolean isWriting;
+    /**
+     * Holds all of the email server properties.
+     */
+    private Properties mailProps;
+    /**
+     * Holds the authenticator required to login to the email server.
+     */
+    private Authenticator auth;
+    /**
+     * Holds the session object used to generate emails.
+     * Sessions can be shared by multiple threads.
+     * See JDK-6228391 and K 6278.
+     */
+    private Session session;
+    /**
+     * A mapping of log record to matching filter index.  Negative one is used
+     * to track the body filter.  Zero and greater is used to track the
+     * attachment parts.  All indexes less than or equal to the matched value
+     * have already seen the given log record.
+     */
+    private int[] matched;
+    /**
+     * Holds all of the log records that will be used to create the email.
+     */
+    private LogRecord[] data;
+    /**
+     * The number of log records in the buffer.
+     */
+    private int size;
+    /**
+     * The maximum number of log records to format per email.
+     * Used to roughly bound the size of an email.
+     * Every time the capacity is reached, the handler will push.
+     * The capacity will be negative if this handler is closed.
+     * Negative values are used to ensure all records are pushed.
+     */
+    private int capacity;
+    /**
+     * Used to order all log records prior to formatting.  The main email body
+     * and all attachments use the order determined by this comparator.  If no
+     * comparator is present the log records will be in no specified order.
+     */
+    private Comparator<? super LogRecord> comparator;
+    /**
+     * Holds the formatter used to create the subject line of the email.
+     * A subject formatter is not required for the email message.
+     * All published records pass through the subject formatter.
+     */
+    private Formatter subjectFormatter;
+    /**
+     * Holds the push level for this handler.
+     * This is only required if an email must be sent prior to shutdown
+     * or before the buffer is full.
+     */
+    private Level pushLevel;
+    /**
+     * Holds the push filter for trigger conditions requiring an early push.
+     * Only gets called if the given log record is greater than or equal
+     * to the push level and the push level is not Level.OFF.
+     */
+    private Filter pushFilter;
+    /**
+     * Holds the entry and body filter for this handler.
+     * There is no way to un-seal the super handler.
+     */
+    private volatile Filter filter;
+    /**
+     * Holds the level for this handler.
+     * There is no way to un-seal the super handler.
+     */
+    private volatile Level logLevel = Level.ALL;
+    /**
+     * Holds the filters for each attachment.  Filters are optional for
+     * each attachment.  This is declared volatile because this is treated as
+     * copy-on-write. The VO_VOLATILE_REFERENCE_TO_ARRAY warning is a false
+     * positive.
+     */
+    @SuppressWarnings("VolatileArrayField")
+    private volatile Filter[] attachmentFilters;
+    /**
+     * Holds the encoding name for this handler.
+     * There is no way to un-seal the super handler.
+     */
+    private String encoding;
+    /**
+     * Holds the entry and body filter for this handler.
+     * There is no way to un-seal the super handler.
+     */
+    private Formatter formatter;
+    /**
+     * Holds the formatters that create the content for each attachment.
+     * Each formatter maps directly to an attachment.  The formatters
+     * getHead, format, and getTail methods are only called if one or more
+     * log records pass through the attachment filters.
+     */
+    private Formatter[] attachmentFormatters;
+    /**
+     * Holds the formatters that create the file name for each attachment.
+     * Each formatter must produce a non null and non empty name.
+     * The final file name will be the concatenation of one getHead call, plus
+     * all of the format calls, plus one getTail call.
+     */
+    private Formatter[] attachmentNames;
+    /**
+     * Used to override the content type for the body and set the content type
+     * for each attachment.
+     */
+    private FileTypeMap contentTypes;
+    /**
+     * Holds the error manager for this handler.
+     * There is no way to un-seal the super handler.
+     */
+    private volatile ErrorManager errorManager = defaultErrorManager();
+
+    /**
+     * Creates a <tt>MailHandler</tt> that is configured by the
+     * <tt>LogManager</tt> configuration properties.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     */
+    public MailHandler() {
+        init((Properties) null);
+        sealed = true;
+        checkAccess();
+    }
+
+    /**
+     * Creates a <tt>MailHandler</tt> that is configured by the
+     * <tt>LogManager</tt> configuration properties but overrides the
+     * <tt>LogManager</tt> capacity with the given capacity.
+     * @param capacity of the internal buffer.
+     * @throws IllegalArgumentException if <tt>capacity</tt> less than one.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     */
+    public MailHandler(final int capacity) {
+        init((Properties) null);
+        sealed = true;
+        setCapacity0(capacity);
+    }
+
+    /**
+     * Creates a mail handler with the given mail properties.
+     * The key/value pairs are defined in the <tt>Java Mail API</tt>
+     * documentation.  This <tt>Handler</tt> will also search the
+     * <tt>LogManager</tt> for defaults if needed.
+     * @param props a non <tt>null</tt> properties object.
+     * @throws NullPointerException if <tt>props</tt> is <tt>null</tt>.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     */
+    public MailHandler(final Properties props) {
+        if (props == null) {
+            throw new NullPointerException();
+        }
+        init(props);
+        sealed = true;
+        setMailProperties0(props);
+    }
+
+    /**
+     * Check if this <tt>Handler</tt> would actually log a given
+     * <tt>LogRecord</tt> into its internal buffer.
+     * <p>
+     * This method checks if the <tt>LogRecord</tt> has an appropriate level and
+     * whether it satisfies any <tt>Filter</tt> including any attachment filters.
+     * However it does <b>not</b> check whether the <tt>LogRecord</tt> would
+     * result in a "push" of the buffer contents.
+     * <p>
+     * @param record  a <tt>LogRecord</tt>
+     * @return true if the <tt>LogRecord</tt> would be logged.
+     */
+    @Override
+    public boolean isLoggable(final LogRecord record) {
+        int levelValue = getLevel().intValue();
+        if (record.getLevel().intValue() < levelValue || levelValue == offValue) {
+            return false;
+        }
+
+        Filter body = getFilter();
+        if (body == null || body.isLoggable(record)) {
+            setMatchedPart(-1);
+            return true;
+        }
+
+        return isAttachmentLoggable(record);
+    }
+
+    /**
+     * Stores a <tt>LogRecord</tt> in the internal buffer.
+     * <p>
+     * The <tt>isLoggable</tt> method is called to check if the given log record
+     * is loggable. If the given record is loggable, it is copied into
+     * an internal buffer.  Then the record's level property is compared with
+     * the push level. If the given level of the <tt>LogRecord</tt>
+     * is greater than or equal to the push level then the push filter is
+     * called.  If no push filter exists, the push filter returns true,
+     * or the capacity of the internal buffer has been reached then all buffered
+     * records are formatted into one email and sent to the server.
+     *
+     * @param  record  description of the log event.
+     */
+    @Override
+    public void publish(final LogRecord record) {
+        /**
+         * It is possible for the handler to be closed after the
+         * call to isLoggable.  In that case, the current thread
+         * will push to ensure that all published records are sent.
+         * See close().
+         */
+
+        if (tryMutex()) {
+            try {
+                if (isLoggable(record)) {
+                    record.getSourceMethodName(); //Infer caller.
+                    publish0(record);
+                }
+            } catch (final LinkageError JDK8152515) {
+                reportLinkageError(JDK8152515, ErrorManager.WRITE_FAILURE);
+            } finally {
+                releaseMutex();
+            }
+        } else {
+            reportUnPublishedError(record);
+        }
+    }
+
+    /**
+     * Performs the publish after the record has been filtered.
+     * @param record the record.
+     * @since JavaMail 1.4.5
+     */
+    private void publish0(final LogRecord record) {
+        Message msg;
+        boolean priority;
+        synchronized (this) {
+            if (size == data.length && size < capacity) {
+                grow();
+            }
+
+            if (size < data.length) {
+                //assert data.length == matched.length;
+                matched[size] = getMatchedPart();
+                data[size] = record;
+                ++size; //Be nice to client compiler.
+                priority = isPushable(record);
+                if (priority || size >= capacity) {
+                    msg = writeLogRecords(ErrorManager.WRITE_FAILURE);
+                } else {
+                    msg = null;
+                }
+            } else {
+                priority = false;
+                msg = null;
+            }
+        }
+
+        if (msg != null) {
+            send(msg, priority, ErrorManager.WRITE_FAILURE);
+        }
+    }
+
+    /**
+     * Report to the error manager that a logging loop was detected and
+     * we are going to break the cycle of messages.  It is possible that
+     * a custom error manager could continue the cycle in which case
+     * we will stop trying to report errors.
+     * @param record the record or null.
+     * @since JavaMail 1.4.6
+     */
+    private void reportUnPublishedError(LogRecord record) {
+        final Integer idx = MUTEX.get();
+        if (idx == null || idx > MUTEX_REPORT) {
+            MUTEX.set(MUTEX_REPORT);
+            try {
+                final String msg;
+                if (record != null) {
+                    final Formatter f = createSimpleFormatter();
+                    msg = "Log record " + record.getSequenceNumber()
+                            + " was not published. "
+                            + head(f) + format(f, record) + tail(f, "");
+                } else {
+                    msg = null;
+                }
+                Exception e = new IllegalStateException(
+                        "Recursive publish detected by thread "
+                        + Thread.currentThread());
+                reportError(msg, e, ErrorManager.WRITE_FAILURE);
+            } finally {
+                if (idx != null) {
+                    MUTEX.set(idx);
+                } else {
+                    MUTEX.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Used to detect reentrance by the current thread to the publish method.
+     * This mutex is thread local scope and will not block other threads.
+     * The state is advanced on if the current thread is in a reset state.
+     * @return true if the mutex was acquired.
+     * @since JavaMail 1.4.6
+     */
+    private boolean tryMutex() {
+        if (MUTEX.get() == null) {
+            MUTEX.set(MUTEX_PUBLISH);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Releases the mutex held by the current thread.
+     * This mutex is thread local scope and will not block other threads.
+     * @since JavaMail 1.4.6
+     */
+    private void releaseMutex() {
+        MUTEX.remove();
+    }
+
+    /**
+     * This is used to get the filter index from when {@code isLoggable} and
+     * {@code isAttachmentLoggable} was invoked by {@code publish} method.
+     *
+     * @return the filter index or MUTEX_PUBLISH if unknown.
+     * @since JavaMail 1.5.5
+     * @throws NullPointerException if tryMutex was not called.
+     */
+    private int getMatchedPart() {
+        //assert Thread.holdsLock(this);
+        Integer idx = MUTEX.get();
+        if (idx == null || idx >= readOnlyAttachmentFilters().length) {
+           idx = MUTEX_PUBLISH;
+        }
+        return idx;
+    }
+
+    /**
+     * This is used to record the filter index when {@code isLoggable} and
+     * {@code isAttachmentLoggable} was invoked by {@code publish} method.
+     *
+     * @param index the filter index.
+     * @since JavaMail 1.5.5
+     */
+    private void setMatchedPart(int index) {
+        if (MUTEX_PUBLISH.equals(MUTEX.get())) {
+           MUTEX.set(index);
+        }
+    }
+
+    /**
+     * Clear previous matches when the filters are modified and there are
+     * existing log records that were matched.
+     * @param index the lowest filter index to clear.
+     * @since JavaMail 1.5.5
+     */
+    private void clearMatches(int index) {
+        assert Thread.holdsLock(this);
+        for (int r = 0; r < size; ++r) {
+            if (matched[r] >= index) {
+                matched[r] = MUTEX_PUBLISH;
+            }
+        }
+    }
+
+    /**
+     * A callback method for when this object is about to be placed into
+     * commission. This contract is defined by the
+     * {@code org.glassfish.hk2.api.PostConstruct} interface. If this class is
+     * loaded via a lifecycle managed environment other than HK2 then it is
+     * recommended that this method is called either directly or through
+     * extending this class to signal that this object is ready for use.
+     *
+     * @since JavaMail 1.5.3
+     */
+    //@javax.annotation.PostConstruct
+    public void postConstruct() {
+    }
+
+    /**
+     * A callback method for when this object is about to be decommissioned.
+     * This contract is defined by the {@code org.glassfish.hk2.api.PreDestory}
+     * interface. If this class is loaded via a lifecycle managed environment
+     * other than HK2 then it is recommended that this method is called either
+     * directly or through extending this class to signal that this object will
+     * be destroyed.
+     *
+     * @since JavaMail 1.5.3
+     */
+    //@javax.annotation.PreDestroy
+    public void preDestroy() {
+        /**
+         * Close can require permissions so just trigger a push.
+         */
+        push(false, ErrorManager.CLOSE_FAILURE);
+    }
+
+    /**
+     * Pushes any buffered records to the email server as high importance with
+     * urgent priority.  The internal buffer is then cleared.  Does nothing if
+     * called from inside a push.
+     * @see #flush()
+     */
+    public void push() {
+        push(true, ErrorManager.FLUSH_FAILURE);
+    }
+
+    /**
+     * Pushes any buffered records to the email server as normal priority.
+     * The internal buffer is then cleared.  Does nothing if called from inside
+     * a push.
+     * @see #push()
+     */
+    @Override
+    public void flush() {
+        push(false, ErrorManager.FLUSH_FAILURE);
+    }
+
+    /**
+     * Prevents any other records from being published.
+     * Pushes any buffered records to the email server as normal priority.
+     * The internal buffer is then cleared.  Once this handler is closed it
+     * will remain closed.
+     * <p>
+     * If this <tt>Handler</tt> is only implicitly closed by the
+     * <tt>LogManager</tt>, then <a href="#verify">verification</a> should be
+     * turned on.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @see #flush()
+     */
+    @Override
+    public void close() {
+        try {
+            checkAccess(); //Ensure setLevel works before clearing the buffer.
+            Message msg = null;
+            synchronized (this) {
+                try {
+                    msg = writeLogRecords(ErrorManager.CLOSE_FAILURE);
+                } finally {  //Change level after formatting.
+                    this.logLevel = Level.OFF;
+                    /**
+                     * The sign bit of the capacity is set to ensure that
+                     * records that have passed isLoggable, but have yet to be
+                     * added to the internal buffer, are immediately pushed as
+                     * an email.
+                     */
+                    if (this.capacity > 0) {
+                        this.capacity = -this.capacity;
+                    }
+
+                    //Ensure not inside a push.
+                    if (size == 0 && data.length != 1) {
+                        this.data = new LogRecord[1];
+                        this.matched = new int[this.data.length];
+                    }
+                }
+            }
+
+            if (msg != null) {
+                send(msg, false, ErrorManager.CLOSE_FAILURE);
+            }
+        } catch (final LinkageError JDK8152515) {
+            reportLinkageError(JDK8152515, ErrorManager.CLOSE_FAILURE);
+        }
+    }
+
+    /**
+     * Set the log level specifying which message levels will be
+     * logged by this <tt>Handler</tt>.  Message levels lower than this
+     * value will be discarded.
+     * @param newLevel   the new value for the log level
+     * @throws NullPointerException if <tt>newLevel</tt> is <tt>null</tt>.
+     * @throws SecurityException  if a security manager exists and
+     *          the caller does not have <tt>LoggingPermission("control")</tt>.
+     */
+    @Override
+    public void setLevel(final Level newLevel) {
+        if (newLevel == null) {
+            throw new NullPointerException();
+        }
+        checkAccess();
+
+        //Don't allow a closed handler to be opened (half way).
+        synchronized (this) { //Wait for writeLogRecords.
+            if (this.capacity > 0) {
+                this.logLevel = newLevel;
+            }
+        }
+    }
+
+    /**
+     * Get the log level specifying which messages will be logged by this
+     * <tt>Handler</tt>.  Message levels lower than this level will be
+     * discarded.
+     *
+     * @return the level of messages being logged.
+     */
+    @Override
+    public Level getLevel() {
+        return logLevel; //Volatile access.
+    }
+
+    /**
+     * Retrieves the ErrorManager for this Handler.
+     *
+     * @return the ErrorManager for this Handler
+     * @throws SecurityException if a security manager exists and if the caller
+     * does not have <tt>LoggingPermission("control")</tt>.
+     */
+    @Override
+    public ErrorManager getErrorManager() {
+        checkAccess();
+        return this.errorManager; //Volatile access.
+    }
+
+    /**
+     * Define an ErrorManager for this Handler.
+     * <p>
+     * The ErrorManager's "error" method will be invoked if any errors occur
+     * while using this Handler.
+     *
+     * @param em  the new ErrorManager
+     * @throws  SecurityException  if a security manager exists and if the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws NullPointerException if the given error manager is null.
+     */
+    @Override
+    public void setErrorManager(final ErrorManager em) {
+        checkAccess();
+        setErrorManager0(em);
+    }
+
+    /**
+     * Sets the error manager on this handler and the super handler.  In secure
+     * environments the super call may not be allowed which is not a failure
+     * condition as it is an attempt to free the unused handler error manager.
+     *
+     * @param em a non null error manager.
+     * @throws NullPointerException if the given error manager is null.
+     * @since JavaMail 1.5.6
+     */
+    private void setErrorManager0(final ErrorManager em) {
+        if (em == null) {
+           throw new NullPointerException();
+        }
+        try {
+            synchronized (this) { //Wait for writeLogRecords.
+               this.errorManager = em;
+               super.setErrorManager(em); //Try to free super error manager.
+            }
+        } catch (RuntimeException | LinkageError ignore) {
+        }
+    }
+
+    /**
+     * Get the current <tt>Filter</tt> for this <tt>Handler</tt>.
+     *
+     * @return  a <tt>Filter</tt> object (may be null)
+     */
+    @Override
+    public Filter getFilter() {
+        return this.filter; //Volatile access.
+    }
+
+    /**
+     * Set a <tt>Filter</tt> to control output on this <tt>Handler</tt>.
+     * <P>
+     * For each call of <tt>publish</tt> the <tt>Handler</tt> will call this
+     * <tt>Filter</tt> (if it is non-null) to check if the <tt>LogRecord</tt>
+     * should be published or discarded.
+     *
+     * @param newFilter  a <tt>Filter</tt> object (may be null)
+     * @throws SecurityException  if a security manager exists and if the caller
+     * does not have <tt>LoggingPermission("control")</tt>.
+     */
+    @Override
+    public void setFilter(final Filter newFilter) {
+        checkAccess();
+        synchronized (this) {  //Wait for writeLogRecords.
+            if (newFilter != filter) {
+                clearMatches(-1);
+            }
+            this.filter = newFilter; //Volatile access.
+        }
+    }
+
+    /**
+     * Return the character encoding for this <tt>Handler</tt>.
+     *
+     * @return  The encoding name.  May be null, which indicates the default
+     * encoding should be used.
+     */
+    @Override
+    public synchronized String getEncoding() {
+        return this.encoding;
+    }
+
+    /**
+     * Set the character encoding used by this <tt>Handler</tt>.
+     * <p>
+     * The encoding should be set before any <tt>LogRecords</tt> are written
+     * to the <tt>Handler</tt>.
+     *
+     * @param encoding  The name of a supported character encoding.  May be
+     * null, to indicate the default platform encoding.
+     * @throws SecurityException  if a security manager exists and if the caller
+     * does not have <tt>LoggingPermission("control")</tt>.
+     * @throws UnsupportedEncodingException if the named encoding is not
+     * supported.
+     */
+    @Override
+    public void setEncoding(String encoding) throws UnsupportedEncodingException {
+        checkAccess();
+        setEncoding0(encoding);
+    }
+
+    /**
+     * Set the character encoding used by this handler.  This method does not
+     * check permissions of the caller.
+     *
+     * @param e any encoding name or null for the default.
+     * @throws UnsupportedEncodingException if the given encoding is not supported.
+     */
+    private void setEncoding0(String e) throws UnsupportedEncodingException {
+        if (e != null) {
+            try {
+                if (!java.nio.charset.Charset.isSupported(e)) {
+                    throw new UnsupportedEncodingException(e);
+                }
+            } catch (java.nio.charset.IllegalCharsetNameException icne) {
+                throw new UnsupportedEncodingException(e);
+            }
+        }
+
+        synchronized (this) {  //Wait for writeLogRecords.
+            this.encoding = e;
+        }
+    }
+
+    /**
+     * Return the <tt>Formatter</tt> for this <tt>Handler</tt>.
+     *
+     * @return the <tt>Formatter</tt> (may be null).
+     */
+    @Override
+    public synchronized Formatter getFormatter() {
+        return this.formatter;
+    }
+
+    /**
+     * Set a <tt>Formatter</tt>.  This <tt>Formatter</tt> will be used to format
+     * <tt>LogRecords</tt> for this <tt>Handler</tt>.
+     * <p>
+     * Some <tt>Handlers</tt> may not use <tt>Formatters</tt>, in which case the
+     * <tt>Formatter</tt> will be remembered, but not used.
+     * <p>
+     * @param newFormatter the <tt>Formatter</tt> to use (may not be null)
+     * @throws SecurityException  if a security manager exists and if the caller
+     * does not have <tt>LoggingPermission("control")</tt>.
+     * @throws NullPointerException if the given formatter is null.
+     */
+    @Override
+    public synchronized void setFormatter(Formatter newFormatter) throws SecurityException {
+        checkAccess();
+        if (newFormatter == null) {
+           throw new NullPointerException();
+        }
+        this.formatter = newFormatter;
+    }
+
+    /**
+     * Gets the push level.  The default is <tt>Level.OFF</tt> meaning that
+     * this <tt>Handler</tt> will only push when the internal buffer is full.
+     * @return the push level.
+     */
+    public final synchronized Level getPushLevel() {
+        return this.pushLevel;
+    }
+
+    /**
+     * Sets the push level.  This level is used to trigger a push so that
+     * all pending records are formatted and sent to the email server.  When
+     * the push level triggers a send, the resulting email is flagged as
+     * high importance with urgent priority.
+     * @param level Level object.
+     * @throws NullPointerException if <tt>level</tt> is <tt>null</tt>.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     */
+    public final synchronized void setPushLevel(final Level level) {
+        checkAccess();
+        if (level == null) {
+            throw new NullPointerException();
+        }
+
+        if (isWriting) {
+            throw new IllegalStateException();
+        }
+        this.pushLevel = level;
+    }
+
+    /**
+     * Gets the push filter.  The default is <tt>null</tt>.
+     * @return the push filter or <tt>null</tt>.
+     */
+    public final synchronized Filter getPushFilter() {
+        return this.pushFilter;
+    }
+
+    /**
+     * Sets the push filter.  This filter is only called if the given
+     * <tt>LogRecord</tt> level was greater than the push level.  If this
+     * filter returns <tt>true</tt>, all pending records are formatted and sent
+     * to the email server.  When the push filter triggers a send, the resulting
+     * email is flagged as high importance with urgent priority.
+     * @param filter push filter or <tt>null</tt>
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     */
+    public final synchronized void setPushFilter(final Filter filter) {
+        checkAccess();
+        if (isWriting) {
+            throw new IllegalStateException();
+        }
+        this.pushFilter = filter;
+    }
+
+    /**
+     * Gets the comparator used to order all <tt>LogRecord</tt> objects prior
+     * to formatting.  If <tt>null</tt> then the order is unspecified.
+     * @return the <tt>LogRecord</tt> comparator.
+     */
+    public final synchronized Comparator<? super LogRecord> getComparator() {
+        return this.comparator;
+    }
+
+    /**
+     * Sets the comparator used to order all <tt>LogRecord</tt> objects prior
+     * to formatting.  If <tt>null</tt> then the order is unspecified.
+     * @param c the <tt>LogRecord</tt> comparator.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     */
+    public final synchronized void setComparator(Comparator<? super LogRecord> c) {
+        checkAccess();
+        if (isWriting) {
+            throw new IllegalStateException();
+        }
+        this.comparator = c;
+    }
+
+    /**
+     * Gets the number of log records the internal buffer can hold.  When
+     * capacity is reached, <tt>Handler</tt> will format all <tt>LogRecord</tt>
+     * objects into one email message.
+     * @return the capacity.
+     */
+    public final synchronized int getCapacity()  {
+        assert capacity != Integer.MIN_VALUE && capacity != 0 : capacity;
+        return Math.abs(capacity);
+    }
+
+    /**
+     * Gets the <tt>Authenticator</tt> used to login to the email server.
+     * @return an <tt>Authenticator</tt> or <tt>null</tt> if none is required.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     */
+    public final synchronized Authenticator getAuthenticator() {
+        checkAccess();
+        return this.auth;
+    }
+
+    /**
+     * Sets the <tt>Authenticator</tt> used to login to the email server.
+     * @param auth an <tt>Authenticator</tt> object or null if none is required.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     */
+    public final void setAuthenticator(final Authenticator auth) {
+        this.setAuthenticator0(auth);
+    }
+
+    /**
+     * Sets the <tt>Authenticator</tt> used to login to the email server.
+     * @param password a password, empty array can be used to only supply a
+     * user name set by <tt>mail.user</tt> property, or null if no credentials
+     * are required.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     * @see String#toCharArray()
+     * @since JavaMail 1.4.6
+     */
+    public final void setAuthenticator(final char... password) {
+        if (password == null) {
+            setAuthenticator0((Authenticator) null);
+        } else {
+            setAuthenticator0(DefaultAuthenticator.of(new String(password)));
+        }
+    }
+
+    /**
+     * A private hook to handle possible future overrides. See public method.
+     * @param auth see public method.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     */
+    private void setAuthenticator0(final Authenticator auth) {
+        checkAccess();
+
+        Session settings;
+        synchronized (this) {
+            if (isWriting) {
+                throw new IllegalStateException();
+            }
+            this.auth = auth;
+            settings = updateSession();
+        }
+        verifySettings(settings);
+    }
+
+    /**
+     * Sets the mail properties used for the session.  The key/value pairs
+     * are defined in the <tt>Java Mail API</tt> documentation.  This
+     * <tt>Handler</tt> will also search the <tt>LogManager</tt> for defaults
+     * if needed.
+     * @param props a non <tt>null</tt> properties object.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws NullPointerException if <tt>props</tt> is <tt>null</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     */
+    public final void setMailProperties(Properties props) {
+        this.setMailProperties0(props);
+    }
+
+    /**
+     * A private hook to handle overrides when the public method is declared
+     * non final. See public method for details.
+     * @param props see public method.
+     */
+    private void setMailProperties0(Properties props) {
+        checkAccess();
+        props = (Properties) props.clone(); //Allow subclass.
+        Session settings;
+        synchronized (this) {
+            if (isWriting) {
+                throw new IllegalStateException();
+            }
+            this.mailProps = props;
+            settings = updateSession();
+        }
+        verifySettings(settings);
+    }
+
+    /**
+     * Gets a copy of the mail properties used for the session.
+     * @return a non null properties object.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     */
+    public final Properties getMailProperties() {
+        checkAccess();
+        final Properties props;
+        synchronized (this) {
+            props = this.mailProps;
+        }
+        return (Properties) props.clone();
+    }
+
+    /**
+     * Gets the attachment filters.  If the attachment filter does not
+     * allow any <tt>LogRecord</tt> to be formatted, the attachment may
+     * be omitted from the email.
+     * @return a non null array of attachment filters.
+     */
+    public final Filter[] getAttachmentFilters() {
+        return readOnlyAttachmentFilters().clone();
+    }
+
+    /**
+     * Sets the attachment filters.
+     * @param filters a non <tt>null</tt> array of filters.  A <tt>null</tt>
+     * index value is allowed.  A <tt>null</tt> value means that all
+     * records are allowed for the attachment at that index.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws NullPointerException if <tt>filters</tt> is <tt>null</tt>
+     * @throws IndexOutOfBoundsException if the number of attachment
+     * name formatters do not match the number of attachment formatters.
+     * @throws IllegalStateException if called from inside a push.
+     */
+    public final void setAttachmentFilters(Filter... filters) {
+        checkAccess();
+        if (filters.length == 0) {
+            filters = emptyFilterArray();
+        } else {
+            filters = Arrays.copyOf(filters, filters.length, Filter[].class);
+        }
+        synchronized (this) {
+            if (this.attachmentFormatters.length != filters.length) {
+                throw attachmentMismatch(this.attachmentFormatters.length, filters.length);
+            }
+
+            if (isWriting) {
+                throw new IllegalStateException();
+            }
+
+            if (size != 0) {
+                for (int i = 0; i < filters.length; ++i) {
+                    if (filters[i] != attachmentFilters[i]) {
+                        clearMatches(i);
+                        break;
+                    }
+                }
+            }
+            this.attachmentFilters = filters;
+        }
+    }
+
+    /**
+     * Gets the attachment formatters.  This <tt>Handler</tt> is using
+     * attachments only if the returned array length is non zero.
+     * @return a non <tt>null</tt> array of formatters.
+     */
+    public final Formatter[] getAttachmentFormatters() {
+        Formatter[] formatters;
+        synchronized (this) {
+            formatters = this.attachmentFormatters;
+        }
+        return formatters.clone();
+    }
+
+    /**
+     * Sets the attachment <tt>Formatter</tt> object for this handler.
+     * The number of formatters determines the number of attachments per
+     * email.  This method should be the first attachment method called.
+     * To remove all attachments, call this method with empty array.
+     * @param formatters a non null array of formatters.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws NullPointerException if the given array or any array index is
+     * <tt>null</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     */
+    public final void setAttachmentFormatters(Formatter... formatters) {
+        checkAccess();
+        if (formatters.length == 0) { //Null check and length check.
+            formatters = emptyFormatterArray();
+        } else {
+            formatters = Arrays.copyOf(formatters,
+                    formatters.length, Formatter[].class);
+            for (int i = 0; i < formatters.length; ++i) {
+                if (formatters[i] == null) {
+                    throw new NullPointerException(atIndexMsg(i));
+                }
+            }
+        }
+
+        synchronized (this) {
+            if (isWriting) {
+                throw new IllegalStateException();
+            }
+
+            this.attachmentFormatters = formatters;
+            this.alignAttachmentFilters();
+            this.alignAttachmentNames();
+        }
+    }
+
+    /**
+     * Gets the attachment name formatters.
+     * If the attachment names were set using explicit names then
+     * the names can be returned by calling <tt>toString</tt> on each
+     * attachment name formatter.
+     * @return non <tt>null</tt> array of attachment name formatters.
+     */
+    public final Formatter[] getAttachmentNames() {
+        final Formatter[] formatters;
+        synchronized (this) {
+            formatters = this.attachmentNames;
+        }
+        return formatters.clone();
+    }
+
+    /**
+     * Sets the attachment file name for each attachment.  All control
+     * characters are removed from the attachment names.
+     * This method will create a set of custom formatters.
+     * @param names an array of names.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws IndexOutOfBoundsException if the number of attachment
+     * names do not match the number of attachment formatters.
+     * @throws IllegalArgumentException  if any name is empty.
+     * @throws NullPointerException if any given array or name is <tt>null</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     * @see Character#isISOControl(char)
+     * @see Character#isISOControl(int)
+     */
+    public final void setAttachmentNames(final String... names) {
+        checkAccess();
+
+        final Formatter[] formatters;
+        if (names.length == 0) {
+            formatters = emptyFormatterArray();
+        } else {
+            formatters = new Formatter[names.length];
+        }
+
+        for (int i = 0; i < names.length; ++i) {
+            final String name = names[i];
+            if (name != null) {
+                if (name.length() > 0) {
+                    formatters[i] = TailNameFormatter.of(name);
+                } else {
+                    throw new IllegalArgumentException(atIndexMsg(i));
+                }
+            } else {
+                throw new NullPointerException(atIndexMsg(i));
+            }
+        }
+
+        synchronized (this) {
+            if (this.attachmentFormatters.length != names.length) {
+                throw attachmentMismatch(this.attachmentFormatters.length, names.length);
+            }
+
+            if (isWriting) {
+                throw new IllegalStateException();
+            }
+            this.attachmentNames = formatters;
+        }
+    }
+
+    /**
+     * Sets the attachment file name formatters.  The format method of each
+     * attachment formatter will see only the <tt>LogRecord</tt> objects that
+     * passed its attachment filter during formatting. The format method will
+     * typically return an empty string. Instead of being used to format
+     * records, it is used to gather information about the contents of an
+     * attachment.  The <tt>getTail</tt> method should be used to construct the
+     * attachment file name and reset any formatter collected state.  All
+     * control characters will be removed from the output of the formatter.  The
+     * <tt>toString</tt> method of the given formatter should be overridden to
+     * provide a useful attachment file name, if possible.
+     * @param formatters and array of attachment name formatters.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws IndexOutOfBoundsException if the number of attachment
+     * name formatters do not match the number of attachment formatters.
+     * @throws NullPointerException if any given array or name is <tt>null</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     * @see Character#isISOControl(char)
+     * @see Character#isISOControl(int)
+     */
+    public final void setAttachmentNames(Formatter... formatters) {
+        checkAccess();
+
+        if (formatters.length == 0) {
+            formatters = emptyFormatterArray();
+        } else {
+            formatters = Arrays.copyOf(formatters, formatters.length,
+                    Formatter[].class);
+        }
+
+        for (int i = 0; i < formatters.length; ++i) {
+            if (formatters[i] == null) {
+                throw new NullPointerException(atIndexMsg(i));
+            }
+        }
+
+        synchronized (this) {
+            if (this.attachmentFormatters.length != formatters.length) {
+                throw attachmentMismatch(this.attachmentFormatters.length,
+                        formatters.length);
+            }
+
+            if (isWriting) {
+                throw new IllegalStateException();
+            }
+
+            this.attachmentNames = formatters;
+        }
+    }
+
+    /**
+     * Gets the formatter used to create the subject line.
+     * If the subject was created using a literal string then
+     * the <tt>toString</tt> method can be used to get the subject line.
+     * @return the formatter.
+     */
+    public final synchronized Formatter getSubject() {
+        return this.subjectFormatter;
+    }
+
+    /**
+     * Sets a literal string for the email subject.  All control characters are
+     * removed from the subject line.
+     * @param subject a non <tt>null</tt> string.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws NullPointerException if <tt>subject</tt> is <tt>null</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     * @see Character#isISOControl(char)
+     * @see Character#isISOControl(int)
+     */
+    public final void setSubject(final String subject) {
+        if (subject != null) {
+            this.setSubject(TailNameFormatter.of(subject));
+        } else {
+            checkAccess();
+            throw new NullPointerException();
+        }
+    }
+
+    /**
+     * Sets the subject formatter for email.  The format method of the subject
+     * formatter will see all <tt>LogRecord</tt> objects that were published to
+     * this <tt>Handler</tt> during formatting and will typically return an
+     * empty string.  This formatter is used to gather information to create a
+     * summary about what information is contained in the email.  The
+     * <tt>getTail</tt> method should be used to construct the subject and reset
+     * any formatter collected state.  All control characters
+     * will be removed from the formatter output.  The <tt>toString</tt>
+     * method of the given formatter should be overridden to provide a useful
+     * subject, if possible.
+     * @param format the subject formatter.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws NullPointerException if <tt>format</tt> is <tt>null</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     * @see Character#isISOControl(char)
+     * @see Character#isISOControl(int)
+     */
+    public final void setSubject(final Formatter format) {
+        checkAccess();
+        if (format == null) {
+            throw new NullPointerException();
+        }
+
+        synchronized (this) {
+            if (isWriting) {
+                throw new IllegalStateException();
+            }
+            this.subjectFormatter = format;
+        }
+    }
+
+    /**
+     * Protected convenience method to report an error to this Handler's
+     * ErrorManager.  This method will prefix all non null error messages with
+     * <tt>Level.SEVERE.getName()</tt>.  This allows the receiving error
+     * manager to determine if the <tt>msg</tt> parameter is a simple error
+     * message or a raw email message.
+     * @param msg    a descriptive string (may be null)
+     * @param ex     an exception (may be null)
+     * @param code   an error code defined in ErrorManager
+     */
+    @Override
+    protected void reportError(String msg, Exception ex, int code) {
+        try {
+            if (msg != null) {
+                errorManager.error(Level.SEVERE.getName()
+                        .concat(": ").concat(msg), ex, code);
+            } else {
+                errorManager.error(null, ex, code);
+            }
+        } catch (RuntimeException | LinkageError GLASSFISH_21258) {
+            reportLinkageError(GLASSFISH_21258, code);
+        }
+    }
+
+    /**
+     * Calls log manager checkAccess if this is sealed.
+     */
+    private void checkAccess() {
+        if (sealed) {
+            LogManagerProperties.checkLogManagerAccess();
+        }
+    }
+
+    /**
+     * Determines the mimeType of a formatter from the getHead call.
+     * This could be made protected, or a new class could be created to do
+     * this type of conversion.  Currently, this is only used for the body
+     * since the attachments are computed by filename.
+     * Package-private for unit testing.
+     * @param chunk any char sequence or null.
+     * @return return the mime type or null for text/plain.
+     */
+    final String contentTypeOf(CharSequence chunk) {
+        if (!isEmpty(chunk)) {
+            final int MAX_CHARS = 25;
+            if (chunk.length() > MAX_CHARS) {
+                chunk = chunk.subSequence(0, MAX_CHARS);
+            }
+            try {
+                final String charset = getEncodingName();
+                final byte[] b = chunk.toString().getBytes(charset);
+                final ByteArrayInputStream in = new ByteArrayInputStream(b);
+                assert in.markSupported() : in.getClass().getName();
+                return URLConnection.guessContentTypeFromStream(in);
+            } catch (final IOException IOE) {
+                reportError(IOE.getMessage(), IOE, ErrorManager.FORMAT_FAILURE);
+            }
+        }
+        return null; //text/plain
+    }
+
+    /**
+     * Determines the mimeType of a formatter by the class name.  This method
+     * avoids calling getHead and getTail of content formatters during verify
+     * because they might trigger side effects or excessive work.  The name
+     * formatters and subject are usually safe to call.
+     * Package-private for unit testing.
+     *
+     * @param f the formatter or null.
+     * @return return the mime type or null, meaning text/plain.
+     * @since JavaMail 1.5.6
+     */
+    final String contentTypeOf(final Formatter f) {
+        assert Thread.holdsLock(this);
+        if (f != null) {
+            String type = getContentType(f.getClass().getName());
+            if (type != null) {
+                return type;
+            }
+
+            for (Class<?> k = f.getClass(); k != Formatter.class;
+                    k = k.getSuperclass()) {
+                String name;
+                try {
+                    name = k.getSimpleName();
+                } catch (final InternalError JDK8057919) {
+                    name = k.getName();
+                }
+                name = name.toLowerCase(Locale.ENGLISH);
+                for (int idx = name.indexOf('$') + 1;
+                        (idx = name.indexOf("ml", idx)) > -1; idx += 2) {
+                    if (idx > 0) {
+                       if (name.charAt(idx - 1) == 'x')  {
+                           return "application/xml";
+                       }
+                       if (idx > 1 && name.charAt(idx - 2) == 'h'
+                               && name.charAt(idx - 1) == 't') {
+                           return "text/html";
+                       }
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+   /**
+     * Determines if the given throwable is a no content exception.  It is
+     * assumed Transport.sendMessage will call Message.writeTo so we need to
+     * ignore any exceptions that could be layered on top of that call chain to
+     * infer that sendMessage is failing because of writeTo.  Package-private
+     * for unit testing.
+     * @param msg the message without content.
+     * @param t the throwable chain to test.
+     * @return true if the throwable is a missing content exception.
+     * @throws NullPointerException if any of the arguments are null.
+     * @since JavaMail 1.4.5
+     */
+    @SuppressWarnings({"UseSpecificCatch", "ThrowableResultIgnored"})
+    final boolean isMissingContent(Message msg, Throwable t) {
+        final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+        try {
+            msg.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
+        } catch (final RuntimeException RE) {
+            throw RE; //Avoid catch all.
+        } catch (final Exception noContent) {
+            final String txt = noContent.getMessage();
+            if (!isEmpty(txt)) {
+                int limit = 0;
+                while (t != null) {
+                    if (noContent.getClass() == t.getClass()
+                            && txt.equals(t.getMessage())) {
+                       return true;
+                    }
+
+                    //Not all JavaMail implementations support JDK 1.4 exception
+                    //chaining.
+                    final Throwable cause = t.getCause();
+                    if (cause == null && t instanceof MessagingException) {
+                        t = ((MessagingException) t).getNextException();
+                    } else {
+                        t = cause;
+                    }
+
+                    //Deal with excessive cause chains and cyclic throwables.
+                    if (++limit == (1 << 16)) {
+                        break; //Give up.
+                    }
+                }
+            }
+        } finally {
+            getAndSetContextClassLoader(ccl);
+        }
+        return false;
+    }
+
+    /**
+     * Converts a mime message to a raw string or formats the reason
+     * why message can't be changed to raw string and reports it.
+     * @param msg the mime message.
+     * @param ex the original exception.
+     * @param code the ErrorManager code.
+     * @since JavaMail 1.4.5
+     */
+    @SuppressWarnings("UseSpecificCatch")
+    private void reportError(Message msg, Exception ex, int code) {
+        try {
+            try { //Use direct call so we do not prefix raw email.
+                errorManager.error(toRawString(msg), ex, code);
+            } catch (final RuntimeException re) {
+                reportError(toMsgString(re), ex, code);
+            } catch (final Exception e) {
+                reportError(toMsgString(e), ex, code);
+            }
+        } catch (final LinkageError GLASSFISH_21258) {
+            reportLinkageError(GLASSFISH_21258, code);
+        }
+    }
+
+    /**
+     * Reports the given linkage error or runtime exception.
+     *
+     * The current LogManager code will stop closing all remaining handlers if
+     * an error is thrown during resetLogger.  This is a workaround for
+     * GLASSFISH-21258 and JDK-8152515.
+     * @param le the linkage error or a RuntimeException.
+     * @param code the ErrorManager code.
+     * @throws NullPointerException if error is null.
+     * @since JavaMail 1.5.3
+     */
+    private void reportLinkageError(final Throwable le, final int code) {
+        if (le == null) {
+           throw new NullPointerException(String.valueOf(code));
+        }
+
+        final Integer idx = MUTEX.get();
+        if (idx == null || idx > MUTEX_LINKAGE) {
+            MUTEX.set(MUTEX_LINKAGE);
+            try {
+                Thread.currentThread().getUncaughtExceptionHandler()
+                        .uncaughtException(Thread.currentThread(), le);
+            } catch (RuntimeException | LinkageError ignore) {
+            } finally {
+                if (idx != null) {
+                    MUTEX.set(idx);
+                } else {
+                    MUTEX.remove();
+                }
+            }
+        }
+    }
+
+    /**
+     * Determines the mimeType from the given file name.
+     * Used to override the body content type and used for all attachments.
+     * @param name the file name or class name.
+     * @return the mime type or null for text/plain.
+     */
+    private String getContentType(final String name) {
+        assert Thread.holdsLock(this);
+        final String type = contentTypes.getContentType(name);
+        if ("application/octet-stream".equalsIgnoreCase(type)) {
+            return null; //Formatters return strings, default to text/plain.
+        }
+        return type;
+    }
+
+    /**
+     * Gets the encoding set for this handler, mime encoding, or file encoding.
+     * @return the java charset name, never null.
+     * @since JavaMail 1.4.5
+     */
+    private String getEncodingName() {
+        String charset = getEncoding();
+        if (charset == null) {
+            charset = MimeUtility.getDefaultJavaCharset();
+        }
+        return charset;
+    }
+
+    /**
+     * Set the content for a part using the encoding assigned to the handler.
+     * @param part the part to assign.
+     * @param buf the formatted data.
+     * @param type the mime type or null, meaning text/plain.
+     * @throws MessagingException if there is a problem.
+     */
+    private void setContent(MimePart part, CharSequence buf, String type) throws MessagingException {
+        final String charset = getEncodingName();
+        if (type != null && !"text/plain".equalsIgnoreCase(type)) {
+            type = contentWithEncoding(type, charset);
+            try {
+                DataSource source = new ByteArrayDataSource(buf.toString(), type);
+                part.setDataHandler(new DataHandler(source));
+            } catch (final IOException IOE) {
+                reportError(IOE.getMessage(), IOE, ErrorManager.FORMAT_FAILURE);
+                part.setText(buf.toString(), charset);
+            }
+        } else {
+            part.setText(buf.toString(), MimeUtility.mimeCharset(charset));
+        }
+    }
+
+    /**
+     * Replaces the charset parameter with the current encoding.
+     * @param type the content type.
+     * @param encoding the java charset name.
+     * @return the type with a specified encoding.
+     */
+    private String contentWithEncoding(String type, String encoding) {
+        assert encoding != null;
+        try {
+            final ContentType ct = new ContentType(type);
+            ct.setParameter("charset", MimeUtility.mimeCharset(encoding));
+            encoding = ct.toString(); //See javax.mail.internet.ContentType.
+            if (!isEmpty(encoding)) { //Support pre K5687.
+                type = encoding;
+            }
+        } catch (final MessagingException ME) {
+            reportError(type, ME, ErrorManager.FORMAT_FAILURE);
+        }
+        return type;
+    }
+
+    /**
+     * Sets the capacity for this handler.  This method is kept private
+     * because we would have to define a public policy for when the size is
+     * greater than the capacity.
+     * E.G. do nothing, flush now, truncate now, push now and resize.
+     * @param newCapacity the max number of records.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     * @throws IllegalStateException if called from inside a push.
+     */
+    private synchronized void setCapacity0(final int newCapacity) {
+        checkAccess();
+        if (newCapacity <= 0) {
+            throw new IllegalArgumentException("Capacity must be greater than zero.");
+        }
+
+        if (isWriting) {
+            throw new IllegalStateException();
+        }
+
+        if (this.capacity < 0) { //If closed, remain closed.
+            this.capacity = -newCapacity;
+        } else {
+            this.capacity = newCapacity;
+        }
+    }
+
+    /**
+     * Gets the attachment filters using a happens-before relationship between
+     * this method and setAttachmentFilters.  The attachment filters are treated
+     * as copy-on-write, so the returned array must never be modified or
+     * published outside this class.
+     * @return a read only array of filters.
+     */
+    private Filter[] readOnlyAttachmentFilters() {
+        return this.attachmentFilters;
+    }
+
+    /**
+     * Factory for empty formatter arrays.
+     * @return an empty array.
+     */
+    private static Formatter[] emptyFormatterArray() {
+        return EMPTY_FORMATTERS;
+    }
+
+    /**
+     * Factory for empty filter arrays.
+     * @return an empty array.
+     */
+    private static Filter[] emptyFilterArray() {
+        return EMPTY_FILTERS;
+    }
+
+    /**
+     * Expand or shrink the attachment name formatters with the attachment
+     * formatters.
+     * @return true if size was changed.
+     */
+    private boolean alignAttachmentNames() {
+        assert Thread.holdsLock(this);
+        boolean fixed = false;
+        final int expect = this.attachmentFormatters.length;
+        final int current = this.attachmentNames.length;
+        if (current != expect) {
+            this.attachmentNames = Arrays.copyOf(attachmentNames, expect,
+                    Formatter[].class);
+            fixed = current != 0;
+        }
+
+        //Copy of zero length array is cheap, warm up copyOf.
+        if (expect == 0) {
+            this.attachmentNames = emptyFormatterArray();
+            assert this.attachmentNames.length == 0;
+        } else {
+            for (int i = 0; i < expect; ++i) {
+                if (this.attachmentNames[i] == null) {
+                    this.attachmentNames[i] = TailNameFormatter.of(
+                            toString(this.attachmentFormatters[i]));
+                }
+            }
+        }
+        return fixed;
+    }
+
+    /**
+     * Expand or shrink the attachment filters with the attachment formatters.
+     * @return true if the size was changed.
+     */
+    private boolean alignAttachmentFilters() {
+        assert Thread.holdsLock(this);
+
+        boolean fixed = false;
+        final int expect = this.attachmentFormatters.length;
+        final int current = this.attachmentFilters.length;
+        if (current != expect) {
+            this.attachmentFilters = Arrays.copyOf(attachmentFilters, expect,
+                    Filter[].class);
+            clearMatches(current);
+            fixed = current != 0;
+
+            //Array elements default to null so skip filling if body filter
+            //is null.  If not null then only assign to expanded elements.
+            final Filter body = this.filter;
+            if (body != null) {
+                for (int i = current; i < expect; ++i) {
+                    this.attachmentFilters[i] = body;
+                }
+            }
+        }
+
+        //Copy of zero length array is cheap, warm up copyOf.
+        if (expect == 0) {
+            this.attachmentFilters = emptyFilterArray();
+            assert this.attachmentFilters.length == 0;
+        }
+        return fixed;
+    }
+
+    /**
+     * Sets the size to zero and clears the current buffer.
+     */
+    private void reset() {
+        assert Thread.holdsLock(this);
+        if (size < data.length) {
+            Arrays.fill(data, 0, size, null);
+        } else {
+            Arrays.fill(data, null);
+        }
+        this.size = 0;
+    }
+
+    /**
+     * Expands the internal buffer up to the capacity.
+     */
+    private void grow() {
+        assert Thread.holdsLock(this);
+        final int len = data.length;
+        int newCapacity = len + (len >> 1) + 1;
+        if (newCapacity > capacity || newCapacity < len) {
+            newCapacity = capacity;
+        }
+        assert len != capacity : len;
+        this.data = Arrays.copyOf(data, newCapacity, LogRecord[].class);
+        this.matched = Arrays.copyOf(matched, newCapacity);
+    }
+
+    /**
+     * Configures the handler properties from the log manager.
+     * @param props the given mail properties.  Maybe null and are never
+     * captured by this handler.
+     * @throws SecurityException  if a security manager exists and the
+     * caller does not have <tt>LoggingPermission("control")</tt>.
+     */
+    private synchronized void init(final Properties props) {
+        assert this.errorManager != null;
+        final String p = getClass().getName();
+        this.mailProps = new Properties(); //See method param comments.
+        final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+        try {
+            this.contentTypes = FileTypeMap.getDefaultFileTypeMap();
+        } finally {
+            getAndSetContextClassLoader(ccl);
+        }
+
+        //Assign any custom error manager first so it can detect all failures.
+        initErrorManager(p);
+
+        initLevel(p);
+        initFilter(p);
+        initCapacity(p);
+        initAuthenticator(p);
+
+        initEncoding(p);
+        initFormatter(p);
+        initComparator(p);
+        initPushLevel(p);
+        initPushFilter(p);
+
+        initSubject(p);
+
+        initAttachmentFormaters(p);
+        initAttachmentFilters(p);
+        initAttachmentNames(p);
+
+        if (props == null && fromLogManager(p.concat(".verify")) != null) {
+            verifySettings(initSession());
+        }
+        intern(); //Show verify warnings first.
+    }
+
+    /**
+     * Interns the error manager, formatters, and filters contained in this
+     * handler.  The comparator is not interned.  This method can only be
+     * called from init after all of formatters and filters are in a constructed
+     * and in a consistent state.
+     * @since JavaMail 1.5.0
+     */
+    private void intern() {
+        assert Thread.holdsLock(this);
+        try {
+            Object canidate;
+            Object result;
+            final Map<Object, Object> seen = new HashMap<>();
+            try {
+                intern(seen, this.errorManager);
+            } catch (final SecurityException se) {
+                reportError(se.getMessage(), se, ErrorManager.OPEN_FAILURE);
+            }
+
+            try {
+                canidate = this.filter;
+                result = intern(seen, canidate);
+                if (result != canidate && result instanceof Filter) {
+                    this.filter = (Filter) result;
+                }
+
+                canidate = this.formatter;
+                result = intern(seen, canidate);
+                if (result != canidate && result instanceof Formatter) {
+                    this.formatter = (Formatter) result;
+                }
+            } catch (final SecurityException se) {
+                reportError(se.getMessage(), se, ErrorManager.OPEN_FAILURE);
+            }
+
+            canidate = this.subjectFormatter;
+            result = intern(seen, canidate);
+            if (result != canidate && result instanceof Formatter) {
+                this.subjectFormatter = (Formatter) result;
+            }
+
+            canidate = this.pushFilter;
+            result = intern(seen, canidate);
+            if (result != canidate && result instanceof Filter) {
+                this.pushFilter = (Filter) result;
+            }
+
+            for (int i = 0; i < attachmentFormatters.length; ++i) {
+                canidate = attachmentFormatters[i];
+                result = intern(seen, canidate);
+                if (result != canidate && result instanceof Formatter) {
+                    attachmentFormatters[i] = (Formatter) result;
+                }
+
+                canidate = attachmentFilters[i];
+                result = intern(seen, canidate);
+                if (result != canidate && result instanceof Filter) {
+                    attachmentFilters[i] = (Filter) result;
+                }
+
+                canidate = attachmentNames[i];
+                result = intern(seen, canidate);
+                if (result != canidate && result instanceof Formatter) {
+                    attachmentNames[i] = (Formatter) result;
+                }
+            }
+        } catch (final Exception skip) {
+            reportError(skip.getMessage(), skip, ErrorManager.OPEN_FAILURE);
+        } catch (final LinkageError skip) {
+            reportError(skip.getMessage(), new InvocationTargetException(skip),
+                    ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * If possible performs an intern of the given object into the
+     * map.  If the object can not be interned the given object is returned.
+     * @param m the map used to record the interned values.
+     * @param o the object to try an intern.
+     * @return the original object or an intern replacement.
+     * @throws SecurityException if this operation is not allowed by the
+     * security manager.
+     * @throws Exception if there is an unexpected problem.
+     * @since JavaMail 1.5.0
+     */
+    private Object intern(Map<Object, Object> m, Object o) throws Exception {
+        if (o == null) {
+            return null;
+        }
+
+        /**
+         * The common case is that most objects will not intern.  The given
+         * object has a public no argument constructor or is an instance of a
+         * TailNameFormatter.  TailNameFormatter is safe use as a map key.
+         * For everything else we create a clone of the given object.
+         * This is done because of the following:
+         * 1. Clones can be used to test that a class provides an equals method
+         * and that the equals method works correctly.
+         * 2. Calling equals on the given object is assumed to be cheap.
+         * 3. The intern map can be filtered so it only contains objects that
+         * can be interned, which reduces the memory footprint.
+         * 4. Clones are method local garbage.
+         * 5. Hash code is only called on the clones so bias locking is not
+         * disabled on the objects the handler will use.
+         */
+        final Object key;
+        if (o.getClass().getName().equals(TailNameFormatter.class.getName())) {
+            key = o;
+        } else {
+            //This call was already made in the LogManagerProperties so this
+            //shouldn't trigger loading of any lazy reflection code.
+            key = o.getClass().getConstructor().newInstance();
+        }
+
+        final Object use;
+        //Check the classloaders of each object avoiding the security manager.
+        if (key.getClass() == o.getClass()) {
+            Object found = m.get(key); //Transitive equals test.
+            if (found == null) {
+                //Ensure that equals is symmetric to prove intern is safe.
+                final boolean right = key.equals(o);
+                final boolean left = o.equals(key);
+                if (right && left) {
+                    //Assume hashCode is defined at this point.
+                    found = m.put(o, o);
+                    if (found != null) {
+                        reportNonDiscriminating(key, found);
+                        found = m.remove(key);
+                        if (found != o) {
+                            reportNonDiscriminating(key, found);
+                            m.clear(); //Try to restore order.
+                        }
+                    }
+                } else {
+                    if (right != left) {
+                        reportNonSymmetric(o, key);
+                    }
+                }
+                use = o;
+            } else {
+                //Check for a discriminating equals method.
+                if (o.getClass() == found.getClass()) {
+                    use = found;
+                } else {
+                    reportNonDiscriminating(o, found);
+                    use = o;
+                }
+            }
+        } else {
+            use = o;
+        }
+        return use;
+    }
+
+    /**
+     * Factory method used to create a java.util.logging.SimpleFormatter.
+     * @return a new SimpleFormatter.
+     * @since JavaMail 1.5.6
+     */
+    private static Formatter createSimpleFormatter() {
+        //Don't force the byte code verifier to load the formatter.
+        return Formatter.class.cast(new SimpleFormatter());
+    }
+
+    /**
+     * Checks a char sequence value for null or empty.
+     * @param s the char sequence.
+     * @return true if the given string is null or zero length.
+     */
+    private static boolean isEmpty(final CharSequence s) {
+        return s == null || s.length() == 0;
+    }
+
+    /**
+     * Checks that a string is not empty and not equal to the literal "null".
+     * @param name the string to check for a value.
+     * @return true if the string has a valid value.
+     */
+    private static boolean hasValue(final String name) {
+        return !isEmpty(name) && !"null".equalsIgnoreCase(name);
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initAttachmentFilters(final String p) {
+        assert Thread.holdsLock(this);
+        assert this.attachmentFormatters != null;
+        final String list = fromLogManager(p.concat(".attachment.filters"));
+        if (!isEmpty(list)) {
+            final String[] names = list.split(",");
+            Filter[] a = new Filter[names.length];
+            for (int i = 0; i < a.length; ++i) {
+                names[i] = names[i].trim();
+                if (!"null".equalsIgnoreCase(names[i])) {
+                    try {
+                        a[i] = LogManagerProperties.newFilter(names[i]);
+                    } catch (final SecurityException SE) {
+                        throw SE; //Avoid catch all.
+                    } catch (final Exception E) {
+                        reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+                    }
+                }
+            }
+
+            this.attachmentFilters = a;
+            if (alignAttachmentFilters()) {
+                reportError("Attachment filters.",
+                        attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
+            }
+        } else {
+            this.attachmentFilters = emptyFilterArray();
+            alignAttachmentFilters();
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initAttachmentFormaters(final String p) {
+        assert Thread.holdsLock(this);
+        final String list = fromLogManager(p.concat(".attachment.formatters"));
+        if (!isEmpty(list)) {
+            final Formatter[] a;
+            final String[] names = list.split(",");
+            if (names.length == 0) {
+                a = emptyFormatterArray();
+            } else {
+                a = new Formatter[names.length];
+            }
+
+            for (int i = 0; i < a.length; ++i) {
+                names[i] = names[i].trim();
+                if (!"null".equalsIgnoreCase(names[i])) {
+                    try {
+                        a[i] = LogManagerProperties.newFormatter(names[i]);
+                        if (a[i] instanceof TailNameFormatter) {
+                            final Exception CNFE = new ClassNotFoundException(a[i].toString());
+                            reportError("Attachment formatter.", CNFE, ErrorManager.OPEN_FAILURE);
+                            a[i] = createSimpleFormatter();
+                        }
+                    } catch (final SecurityException SE) {
+                        throw SE; //Avoid catch all.
+                    } catch (final Exception E) {
+                        reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+                        a[i] = createSimpleFormatter();
+                    }
+                } else {
+                    final Exception NPE = new NullPointerException(atIndexMsg(i));
+                    reportError("Attachment formatter.", NPE, ErrorManager.OPEN_FAILURE);
+                    a[i] = createSimpleFormatter();
+                }
+            }
+
+            this.attachmentFormatters = a;
+        } else {
+            this.attachmentFormatters = emptyFormatterArray();
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initAttachmentNames(final String p) {
+        assert Thread.holdsLock(this);
+        assert this.attachmentFormatters != null;
+
+        final String list = fromLogManager(p.concat(".attachment.names"));
+        if (!isEmpty(list)) {
+            final String[] names = list.split(",");
+            final Formatter[] a = new Formatter[names.length];
+            for (int i = 0; i < a.length; ++i) {
+                names[i] = names[i].trim();
+                if (!"null".equalsIgnoreCase(names[i])) {
+                    try {
+                        try {
+                            a[i] = LogManagerProperties.newFormatter(names[i]);
+                        } catch (ClassNotFoundException
+                                | ClassCastException literal) {
+                            a[i] = TailNameFormatter.of(names[i]);
+                        }
+                    } catch (final SecurityException SE) {
+                        throw SE; //Avoid catch all.
+                    } catch (final Exception E) {
+                        reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+                    }
+                } else {
+                    final Exception NPE = new NullPointerException(atIndexMsg(i));
+                    reportError("Attachment names.", NPE, ErrorManager.OPEN_FAILURE);
+                }
+            }
+
+            this.attachmentNames = a;
+            if (alignAttachmentNames()) { //Any null indexes are repaired.
+                reportError("Attachment names.",
+                        attachmentMismatch("Length mismatch."), ErrorManager.OPEN_FAILURE);
+            }
+        } else {
+            this.attachmentNames = emptyFormatterArray();
+            alignAttachmentNames();
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initAuthenticator(final String p) {
+        assert Thread.holdsLock(this);
+        String name = fromLogManager(p.concat(".authenticator"));
+        if (name != null && !"null".equalsIgnoreCase(name)) {
+            if (name.length() != 0) {
+                try {
+                    this.auth = LogManagerProperties
+                            .newObjectFrom(name, Authenticator.class);
+                } catch (final SecurityException SE) {
+                    throw SE;
+                } catch (final ClassNotFoundException
+                        | ClassCastException literalAuth) {
+                    this.auth = DefaultAuthenticator.of(name);
+                } catch (final Exception E) {
+                    reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+                }
+            } else { //Authenticator is installed to provide the user name.
+                this.auth = DefaultAuthenticator.of(name);
+            }
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initLevel(final String p) {
+        assert Thread.holdsLock(this);
+        try {
+            final String val = fromLogManager(p.concat(".level"));
+            if (val != null) {
+                logLevel = Level.parse(val);
+            } else {
+                logLevel = Level.WARNING;
+            }
+        } catch (final SecurityException SE) {
+             throw SE; //Avoid catch all.
+        } catch (final RuntimeException RE) {
+            reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+            logLevel = Level.WARNING;
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initFilter(final String p) {
+        assert Thread.holdsLock(this);
+        try {
+            String name = fromLogManager(p.concat(".filter"));
+            if (hasValue(name)) {
+                filter = LogManagerProperties.newFilter(name);
+            }
+        } catch (final SecurityException SE) {
+            throw SE; //Avoid catch all.
+        } catch (final Exception E) {
+            reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initCapacity(final String p) {
+        assert Thread.holdsLock(this);
+        final int DEFAULT_CAPACITY = 1000;
+        try {
+            final String value = fromLogManager(p.concat(".capacity"));
+            if (value != null) {
+                this.setCapacity0(Integer.parseInt(value));
+            } else {
+                this.setCapacity0(DEFAULT_CAPACITY);
+            }
+        } catch (final SecurityException SE) {
+            throw SE; //Avoid catch all.
+        } catch (final RuntimeException RE) {
+            reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+        }
+
+        if (capacity <= 0) {
+            capacity = DEFAULT_CAPACITY;
+        }
+
+        this.data = new LogRecord[1];
+        this.matched = new int[this.data.length];
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initEncoding(final String p) {
+        assert Thread.holdsLock(this);
+        try {
+            String e = fromLogManager(p.concat(".encoding"));
+            if (e != null) {
+                setEncoding0(e);
+            }
+        } catch (final SecurityException SE) {
+            throw SE; //Avoid catch all.
+        } catch (UnsupportedEncodingException | RuntimeException UEE) {
+            reportError(UEE.getMessage(), UEE, ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * Used to get or create the default ErrorManager used before init.
+     * @return the super error manager or a new ErrorManager.
+     * @since JavaMail 1.5.3
+     */
+    private ErrorManager defaultErrorManager() {
+        ErrorManager em;
+        try { //Try to share the super error manager.
+            em = super.getErrorManager();
+        } catch (RuntimeException | LinkageError ignore) {
+            em = null;
+        }
+
+        //Don't assume that the super call is not null.
+        if (em == null) {
+            em = new ErrorManager();
+        }
+        return em;
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initErrorManager(final String p) {
+        assert Thread.holdsLock(this);
+        try {
+            String name = fromLogManager(p.concat(".errorManager"));
+            if (name != null) {
+                setErrorManager0(LogManagerProperties.newErrorManager(name));
+            }
+        } catch (final SecurityException SE) {
+            throw SE; //Avoid catch all.
+        } catch (final Exception E) {
+            reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initFormatter(final String p) {
+        assert Thread.holdsLock(this);
+        try {
+            String name = fromLogManager(p.concat(".formatter"));
+            if (hasValue(name)) {
+                final Formatter f
+                        = LogManagerProperties.newFormatter(name);
+                assert f != null;
+                if (f instanceof TailNameFormatter == false) {
+                    formatter = f;
+                } else {
+                    formatter = createSimpleFormatter();
+                }
+            } else {
+                formatter = createSimpleFormatter();
+            }
+        } catch (final SecurityException SE) {
+            throw SE; //Avoid catch all.
+        } catch (final Exception E) {
+            reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+            formatter = createSimpleFormatter();
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initComparator(final String p) {
+        assert Thread.holdsLock(this);
+        try {
+            String name = fromLogManager(p.concat(".comparator"));
+            String reverse = fromLogManager(p.concat(".comparator.reverse"));
+            if (hasValue(name)) {
+                comparator = LogManagerProperties.newComparator(name);
+                if (Boolean.parseBoolean(reverse)) {
+                    assert comparator != null : "null";
+                    comparator = LogManagerProperties.reverseOrder(comparator);
+                }
+            } else {
+                if (!isEmpty(reverse)) {
+                    throw new IllegalArgumentException(
+                            "No comparator to reverse.");
+                }
+            }
+        } catch (final SecurityException SE) {
+            throw SE; //Avoid catch all.
+        } catch (final Exception E) {
+            reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initPushLevel(final String p) {
+        assert Thread.holdsLock(this);
+        try {
+            final String val = fromLogManager(p.concat(".pushLevel"));
+            if (val != null) {
+                this.pushLevel = Level.parse(val);
+            }
+        } catch (final RuntimeException RE) {
+            reportError(RE.getMessage(), RE, ErrorManager.OPEN_FAILURE);
+        }
+
+        if (this.pushLevel == null) {
+            this.pushLevel = Level.OFF;
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initPushFilter(final String p) {
+        assert Thread.holdsLock(this);
+        try {
+            String name = fromLogManager(p.concat(".pushFilter"));
+            if (hasValue(name)) {
+                this.pushFilter = LogManagerProperties.newFilter(name);
+            }
+        } catch (final SecurityException SE) {
+            throw SE; //Avoid catch all.
+        } catch (final Exception E) {
+            reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * Parses LogManager string values into objects used by this handler.
+     * @param p the handler class name used as the prefix.
+     * @throws NullPointerException if the given argument is null.
+     * @throws SecurityException if not allowed.
+     */
+    private void initSubject(final String p) {
+        assert Thread.holdsLock(this);
+        String name = fromLogManager(p.concat(".subject"));
+        if (name == null) { //Soft dependency on CollectorFormatter.
+            name = "com.sun.mail.util.logging.CollectorFormatter";
+        }
+
+        if (hasValue(name)) {
+            try {
+                this.subjectFormatter = LogManagerProperties.newFormatter(name);
+            } catch (final SecurityException SE) {
+                throw SE; //Avoid catch all.
+            } catch (ClassNotFoundException
+                    | ClassCastException literalSubject) {
+                this.subjectFormatter = TailNameFormatter.of(name);
+            } catch (final Exception E) {
+                this.subjectFormatter = TailNameFormatter.of(name);
+                reportError(E.getMessage(), E, ErrorManager.OPEN_FAILURE);
+            }
+        } else { //User has forced empty or literal null.
+            this.subjectFormatter = TailNameFormatter.of(name);
+        }
+    }
+
+    /**
+     * Check if any attachment would actually format the given
+     * <tt>LogRecord</tt>.  This method does not check if the handler
+     * is level is set to OFF or if the handler is closed.
+     * @param record  a <tt>LogRecord</tt>
+     * @return true if the <tt>LogRecord</tt> would be formatted.
+     */
+    private boolean isAttachmentLoggable(final LogRecord record) {
+        final Filter[] filters = readOnlyAttachmentFilters();
+        for (int i = 0; i < filters.length; ++i) {
+            final Filter f = filters[i];
+            if (f == null || f.isLoggable(record)) {
+                setMatchedPart(i);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Check if this <tt>Handler</tt> would push after storing the
+     * <tt>LogRecord</tt> into its internal buffer.
+     * @param record  a <tt>LogRecord</tt>
+     * @return true if the <tt>LogRecord</tt> triggers an email push.
+     * @throws NullPointerException if tryMutex was not called.
+     */
+    private boolean isPushable(final LogRecord record) {
+        assert Thread.holdsLock(this);
+        final int value = getPushLevel().intValue();
+        if (value == offValue || record.getLevel().intValue() < value) {
+            return false;
+        }
+
+        final Filter push = getPushFilter();
+        if (push == null) {
+            return true;
+        }
+
+        final int match = getMatchedPart();
+        if ((match == -1 && getFilter() == push)
+                || (match >= 0 && attachmentFilters[match] == push)) {
+            return true;
+        } else {
+            return push.isLoggable(record);
+        }
+    }
+
+    /**
+     * Used to perform push or flush.
+     * @param priority true for high priority otherwise false for normal.
+     * @param code the error manager code.
+     */
+    private void push(final boolean priority, final int code) {
+        if (tryMutex()) {
+            try {
+                final Message msg = writeLogRecords(code);
+                if (msg != null) {
+                    send(msg, priority, code);
+                }
+            } catch (final LinkageError JDK8152515) {
+                reportLinkageError(JDK8152515, code);
+            } finally {
+                releaseMutex();
+            }
+        } else {
+            reportUnPublishedError(null);
+        }
+    }
+
+    /**
+     * Used to send the generated email or write its contents to the
+     * error manager for this handler.  This method does not hold any
+     * locks so new records can be added to this handler during a send or
+     * failure.
+     * @param msg the message or null.
+     * @param priority true for high priority or false for normal.
+     * @param code the ErrorManager code.
+     * @throws NullPointerException if message is null.
+     */
+    private void send(Message msg, boolean priority, int code) {
+        try {
+            envelopeFor(msg, priority);
+            final Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+            try {  //JDK-8025251
+                Transport.send(msg); //Calls save changes.
+            } finally {
+                getAndSetContextClassLoader(ccl);
+            }
+        } catch (final RuntimeException re) {
+            reportError(msg, re, code);
+        } catch (final Exception e) {
+            reportError(msg, e, code);
+        }
+    }
+
+    /**
+     * Performs a sort on the records if needed.
+     * Any exception thrown during a sort is considered a formatting error.
+     */
+    private void sort() {
+        assert Thread.holdsLock(this);
+        if (comparator != null) {
+            try {
+                if (size != 1) {
+                    Arrays.sort(data, 0, size, comparator);
+                } else {
+                    if (comparator.compare(data[0], data[0]) != 0) {
+                        throw new IllegalArgumentException(
+                                comparator.getClass().getName());
+                    }
+                }
+            } catch (final RuntimeException RE) {
+                reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
+            }
+        }
+    }
+
+    /**
+     * Formats all records in the buffer and places the output in a Message.
+     * This method under most conditions will catch, report, and continue when
+     * exceptions occur.  This method holds a lock on this handler.
+     * @param code the error manager code.
+     * @return null if there are no records or is currently in a push.
+     * Otherwise a new message is created with a formatted message and
+     * attached session.
+     */
+    private Message writeLogRecords(final int code) {
+        try {
+            synchronized (this) {
+                if (size > 0 && !isWriting) {
+                    isWriting = true;
+                    try {
+                        return writeLogRecords0();
+                    } finally {
+                        isWriting = false;
+                        if (size > 0) {
+                            reset();
+                        }
+                    }
+                }
+            }
+        } catch (final RuntimeException re) {
+            reportError(re.getMessage(), re, code);
+        } catch (final Exception e) {
+            reportError(e.getMessage(), e, code);
+        }
+        return null;
+    }
+
+    /**
+     * Formats all records in the buffer and places the output in a Message.
+     * This method under most conditions will catch, report, and continue when
+     * exceptions occur.
+     *
+     * @return null if there are no records or is currently in a push. Otherwise
+     * a new message is created with a formatted message and attached session.
+     * @throws MessagingException if there is a problem.
+     * @throws IOException if there is a problem.
+     * @throws RuntimeException if there is an unexpected problem.
+     * @since JavaMail 1.5.3
+     */
+    private Message writeLogRecords0() throws Exception {
+        assert Thread.holdsLock(this);
+        sort();
+        if (session == null) {
+            initSession();
+        }
+        MimeMessage msg = new MimeMessage(session);
+
+        /**
+         * Parts are lazily created when an attachment performs a getHead
+         * call.  Therefore, a null part at an index means that the head is
+         * required.
+         */
+        MimeBodyPart[] parts = new MimeBodyPart[attachmentFormatters.length];
+
+        /**
+         * The buffers are lazily created when the part requires a getHead.
+         */
+        StringBuilder[] buffers = new StringBuilder[parts.length];
+        StringBuilder buf = null;
+        final MimePart body;
+        if (parts.length == 0) {
+            msg.setDescription(descriptionFrom(
+                    getFormatter(), getFilter(), subjectFormatter));
+            body = msg;
+        } else {
+            msg.setDescription(descriptionFrom(
+                    comparator, pushLevel, pushFilter));
+            body = createBodyPart();
+        }
+
+        appendSubject(msg, head(subjectFormatter));
+        final Formatter bodyFormat = getFormatter();
+        final Filter bodyFilter = getFilter();
+
+        Locale lastLocale = null;
+        for (int ix = 0; ix < size; ++ix) {
+            boolean formatted = false;
+            final int match = matched[ix];
+            final LogRecord r = data[ix];
+            data[ix] = null; //Clear while formatting.
+
+            final Locale locale = localeFor(r);
+            appendSubject(msg, format(subjectFormatter, r));
+            Filter lmf = null; //Identity of last matched filter.
+            if (bodyFilter == null || match == -1 || parts.length == 0
+                    || (match < -1 && bodyFilter.isLoggable(r))) {
+                lmf = bodyFilter;
+                if (buf == null) {
+                    buf = new StringBuilder();
+                    buf.append(head(bodyFormat));
+                }
+                formatted = true;
+                buf.append(format(bodyFormat, r));
+                if (locale != null && !locale.equals(lastLocale)) {
+                    appendContentLang(body, locale);
+                }
+            }
+
+            for (int i = 0; i < parts.length; ++i) {
+                //A match index less than the attachment index means that
+                //the filter has not seen this record.
+                final Filter af = attachmentFilters[i];
+                if (af == null || lmf == af || match == i
+                        || (match < i && af.isLoggable(r))) {
+                    if (lmf == null && af != null) {
+                        lmf = af;
+                    }
+                    if (parts[i] == null) {
+                        parts[i] = createBodyPart(i);
+                        buffers[i] = new StringBuilder();
+                        buffers[i].append(head(attachmentFormatters[i]));
+                        appendFileName(parts[i], head(attachmentNames[i]));
+                    }
+                    formatted = true;
+                    appendFileName(parts[i], format(attachmentNames[i], r));
+                    buffers[i].append(format(attachmentFormatters[i], r));
+                    if (locale != null && !locale.equals(lastLocale)) {
+                        appendContentLang(parts[i], locale);
+                    }
+                }
+            }
+
+            if (formatted) {
+                if (body != msg && locale != null
+                        && !locale.equals(lastLocale)) {
+                    appendContentLang(msg, locale);
+                }
+            } else {  //Belongs to no mime part.
+                reportFilterError(r);
+            }
+            lastLocale = locale;
+        }
+        this.size = 0;
+
+        for (int i = parts.length - 1; i >= 0; --i) {
+            if (parts[i] != null) {
+                appendFileName(parts[i], tail(attachmentNames[i], "err"));
+                buffers[i].append(tail(attachmentFormatters[i], ""));
+
+                if (buffers[i].length() > 0) {
+                    String name = parts[i].getFileName();
+                    if (isEmpty(name)) { //Exceptional case.
+                        name = toString(attachmentFormatters[i]);
+                        parts[i].setFileName(name);
+                    }
+                    setContent(parts[i], buffers[i], getContentType(name));
+                } else {
+                    setIncompleteCopy(msg);
+                    parts[i] = null; //Skip this part.
+                }
+                buffers[i] = null;
+            }
+        }
+
+        if (buf != null) {
+            buf.append(tail(bodyFormat, ""));
+            //This body part is always added, even if the buffer is empty,
+            //so the body is never considered an incomplete-copy.
+        } else {
+            buf = new StringBuilder(0);
+        }
+
+        appendSubject(msg, tail(subjectFormatter, ""));
+
+        String contentType = contentTypeOf(buf);
+        String altType = contentTypeOf(bodyFormat);
+        setContent(body, buf, altType == null ? contentType : altType);
+        if (body != msg) {
+            final MimeMultipart multipart = new MimeMultipart();
+            //assert body instanceof BodyPart : body;
+            multipart.addBodyPart((BodyPart) body);
+
+            for (int i = 0; i < parts.length; ++i) {
+                if (parts[i] != null) {
+                    multipart.addBodyPart(parts[i]);
+                }
+            }
+            msg.setContent(multipart);
+        }
+
+        return msg;
+    }
+
+    /**
+     * Checks all of the settings if the caller requests a verify and a verify
+     * was not performed yet and no verify is in progress.  A verify is
+     * performed on create because this handler may be at the end of a handler
+     * chain and therefore may not see any log records until LogManager.reset()
+     * is called and at that time all of the settings have been cleared.
+     * @param session the current session or null.
+     * @since JavaMail 1.4.4
+     */
+    private void verifySettings(final Session session) {
+        try {
+            if (session != null) {
+                final Properties props = session.getProperties();
+                final Object check = props.put("verify", "");
+                if (check instanceof String) {
+                    String value = (String) check;
+                    //Perform the verify if needed.
+                    if (hasValue(value)) {
+                        verifySettings0(session, value);
+                    }
+                } else {
+                    if (check != null) { //Pass some invalid string.
+                        verifySettings0(session, check.getClass().toString());
+                    }
+                }
+            }
+        } catch (final LinkageError JDK8152515) {
+            reportLinkageError(JDK8152515, ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * Checks all of the settings using the given setting.
+     * This triggers the LogManagerProperties to copy all of the mail
+     * settings without explictly knowing them.  Once all of the properties
+     * are copied this handler can handle LogManager.reset clearing all of the
+     * properties.  It is expected that this method is, at most, only called
+     * once per session.
+     * @param session the current session.
+     * @param verify the type of verify to perform.
+     * @since JavaMail 1.4.4
+     */
+    private void verifySettings0(Session session, String verify) {
+        assert verify != null : (String) null;
+        if (!"local".equals(verify) && !"remote".equals(verify)
+                && !"limited".equals(verify) && !"resolve".equals(verify)
+                && !"login".equals(verify)) {
+            reportError("Verify must be 'limited', local', "
+                    + "'resolve', 'login', or 'remote'.",
+                    new IllegalArgumentException(verify),
+                    ErrorManager.OPEN_FAILURE);
+            return;
+        }
+
+        final MimeMessage abort = new MimeMessage(session);
+        final String msg;
+        if (!"limited".equals(verify)) {
+            msg = "Local address is "
+                    + InternetAddress.getLocalAddress(session) + '.';
+
+            try { //Verify subclass or declared mime charset.
+                Charset.forName(getEncodingName());
+            } catch (final RuntimeException RE) {
+                UnsupportedEncodingException UEE =
+                        new UnsupportedEncodingException(RE.toString());
+                UEE.initCause(RE);
+                reportError(msg, UEE, ErrorManager.FORMAT_FAILURE);
+            }
+        } else {
+            msg = "Skipping local address check.";
+        }
+
+        //Perform all of the copy actions first.
+        String[] atn;
+        synchronized (this) { //Create the subject.
+            appendSubject(abort, head(subjectFormatter));
+            appendSubject(abort, tail(subjectFormatter, ""));
+            atn = new String[attachmentNames.length];
+            for (int i = 0; i < atn.length; ++i) {
+                atn[i] = head(attachmentNames[i]);
+                if (atn[i].length() == 0) {
+                    atn[i] = tail(attachmentNames[i], "");
+                } else {
+                    atn[i] = atn[i].concat(tail(attachmentNames[i], ""));
+                }
+            }
+        }
+
+        setIncompleteCopy(abort); //Original body part is never added.
+        envelopeFor(abort, true);
+        saveChangesNoContent(abort, msg);
+        try {
+            //Ensure transport provider is installed.
+            Address[] all = abort.getAllRecipients();
+            if (all == null) { //Don't pass null to sendMessage.
+                all = new InternetAddress[0];
+            }
+            Transport t;
+            try {
+                final Address[] any = all.length != 0 ? all : abort.getFrom();
+                if (any != null && any.length != 0) {
+                    t = session.getTransport(any[0]);
+                    session.getProperty("mail.transport.protocol"); //Force copy
+                } else {
+                    MessagingException me = new MessagingException(
+                            "No recipient or from address.");
+                    reportError(msg, me, ErrorManager.OPEN_FAILURE);
+                    throw me;
+                }
+            } catch (final MessagingException protocol) {
+                //Switching the CCL emulates the current send behavior.
+                Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+                try {
+                    t = session.getTransport();
+                } catch (final MessagingException fail) {
+                    throw attach(protocol, fail);
+                } finally {
+                    getAndSetContextClassLoader(ccl);
+                }
+            }
+
+            String local = null;
+            if ("remote".equals(verify) || "login".equals(verify)) {
+                MessagingException closed = null;
+                t.connect();
+                try {
+                    try {
+                        //Capture localhost while connection is open.
+                        local = getLocalHost(t);
+
+                        //A message without content will fail at message writeTo
+                        //when sendMessage is called.  This allows the handler
+                        //to capture all mail properties set in the LogManager.
+                        if ("remote".equals(verify)) {
+                            t.sendMessage(abort, all);
+                        }
+                    } finally {
+                        try {
+                            t.close();
+                        } catch (final MessagingException ME) {
+                            closed = ME;
+                        }
+                    }
+                    //Close the transport before reporting errors.
+                    if ("remote".equals(verify)) {
+                        reportUnexpectedSend(abort, verify, null);
+                    } else {
+                        final String protocol = t.getURLName().getProtocol();
+                        verifyProperties(session, protocol);
+                    }
+                } catch (final SendFailedException sfe) {
+                    Address[] recip = sfe.getInvalidAddresses();
+                    if (recip != null && recip.length != 0) {
+                        setErrorContent(abort, verify, sfe);
+                        reportError(abort, sfe, ErrorManager.OPEN_FAILURE);
+                    }
+
+                    recip = sfe.getValidSentAddresses();
+                    if (recip != null && recip.length != 0) {
+                        reportUnexpectedSend(abort, verify, sfe);
+                    }
+                } catch (final MessagingException ME) {
+                    if (!isMissingContent(abort, ME)) {
+                        setErrorContent(abort, verify, ME);
+                        reportError(abort, ME, ErrorManager.OPEN_FAILURE);
+                    }
+                }
+
+                if (closed != null) {
+                    setErrorContent(abort, verify, closed);
+                    reportError(abort, closed, ErrorManager.CLOSE_FAILURE);
+                }
+            } else {
+                //Force a property copy, JDK-7092981.
+                final String protocol = t.getURLName().getProtocol();
+                verifyProperties(session, protocol);
+                String mailHost = session.getProperty("mail."
+                        + protocol + ".host");
+                if (isEmpty(mailHost)) {
+                    mailHost = session.getProperty("mail.host");
+                } else {
+                    session.getProperty("mail.host");
+                }
+
+                local = session.getProperty("mail." + protocol + ".localhost");
+                if (isEmpty(local)) {
+                    local = session.getProperty("mail."
+                            + protocol + ".localaddress");
+                } else {
+                    session.getProperty("mail." + protocol + ".localaddress");
+                }
+
+                if ("resolve".equals(verify)) {
+                    try { //Resolve the remote host name.
+                        String transportHost = t.getURLName().getHost();
+                        if (!isEmpty(transportHost)) {
+                            verifyHost(transportHost);
+                            if (!transportHost.equalsIgnoreCase(mailHost)) {
+                                verifyHost(mailHost);
+                            }
+                        } else {
+                            verifyHost(mailHost);
+                        }
+                    } catch (final RuntimeException | IOException IOE) {
+                        MessagingException ME =
+                                new MessagingException(msg, IOE);
+                        setErrorContent(abort, verify, ME);
+                        reportError(abort, ME, ErrorManager.OPEN_FAILURE);
+                    }
+                }
+            }
+
+            if (!"limited".equals(verify)) {
+                try { //Verify host name and hit the host name cache.
+                    if (!"remote".equals(verify) && !"login".equals(verify)) {
+                        local = getLocalHost(t);
+                    }
+                    verifyHost(local);
+                } catch (final RuntimeException | IOException IOE) {
+                    MessagingException ME = new MessagingException(msg, IOE);
+                    setErrorContent(abort, verify, ME);
+                    reportError(abort, ME, ErrorManager.OPEN_FAILURE);
+                }
+
+                try { //Verify that the DataHandler can be loaded.
+                    Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+                    try {
+                        //Always load the multipart classes.
+                        MimeMultipart multipart = new MimeMultipart();
+                        MimeBodyPart[] ambp = new MimeBodyPart[atn.length];
+                        final MimeBodyPart body;
+                        final String bodyContentType;
+                        synchronized (this) {
+                            bodyContentType = contentTypeOf(getFormatter());
+                            body = createBodyPart();
+                            for (int i = 0; i < atn.length; ++i) {
+                                ambp[i] = createBodyPart(i);
+                                ambp[i].setFileName(atn[i]);
+                                //Convert names to mime type under lock.
+                                atn[i] = getContentType(atn[i]);
+                            }
+                        }
+
+                        body.setDescription(verify);
+                        setContent(body, "", bodyContentType);
+                        multipart.addBodyPart(body);
+                        for (int i = 0; i < ambp.length; ++i) {
+                            ambp[i].setDescription(verify);
+                            setContent(ambp[i], "", atn[i]);
+                        }
+
+                        abort.setContent(multipart);
+                        abort.saveChanges();
+                        abort.writeTo(new ByteArrayOutputStream(MIN_HEADER_SIZE));
+                    } finally {
+                        getAndSetContextClassLoader(ccl);
+                    }
+                } catch (final IOException IOE) {
+                    MessagingException ME = new MessagingException(msg, IOE);
+                    setErrorContent(abort, verify, ME);
+                    reportError(abort, ME, ErrorManager.FORMAT_FAILURE);
+                }
+            }
+
+            //Verify all recipients.
+            if (all.length != 0) {
+                verifyAddresses(all);
+            } else {
+                throw new MessagingException("No recipient addresses.");
+            }
+
+            //Verify from and sender addresses.
+            Address[] from = abort.getFrom();
+            Address sender = abort.getSender();
+            if (sender instanceof InternetAddress) {
+                ((InternetAddress) sender).validate();
+            }
+
+            //If from address is declared then check sender.
+            if (abort.getHeader("From", ",") != null && from.length != 0) {
+                verifyAddresses(from);
+                for (int i = 0; i < from.length; ++i) {
+                    if (from[i].equals(sender)) {
+                        MessagingException ME = new MessagingException(
+                                "Sender address '" + sender
+                                + "' equals from address.");
+                        throw new MessagingException(msg, ME);
+                    }
+                }
+            } else {
+                if (sender == null) {
+                    MessagingException ME = new MessagingException(
+                            "No from or sender address.");
+                    throw new MessagingException(msg, ME);
+                }
+            }
+
+            //Verify reply-to addresses.
+            verifyAddresses(abort.getReplyTo());
+        } catch (final RuntimeException RE) {
+            setErrorContent(abort, verify, RE);
+            reportError(abort, RE, ErrorManager.OPEN_FAILURE);
+        } catch (final Exception ME) {
+            setErrorContent(abort, verify, ME);
+            reportError(abort, ME, ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * Handles all exceptions thrown when save changes is called on a message
+     * that doesn't have any content.
+     *
+     * @param abort the message requiring save changes.
+     * @param msg the error description.
+     * @since JavaMail 1.6.0
+     */
+    private void saveChangesNoContent(final Message abort, final String msg) {
+        if (abort != null) {
+            try {
+                try {
+                    abort.saveChanges();
+                } catch (final NullPointerException xferEncoding) {
+                    //Workaround GNU JavaMail bug in MimeUtility.getEncoding
+                    //when the mime message has no content.
+                    try {
+                        String cte = "Content-Transfer-Encoding";
+                        if (abort.getHeader(cte) == null) {
+                            abort.setHeader(cte, "base64");
+                            abort.saveChanges();
+                        } else {
+                            throw xferEncoding;
+                        }
+                    } catch (RuntimeException | MessagingException e) {
+                        if (e != xferEncoding) {
+                           e.addSuppressed(xferEncoding);
+                        }
+                        throw e;
+                    }
+                }
+            } catch (RuntimeException | MessagingException ME) {
+                reportError(msg, ME, ErrorManager.FORMAT_FAILURE);
+            }
+        }
+    }
+
+    /**
+     * Cache common session properties into the LogManagerProperties.  This is
+     * a workaround for JDK-7092981.
+     *
+     * @param session the session.
+     * @param protocol the mail protocol.
+     * @throws NullPointerException if session is null.
+     * @since JavaMail 1.6.0
+     */
+    private static void verifyProperties(Session session, String protocol) {
+        session.getProperty("mail.from");
+        session.getProperty("mail." + protocol + ".from");
+        session.getProperty("mail.dsn.ret");
+        session.getProperty("mail." + protocol + ".dsn.ret");
+        session.getProperty("mail.dsn.notify");
+        session.getProperty("mail." + protocol + ".dsn.notify");
+        session.getProperty("mail." + protocol + ".port");
+        session.getProperty("mail.user");
+        session.getProperty("mail." + protocol + ".user");
+        session.getProperty("mail." + protocol + ".localport");
+    }
+
+    /**
+     * Perform a lookup of the host address or FQDN.
+     * @param host the host or null.
+     * @return the address.
+     * @throws IOException if the host name is not valid.
+     * @throws SecurityException if security manager is present and doesn't
+     * allow access to check connect permission.
+     * @since JavaMail 1.5.0
+     */
+    private static InetAddress verifyHost(String host) throws IOException {
+        InetAddress a;
+        if (isEmpty(host)) {
+            a = InetAddress.getLocalHost();
+        } else {
+            a = InetAddress.getByName(host);
+        }
+        if (a.getCanonicalHostName().length() == 0) {
+            throw new UnknownHostException();
+        }
+        return a;
+    }
+
+    /**
+     * Calls validate for every address given.
+     * If the addresses given are null, empty or not an InternetAddress then
+     * the check is skipped.
+     * @param all any address array, null or empty.
+     * @throws AddressException if there is a problem.
+     * @since JavaMail 1.4.5
+     */
+    private static void verifyAddresses(Address[] all) throws AddressException {
+        if (all != null) {
+            for (int i = 0; i < all.length; ++i) {
+                final Address a = all[i];
+                if (a instanceof InternetAddress) {
+                    ((InternetAddress) a).validate();
+                }
+            }
+        }
+    }
+
+    /**
+     * Reports that an empty content message was sent and should not have been.
+     * @param msg the MimeMessage.
+     * @param verify the verify enum.
+     * @param cause the exception that caused the problem or null.
+     * @since JavaMail 1.4.5
+     */
+    private void reportUnexpectedSend(MimeMessage msg, String verify, Exception cause) {
+        final MessagingException write = new MessagingException(
+                "An empty message was sent.", cause);
+        setErrorContent(msg, verify, write);
+        reportError(msg, write, ErrorManager.OPEN_FAILURE);
+    }
+
+    /**
+     * Creates and sets the message content from the given Throwable.
+     * When verify fails, this method fixes the 'abort' message so that any
+     * created envelope data can be used in the error manager.
+     * @param msg the message with or without content.
+     * @param verify the verify enum.
+     * @param t the throwable or null.
+     * @since JavaMail 1.4.5
+     */
+    private void setErrorContent(MimeMessage msg, String verify, Throwable t) {
+        try { //Add content so toRawString doesn't fail.
+            final MimeBodyPart body;
+            final String subjectType;
+            final String msgDesc;
+            synchronized (this) {
+                body = createBodyPart();
+                msgDesc = descriptionFrom(comparator, pushLevel, pushFilter);
+                subjectType = getClassId(subjectFormatter);
+            }
+
+            body.setDescription("Formatted using "
+                    + (t == null ? Throwable.class.getName()
+                            : t.getClass().getName()) + ", filtered with "
+                    + verify + ", and named by "
+                    + subjectType + '.');
+            setContent(body, toMsgString(t), "text/plain");
+            final MimeMultipart multipart = new MimeMultipart();
+            multipart.addBodyPart(body);
+            msg.setContent(multipart);
+            msg.setDescription(msgDesc);
+            setAcceptLang(msg);
+            msg.saveChanges();
+        } catch (MessagingException | RuntimeException ME) {
+            reportError("Unable to create body.", ME, ErrorManager.OPEN_FAILURE);
+        }
+    }
+
+    /**
+     * Used to update the cached session object based on changes in
+     * mail properties or authenticator.
+     * @return the current session or null if no verify is required.
+     */
+    private Session updateSession() {
+        assert Thread.holdsLock(this);
+        final Session settings;
+        if (mailProps.getProperty("verify") != null) {
+            settings = initSession();
+            assert settings == session : session;
+        } else {
+            session = null; //Remove old session.
+            settings = null;
+        }
+        return settings;
+    }
+
+    /**
+     * Creates a session using a proxy properties object.
+     * @return the session that was created and assigned.
+     */
+    private Session initSession() {
+        assert Thread.holdsLock(this);
+        final String p = getClass().getName();
+        LogManagerProperties proxy = new LogManagerProperties(mailProps, p);
+        session = Session.getInstance(proxy, auth);
+        return session;
+    }
+
+    /**
+     * Creates all of the envelope information for a message.
+     * This method is safe to call outside of a lock because the message
+     * provides the safe snapshot of the mail properties.
+     * @param msg the Message to write the envelope information.
+     * @param priority true for high priority.
+     */
+    private void envelopeFor(Message msg, boolean priority) {
+        setAcceptLang(msg);
+        setFrom(msg);
+        if (!setRecipient(msg, "mail.to", Message.RecipientType.TO)) {
+            setDefaultRecipient(msg, Message.RecipientType.TO);
+        }
+        setRecipient(msg, "mail.cc", Message.RecipientType.CC);
+        setRecipient(msg, "mail.bcc", Message.RecipientType.BCC);
+        setReplyTo(msg);
+        setSender(msg);
+        setMailer(msg);
+        setAutoSubmitted(msg);
+        if (priority) {
+            setPriority(msg);
+        }
+
+        try {
+            msg.setSentDate(new java.util.Date());
+        } catch (final MessagingException ME) {
+            reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Factory to create the in-line body part.
+     * @return a body part with default headers set.
+     * @throws MessagingException if there is a problem.
+     */
+    private MimeBodyPart createBodyPart() throws MessagingException {
+        assert Thread.holdsLock(this);
+        final MimeBodyPart part = new MimeBodyPart();
+        part.setDisposition(Part.INLINE);
+        part.setDescription(descriptionFrom(getFormatter(),
+                getFilter(), subjectFormatter));
+        setAcceptLang(part);
+        return part;
+    }
+
+    /**
+     * Factory to create the attachment body part.
+     * @param index the attachment index.
+     * @return a body part with default headers set.
+     * @throws MessagingException if there is a problem.
+     * @throws IndexOutOfBoundsException if the given index is not an valid
+     * attachment index.
+     */
+    private MimeBodyPart createBodyPart(int index) throws MessagingException {
+        assert Thread.holdsLock(this);
+        final MimeBodyPart part = new MimeBodyPart();
+        part.setDisposition(Part.ATTACHMENT);
+        part.setDescription(descriptionFrom(
+                attachmentFormatters[index],
+                attachmentFilters[index],
+                attachmentNames[index]));
+        setAcceptLang(part);
+        return part;
+    }
+
+    /**
+     * Gets the description for the MimeMessage itself.
+     * The push level and filter are included because they play a role in
+     * formatting of a message when triggered or not triggered.
+     * @param c the comparator.
+     * @param l the pushLevel.
+     * @param f the pushFilter
+     * @return the description.
+     * @throws NullPointerException if level is null.
+     * @since JavaMail 1.4.5
+     */
+    private String descriptionFrom(Comparator<?> c, Level l, Filter f) {
+        return "Sorted using "+ (c == null ? "no comparator"
+                : c.getClass().getName()) + ", pushed when "+ l.getName()
+                + ", and " + (f == null ? "no push filter"
+                        : f.getClass().getName()) + '.';
+    }
+
+    /**
+     * Creates a description for a body part.
+     * @param f the content formatter.
+     * @param filter the content filter.
+     * @param name the naming formatter.
+     * @return the description for the body part.
+     */
+    private String descriptionFrom(Formatter f, Filter filter, Formatter name) {
+        return "Formatted using " + getClassId(f)
+                + ", filtered with " + (filter == null ? "no filter"
+                : filter.getClass().getName()) +", and named by "
+                + getClassId(name) + '.';
+    }
+
+    /**
+     * Gets a class name represents the behavior of the formatter.
+     * The class name may not be assignable to a Formatter.
+     * @param f the formatter.
+     * @return a class name that represents the given formatter.
+     * @throws NullPointerException if the parameter is null.
+     * @since JavaMail 1.4.5
+     */
+    private String getClassId(final Formatter f) {
+        if (f instanceof TailNameFormatter) {
+            return String.class.getName(); //Literal string.
+        } else {
+            return f.getClass().getName();
+        }
+    }
+
+    /**
+     * Ensure that a formatter creates a valid string for a part name.
+     * @param f the formatter.
+     * @return the to string value or the class name.
+     */
+    private String toString(final Formatter f) {
+        //Should never be null but, guard against formatter bugs.
+        final String name = f.toString();
+        if (!isEmpty(name)) {
+            return name;
+        } else {
+            return getClassId(f);
+        }
+    }
+
+    /**
+     * Constructs a file name from a formatter.  This method is called often
+     * but, rarely does any work.
+     * @param part to append to.
+     * @param chunk non null string to append.
+     */
+    private void appendFileName(final Part part, final String chunk) {
+        if (chunk != null) {
+            if (chunk.length() > 0) {
+                appendFileName0(part, chunk);
+            }
+        } else {
+            reportNullError(ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * It is assumed that file names are short and that in most cases
+     * getTail will be the only method that will produce a result.
+     * @param part to append to.
+     * @param chunk non null string to append.
+     */
+    private void appendFileName0(final Part part, String chunk) {
+        try {
+            //Remove all control character groups.
+            chunk = chunk.replaceAll("[\\x00-\\x1F\\x7F]+", "");
+            final String old = part.getFileName();
+            part.setFileName(old != null ? old.concat(chunk) : chunk);
+        } catch (final MessagingException ME) {
+            reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Constructs a subject line from a formatter.
+     * @param msg to append to.
+     * @param chunk non null string to append.
+     */
+    private void appendSubject(final Message msg, final String chunk) {
+        if (chunk != null) {
+            if (chunk.length() > 0) {
+                appendSubject0(msg, chunk);
+            }
+        } else {
+            reportNullError(ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * It is assumed that subject lines are short and that in most cases
+     * getTail will be the only method that will produce a result.
+     * @param msg to append to.
+     * @param chunk non null string to append.
+     */
+    private void appendSubject0(final Message msg, String chunk) {
+        try {
+            //Remove all control character groups.
+            chunk = chunk.replaceAll("[\\x00-\\x1F\\x7F]+", "");
+            final String charset = getEncodingName();
+            final String old = msg.getSubject();
+            assert msg instanceof MimeMessage : msg;
+            ((MimeMessage) msg).setSubject(old != null ? old.concat(chunk)
+                    : chunk, MimeUtility.mimeCharset(charset));
+        } catch (final MessagingException ME) {
+            reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Gets the locale for the given log record from the resource bundle.
+     * If the resource bundle is using the root locale then the default locale
+     * is returned.
+     * @param r the log record.
+     * @return null if not localized otherwise, the locale of the record.
+     * @since JavaMail 1.4.5
+     */
+    private Locale localeFor(final LogRecord r) {
+        Locale l;
+        final ResourceBundle rb = r.getResourceBundle();
+        if (rb != null) {
+            l = rb.getLocale();
+            if (l == null || isEmpty(l.getLanguage())) {
+                //The language of the fallback bundle (root) is unknown.
+                //1. Use default locale.  Should only be wrong if the app is
+                //   used with a langauge that was unintended. (unlikely)
+                //2. Mark it as not localized (force null, info loss).
+                //3. Use the bundle name (encoded) as an experimental language.
+                l = Locale.getDefault();
+            }
+        } else {
+            l = null;
+        }
+        return l;
+    }
+
+    /**
+     * Appends the content language to the given mime part.
+     * The language tag is only appended if the given language has not been
+     * specified.  This method is only used when we have LogRecords that are
+     * localized with an assigned resource bundle.
+     * @param p the mime part.
+     * @param l the locale to append.
+     * @throws NullPointerException if any argument is null.
+     * @since JavaMail 1.4.5
+     */
+    private void appendContentLang(final MimePart p, final Locale l) {
+        try {
+            String lang = LogManagerProperties.toLanguageTag(l);
+            if (lang.length() != 0) {
+                String header = p.getHeader("Content-Language", null);
+                if (isEmpty(header)) {
+                    p.setHeader("Content-Language", lang);
+                } else if (!header.equalsIgnoreCase(lang)) {
+                    lang = ",".concat(lang);
+                    int idx = 0;
+                    while ((idx = header.indexOf(lang, idx)) > -1) {
+                        idx += lang.length();
+                        if (idx == header.length()
+                                || header.charAt(idx) == ',') {
+                            break;
+                        }
+                    }
+
+                    if (idx < 0) {
+                        int len = header.lastIndexOf("\r\n\t");
+                        if (len < 0) { //If not folded.
+                            len = (18 + 2) + header.length();
+                        } else {
+                            len = (header.length() - len) + 8;
+                        }
+
+                        //Perform folding of header if needed.
+                        if ((len + lang.length()) > 76) {
+                            header = header.concat("\r\n\t".concat(lang));
+                        } else {
+                            header = header.concat(lang);
+                        }
+                        p.setHeader("Content-Language", header);
+                    }
+                }
+            }
+        } catch (final MessagingException ME) {
+            reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Sets the accept language to the default locale of the JVM.
+     * If the locale is the root locale the header is not added.
+     * @param p the part to set.
+     * @since JavaMail 1.4.5
+     */
+    private void setAcceptLang(final Part p) {
+        try {
+            final String lang = LogManagerProperties
+                    .toLanguageTag(Locale.getDefault());
+            if (lang.length() != 0) {
+                p.setHeader("Accept-Language", lang);
+            }
+        } catch (final MessagingException ME) {
+            reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Used when a log record was loggable prior to being inserted
+     * into the buffer but at the time of formatting was no longer loggable.
+     * Filters were changed after publish but prior to a push or a bug in the
+     * body filter or one of the attachment filters.
+     * @param record that was not formatted.
+     * @since JavaMail 1.4.5
+     */
+    private void reportFilterError(final LogRecord record) {
+        assert Thread.holdsLock(this);
+        final Formatter f = createSimpleFormatter();
+        final String msg = "Log record " + record.getSequenceNumber()
+                + " was filtered from all message parts.  "
+                + head(f) + format(f, record) + tail(f, "");
+        final String txt = getFilter() + ", "
+                + Arrays.asList(readOnlyAttachmentFilters());
+        reportError(msg, new IllegalArgumentException(txt),
+                ErrorManager.FORMAT_FAILURE);
+    }
+
+    /**
+     * Reports symmetric contract violations an equals implementation.
+     * @param o the test object must be non null.
+     * @param found the possible intern, must be non null.
+     * @throws NullPointerException if any argument is null.
+     * @since JavaMail 1.5.0
+     */
+    private void reportNonSymmetric(final Object o, final Object found) {
+        reportError("Non symmetric equals implementation."
+                , new IllegalArgumentException(o.getClass().getName()
+                + " is not equal to " + found.getClass().getName())
+                , ErrorManager.OPEN_FAILURE);
+    }
+
+    /**
+     * Reports equals implementations that do not discriminate between objects
+     * of different types or subclass types.
+     * @param o the test object must be non null.
+     * @param found the possible intern, must be non null.
+     * @throws NullPointerException if any argument is null.
+     * @since JavaMail 1.5.0
+     */
+    private void reportNonDiscriminating(final Object o, final Object found) {
+        reportError("Non discriminating equals implementation."
+                , new IllegalArgumentException(o.getClass().getName()
+                + " should not be equal to " + found.getClass().getName())
+                , ErrorManager.OPEN_FAILURE);
+    }
+
+    /**
+     * Used to outline the bytes to report a null pointer exception.
+     * See BUD ID 6533165.
+     * @param code the ErrorManager code.
+     */
+    private void reportNullError(final int code) {
+        reportError("null", new NullPointerException(), code);
+    }
+
+    /**
+     * Creates the head or reports a formatting error.
+     * @param f the formatter.
+     * @return the head string or an empty string.
+     */
+    private String head(final Formatter f) {
+        try {
+            return f.getHead(this);
+        } catch (final RuntimeException RE) {
+            reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
+            return "";
+        }
+    }
+
+    /**
+     * Creates the formatted log record or reports a formatting error.
+     * @param f the formatter.
+     * @param r the log record.
+     * @return the formatted string or an empty string.
+     */
+    private String format(final Formatter f, final LogRecord r) {
+        try {
+            return f.format(r);
+        } catch (final RuntimeException RE) {
+            reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
+            return "";
+        }
+    }
+
+    /**
+     * Creates the tail or reports a formatting error.
+     * @param f the formatter.
+     * @param def the default string to use when there is an error.
+     * @return the tail string or the given default string.
+     */
+    private String tail(final Formatter f, final String def) {
+        try {
+            return f.getTail(this);
+        } catch (final RuntimeException RE) {
+            reportError(RE.getMessage(), RE, ErrorManager.FORMAT_FAILURE);
+            return def;
+        }
+    }
+
+    /**
+     * Sets the x-mailer header.
+     * @param msg the target message.
+     */
+    private void setMailer(final Message msg) {
+        try {
+            final Class<?> mail = MailHandler.class;
+            final Class<?> k = getClass();
+            String value;
+            if (k == mail) {
+                value = mail.getName();
+            } else {
+                try {
+                    value = MimeUtility.encodeText(k.getName());
+                } catch (final UnsupportedEncodingException E) {
+                    reportError(E.getMessage(), E, ErrorManager.FORMAT_FAILURE);
+                    value = k.getName().replaceAll("[^\\x00-\\x7F]", "\uu001A");
+                }
+                value = MimeUtility.fold(10, mail.getName() + " using the "
+                        + value + " extension.");
+            }
+            msg.setHeader("X-Mailer", value);
+        } catch (final MessagingException ME) {
+            reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Sets the priority and importance headers.
+     * @param msg the target message.
+     */
+    private void setPriority(final Message msg) {
+        try {
+            msg.setHeader("Importance", "High");
+            msg.setHeader("Priority", "urgent");
+            msg.setHeader("X-Priority", "2"); //High
+        } catch (final MessagingException ME) {
+            reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Used to signal that body parts are missing from a message.  Also used
+     * when LogRecords were passed to an attachment formatter but the formatter
+     * produced no output, which is allowed.  Used during a verify because all
+     * parts are omitted, none of the content formatters are used.  This is
+     * not used when a filter prevents LogRecords from being formatted.
+     * This header is defined in RFC 2156 and RFC 4021.
+     * @param msg the message.
+     * @since JavaMail 1.4.5
+     */
+    private void setIncompleteCopy(final Message msg) {
+        try {
+            msg.setHeader("Incomplete-Copy", "");
+        } catch (final MessagingException ME) {
+            reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Signals that this message was generated by automatic process.
+     * This header is defined in RFC 3834 section 5.
+     * @param msg the message.
+     * @since JavaMail 1.4.6
+     */
+    private void setAutoSubmitted(final Message msg) {
+        if (allowRestrictedHeaders()) {
+            try { //RFC 3834 (5.2)
+                msg.setHeader("auto-submitted", "auto-generated");
+            } catch (final MessagingException ME) {
+                reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+            }
+        }
+    }
+
+    /**
+     * Sets from address header.
+     * @param msg the target message.
+     */
+    private void setFrom(final Message msg) {
+        final String from = getSession(msg).getProperty("mail.from");
+        if (from != null) {
+            try {
+                final Address[] address = InternetAddress.parse(from, false);
+                if (address.length > 0) {
+                    if (address.length == 1) {
+                        msg.setFrom(address[0]);
+                    } else { //Greater than 1 address.
+                        msg.addFrom(address);
+                    }
+                }
+                //Can't place an else statement here because the 'from' is
+                //not null which causes the local address computation
+                //to fail.  Assume the user wants to omit the from address
+                //header.
+            } catch (final MessagingException ME) {
+                reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+                setDefaultFrom(msg);
+            }
+        } else {
+            setDefaultFrom(msg);
+        }
+    }
+
+    /**
+     * Sets the from header to the local address.
+     * @param msg the target message.
+     */
+    private void setDefaultFrom(final Message msg) {
+        try {
+            msg.setFrom();
+        } catch (final MessagingException ME) {
+            reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Computes the default to-address if none was specified.  This can
+     * fail if the local address can't be computed.
+     * @param msg the message
+     * @param type the recipient type.
+     * @since JavaMail 1.5.0
+     */
+    private void setDefaultRecipient(final Message msg,
+            final Message.RecipientType type) {
+        try {
+            Address a = InternetAddress.getLocalAddress(getSession(msg));
+            if (a != null) {
+                msg.setRecipient(type, a);
+            } else {
+                final MimeMessage m = new MimeMessage(getSession(msg));
+                m.setFrom(); //Should throw an exception with a cause.
+                Address[] from = m.getFrom();
+                if (from.length > 0) {
+                    msg.setRecipients(type, from);
+                } else {
+                    throw new MessagingException("No local address.");
+                }
+            }
+        } catch (MessagingException | RuntimeException ME) {
+            reportError("Unable to compute a default recipient.",
+                    ME, ErrorManager.FORMAT_FAILURE);
+        }
+    }
+
+    /**
+     * Sets reply-to address header.
+     * @param msg the target message.
+     */
+    private void setReplyTo(final Message msg) {
+        final String reply = getSession(msg).getProperty("mail.reply.to");
+        if (!isEmpty(reply)) {
+            try {
+                final Address[] address = InternetAddress.parse(reply, false);
+                if (address.length > 0) {
+                    msg.setReplyTo(address);
+                }
+            } catch (final MessagingException ME) {
+                reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+            }
+        }
+    }
+
+    /**
+     * Sets sender address header.
+     * @param msg the target message.
+     */
+    private void setSender(final Message msg) {
+        assert msg instanceof MimeMessage : msg;
+        final String sender = getSession(msg).getProperty("mail.sender");
+        if (!isEmpty(sender)) {
+            try {
+                final InternetAddress[] address =
+                        InternetAddress.parse(sender, false);
+                if (address.length > 0) {
+                    ((MimeMessage) msg).setSender(address[0]);
+                    if (address.length > 1) {
+                        reportError("Ignoring other senders.",
+                                tooManyAddresses(address, 1),
+                                ErrorManager.FORMAT_FAILURE);
+                    }
+                }
+            } catch (final MessagingException ME) {
+                reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+            }
+        }
+    }
+
+    /**
+     * A common factory used to create the too many addresses exception.
+     * @param address the addresses, never null.
+     * @param offset the starting address to display.
+     * @return the too many addresses exception.
+     */
+    private AddressException tooManyAddresses(Address[] address, int offset) {
+        Object l = Arrays.asList(address).subList(offset, address.length);
+        return new AddressException(l.toString());
+    }
+
+    /**
+     * Sets the recipient for the given message.
+     * @param msg the message.
+     * @param key the key to search in the session.
+     * @param type the recipient type.
+     * @return true if the key was contained in the session.
+     */
+    private boolean setRecipient(final Message msg,
+            final String key, final Message.RecipientType type) {
+        boolean containsKey;
+        final String value = getSession(msg).getProperty(key);
+        containsKey = value != null;
+        if (!isEmpty(value)) {
+            try {
+                final Address[] address = InternetAddress.parse(value, false);
+                if (address.length > 0) {
+                    msg.setRecipients(type, address);
+                }
+            } catch (final MessagingException ME) {
+                reportError(ME.getMessage(), ME, ErrorManager.FORMAT_FAILURE);
+            }
+        }
+        return containsKey;
+    }
+
+    /**
+     * Converts an email message to a raw string.  This raw string
+     * is passed to the error manager to allow custom error managers
+     * to recreate the original MimeMessage object.
+     * @param msg a Message object.
+     * @return the raw string or null if msg was null.
+     * @throws MessagingException if there was a problem with the message.
+     * @throws IOException if there was a problem.
+     */
+    private String toRawString(final Message msg) throws MessagingException, IOException {
+        if (msg != null) {
+            Object ccl = getAndSetContextClassLoader(MAILHANDLER_LOADER);
+            try {  //JDK-8025251
+                int nbytes = Math.max(msg.getSize() + MIN_HEADER_SIZE, MIN_HEADER_SIZE);
+                ByteArrayOutputStream out = new ByteArrayOutputStream(nbytes);
+                msg.writeTo(out);  //Headers can be UTF-8 or US-ASCII.
+                return out.toString("UTF-8");
+            } finally {
+                getAndSetContextClassLoader(ccl);
+            }
+        } else { //Must match this.reportError behavior, see push method.
+            return null; //Null is the safe choice.
+        }
+    }
+
+    /**
+     * Converts a throwable to a message string.
+     * @param t any throwable or null.
+     * @return the throwable with a stack trace or the literal null.
+     */
+    private String toMsgString(final Throwable t) {
+        if (t == null) {
+            return "null";
+        }
+
+        final String charset = getEncodingName();
+        try {
+            final ByteArrayOutputStream out =
+                    new ByteArrayOutputStream(MIN_HEADER_SIZE);
+
+            //Create an output stream writer so streams are not double buffered.
+            try (OutputStreamWriter ows = new OutputStreamWriter(out, charset);
+                 PrintWriter pw = new PrintWriter(ows)) {
+                pw.println(t.getMessage());
+                t.printStackTrace(pw);
+                pw.flush();
+            } //Close OSW before generating string. JDK-6995537
+            return out.toString(charset);
+        } catch (final RuntimeException unexpected) {
+            return t.toString() + ' ' + unexpected.toString();
+        } catch (final Exception badMimeCharset) {
+            return t.toString() + ' ' + badMimeCharset.toString();
+        }
+    }
+
+    /**
+     * Replaces the current context class loader with our class loader.
+     * @param ccl null for boot class loader, a class loader, a class used to
+     * get the class loader, or a source object to get the class loader.
+     * @return null for the boot class loader, a class loader, or a marker
+     * object to signal that no modification was required.
+     * @since JavaMail 1.5.3
+     */
+    private Object getAndSetContextClassLoader(final Object ccl) {
+        if (ccl != GetAndSetContext.NOT_MODIFIED) {
+            try {
+                final PrivilegedAction<?> pa;
+                if (ccl instanceof PrivilegedAction) {
+                    pa = (PrivilegedAction<?>) ccl;
+                } else {
+                    pa = new GetAndSetContext(ccl);
+                }
+                return AccessController.doPrivileged(pa);
+            } catch (final SecurityException ignore) {
+            }
+        }
+        return GetAndSetContext.NOT_MODIFIED;
+    }
+
+    /**
+     * A factory used to create a common attachment mismatch type.
+     * @param msg the exception message.
+     * @return a RuntimeException to represent the type of error.
+     */
+    private static RuntimeException attachmentMismatch(final String msg) {
+        return new IndexOutOfBoundsException(msg);
+    }
+
+    /**
+     * Outline the attachment mismatch message. See Bug ID 6533165.
+     * @param expected the expected array length.
+     * @param found the array length that was given.
+     * @return a RuntimeException populated with a message.
+     */
+    private static RuntimeException attachmentMismatch(int expected, int found) {
+        return attachmentMismatch("Attachments mismatched, expected "
+                + expected + " but given " + found + '.');
+    }
+
+    /**
+     * Try to attach a suppressed exception to a MessagingException in any order
+     * that is possible.
+     * @param required the exception expected to see as a reported failure.
+     * @param optional the suppressed exception.
+     * @return either the required or the optional exception.
+     */
+    private static MessagingException attach(
+            MessagingException required, Exception optional) {
+        if (optional != null && !required.setNextException(optional)) {
+            if (optional instanceof MessagingException) {
+                final MessagingException head = (MessagingException) optional;
+                if (head.setNextException(required)) {
+                    return head;
+                }
+            }
+
+            if (optional != required) {
+                required.addSuppressed(optional);
+            }
+        }
+        return required;
+    }
+
+    /**
+     * Gets the local host from the given service object.
+     * @param s the service to check.
+     * @return the local host or null.
+     * @since JavaMail 1.5.3
+     */
+    private String getLocalHost(final Service s) {
+        try {
+            return LogManagerProperties.getLocalHost(s);
+        } catch (SecurityException | NoSuchMethodException
+                | LinkageError ignore) {
+        } catch (final Exception ex) {
+            reportError(s.toString(), ex, ErrorManager.OPEN_FAILURE);
+        }
+        return null;
+    }
+
+    /**
+     * Google App Engine doesn't support Message.getSession.
+     * @param msg the message.
+     * @return the session from the given message.
+     * @throws NullPointerException if the given message is null.
+     * @since JavaMail 1.5.3
+     */
+    private Session getSession(final Message msg) {
+        if (msg == null) {
+            throw new NullPointerException();
+        }
+        return new MessageContext(msg).getSession();
+    }
+
+    /**
+     * Determines if restricted headers are allowed in the current environment.
+     *
+     * @return true if restricted headers are allowed.
+     * @since JavaMail 1.5.3
+     */
+    private boolean allowRestrictedHeaders() {
+        //GAE will prevent delivery of email with forbidden headers.
+        //Assume the environment is GAE if access to the LogManager is
+        //forbidden.
+        return LogManagerProperties.hasLogManager();
+    }
+
+    /**
+     * Outline the creation of the index error message. See JDK-6533165.
+     * @param i the index.
+     * @return the error message.
+     */
+    private static String atIndexMsg(final int i) {
+        return "At index: " + i + '.';
+    }
+
+    /**
+     * Used for storing a password from the LogManager or literal string.
+     * @since JavaMail 1.4.6
+     */
+    private static final class DefaultAuthenticator extends Authenticator {
+
+        /**
+         * Creates an Authenticator for the given password.  This method is used
+         * so class verification of assignments in MailHandler doesn't require
+         * loading this class which otherwise can occur when using the
+         * constructor.  Default access to avoid generating extra class files.
+         *
+         * @param pass the password.
+         * @return an Authenticator for the password.
+         * @since JavaMail 1.5.6
+         */
+        static Authenticator of(final String pass) {
+            return new DefaultAuthenticator(pass);
+        }
+
+        /**
+         * The password to use.
+         */
+        private final String pass;
+
+        /**
+         * Use the factory method instead of this constructor.
+         * @param pass the password.
+         */
+        private DefaultAuthenticator(final String pass) {
+            assert pass != null;
+            this.pass = pass;
+        }
+
+        @Override
+        protected final PasswordAuthentication getPasswordAuthentication() {
+            return new PasswordAuthentication(getDefaultUserName(), pass);
+        }
+    }
+
+    /**
+     * Performs a get and set of the context class loader with privileges
+     * enabled.
+     * @since JavaMail 1.4.6
+     */
+    private static final class GetAndSetContext implements PrivilegedAction<Object> {
+        /**
+         * A marker object used to signal that the class loader was not
+         * modified.
+         */
+        public static final Object NOT_MODIFIED = GetAndSetContext.class;
+        /**
+         * The source containing the class loader.
+         */
+        private final Object source;
+        /**
+         * Create the action.
+         * @param source null for boot class loader, a class loader, a class
+         * used to get the class loader, or a source object to get the class
+         * loader. Default access to avoid generating extra class files.
+         */
+        GetAndSetContext(final Object source) {
+            this.source = source;
+        }
+
+        /**
+         * Gets the class loader from the source and sets the CCL only if
+         * the source and CCL are not the same.
+         * @return the replaced context class loader which can be null or
+         * NOT_MODIFIED to indicate that nothing was modified.
+         */
+        @SuppressWarnings("override") //JDK-6954234
+        public final Object run() {
+            final Thread current = Thread.currentThread();
+            final ClassLoader ccl = current.getContextClassLoader();
+            final ClassLoader loader;
+            if (source == null) {
+                loader = null; //boot class loader
+            } else if (source instanceof ClassLoader) {
+                loader = (ClassLoader) source;
+            } else if (source instanceof Class) {
+                loader = ((Class<?>) source).getClassLoader();
+            } else if (source instanceof Thread) {
+                loader = ((Thread) source).getContextClassLoader();
+            } else {
+                assert !(source instanceof Class) : source;
+                loader = source.getClass().getClassLoader();
+            }
+
+            if (ccl != loader) {
+                current.setContextClassLoader(loader);
+                return ccl;
+            } else {
+                return NOT_MODIFIED;
+            }
+        }
+    }
+
+    /**
+     * Used for naming attachment file names and the main subject line.
+     */
+    private static final class TailNameFormatter extends Formatter {
+
+        /**
+         * Creates or gets a formatter from the given name.  This method is used
+         * so class verification of assignments in MailHandler doesn't require
+         * loading this class which otherwise can occur when using the
+         * constructor.  Default access to avoid generating extra class files.
+         *
+         * @param name any not null string.
+         * @return a formatter for that string.
+         * @since JavaMail 1.5.6
+         */
+        static Formatter of(final String name) {
+            return new TailNameFormatter(name);
+        }
+
+        /**
+         * The value used as the output.
+         */
+        private final String name;
+
+        /**
+         * Use the factory method instead of this constructor.
+         * @param name any not null string.
+         */
+        private TailNameFormatter(final String name) {
+            assert name != null;
+            this.name = name;
+        }
+
+        @Override
+        public final String format(LogRecord record) {
+            return "";
+        }
+
+        @Override
+        public final String getTail(Handler h) {
+            return name;
+        }
+
+        /**
+         * Equals method.
+         * @param o the other object.
+         * @return true if equal
+         * @since JavaMail 1.4.4
+         */
+        @Override
+        public final boolean equals(Object o) {
+            if (o instanceof TailNameFormatter) {
+                return name.equals(((TailNameFormatter) o).name);
+            }
+            return false;
+        }
+
+        /**
+         * Hash code method.
+         * @return the hash code.
+         * @since JavaMail 1.4.4
+         */
+        @Override
+        public final int hashCode() {
+            return getClass().hashCode() + name.hashCode();
+        }
+
+        @Override
+        public final String toString() {
+            return name;
+        }
+    }
+}
diff --git a/mail/src/main/java/com/sun/mail/util/logging/SeverityComparator.java b/mail/src/main/java/com/sun/mail/util/logging/SeverityComparator.java
new file mode 100644
index 0000000..338a780
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/logging/SeverityComparator.java
@@ -0,0 +1,332 @@
+/*

+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2013, 2018 Jason Mehrens. All rights reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0, which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * This Source Code may also be made available under the following Secondary

+ * Licenses when the conditions for such availability set forth in the

+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,

+ * version 2 with the GNU Classpath Exception, which is available at

+ * https://www.gnu.org/software/classpath/license.html.

+ *

+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

+ */

+package com.sun.mail.util.logging;

+

+import java.io.Serializable;

+import java.util.Comparator;

+import java.util.logging.Level;

+import java.util.logging.LogRecord;

+

+/**

+ * Orders log records by level, thrown, sequence, and time.

+ *

+ * This comparator orders LogRecords by how severely each is attributed to

+ * failures in a program. The primary ordering is determined by the use of the

+ * logging API throughout a program by specifying a level to each log message.

+ * The secondary ordering is determined at runtime by the type of errors and

+ * exceptions generated by the program. The remaining ordering assumes that

+ * older log records are less severe than newer log records.

+ *

+ * <p>

+ * The following LogRecord properties determine severity ordering:

+ * <ol>

+ * <li> The natural comparison of the LogRecord

+ * {@linkplain Level#intValue level}.

+ * <li> The expected recovery order of {@linkplain LogRecord#getThrown() thrown}

+ * property of a LogRecord and its cause chain. This ordering is derived from

+ * the JLS 11.1.1. The Kinds of Exceptions and JLS 11.5 The Exception Hierarchy.

+ * This is performed by {@linkplain #apply(java.lang.Throwable) finding} the

+ * throwable that best describes the entire cause chain. Once a specific

+ * throwable of each chain is identified it is then ranked lowest to highest by

+ * the following rules:

+ *

+ * <ul>

+ * <li>All LogRecords with a {@code Throwable} defined as

+ * "{@link #isNormal(java.lang.Throwable) normal occurrence}".

+ * <li>All LogRecords that do not have a thrown object.

+ * <li>All checked exceptions. This is any class that is assignable to the

+ * {@code java.lang.Throwable} class and is not a

+ * {@code java.lang.RuntimeException} or a {@code java.lang.Error}.

+ * <li>All unchecked exceptions. This is all {@code java.lang.RuntimeException}

+ * objects.

+ * <li>All errors that indicate a serious problem. This is all

+ * {@code java.lang.Error} objects.

+ * </ul>

+ * <li> The natural comparison of the LogRecord

+ * {@linkplain LogRecord#getSequenceNumber() sequence}.

+ * <li> The natural comparison of the LogRecord

+ * {@linkplain LogRecord#getMillis() millis}.

+ * </ol>

+ *

+ * @author Jason Mehrens

+ * @since JavaMail 1.5.2

+ */

+public class SeverityComparator implements Comparator<LogRecord>, Serializable {

+

+    /**

+     * The generated serial version UID.

+     */

+    private static final long serialVersionUID = -2620442245251791965L;

+

+    /**

+     * A single instance that is shared among the logging package.

+     * The field is declared as java.util.Comparator so

+     * WebappClassLoader.clearReferencesStaticFinal() method will ignore this

+     * field.

+     */

+    private static final Comparator<LogRecord> INSTANCE

+            = new SeverityComparator();

+

+    /**

+     * A shared instance of a SeverityComparator. This is package private so the

+     * public API is not polluted with more methods.

+     *

+     * @return a shared instance of a SeverityComparator.

+     */

+    static SeverityComparator getInstance() {

+        return (SeverityComparator) INSTANCE;

+    }

+

+    /**

+     * Identifies a single throwable that best describes the given throwable and

+     * the entire {@linkplain Throwable#getCause() cause} chain. This method can

+     * be overridden to change the behavior of

+     * {@link #compare(java.util.logging.LogRecord, java.util.logging.LogRecord)}.

+     *

+     * @param chain the throwable or null.

+     * @return null if null was given, otherwise the throwable that best

+     * describes the entire chain.

+     * @see #isNormal(java.lang.Throwable)

+     */

+    public Throwable apply(final Throwable chain) {

+        //Matches the j.u.f.UnaryOperator<Throwable> interface.

+        int limit = 0;

+        Throwable root = chain;

+        Throwable high = null;

+        Throwable normal = null;

+        for (Throwable cause = chain; cause != null; cause = cause.getCause()) {

+            root = cause;  //Find the deepest cause.

+

+            //Find the deepest nomral occurrance.

+            if (isNormal(cause)) {

+                normal = cause;

+            }

+

+            //Find the deepest error that happened before a normal occurance.

+            if (normal == null && cause instanceof Error) {

+                high = cause;

+            }

+

+            //Deal with excessive cause chains and cyclic throwables.

+            if (++limit == (1 << 16)) {

+                break; //Give up.

+            }

+        }

+        return high != null ? high : normal != null ? normal : root;

+    }

+

+    /**

+     * {@link #apply(java.lang.Throwable) Reduces} each throwable chain argument

+     * then compare each throwable result.

+     *

+     * @param tc1 the first throwable chain or null.

+     * @param tc2 the second throwable chain or null.

+     * @return a negative integer, zero, or a positive integer as the first

+     * argument is less than, equal to, or greater than the second.

+     * @see #apply(java.lang.Throwable)

+     * @see #compareThrowable(java.lang.Throwable, java.lang.Throwable)

+     */

+    public final int applyThenCompare(Throwable tc1, Throwable tc2) {

+        return tc1 == tc2 ? 0 : compareThrowable(apply(tc1), apply(tc2));

+    }

+

+    /**

+     * Compares two throwable objects or null. This method does not

+     * {@link #apply(java.lang.Throwable) reduce} each argument before

+     * comparing. This is method can be overridden to change the behavior of

+     * {@linkplain #compare(LogRecord, LogRecord)}.

+     *

+     * @param t1 the first throwable or null.

+     * @param t2 the second throwable or null.

+     * @return a negative integer, zero, or a positive integer as the first

+     * argument is less than, equal to, or greater than the second.

+     * @see #isNormal(java.lang.Throwable)

+     */

+    public int compareThrowable(final Throwable t1, final Throwable t2) {

+        if (t1 == t2) { //Reflexive test including null.

+            return 0;

+        } else {

+            //Only one or the other is null at this point.

+            //Force normal occurrence to be lower than null.

+            if (t1 == null) {

+                return isNormal(t2) ? 1 : -1;

+            } else {

+                if (t2 == null) {

+                    return isNormal(t1) ? -1 : 1;

+                }

+            }

+

+            //From this point on neither are null.

+            //Follow the shortcut if we can.

+            if (t1.getClass() == t2.getClass()) {

+                return 0;

+            }

+

+            //Ensure normal occurrence flow control is ordered low.

+            if (isNormal(t1)) {

+                return isNormal(t2) ? 0 : -1;

+            } else {

+                if (isNormal(t2)) {

+                    return 1;

+                }

+            }

+

+            //Rank the two unidenticial throwables using the rules from

+            //JLS 11.1.1. The Kinds of Exceptions and

+            //JLS 11.5 The Exception Hierarchy.

+            if (t1 instanceof Error) {

+                return t2 instanceof Error ? 0 : 1;

+            } else if (t1 instanceof RuntimeException) {

+                return t2 instanceof Error ? -1

+                        : t2 instanceof RuntimeException ? 0 : 1;

+            } else {

+                return t2 instanceof Error

+                        || t2 instanceof RuntimeException ? -1 : 0;

+            }

+        }

+    }

+

+    /**

+     * Compares two log records based on severity.

+     *

+     * @param o1 the first log record.

+     * @param o2 the second log record.

+     * @return a negative integer, zero, or a positive integer as the first

+     * argument is less than, equal to, or greater than the second.

+     * @throws NullPointerException if either argument is null.

+     */

+    @SuppressWarnings("override") //JDK-6954234

+    public int compare(final LogRecord o1, final LogRecord o2) {

+        if (o1 == null || o2 == null) { //Don't allow null.

+            throw new NullPointerException(toString(o1, o2));

+        }

+

+        /**

+         * LogRecords are mutable so a reflexive relationship test is a safety

+         * requirement.

+         */

+        if (o1 == o2) {

+            return 0;

+        }

+

+        int cmp = compare(o1.getLevel(), o2.getLevel());

+        if (cmp == 0) {

+            cmp = applyThenCompare(o1.getThrown(), o2.getThrown());

+            if (cmp == 0) {

+                cmp = compare(o1.getSequenceNumber(), o2.getSequenceNumber());

+                if (cmp == 0) {

+                    cmp = compare(o1.getMillis(), o2.getMillis());

+                }

+            }

+        }

+        return cmp;

+    }

+

+    /**

+     * Determines if the given object is also a comparator and it imposes the

+     * same ordering as this comparator.

+     *

+     * @param o the reference object with which to compare.

+     * @return true if this object equal to the argument; false otherwise.

+     */

+    @Override

+    public boolean equals(final Object o) {

+        return o == null ? false : o.getClass() == getClass();

+    }

+

+    /**

+     * Returns a hash code value for the object.

+     *

+     * @return Returns a hash code value for the object.

+     */

+    @Override

+    public int hashCode() {

+        return 31 * getClass().hashCode();

+    }

+

+    /**

+     * Determines if the given throwable instance is "normal occurrence". This

+     * is any checked or unchecked exception with 'Interrupt' in the class name

+     * or ancestral class name. Any {@code java.lang.ThreadDeath} object or

+     * subclasses.

+     *

+     * This method can be overridden to change the behavior of the

+     * {@linkplain #apply(java.lang.Throwable)} method.

+     *

+     * @param t a throwable or null.

+     * @return true the given throwable is a "normal occurrence".

+     */

+    public boolean isNormal(final Throwable t) {

+        if (t == null) { //This is only needed when called directly.

+            return false;

+        }

+

+        /**

+         * Use the class names to avoid loading more classes.

+         */

+        final Class<?> root = Throwable.class;

+        final Class<?> error = Error.class;

+        for (Class<?> c = t.getClass(); c != root; c = c.getSuperclass()) {

+            if (error.isAssignableFrom(c)) {

+                if (c.getName().equals("java.lang.ThreadDeath")) {

+                    return true;

+                }

+            } else {

+                //Interrupt, Interrupted or Interruption.

+                if (c.getName().contains("Interrupt")) {

+                    return true;

+                }

+            }

+        }

+        return false;

+    }

+

+    /**

+     * Compare two level objects.

+     *

+     * @param a the first level.

+     * @param b the second level.

+     * @return a negative integer, zero, or a positive integer as the first

+     * argument is less than, equal to, or greater than the second.

+     */

+    private int compare(final Level a, final Level b) {

+        return a == b ? 0 : compare(a.intValue(), b.intValue());

+    }

+

+    /**

+     * Outline the message create string.

+     *

+     * @param o1 argument one.

+     * @param o2 argument two.

+     * @return the message string.

+     */

+    private static String toString(final Object o1, final Object o2) {

+        return o1 + ", " + o2;

+    }

+

+    /**

+     * Compare two longs. Can be removed when JDK 1.7 is required.

+     *

+     * @param x the first long.

+     * @param y the second long.

+     * @return a negative integer, zero, or a positive integer as the first

+     * argument is less than, equal to, or greater than the second.

+     */

+    private int compare(final long x, final long y) {

+        return x < y ? -1 : x > y ? 1 : 0;

+    }

+}

diff --git a/mail/src/main/java/com/sun/mail/util/logging/package.html b/mail/src/main/java/com/sun/mail/util/logging/package.html
new file mode 100644
index 0000000..48dabfb
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/logging/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<html>
+  <head>
+<!--
+
+    Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+    Copyright (c) 2009, 2018 Jason Mehrens. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+    <title></title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  </head>
+  <body>
+      Contains JavaMail&trade; extensions for
+      the Java&trade; platform's core logging
+      facilities.  This package contains classes used to export log messages
+      as a formatted email message.  Classes in this package typically use
+      LogManager properties to set default values; see the specific
+      documentation for each concrete class.
+  </body>
+</html>
diff --git a/mail/src/main/java/com/sun/mail/util/package.html b/mail/src/main/java/com/sun/mail/util/package.html
new file mode 100644
index 0000000..db7a8ea
--- /dev/null
+++ b/mail/src/main/java/com/sun/mail/util/package.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>com.sun.mail.util package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+Utility classes for use with the JavaMail API.
+These utility classes are not part of the JavaMail specification.
+While this package contains many classes used by the JavaMail implementation
+and not intended for direct use by applications, the classes documented
+here may be of use to applications.
+</P>
+<P>
+Classes in this package log debugging information using
+{@link java.util.logging} as described in the following table:
+</P>
+<TABLE BORDER SUMMARY="com.sun.mail.util Loggers">
+<TR>
+<TH>Logger Name</TH>
+<TH>Logging Level</TH>
+<TH>Purpose</TH>
+</TR>
+
+<TR>
+<TD>com.sun.mail.util.socket</TD>
+<TD>FINER</TD>
+<TD>Debugging output related to creating sockets</TD>
+</TR>
+</TABLE>
+
+<P>
+<strong>WARNING:</strong> The APIs in this package should be
+considered <strong>EXPERIMENTAL</strong>.  They may be changed in the
+future in ways that are incompatible with applications using the
+current APIs.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/javax/mail/Address.java b/mail/src/main/java/javax/mail/Address.java
new file mode 100644
index 0000000..2b87cf4
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Address.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.io.Serializable;
+
+/**
+ * This abstract class models the addresses in a message.
+ * Subclasses provide specific implementations.  Subclasses
+ * will typically be serializable so that (for example) the
+ * use of Address objects in search terms can be serialized
+ * along with the search terms.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public abstract class Address implements Serializable {
+
+    private static final long serialVersionUID = -5822459626751992278L;
+
+    /**
+     * Return a type string that identifies this address type.
+     *
+     * @return	address type
+     * @see	javax.mail.internet.InternetAddress
+     */
+    public abstract String getType();
+
+    /**
+     * Return a String representation of this address object.
+     *
+     * @return	string representation of this address
+     */
+    @Override
+    public abstract String toString();
+
+    /**
+     * The equality operator.  Subclasses should provide an
+     * implementation of this method that supports value equality
+     * (do the two Address objects represent the same destination?),
+     * not object reference equality.  A subclass must also provide
+     * a corresponding implementation of the <code>hashCode</code>
+     * method that preserves the general contract of
+     * <code>equals</code> and <code>hashCode</code> - objects that
+     * compare as equal must have the same hashCode.
+     *
+     * @param	address	Address object
+     */
+    @Override
+    public abstract boolean equals(Object address);
+}
diff --git a/mail/src/main/java/javax/mail/AuthenticationFailedException.java b/mail/src/main/java/javax/mail/AuthenticationFailedException.java
new file mode 100644
index 0000000..e0de809
--- /dev/null
+++ b/mail/src/main/java/javax/mail/AuthenticationFailedException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This exception is thrown when the connect method on a Store or
+ * Transport object fails due to an authentication failure (e.g.,
+ * bad user name or password).
+ *
+ * @author Bill Shannon
+ */
+
+public class AuthenticationFailedException extends MessagingException {
+
+    private static final long serialVersionUID = 492080754054436511L;
+
+    /**
+     * Constructs an AuthenticationFailedException.
+     */
+    public AuthenticationFailedException() {
+	super();
+    }
+
+    /**
+     * Constructs an AuthenticationFailedException with the specified
+     * detail message.
+     *
+     * @param message	The detailed error message
+     */
+    public AuthenticationFailedException(String message) {
+	super(message);
+    }
+
+    /**
+     * Constructs an AuthenticationFailedException with the specified
+     * detail message and embedded exception.  The exception is chained
+     * to this exception.
+     *
+     * @param message	The detailed error message
+     * @param e		The embedded exception
+     * @since		JavaMail 1.5
+     */
+    public AuthenticationFailedException(String message, Exception e) {
+	super(message, e);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Authenticator.java b/mail/src/main/java/javax/mail/Authenticator.java
new file mode 100644
index 0000000..5fa7838
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Authenticator.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.net.InetAddress;
+
+/**
+ * The class Authenticator represents an object that knows how to obtain
+ * authentication for a network connection.  Usually, it will do this
+ * by prompting the user for information.
+ * <p>
+ * Applications use this class by creating a subclass, and registering
+ * an instance of that subclass with the session when it is created.
+ * When authentication is required, the system will invoke a method
+ * on the subclass (like getPasswordAuthentication).  The subclass's
+ * method can query about the authentication being requested with a
+ * number of inherited methods (getRequestingXXX()), and form an
+ * appropriate message for the user.
+ * <p>
+ * All methods that request authentication have a default implementation
+ * that fails.
+ *
+ * @see java.net.Authenticator
+ * @see javax.mail.Session#getInstance(java.util.Properties,
+ *					javax.mail.Authenticator)
+ * @see javax.mail.Session#getDefaultInstance(java.util.Properties,
+ *					javax.mail.Authenticator)
+ * @see javax.mail.Session#requestPasswordAuthentication
+ * @see javax.mail.PasswordAuthentication
+ *
+ * @author  Bill Foote
+ * @author  Bill Shannon
+ */
+
+// There are no abstract methods, but to be useful the user must
+// subclass.
+public abstract class Authenticator {
+
+    private InetAddress requestingSite;
+    private int requestingPort;
+    private String requestingProtocol;
+    private String requestingPrompt;
+    private String requestingUserName;
+
+    /**
+     * Ask the authenticator for a password.
+     * <p>
+     *
+     * @param addr The InetAddress of the site requesting authorization,
+     *             or null if not known.
+     * @param port the port for the requested connection
+     * @param protocol The protocol that's requesting the connection
+     *          (@see java.net.Authenticator.getProtocol())
+     * @param prompt A prompt string for the user
+     *
+     * @return The username/password, or null if one can't be gotten.
+     */
+    final synchronized PasswordAuthentication requestPasswordAuthentication(
+				InetAddress addr, int port, String protocol,
+				String prompt, String defaultUserName) {
+	requestingSite = addr;
+	requestingPort = port;
+	requestingProtocol = protocol;
+	requestingPrompt = prompt;
+	requestingUserName = defaultUserName;
+	return getPasswordAuthentication();
+    }
+
+    /**
+     * @return the InetAddress of the site requesting authorization, or null
+     *		if it's not available.
+     */
+    protected final InetAddress getRequestingSite() {
+	return requestingSite;
+    }
+
+    /**
+     * @return the port for the requested connection
+     */
+    protected final int getRequestingPort() {
+	return requestingPort;
+    }
+
+    /**
+     * Give the protocol that's requesting the connection.  Often this
+     * will be based on a URLName.
+     *
+     * @return the protcol
+     *
+     * @see javax.mail.URLName#getProtocol
+     */
+    protected final String getRequestingProtocol() {
+	return requestingProtocol;
+    }
+
+    /**
+     * @return the prompt string given by the requestor
+     */
+    protected final String getRequestingPrompt() {
+	return requestingPrompt;
+    }
+
+    /**
+     * @return the default user name given by the requestor
+     */
+    protected final String getDefaultUserName() {
+	return requestingUserName;
+    }
+
+    /**
+     * Called when password authentication is needed.  Subclasses should
+     * override the default implementation, which returns null. <p>
+     *
+     * Note that if this method uses a dialog to prompt the user for this
+     * information, the dialog needs to block until the user supplies the
+     * information.  This method can not simply return after showing the
+     * dialog.
+     * @return The PasswordAuthentication collected from the
+     *		user, or null if none is provided.
+     */
+    protected PasswordAuthentication getPasswordAuthentication() {
+	return null;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/BodyPart.java b/mail/src/main/java/javax/mail/BodyPart.java
new file mode 100644
index 0000000..bb94f75
--- /dev/null
+++ b/mail/src/main/java/javax/mail/BodyPart.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This class models a Part that is contained within a Multipart.
+ * This is an abstract class. Subclasses provide actual implementations.<p>
+ *
+ * BodyPart implements the Part interface. Thus, it contains a set of
+ * attributes and a "content".
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public abstract class BodyPart implements Part {
+
+    /**
+     * The <code>Multipart</code> object containing this <code>BodyPart</code>,
+     * if known.
+     * @since	JavaMail 1.1
+     */
+    protected Multipart parent;
+
+    /**
+     * Return the containing <code>Multipart</code> object,
+     * or <code>null</code> if not known.
+     *
+     * @return	the parent Multipart
+     */
+    public Multipart getParent() {
+	return parent;
+    }
+
+    /**
+     * Set the parent of this <code>BodyPart</code> to be the specified
+     * <code>Multipart</code>.  Normally called by <code>Multipart</code>'s
+     * <code>addBodyPart</code> method.  <code>parent</code> may be
+     * <code>null</code> if the <code>BodyPart</code> is being removed
+     * from its containing <code>Multipart</code>.
+     * @since	JavaMail 1.1
+     */
+    void setParent(Multipart parent) {
+	this.parent = parent;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/EncodingAware.java b/mail/src/main/java/javax/mail/EncodingAware.java
new file mode 100644
index 0000000..27efc70
--- /dev/null
+++ b/mail/src/main/java/javax/mail/EncodingAware.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * A {@link javax.activation.DataSource DataSource} that also implements
+ * <code>EncodingAware</code> may specify the Content-Transfer-Encoding
+ * to use for its data.  Valid Content-Transfer-Encoding values specified
+ * by RFC 2045 are "7bit", "8bit", "quoted-printable", "base64", and "binary".
+ * <p>
+ * For example, a {@link javax.activation.FileDataSource FileDataSource}
+ * could be created that forces all files to be base64 encoded:
+ * <blockquote><pre>
+ *  public class Base64FileDataSource extends FileDataSource
+ *					implements EncodingAware {
+ *	public Base64FileDataSource(File file) {
+ *	    super(file);
+ *	}
+ *
+ *	// implements EncodingAware.getEncoding()
+ *	public String getEncoding() {
+ *	    return "base64";
+ *	}
+ *  }
+ * </pre></blockquote><p>
+ *
+ * @since	JavaMail 1.5
+ * @author	Bill Shannon
+ */
+
+public interface EncodingAware {
+
+    /**
+     * Return the MIME Content-Transfer-Encoding to use for this data,
+     * or null to indicate that an appropriate value should be chosen
+     * by the caller.
+     *
+     * @return		the Content-Transfer-Encoding value, or null
+     */
+    public String getEncoding();
+}
diff --git a/mail/src/main/java/javax/mail/EventQueue.java b/mail/src/main/java/javax/mail/EventQueue.java
new file mode 100644
index 0000000..1264ebd
--- /dev/null
+++ b/mail/src/main/java/javax/mail/EventQueue.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.util.EventListener;
+import java.util.Vector;
+import java.util.Queue;
+import java.util.WeakHashMap;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.Executor;
+import javax.mail.event.MailEvent;
+
+/**
+ * Package private class used by Store & Folder to dispatch events.
+ * This class implements an event queue, and a dispatcher thread that
+ * dequeues and dispatches events from the queue.
+ *
+ * @author	Bill Shannon
+ */
+class EventQueue implements Runnable {
+
+    private volatile BlockingQueue<QueueElement> q;
+    private Executor executor;
+
+    private static WeakHashMap<ClassLoader,EventQueue> appq;
+
+    /**
+     * A special event that causes the queue processing task to terminate.
+     */
+    static class TerminatorEvent extends MailEvent {
+	private static final long serialVersionUID = -2481895000841664111L;
+
+	TerminatorEvent() {
+	    super(new Object());
+	}
+
+	@Override
+	public void dispatch(Object listener) {
+	    // Kill the event dispatching thread.
+	    Thread.currentThread().interrupt();
+	}
+    }
+
+    /**
+     * A "struct" to put on the queue.
+     */
+    static class QueueElement {
+	MailEvent event = null;
+	Vector<? extends EventListener> vector = null;
+
+	QueueElement(MailEvent event, Vector<? extends EventListener> vector) {
+	    this.event = event;
+	    this.vector = vector;
+	}
+    }
+
+    /**
+     * Construct an EventQueue using the specified Executor.
+     * If the Executor is null, threads will be created as needed.
+     */
+    EventQueue(Executor ex) {
+	this.executor = ex;
+    }
+
+    /**
+     * Enqueue an event.
+     */
+    synchronized void enqueue(MailEvent event,
+	    Vector<? extends EventListener> vector) {
+	// if this is the first event, create the queue and start the event task
+	if (q == null) {
+	    q = new LinkedBlockingQueue<>();
+	    if (executor != null) {
+		executor.execute(this);
+	    } else {
+		Thread qThread = new Thread(this, "JavaMail-EventQueue");
+		qThread.setDaemon(true);  // not a user thread
+		qThread.start();
+	    }
+	}
+	q.add(new QueueElement(event, vector));
+    }
+
+    /**
+     * Terminate the task running the queue, but only if there is a queue.
+     */
+    synchronized void terminateQueue() {
+	if (q != null) {
+	    Vector<EventListener> dummyListeners = new Vector<>();
+	    dummyListeners.setSize(1); // need atleast one listener
+	    q.add(new QueueElement(new TerminatorEvent(), dummyListeners));
+	    q = null;
+	}
+    }
+
+    /**
+     * Create (if necessary) an application-scoped event queue.
+     * Application scoping is based on the thread's context class loader.
+     */
+    static synchronized EventQueue getApplicationEventQueue(Executor ex) {
+	ClassLoader cl = Session.getContextClassLoader();
+	if (appq == null)
+	    appq = new WeakHashMap<>();
+	EventQueue q = appq.get(cl);
+	if (q == null) {
+	    q = new EventQueue(ex);
+	    appq.put(cl, q);
+	}
+	return q;
+    }
+
+    /**
+     * Pull events off the queue and dispatch them.
+     */
+    @Override
+    public void run() {
+
+	BlockingQueue<QueueElement> bq = q;
+	if (bq == null)
+	    return;
+	try {
+	    loop:
+	    for (;;) {
+		// block until an item is available
+		QueueElement qe = bq.take();
+		MailEvent e = qe.event;
+		Vector<? extends EventListener> v = qe.vector;
+
+		for (int i = 0; i < v.size(); i++)
+		    try {
+			e.dispatch(v.elementAt(i));
+		    } catch (Throwable t) {
+			if (t instanceof InterruptedException)
+			    break loop;
+			// ignore anything else thrown by the listener
+		    }
+
+		qe = null; e = null; v = null;
+	    }
+	} catch (InterruptedException e) {
+	    // just die
+	}
+    }
+}
diff --git a/mail/src/main/java/javax/mail/FetchProfile.java b/mail/src/main/java/javax/mail/FetchProfile.java
new file mode 100644
index 0000000..1b49454
--- /dev/null
+++ b/mail/src/main/java/javax/mail/FetchProfile.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.util.Vector;
+
+/**
+ * Clients use a FetchProfile to list the Message attributes that 
+ * it wishes to prefetch from the server for a range of messages.<p>
+ *
+ * Messages obtained from a Folder are light-weight objects that 
+ * typically start off as empty references to the actual messages.
+ * Such a Message object is filled in "on-demand" when the appropriate 
+ * get*() methods are invoked on that particular Message. Certain
+ * server-based message access protocols (Ex: IMAP) allow batch
+ * fetching of message attributes for a range of messages in a single
+ * request. Clients that want to use message attributes for a range of
+ * Messages (Example: to display the top-level headers in a headerlist)
+ * might want to use the optimization provided by such servers. The
+ * <code>FetchProfile</code> allows the client to indicate this desire
+ * to the server. <p>
+ *
+ * Note that implementations are not obligated to support
+ * FetchProfiles, since there might be cases where the backend service 
+ * does not allow easy, efficient fetching of such profiles. <p>
+ *
+ * Sample code that illustrates the use of a FetchProfile is given
+ * below:
+ * <blockquote>
+ * <pre>
+ *
+ *  Message[] msgs = folder.getMessages();
+ *
+ *  FetchProfile fp = new FetchProfile();
+ *  fp.add(FetchProfile.Item.ENVELOPE);
+ *  fp.add("X-mailer");
+ *  folder.fetch(msgs, fp);
+ *
+ * </pre></blockquote><p>
+ *
+ * @see javax.mail.Folder#fetch
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class FetchProfile {
+
+    private Vector<Item> specials; // specials
+    private Vector<String> headers; // vector of header names
+
+    /**
+     * This inner class is the base class of all items that
+     * can be requested in a FetchProfile. The items currently
+     * defined here are <code>ENVELOPE</code>, <code>CONTENT_INFO</code>
+     * and <code>FLAGS</code>. The <code>UIDFolder</code> interface 
+     * defines the <code>UID</code> Item as well. <p>
+     *
+     * Note that this class only has a protected constructor, therby
+     * restricting new Item types to either this class or subclasses.
+     * This effectively implements a enumeration of allowed Item types.
+     *
+     * @see UIDFolder
+     */
+
+    public static class Item {
+	/**
+	 * This is the Envelope item. <p>
+	 *
+	 * The Envelope is an aggregration of the common attributes
+	 * of a Message. Implementations should include the following
+	 * attributes: From, To, Cc, Bcc, ReplyTo, Subject and Date.
+	 * More items may be included as well. <p>
+	 *
+	 * For implementations of the IMAP4 protocol (RFC 2060), the 
+	 * Envelope should include the ENVELOPE data item. More items
+	 * may be included too.
+	 */
+	public static final Item ENVELOPE = new Item("ENVELOPE");
+
+	/**
+	 * This item is for fetching information about the 
+	 * content of the message. <p>
+	 *
+	 * This includes all the attributes that describe the content
+	 * of the message. Implementations should include the following
+	 * attributes: ContentType, ContentDisposition, 
+	 * ContentDescription, Size and LineCount. Other items may be
+	 * included as well.
+	 */
+	public static final Item CONTENT_INFO = new Item("CONTENT_INFO");
+
+	/**
+	 * SIZE is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the sizes of the messages in the specified 
+	 * range should be prefetched. <p>
+	 *
+	 * @since	JavaMail 1.5
+	 */
+	public static final Item SIZE = new Item("SIZE");
+
+	/**
+	 * This is the Flags item.
+	 */
+	public static final Item FLAGS = new Item("FLAGS");
+
+	private String name;
+
+	/**
+	 * Constructor for an item.  The name is used only for debugging.
+	 *
+	 * @param	name	the item name
+	 */
+	protected Item(String name) {
+	    this.name = name;
+	}
+
+	/**
+	 * Include the name in the toString return value for debugging.
+	 */
+	@Override
+	public String toString() {
+	    return getClass().getName() + "[" + name + "]";
+	}
+    }
+
+    /**
+     * Create an empty FetchProfile.
+     */
+    public FetchProfile() { 
+	specials = null;
+	headers = null;
+    }
+    
+    /**
+     * Add the given special item as one of the attributes to
+     * be prefetched.
+     *
+     * @param	item		the special item to be fetched
+     * @see	FetchProfile.Item#ENVELOPE
+     * @see	FetchProfile.Item#CONTENT_INFO
+     * @see	FetchProfile.Item#FLAGS
+     */
+    public void add(Item item) { 
+	if (specials == null)
+	    specials = new Vector<>();
+	specials.addElement(item);
+    }
+
+    /**
+     * Add the specified header-field to the list of attributes
+     * to be prefetched.
+     *
+     * @param	headerName	header to be prefetched
+     */
+    public void add(String headerName) { 
+   	if (headers == null)
+	    headers = new Vector<>();
+	headers.addElement(headerName);
+    }
+
+    /**
+     * Returns true if the fetch profile contains the given special item.
+     *
+     * @param	item	the Item to test
+     * @return true if the fetch profile contains the given special item
+     */
+    public boolean contains(Item item) { 
+   	return specials != null && specials.contains(item);
+    }
+
+    /**
+     * Returns true if the fetch profile contains the given header name.
+     *
+     * @param	headerName	the header to test
+     * @return	true if the fetch profile contains the given header name
+     */
+    public boolean contains(String headerName) { 
+   	return headers != null && headers.contains(headerName);
+    }
+
+    /**
+     * Get the items set in this profile. 
+     *
+     * @return		items set in this profile
+     */
+    public Item[] getItems() { 
+	if (specials == null)
+	    return new Item[0];
+
+   	Item[] s = new Item[specials.size()];
+	specials.copyInto(s);
+	return s;
+    }
+
+    /**
+     * Get the names of the header-fields set in this profile. 
+     *
+     * @return		headers set in this profile
+     */
+    public String[] getHeaderNames() { 
+	if (headers == null)
+	    return new String[0];
+
+   	String[] s = new String[headers.size()];
+	headers.copyInto(s);
+	return s;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Flags.java b/mail/src/main/java/javax/mail/Flags.java
new file mode 100644
index 0000000..f97cad3
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Flags.java
@@ -0,0 +1,667 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * The Flags class represents the set of flags on a Message.  Flags
+ * are composed of predefined system flags, and user defined flags. <p>
+ *
+ * A System flag is represented by the <code>Flags.Flag</code> 
+ * inner class. A User defined flag is represented as a String.
+ * User flags are case-independent. <p>
+ *
+ * A set of standard system flags are predefined.  Most folder
+ * implementations are expected to support these flags.  Some
+ * implementations may also support arbitrary user-defined flags.  The
+ * <code>getPermanentFlags</code> method on a Folder returns a Flags
+ * object that holds all the flags that are supported by that folder
+ * implementation. <p>
+ *
+ * A Flags object is serializable so that (for example) the
+ * use of Flags objects in search terms can be serialized
+ * along with the search terms. <p>
+ *
+ * <strong>Warning:</strong>
+ * Serialized objects of this class may not be compatible with future
+ * JavaMail API releases.  The current serialization support is
+ * appropriate for short term storage. <p>
+ *
+ * The below code sample illustrates how to set, examine, and get the 
+ * flags for a message.
+ * <pre>
+ *
+ * Message m = folder.getMessage(1);
+ * m.setFlag(Flags.Flag.DELETED, true); // set the DELETED flag
+ *
+ * // Check if DELETED flag is set on this message
+ * if (m.isSet(Flags.Flag.DELETED))
+ *	System.out.println("DELETED message");
+ *
+ * // Examine ALL system flags for this message
+ * Flags flags = m.getFlags();
+ * Flags.Flag[] sf = flags.getSystemFlags();
+ * for (int i = 0; i &lt; sf.length; i++) {
+ *	if (sf[i] == Flags.Flag.DELETED)
+ *            System.out.println("DELETED message");
+ *	else if (sf[i] == Flags.Flag.SEEN)
+ *            System.out.println("SEEN message");
+ *      ......
+ *      ......
+ * }
+ * </pre>
+ * <p>
+ *
+ * @see    Folder#getPermanentFlags
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class Flags implements Cloneable, Serializable {
+
+    private int system_flags = 0;
+    // used as a case-independent Set that preserves the original case,
+    // the key is the lowercase flag name and the value is the original
+    private Hashtable<String, String> user_flags = null;
+
+    private final static int ANSWERED_BIT 	= 0x01;
+    private final static int DELETED_BIT 	= 0x02;
+    private final static int DRAFT_BIT 		= 0x04;
+    private final static int FLAGGED_BIT 	= 0x08;
+    private final static int RECENT_BIT		= 0x10;
+    private final static int SEEN_BIT		= 0x20;
+    private final static int USER_BIT		= 0x80000000;
+
+    private static final long serialVersionUID = 6243590407214169028L;
+
+    /**
+     * This inner class represents an individual system flag. A set
+     * of standard system flag objects are predefined here.
+     */
+    public static final class Flag {
+	/**
+	 * This message has been answered. This flag is set by clients 
+	 * to indicate that this message has been answered to.
+	 */
+	public static final Flag ANSWERED = new Flag(ANSWERED_BIT);
+
+	/**
+	 * This message is marked deleted. Clients set this flag to
+	 * mark a message as deleted. The expunge operation on a folder
+	 * removes all messages in that folder that are marked for deletion.
+	 */
+	public static final Flag DELETED = new Flag(DELETED_BIT);
+
+	/**
+	 * This message is a draft. This flag is set by clients
+	 * to indicate that the message is a draft message.
+	 */
+	public static final Flag DRAFT = new Flag(DRAFT_BIT);
+
+	/**
+	 * This message is flagged. No semantic is defined for this flag.
+	 * Clients alter this flag.
+	 */
+	public static final Flag FLAGGED = new Flag(FLAGGED_BIT);
+
+	/**
+	 * This message is recent. Folder implementations set this flag
+	 * to indicate that this message is new to this folder, that is,
+	 * it has arrived since the last time this folder was opened. <p>
+	 *
+	 * Clients cannot alter this flag.
+	 */
+	public static final Flag RECENT = new Flag(RECENT_BIT);
+
+	/**
+	 * This message is seen. This flag is implicitly set by the 
+	 * implementation when this Message's content is returned 
+	 * to the client in some form. The <code>getInputStream</code>
+	 * and <code>getContent</code> methods on Message cause this
+	 * flag to be set. <p>
+	 *
+	 * Clients can alter this flag.
+	 */
+	public static final Flag SEEN = new Flag(SEEN_BIT);
+
+	/**
+	 * A special flag that indicates that this folder supports
+	 * user defined flags. <p>
+	 *
+	 * The implementation sets this flag. Clients cannot alter 
+	 * this flag but can use it to determine if a folder supports
+	 * user defined flags by using
+	 * <code>folder.getPermanentFlags().contains(Flags.Flag.USER)</code>.
+	 */
+	public static final Flag USER = new Flag(USER_BIT);
+
+	// flags are stored as bits for efficiency
+	private int bit;
+	private Flag(int bit) {
+	    this.bit = bit;
+	}
+    }
+
+
+    /**
+     * Construct an empty Flags object.
+     */
+    public Flags() { }
+
+    /**
+     * Construct a Flags object initialized with the given flags.
+     *
+     * @param flags	the flags for initialization
+     */
+    @SuppressWarnings("unchecked")
+    public Flags(Flags flags) {
+	this.system_flags = flags.system_flags;
+	if (flags.user_flags != null)
+	    this.user_flags = (Hashtable)flags.user_flags.clone();
+    }
+
+    /**
+     * Construct a Flags object initialized with the given system flag.
+     *
+     * @param flag	the flag for initialization
+     */
+    public Flags(Flag flag) {
+	this.system_flags |= flag.bit;
+    }
+
+    /**
+     * Construct a Flags object initialized with the given user flag.
+     *
+     * @param flag	the flag for initialization
+     */
+    public Flags(String flag) {
+	user_flags = new Hashtable<>(1);
+	user_flags.put(flag.toLowerCase(Locale.ENGLISH), flag);
+    }
+
+    /**
+     * Add the specified system flag to this Flags object.
+     *
+     * @param flag	the flag to add
+     */
+    public void add(Flag flag) {
+	system_flags |= flag.bit;
+    }
+
+    /**
+     * Add the specified user flag to this Flags object.
+     *
+     * @param flag	the flag to add
+     */
+    public void add(String flag) {
+	if (user_flags == null)
+	    user_flags = new Hashtable<>(1);
+	user_flags.put(flag.toLowerCase(Locale.ENGLISH), flag);
+    }
+
+    /**
+     * Add all the flags in the given Flags object to this
+     * Flags object.
+     *
+     * @param f	Flags object
+     */
+    public void add(Flags f) {
+	system_flags |= f.system_flags; // add system flags
+
+	if (f.user_flags != null) { // add user-defined flags
+	    if (user_flags == null)
+		user_flags = new Hashtable<>(1);
+
+	    Enumeration<String> e = f.user_flags.keys();
+
+	    while (e.hasMoreElements()) {
+		String s = e.nextElement();
+		user_flags.put(s, f.user_flags.get(s));
+	    }
+	}
+    }
+
+    /**
+     * Remove the specified system flag from this Flags object.
+     *
+     * @param	flag 	the flag to be removed
+     */
+    public void remove(Flag flag) {
+	system_flags &= ~flag.bit;
+    }
+
+    /**
+     * Remove the specified user flag from this Flags object.
+     *
+     * @param	flag 	the flag to be removed
+     */
+    public void remove(String flag) {
+	if (user_flags != null)
+	    user_flags.remove(flag.toLowerCase(Locale.ENGLISH));
+    }
+
+    /**
+     * Remove all flags in the given Flags object from this 
+     * Flags object.
+     *
+     * @param	f 	the flag to be removed
+     */
+    public void remove(Flags f) {
+	system_flags &= ~f.system_flags; // remove system flags
+
+	if (f.user_flags != null) {
+	    if (user_flags == null)
+		return;
+
+	    Enumeration<String> e = f.user_flags.keys();
+	    while (e.hasMoreElements())
+		user_flags.remove(e.nextElement());
+	}
+    }
+
+    /**
+     * Remove any flags <strong>not</strong> in the given Flags object.
+     * Useful for clearing flags not supported by a server.  If the
+     * given Flags object includes the Flags.Flag.USER flag, all user
+     * flags in this Flags object are retained.
+     *
+     * @param	f	the flags to keep
+     * @return		true if this Flags object changed
+     * @since		JavaMail 1.6
+     */
+    public boolean retainAll(Flags f) {
+	boolean changed = false;
+	int sf = system_flags & f.system_flags;
+	if (system_flags != sf) {
+	    system_flags = sf;
+	    changed = true;
+	}
+
+	// if we have user flags, and the USER flag is not set in "f",
+	// determine which user flags to clear
+	if (user_flags != null && (f.system_flags & USER_BIT) == 0) {
+	    if (f.user_flags != null) {
+		Enumeration<String> e = user_flags.keys();
+		while (e.hasMoreElements()) {
+		    String key = e.nextElement();
+		    if (!f.user_flags.containsKey(key)) {
+			user_flags.remove(key);
+			changed = true;
+		    }
+		}
+	    } else {
+		// if anything in user_flags, throw them away
+		changed = user_flags.size() > 0;
+		user_flags = null;
+	    }
+	}
+	return changed;
+    }
+
+    /**
+     * Check whether the specified system flag is present in this Flags object.
+     *
+     * @param	flag	the flag to test
+     * @return 		true of the given flag is present, otherwise false.
+     */
+    public boolean contains(Flag flag) {
+	return (system_flags & flag.bit) != 0;
+    }
+
+    /**
+     * Check whether the specified user flag is present in this Flags object.
+     *
+     * @param	flag	the flag to test
+     * @return 		true of the given flag is present, otherwise false.
+     */
+    public boolean contains(String flag) {
+	if (user_flags == null) 
+	    return false;
+	else
+	    return user_flags.containsKey(flag.toLowerCase(Locale.ENGLISH));
+    }
+
+    /**
+     * Check whether all the flags in the specified Flags object are
+     * present in this Flags object.
+     *
+     * @param	f	the flags to test
+     * @return	true if all flags in the given Flags object are present, 
+     *		otherwise false.
+     */
+    public boolean contains(Flags f) {
+	// Check system flags
+	if ((f.system_flags & system_flags) != f.system_flags)
+	    return false;
+
+	// Check user flags
+	if (f.user_flags != null) {
+	    if (user_flags == null)
+		return false;
+	    Enumeration<String> e = f.user_flags.keys();
+
+	    while (e.hasMoreElements()) {
+		if (!user_flags.containsKey(e.nextElement()))
+		    return false;
+	    }
+	}
+
+	// If we've made it till here, return true
+	return true;
+    }
+
+    /**
+     * Check whether the two Flags objects are equal.
+     *
+     * @return	true if they're equal
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof Flags))
+	    return false;
+
+	Flags f = (Flags)obj;
+
+	// Check system flags
+	if (f.system_flags != this.system_flags)
+	    return false;
+
+	// Check user flags
+	int size = this.user_flags == null ? 0 : this.user_flags.size();
+	int fsize = f.user_flags == null ? 0 : f.user_flags.size();
+	if (size == 0 && fsize == 0)
+	    return true;
+	if (f.user_flags != null && this.user_flags != null && fsize == size)
+	    return user_flags.keySet().equals(f.user_flags.keySet());
+
+	return false;
+    }
+
+    /**
+     * Compute a hash code for this Flags object.
+     *
+     * @return	the hash code
+     */
+    @Override
+    public int hashCode() {
+	int hash = system_flags;
+	if (user_flags != null) {
+	    Enumeration<String> e = user_flags.keys();
+	    while (e.hasMoreElements())
+		hash += e.nextElement().hashCode();
+	}
+	return hash;
+    }
+
+    /**
+     * Return all the system flags in this Flags object.  Returns
+     * an array of size zero if no flags are set.
+     *
+     * @return	array of Flags.Flag objects representing system flags
+     */
+    public Flag[] getSystemFlags() {
+	Vector<Flag> v = new Vector<>();
+	if ((system_flags & ANSWERED_BIT) != 0)
+	    v.addElement(Flag.ANSWERED);
+	if ((system_flags & DELETED_BIT) != 0)
+	    v.addElement(Flag.DELETED);
+	if ((system_flags & DRAFT_BIT) != 0)
+	    v.addElement(Flag.DRAFT);
+	if ((system_flags & FLAGGED_BIT) != 0)
+	    v.addElement(Flag.FLAGGED);
+	if ((system_flags & RECENT_BIT) != 0)
+	    v.addElement(Flag.RECENT);
+	if ((system_flags & SEEN_BIT) != 0)
+	    v.addElement(Flag.SEEN);
+	if ((system_flags & USER_BIT) != 0)
+	    v.addElement(Flag.USER);
+
+	Flag[] f = new Flag[v.size()];
+	v.copyInto(f);
+	return f;
+    }
+
+    /**
+     * Return all the user flags in this Flags object.  Returns
+     * an array of size zero if no flags are set.
+     *
+     * @return	array of Strings, each String represents a flag.
+     */
+    public String[] getUserFlags() {
+	Vector<String> v = new Vector<>();
+	if (user_flags != null) {
+	    Enumeration<String> e = user_flags.elements();
+
+	    while (e.hasMoreElements())
+		v.addElement(e.nextElement());
+	}
+
+	String[] f = new String[v.size()];
+	v.copyInto(f);
+	return f;
+    }
+
+    /**
+     * Clear all of the system flags.
+     *
+     * @since	JavaMail 1.6
+     */
+    public void clearSystemFlags() {
+	system_flags = 0;
+    }
+
+    /**
+     * Clear all of the user flags.
+     *
+     * @since	JavaMail 1.6
+     */
+    public void clearUserFlags() {
+	user_flags = null;
+    }
+
+    /**
+     * Returns a clone of this Flags object.
+     */
+    @SuppressWarnings("unchecked")
+    @Override
+    public Object clone() {
+	Flags f = null;
+	try {
+	    f = (Flags)super.clone();
+	} catch (CloneNotSupportedException cex) {
+	    // ignore, can't happen
+	}
+	if (this.user_flags != null)
+	    f.user_flags = (Hashtable)this.user_flags.clone();
+	return f;
+    }
+
+    /**
+     * Return a string representation of this Flags object.
+     * Note that the exact format of the string is subject to change.
+     */
+    public String toString() {
+	StringBuilder sb = new StringBuilder();
+
+	if ((system_flags & ANSWERED_BIT) != 0)
+	    sb.append("\\Answered ");
+	if ((system_flags & DELETED_BIT) != 0)
+	    sb.append("\\Deleted ");
+	if ((system_flags & DRAFT_BIT) != 0)
+	    sb.append("\\Draft ");
+	if ((system_flags & FLAGGED_BIT) != 0)
+	    sb.append("\\Flagged ");
+	if ((system_flags & RECENT_BIT) != 0)
+	    sb.append("\\Recent ");
+	if ((system_flags & SEEN_BIT) != 0)
+	    sb.append("\\Seen ");
+	if ((system_flags & USER_BIT) != 0)
+	    sb.append("\\* ");
+
+	boolean first = true;
+	if (user_flags != null) {
+	    Enumeration<String> e = user_flags.elements();
+
+	    while (e.hasMoreElements()) {
+		if (first)
+		    first = false;
+		else
+		    sb.append(' ');
+		sb.append(e.nextElement());
+	    }
+	}
+
+	if (first && sb.length() > 0)
+	    sb.setLength(sb.length() - 1);	// smash trailing space
+
+	return sb.toString();
+    }
+
+    /*****
+    public static void main(String argv[]) throws Exception {
+	// a new flags object
+	Flags f1 = new Flags();
+	f1.add(Flags.Flag.DELETED);
+	f1.add(Flags.Flag.SEEN);
+	f1.add(Flags.Flag.RECENT);
+	f1.add(Flags.Flag.ANSWERED);
+
+	// check copy constructor with only system flags
+	Flags fc = new Flags(f1);
+	if (f1.equals(fc) && fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// check clone with only system flags
+	fc = (Flags)f1.clone();
+	if (f1.equals(fc) && fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// add a user flag and make sure it still works right
+	f1.add("MyFlag");
+
+	// shouldn't be equal here
+	if (!f1.equals(fc) && !fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// check clone
+	fc = (Flags)f1.clone();
+	if (f1.equals(fc) && fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// make sure user flag hash tables are separate
+	fc.add("AnotherFlag");
+	if (!f1.equals(fc) && !fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// check copy constructor
+	fc = new Flags(f1);
+	if (f1.equals(fc) && fc.equals(f1))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	// another new flags object
+	Flags f2 = new Flags(Flags.Flag.ANSWERED);
+	f2.add("MyFlag");
+
+	if (f1.contains(Flags.Flag.DELETED))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+		
+	if (f1.contains(Flags.Flag.SEEN))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	if (f1.contains(Flags.Flag.RECENT))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	if (f1.contains("MyFlag"))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	if (f2.contains(Flags.Flag.ANSWERED))
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+
+	System.out.println("----------------");
+
+	String[] s = f1.getUserFlags();
+	for (int i = 0; i < s.length; i++)
+	    System.out.println(s[i]);
+	System.out.println("----------------");
+	s = f2.getUserFlags();
+	for (int i = 0; i < s.length; i++)
+	    System.out.println(s[i]);
+
+	System.out.println("----------------");
+
+	if (f1.contains(f2)) // this should be true
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	if (!f2.contains(f1)) // this should be false
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+
+	Flags f3 = new Flags();
+	f3.add(Flags.Flag.DELETED);
+	f3.add(Flags.Flag.SEEN);
+	f3.add(Flags.Flag.RECENT);
+	f3.add(Flags.Flag.ANSWERED);
+	f3.add("ANOTHERFLAG");
+	f3.add("MYFLAG");
+
+	f1.add("AnotherFlag");
+
+	if (f1.equals(f3))
+	    System.out.println("equals success");
+	else
+	    System.out.println("fail");
+	if (f3.equals(f1))
+	    System.out.println("equals success");
+	else
+	    System.out.println("fail");
+	System.out.println("f1 hash code " + f1.hashCode());
+	System.out.println("f3 hash code " + f3.hashCode());
+	if (f1.hashCode() == f3.hashCode())
+	    System.out.println("success");
+	else
+	    System.out.println("fail");
+    }
+    ****/
+}
diff --git a/mail/src/main/java/javax/mail/Folder.java b/mail/src/main/java/javax/mail/Folder.java
new file mode 100644
index 0000000..0ddce21
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Folder.java
@@ -0,0 +1,1657 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.lang.*;
+import java.util.ArrayList;
+import java.util.EventListener;
+import java.util.List;
+import java.util.Vector;
+import java.util.concurrent.Executor;
+import javax.mail.search.SearchTerm;
+import javax.mail.event.*;
+
+/**
+ * Folder is an abstract class that represents a folder for mail
+ * messages. Subclasses implement protocol specific Folders.<p>
+ *
+ * Folders can contain Messages, other Folders or both, thus providing
+ * a tree-like hierarchy rooted at the Store's default folder. (Note 
+ * that some Folder implementations may not allow both Messages and 
+ * other Folders in the same Folder).<p>
+ *
+ * The interpretation of folder names is implementation dependent.
+ * The different levels of hierarchy in a folder's full name
+ * are separated from each other by the hierarchy delimiter 
+ * character.<p>
+ *
+ * The case-insensitive full folder name (that is, the full name
+ * relative to the default folder for a Store) <strong>INBOX</strong>
+ * is reserved to mean the "primary folder for this user on this
+ * server".  Not all Stores will provide an INBOX folder, and not
+ * all users will have an INBOX folder at all times.  The name
+ * <strong>INBOX</strong> is reserved to refer to this folder,
+ * when it exists, in Stores that provide it. <p>
+ *
+ * A Folder object obtained from a Store need not actually exist
+ * in the backend store. The <code>exists</code> method tests whether
+ * the folder exists or not. The <code>create</code> method
+ * creates a Folder. <p>
+ *
+ * A Folder is initially in the closed state. Certain methods are valid
+ * in this state; the documentation for those methods note this.  A
+ * Folder is opened by calling its 'open' method. All Folder methods,
+ * except <code>open</code>, <code>delete</code> and 
+ * <code>renameTo</code>, are valid in this state. <p>
+ *
+ * The only way to get a Folder is by invoking the 
+ * <code>getFolder</code> method on Store, Folder, or Session, or by invoking 
+ * the <code>list</code> or <code>listSubscribed</code> methods 
+ * on Folder. Folder objects returned by the above methods are not 
+ * cached by the Store. Thus, invoking the <code>getFolder</code> method
+ * with the same folder name multiple times will return distinct Folder 
+ * objects.  Likewise for the <code>list</code> and <code>listSubscribed</code>
+ * methods. <p>
+ *
+ * The Message objects within the Folder are cached by the Folder.
+ * Thus, invoking <code>getMessage(msgno)</code> on the same message number
+ * multiple times will return the same Message object, until an 
+ * expunge is done on this Folder. <p>
+ *
+ * Message objects from a Folder are only valid while a Folder is open
+ * and should not be accessed after the Folder is closed, even if the
+ * Folder is subsequently reopened.  Instead, new Message objects must
+ * be fetched from the Folder after the Folder is reopened. <p>
+ *
+ * Note that a Message's message number can change within a
+ * session if the containing Folder is expunged using the expunge
+ * method.  Clients that use message numbers as references to messages
+ * should be aware of this and should be prepared to deal with this
+ * situation (probably by flushing out existing message number references
+ * and reloading them). Because of this complexity, it is better for
+ * clients to use Message objects as references to messages, rather than
+ * message numbers. Expunged Message objects still have to be
+ * pruned, but other Message objects in that folder are not affected by the 
+ * expunge.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public abstract class Folder implements AutoCloseable {
+
+    /**
+     * The parent store.
+     */
+    protected Store store;
+
+    /**
+     * The open mode of this folder.  The open mode is
+     * <code>Folder.READ_ONLY</code>, <code>Folder.READ_WRITE</code>,
+     * or -1 if not known.
+     * @since	JavaMail 1.1
+     */
+    protected int mode = -1;
+
+    /*
+     * The queue of events to be delivered.
+     */
+    private final EventQueue q;
+
+    /**
+     * Constructor that takes a Store object.
+     *
+     * @param store the Store that holds this folder
+     */
+    protected Folder(Store store) {
+	this.store = store;
+
+	// create or choose the appropriate event queue
+	Session session = store.getSession();
+	String scope =
+	    session.getProperties().getProperty("mail.event.scope", "folder");
+	Executor executor =
+		(Executor)session.getProperties().get("mail.event.executor");
+	if (scope.equalsIgnoreCase("application"))
+	    q = EventQueue.getApplicationEventQueue(executor);
+	else if (scope.equalsIgnoreCase("session"))
+	    q = session.getEventQueue();
+	else if (scope.equalsIgnoreCase("store"))
+	    q = store.getEventQueue();
+	else // if (scope.equalsIgnoreCase("folder"))
+	    q = new EventQueue(executor);
+    }
+
+    /**
+     * Returns the name of this Folder. <p>
+     *
+     * This method can be invoked on a closed Folder.
+     *
+     * @return		name of the Folder
+     */
+    public abstract String getName();
+
+    /**
+     * Returns the full name of this Folder. If the folder resides under
+     * the root hierarchy of this Store, the returned name is relative
+     * to the root. Otherwise an absolute name, starting with the 
+     * hierarchy delimiter, is returned. <p>
+     *
+     * This method can be invoked on a closed Folder.
+     *
+     * @return		full name of the Folder
+     */
+    public abstract String getFullName();
+
+    /**
+     * Return a URLName representing this folder.  The returned URLName
+     * does <em>not</em> include the password used to access the store.
+     *
+     * @return	the URLName representing this folder
+     * @exception	MessagingException for failures
+     * @see	URLName
+     * @since	JavaMail 1.1
+     */
+    public URLName getURLName() throws MessagingException {
+	URLName storeURL = getStore().getURLName();
+	String fullname = getFullName();
+	StringBuilder encodedName = new StringBuilder();
+
+	if (fullname != null) {
+	    /*
+	    // We need to encode each of the folder's names.
+	    char separator = getSeparator();
+	    StringTokenizer tok = new StringTokenizer(
+		fullname, new Character(separator).toString(), true);
+
+	    while (tok.hasMoreTokens()) {
+		String s = tok.nextToken();
+		if (s.charAt(0) == separator)
+		    encodedName.append(separator);
+		else
+		    // XXX - should encode, but since there's no decoder...
+		    //encodedName.append(java.net.URLEncoder.encode(s));
+		    encodedName.append(s);
+	    }
+	    */
+	    // append the whole thing, until we can encode
+	    encodedName.append(fullname);
+	}
+
+	/*
+	 * Sure would be convenient if URLName had a
+	 * constructor that took a base URLName.
+	 */
+	return new URLName(storeURL.getProtocol(), storeURL.getHost(),
+			    storeURL.getPort(), encodedName.toString(),
+			    storeURL.getUsername(),
+			    null /* no password */);
+    }
+
+    /**
+     * Returns the Store that owns this Folder object.
+     * This method can be invoked on a closed Folder.
+     *
+     * @return 		the Store
+     */
+    public Store getStore() {
+	return store;
+    }
+
+    /**
+     * Returns the parent folder of this folder.
+     * This method can be invoked on a closed Folder. If this folder
+     * is the top of a folder hierarchy, this method returns null. <p>
+     *
+     * Note that since Folder objects are not cached, invoking this method
+     * returns a new distinct Folder object.
+     *
+     * @return		Parent folder
+     * @exception	MessagingException for failures
+     */
+    public abstract Folder getParent() throws MessagingException;
+
+    /**
+     * Tests if this folder physically exists on the Store.
+     * This method can be invoked on a closed Folder.
+     *
+     * @return true if the folder exists, otherwise false
+     * @see    #create
+     * @exception	MessagingException typically if the connection 
+     *			to the server is lost.
+     */
+    public abstract boolean exists() throws MessagingException;
+
+    /**
+     * Returns a list of Folders belonging to this Folder's namespace
+     * that match the specified pattern. Patterns may contain the wildcard
+     * characters <code>"%"</code>, which matches any character except hierarchy
+     * delimiters, and <code>"*"</code>, which matches any character. <p>
+     *
+     * As an example, given the folder hierarchy: <pre>
+     *    Personal/
+     *       Finance/
+     *          Stocks
+     *          Bonus
+     *          StockOptions
+     *       Jokes
+     * </pre>
+     * <code>list("*")</code> on "Personal" will return the whole 
+     * hierarchy. <br>
+     * <code>list("%")</code> on "Personal" will return "Finance" and 
+     * "Jokes". <br>
+     * <code>list("Jokes")</code> on "Personal" will return "Jokes".<br>
+     * <code>list("Stock*")</code> on "Finance" will return "Stocks"
+     * and "StockOptions". <p>
+     *
+     * Folder objects are not cached by the Store, so invoking this
+     * method on the same pattern multiple times will return that many
+     * distinct Folder objects. <p>
+     *
+     * This method can be invoked on a closed Folder.
+     *
+     * @param pattern	the match pattern
+     * @return		array of matching Folder objects. An empty
+     *			array is returned if no matching Folders exist.
+     * @see 		#listSubscribed
+     * @exception 	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception 	MessagingException for other failures
+     */
+    public abstract Folder[] list(String pattern) throws MessagingException;
+
+    /**
+     * Returns a list of subscribed Folders belonging to this Folder's
+     * namespace that match the specified pattern. If the folder does
+     * not support subscription, this method should resolve to
+     * <code>list</code>.
+     * (The default implementation provided here, does just this).
+     * The pattern can contain wildcards as for <code>list</code>. <p>
+     *
+     * Note that, at a given level of the folder hierarchy, a particular
+     * folder may not be subscribed, but folders underneath that folder
+     * in the folder hierarchy may be subscribed.  In order to allow
+     * walking the folder hierarchy, such unsubscribed folders may be
+     * returned, indicating that a folder lower in the hierarchy is
+     * subscribed.  The <code>isSubscribed</code> method on a folder will
+     * tell whether any particular folder is actually subscribed. <p>
+     *
+     * Folder objects are not cached by the Store, so invoking this
+     * method on the same pattern multiple times will return that many
+     * distinct Folder objects. <p>
+     *
+     * This method can be invoked on a closed Folder.
+     *
+     * @param pattern	the match pattern
+     * @return		array of matching subscribed Folder objects. An
+     *			empty array is returned if no matching
+     *			subscribed folders exist.
+     * @see 		#list
+     * @exception 	FolderNotFoundException if this folder does
+     *			not exist.
+     * @exception 	MessagingException for other failures
+     */
+    public Folder[] listSubscribed(String pattern) throws MessagingException {
+	return list(pattern);
+    }
+
+    /**
+     * Convenience method that returns the list of folders under this
+     * Folder. This method just calls the <code>list(String pattern)</code>
+     * method with <code>"%"</code> as the match pattern. This method can
+     * be invoked on a closed Folder.
+     *
+     * @return		array of Folder objects under this Folder. An
+     *			empty array is returned if no subfolders exist.
+     * @see		#list
+     * @exception 	FolderNotFoundException if this folder does
+     *			not exist.
+     * @exception 	MessagingException for other failures
+     */
+
+    public Folder[] list() throws MessagingException {
+	return list("%");
+    }
+
+    /**
+     * Convenience method that returns the list of subscribed folders 
+     * under this Folder. This method just calls the
+     * <code>listSubscribed(String pattern)</code> method with <code>"%"</code>
+     * as the match pattern. This method can be invoked on a closed Folder.
+     *
+     * @return		array of subscribed Folder objects under this 
+     *			Folder. An empty array is returned if no subscribed 
+     *			subfolders exist.
+     * @see		#listSubscribed
+     * @exception 	FolderNotFoundException if this folder does
+     *			not exist.
+     * @exception 	MessagingException for other failures
+     */
+    public Folder[] listSubscribed() throws MessagingException {
+	return listSubscribed("%");
+    }
+
+    /**
+     * Return the delimiter character that separates this Folder's pathname
+     * from the names of immediate subfolders. This method can be invoked 
+     * on a closed Folder.
+     *
+     * @exception 	FolderNotFoundException if the implementation
+     *			requires the folder to exist, but it does not
+     * @return          Hierarchy separator character
+     */
+    public abstract char getSeparator() throws MessagingException;
+
+    /**
+     * This folder can contain messages
+     */
+    public final static int HOLDS_MESSAGES = 0x01;
+
+    /**
+     * This folder can contain other folders
+     */
+    public final static int HOLDS_FOLDERS  = 0x02;
+
+    /**
+     * Returns the type of this Folder, that is, whether this folder can hold
+     * messages or subfolders or both. The returned value is an integer
+     * bitfield with the appropriate bits set. This method can be invoked
+     * on a closed folder.
+     * 
+     * @return 		integer with appropriate bits set
+     * @exception 	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @see 		#HOLDS_FOLDERS 
+     * @see		#HOLDS_MESSAGES
+     */
+    public abstract int getType() throws MessagingException; 
+
+    /**
+     * Create this folder on the Store. When this folder is created, any
+     * folders in its path that do not exist are also created. <p>
+     *
+     * If the creation is successful, a CREATED FolderEvent is delivered
+     * to any FolderListeners registered on this Folder and this Store.
+     *
+     * @param  type	The type of this folder. 
+     *
+     * @return		true if the creation succeeds, else false.
+     * @exception 	MessagingException for failures
+     * @see 		#HOLDS_FOLDERS
+     * @see		#HOLDS_MESSAGES
+     * @see		javax.mail.event.FolderEvent
+     */
+    public abstract boolean create(int type) throws MessagingException;
+
+    /**
+     * Returns true if this Folder is subscribed. <p>
+     *
+     * This method can be invoked on a closed Folder. <p>
+     *
+     * The default implementation provided here just returns true.
+     *
+     * @return		true if this Folder is subscribed
+     */
+    public boolean isSubscribed() {
+	return true;
+    }
+
+    /**
+     * Subscribe or unsubscribe this Folder. Not all Stores support
+     * subscription. <p>
+     *
+     * This method can be invoked on a closed Folder. <p>
+     *
+     * The implementation provided here just throws the
+     * MethodNotSupportedException.
+     *
+     * @param subscribe	true to subscribe, false to unsubscribe
+     * @exception 	FolderNotFoundException if this folder does
+     *			not exist.
+     * @exception 	MethodNotSupportedException if this store
+     *			does not support subscription
+     * @exception 	MessagingException for other failures
+     */
+    public void setSubscribed(boolean subscribe) 
+			throws MessagingException {
+	throw new MethodNotSupportedException();
+    }
+
+    /**
+     * Returns true if this Folder has new messages since the last time
+     * this indication was reset.  When this indication is set or reset
+     * depends on the Folder implementation (and in the case of IMAP,
+     * depends on the server).  This method can be used to implement
+     * a lightweight "check for new mail" operation on a Folder without
+     * opening it.  (For example, a thread that monitors a mailbox and
+     * flags when it has new mail.)  This method should indicate whether
+     * any messages in the Folder have the <code>RECENT</code> flag set. <p>
+     *
+     * Note that this is not an incremental check for new mail, i.e.,
+     * it cannot be used to determine whether any new messages have
+     * arrived since the last time this method was invoked. To
+     * implement incremental checks, the Folder needs to be opened. <p>
+     *
+     * This method can be invoked on a closed Folder that can contain
+     * Messages.
+     *
+     * @return		true if the Store has new Messages
+     * @exception	FolderNotFoundException if this folder does
+     *			not exist.
+     * @exception 	MessagingException for other failures
+     */
+    public abstract boolean hasNewMessages() throws MessagingException;
+
+    /**
+     * Return the Folder object corresponding to the given name. Note that
+     * this folder does not physically have to exist in the Store. The
+     * <code>exists()</code> method on a Folder indicates whether it really
+     * exists on the Store. <p>
+     *
+     * In some Stores, name can be an absolute path if it starts with the
+     * hierarchy delimiter.  Otherwise, it is interpreted relative to
+     * this Folder. <p>
+     *
+     * Folder objects are not cached by the Store, so invoking this
+     * method on the same name multiple times will return that many
+     * distinct Folder objects. <p>
+     *
+     * This method can be invoked on a closed Folder.
+     *
+     * @param name 	name of the Folder
+     * @return		Folder object
+     * @exception 	MessagingException for failures
+     */
+    public abstract Folder getFolder(String name)
+				throws MessagingException;
+
+    /**
+     * Delete this Folder. This method will succeed only on a closed
+     * Folder. <p>
+     *
+     * The <code>recurse</code> flag controls whether the deletion affects
+     * subfolders or not. If true, all subfolders are deleted, then this
+     * folder itself is deleted. If false, the behaviour is dependent on
+     * the folder type and is elaborated below:
+     *
+     * <ul>
+     * <li>
+     * The folder can contain only messages: (type == HOLDS_MESSAGES).
+     * <br>
+     * All messages within the folder are removed. The folder 
+     * itself is then removed. An appropriate FolderEvent is generated by 
+     * the Store and this folder.
+     *
+     * <li>
+     * The folder can contain only subfolders: (type == HOLDS_FOLDERS).
+     * <br>
+     * If this folder is empty (does not contain any 
+     * subfolders at all), it is removed. An appropriate FolderEvent is 
+     * generated by the Store and this folder.<br>
+     * If this folder contains any subfolders, the delete fails 
+     * and returns false.
+     *
+     * <li>
+     * The folder can contain subfolders as well as messages: <br>
+     * If the folder is empty (no messages or subfolders), it
+     * is removed. If the folder contains no subfolders, but only messages,
+     * then all messages are removed. The folder itself is then removed.
+     * In both the above cases, an appropriate FolderEvent is
+     * generated by the Store and this folder. <p>
+     *
+     * If the folder contains subfolders there are 3 possible
+     * choices an implementation is free to do:
+     * 
+     *  <ol>
+     *   <li> The operation fails, irrespective of whether this folder
+     * contains messages or not. Some implementations might elect to go
+     * with this simple approach. The delete() method returns false.
+     *
+     *   <li> Any messages within the folder are removed. Subfolders
+     * are not removed. The folder itself is not removed or affected
+     * in any manner. The delete() method returns true. And the 
+     * exists() method on this folder will return true indicating that
+     * this folder still exists. <br>
+     * An appropriate FolderEvent is generated by the Store and this folder.
+     *
+     *   <li> Any messages within the folder are removed. Subfolders are
+     * not removed. The folder itself changes its type from 
+     * HOLDS_FOLDERS | HOLDS_MESSAGES to HOLDS_FOLDERS. Thus new 
+     * messages cannot be added to this folder, but new subfolders can
+     * be created underneath. The delete() method returns true indicating
+     * success. The exists() method on this folder will return true
+     * indicating that this folder still exists. <br>
+     * An appropriate FolderEvent is generated by the Store and this folder.
+     * </ol>
+     * </ul>
+     *
+     * @param	recurse	also delete subfolders?
+     * @return		true if the Folder is deleted successfully
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist
+     * @exception	IllegalStateException if this folder is not in 
+     *			the closed state.
+     * @exception       MessagingException for other failures
+     * @see		javax.mail.event.FolderEvent
+     */
+    public abstract boolean delete(boolean recurse) 
+				throws MessagingException;
+
+    /**
+     * Rename this Folder. This method will succeed only on a closed
+     * Folder. <p>
+     *
+     * If the rename is successful, a RENAMED FolderEvent is delivered
+     * to FolderListeners registered on this folder and its containing
+     * Store.
+     *
+     * @param f		a folder representing the new name for this Folder
+     * @return		true if the Folder is renamed successfully
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist
+     * @exception	IllegalStateException if this folder is not in 
+     *			the closed state.
+     * @exception       MessagingException for other failures
+     * @see		javax.mail.event.FolderEvent
+     */
+    public abstract boolean renameTo(Folder f) throws MessagingException;
+
+    /**
+     * The Folder is read only.  The state and contents of this
+     * folder cannot be modified.
+     */
+    public static final int READ_ONLY 	= 1;
+
+    /**
+     * The state and contents of this folder can be modified.
+     */
+    public static final int READ_WRITE 	= 2;
+
+    /**
+     * Open this Folder. This method is valid only on Folders that
+     * can contain Messages and that are closed. <p>
+     *
+     * If this folder is opened successfully, an OPENED ConnectionEvent
+     * is delivered to any ConnectionListeners registered on this 
+     * Folder. <p>
+     *
+     * The effect of opening multiple connections to the same folder
+     * on a specifc Store is implementation dependent. Some implementations
+     * allow multiple readers, but only one writer. Others allow
+     * multiple writers as well as readers.
+     *
+     * @param mode	open the Folder READ_ONLY or READ_WRITE
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception	IllegalStateException if this folder is not in 
+     *			the closed state.
+     * @exception       MessagingException for other failures
+     * @see 		#READ_ONLY
+     * @see 		#READ_WRITE
+     * @see 		#getType()
+     * @see 		javax.mail.event.ConnectionEvent
+     */
+    public abstract void open(int mode) throws MessagingException;
+
+    /**
+     * Close this Folder. This method is valid only on open Folders. <p>
+     *
+     * A CLOSED ConnectionEvent is delivered to any ConnectionListeners
+     * registered on this Folder. Note that the folder is closed even
+     * if this method terminates abnormally by throwing a
+     * MessagingException.
+     *
+     * @param expunge	expunges all deleted messages if this flag is true
+     * @exception	IllegalStateException if this folder is not opened
+     * @exception       MessagingException for other failures
+     * @see 		javax.mail.event.ConnectionEvent
+     */
+    public abstract void close(boolean expunge) throws MessagingException;
+
+    /**
+     * Close this Folder and expunge deleted messages. <p>
+     *
+     * A CLOSED ConnectionEvent is delivered to any ConnectionListeners
+     * registered on this Folder. Note that the folder is closed even
+     * if this method terminates abnormally by throwing a
+     * MessagingException. <p>
+     *
+     * This method supports the {@link java.lang.AutoCloseable AutoCloseable}
+     * interface. <p>
+     *
+     * This implementation calls <code>close(true)</code>.
+     *
+     * @exception	IllegalStateException if this folder is not opened
+     * @exception       MessagingException for other failures
+     * @see 		javax.mail.event.ConnectionEvent
+     * @since		JavaMail 1.6
+     */
+    @Override
+    public void close() throws MessagingException {
+	close(true);
+    }
+
+    /**
+     * Indicates whether this Folder is in the 'open' state.
+     * @return  true if this Folder is in the 'open' state.
+     */
+    public abstract boolean isOpen();
+
+    /**
+     * Return the open mode of this folder.  Returns
+     * <code>Folder.READ_ONLY</code>, <code>Folder.READ_WRITE</code>,
+     * or -1 if the open mode is not known (usually only because an older
+     * <code>Folder</code> provider has not been updated to use this new
+     * method).
+     *
+     * @exception	IllegalStateException if this folder is not opened
+     * @return	        the open mode of this folder
+     * @since		JavaMail 1.1
+     */
+    public synchronized int getMode() {
+	if (!isOpen())
+	    throw new IllegalStateException("Folder not open");
+	return mode;
+    }
+ 
+    /**
+     * Get the permanent flags supported by this Folder. Returns a Flags
+     * object that contains all the flags supported. <p>
+     *
+     * The special flag <code>Flags.Flag.USER </code> indicates that this Folder
+     * supports arbitrary user-defined flags. <p>
+     *
+     * The supported permanent flags for a folder may not be available
+     * until the folder is opened.
+     * 
+     * @return 		permanent flags, or null if not known
+     */
+    public abstract Flags getPermanentFlags();
+
+    /**
+     * Get total number of messages in this Folder. <p>
+     *
+     * This method can be invoked on a closed folder. However, note
+     * that for some folder implementations, getting the total message
+     * count can be an expensive operation involving actually opening 
+     * the folder. In such cases, a provider can choose not to support 
+     * this functionality in the closed state, in which case this method
+     * must return -1. <p>
+     *
+     * Clients invoking this method on a closed folder must be aware
+     * that this is a potentially expensive operation. Clients must
+     * also be prepared to handle a return value of -1 in this case.
+     * 
+     * @return 		total number of messages. -1 may be returned
+     *			by certain implementations if this method is
+     *			invoked on a closed folder.
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception       MessagingException for other failures
+     */
+    public abstract int getMessageCount() throws MessagingException;
+
+    /**
+     * Get the number of new messages in this Folder. <p>
+     *
+     * This method can be invoked on a closed folder. However, note
+     * that for some folder implementations, getting the new message
+     * count can be an expensive operation involving actually opening 
+     * the folder. In such cases, a provider can choose not to support 
+     * this functionality in the closed state, in which case this method
+     * must return -1. <p>
+     *
+     * Clients invoking this method on a closed folder must be aware
+     * that this is a potentially expensive operation. Clients must
+     * also be prepared to handle a return value of -1 in this case. <p>
+     *
+     * This implementation returns -1 if this folder is closed. Else
+     * this implementation gets each Message in the folder using
+     * <code>getMessage(int)</code> and checks whether its
+     * <code>RECENT</code> flag is set. The total number of messages
+     * that have this flag set is returned.
+     *
+     * @return 		number of new messages. -1 may be returned
+     *			by certain implementations if this method is
+     *			invoked on a closed folder.
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception       MessagingException for other failures
+     */
+    public synchronized int getNewMessageCount() 
+			throws MessagingException {
+	if (!isOpen())
+	    return -1;
+
+	int newmsgs = 0;
+	int total = getMessageCount();
+	for (int i = 1; i <= total; i++) {
+	    try {
+		if (getMessage(i).isSet(Flags.Flag.RECENT))
+		    newmsgs++;
+	    } catch (MessageRemovedException me) {
+		// This is an expunged message, ignore it.
+		continue;
+	    }
+	}
+	return newmsgs;
+    }
+
+    /**
+     * Get the total number of unread messages in this Folder. <p>
+     *
+     * This method can be invoked on a closed folder. However, note
+     * that for some folder implementations, getting the unread message
+     * count can be an expensive operation involving actually opening 
+     * the folder. In such cases, a provider can choose not to support 
+     * this functionality in the closed state, in which case this method
+     * must return -1. <p>
+     *
+     * Clients invoking this method on a closed folder must be aware
+     * that this is a potentially expensive operation. Clients must
+     * also be prepared to handle a return value of -1 in this case. <p>
+     *
+     * This implementation returns -1 if this folder is closed. Else
+     * this implementation gets each Message in the folder using
+     * <code>getMessage(int)</code> and checks whether its
+     * <code>SEEN</code> flag is set. The total number of messages
+     * that do not have this flag set is returned.
+     *
+     * @return 		total number of unread messages. -1 may be returned
+     *			by certain implementations if this method is
+     *			invoked on a closed folder.
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception       MessagingException for other failures
+     */
+    public synchronized int getUnreadMessageCount() 
+			throws MessagingException {
+	if (!isOpen())
+	    return -1;
+
+	int unread = 0;
+	int total = getMessageCount();
+	for (int i = 1; i <= total; i++) {
+	    try {
+		if (!getMessage(i).isSet(Flags.Flag.SEEN))
+		    unread++;
+	    } catch (MessageRemovedException me) {
+		// This is an expunged message, ignore it.
+		continue;
+	    }
+	}
+	return unread;
+    }
+
+    /**
+     * Get the number of deleted messages in this Folder. <p>
+     *
+     * This method can be invoked on a closed folder. However, note
+     * that for some folder implementations, getting the deleted message
+     * count can be an expensive operation involving actually opening 
+     * the folder. In such cases, a provider can choose not to support 
+     * this functionality in the closed state, in which case this method
+     * must return -1. <p>
+     *
+     * Clients invoking this method on a closed folder must be aware
+     * that this is a potentially expensive operation. Clients must
+     * also be prepared to handle a return value of -1 in this case. <p>
+     *
+     * This implementation returns -1 if this folder is closed. Else
+     * this implementation gets each Message in the folder using
+     * <code>getMessage(int)</code> and checks whether its
+     * <code>DELETED</code> flag is set. The total number of messages
+     * that have this flag set is returned.
+     *
+     * @return 		number of deleted messages. -1 may be returned
+     *			by certain implementations if this method is
+     *			invoked on a closed folder.
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception       MessagingException for other failures
+     * @since		JavaMail 1.3
+     */
+    public synchronized int getDeletedMessageCount() throws MessagingException {
+	if (!isOpen())
+	    return -1;
+
+	int deleted = 0;
+	int total = getMessageCount();
+	for (int i = 1; i <= total; i++) {
+	    try {
+		if (getMessage(i).isSet(Flags.Flag.DELETED))
+		    deleted++;
+	    } catch (MessageRemovedException me) {
+		// This is an expunged message, ignore it.
+		continue;
+	    }
+	}
+	return deleted;
+    }
+
+    /**
+     * Get the Message object corresponding to the given message
+     * number.  A Message object's message number is the relative
+     * position of this Message in its Folder. Messages are numbered
+     * starting at 1 through the total number of message in the folder.
+     * Note that the message number for a particular Message can change
+     * during a session if other messages in the Folder are deleted and
+     * the Folder is expunged. <p>
+     *
+     * Message objects are light-weight references to the actual message
+     * that get filled up on demand. Hence Folder implementations are 
+     * expected to provide light-weight Message objects. <p>
+     *
+     * Unlike Folder objects, repeated calls to getMessage with the
+     * same message number will return the same Message object, as
+     * long as no messages in this folder have been expunged. <p>
+     *
+     * Since message numbers can change within a session if the folder
+     * is expunged , clients are advised not to use message numbers as 
+     * references to messages. Use Message objects instead.
+     *
+     * @param msgnum	the message number
+     * @return 		the Message object
+     * @see		#getMessageCount
+     * @see		#fetch
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception	IllegalStateException if this folder is not opened
+     * @exception	IndexOutOfBoundsException if the message number
+     *			is out of range.
+     * @exception 	MessagingException for other failures
+     */
+    public abstract Message getMessage(int msgnum)
+				throws MessagingException;
+
+    /**
+     * Get the Message objects for message numbers ranging from start
+     * through end, both start and end inclusive. Note that message 
+     * numbers start at 1, not 0. <p>
+     *
+     * Message objects are light-weight references to the actual message
+     * that get filled up on demand. Hence Folder implementations are 
+     * expected to provide light-weight Message objects. <p>
+     *
+     * This implementation uses getMessage(index) to obtain the required
+     * Message objects. Note that the returned array must contain 
+     * <code>(end-start+1)</code> Message objects.
+     * 
+     * @param start	the number of the first message
+     * @param end	the number of the last message
+     * @return 		the Message objects
+     * @see		#fetch
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception	IllegalStateException if this folder is not opened.
+     * @exception	IndexOutOfBoundsException if the start or end
+     *			message numbers are out of range.
+     * @exception 	MessagingException for other failures
+     */ 
+    public synchronized Message[] getMessages(int start, int end) 
+			throws MessagingException {
+	Message[] msgs = new Message[end - start +1];
+	for (int i = start; i <= end; i++)
+	    msgs[i - start] = getMessage(i);
+	return msgs;
+    }
+
+    /**
+     * Get the Message objects for message numbers specified in
+     * the array. <p>
+     *
+     * Message objects are light-weight references to the actual message
+     * that get filled up on demand. Hence Folder implementations are 
+     * expected to provide light-weight Message objects. <p>
+     *
+     * This implementation uses getMessage(index) to obtain the required
+     * Message objects. Note that the returned array must contain 
+     * <code>msgnums.length</code> Message objects
+     *
+     * @param msgnums	the array of message numbers
+     * @return 		the array of Message objects. 
+     * @see		#fetch
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception	IllegalStateException if this folder is not opened.
+     * @exception	IndexOutOfBoundsException if any message number
+     *			in the given array is out of range.
+     * @exception 	MessagingException for other failures
+     */ 
+    public synchronized Message[] getMessages(int[] msgnums)
+			throws MessagingException {
+	int len = msgnums.length;
+	Message[] msgs = new Message[len];
+	for (int i = 0; i < len; i++)
+	    msgs[i] = getMessage(msgnums[i]);
+	return msgs;
+    }
+
+    /**
+     * Get all Message objects from this Folder. Returns an empty array
+     * if the folder is empty.
+     *
+     * Clients can use Message objects (instead of sequence numbers) 
+     * as references to the messages within a folder; this method supplies 
+     * the Message objects to the client. Folder implementations are 
+     * expected to provide light-weight Message objects, which get
+     * filled on demand. <p>
+     *
+     * This implementation invokes <code>getMessageCount()</code> to get
+     * the current message count and then uses <code>getMessage()</code>
+     * to get Message objects from 1 till the message count.
+     *
+     * @return 		array of Message objects, empty array if folder
+     *			is empty.
+     * @see		#fetch
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception	IllegalStateException if this folder is not opened.
+     * @exception 	MessagingException for other failures
+     */ 
+    public synchronized Message[] getMessages() throws MessagingException {
+	if (!isOpen())	// otherwise getMessageCount might return -1
+	    throw new IllegalStateException("Folder not open");
+	int total = getMessageCount();
+	Message[] msgs = new Message[total];
+	for (int i = 1; i <= total; i++)
+	    msgs[i-1] = getMessage(i);	
+	return msgs;
+    }
+
+    /**
+     * Append given Messages to this folder. This method can be 
+     * invoked on a closed Folder. An appropriate MessageCountEvent 
+     * is delivered to any MessageCountListener registered on this 
+     * folder when the messages arrive in the folder. <p>
+     *
+     * Folder implementations must not abort this operation if a
+     * Message in the given message array turns out to be an
+     * expunged Message.
+     *
+     * @param msgs	array of Messages to be appended
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception 	MessagingException if the append failed.
+     */
+    public abstract void appendMessages(Message[] msgs)
+				throws MessagingException;
+
+    /**
+     * Prefetch the items specified in the FetchProfile for the
+     * given Messages. <p>
+     *
+     * Clients use this method to indicate that the specified items are 
+     * needed en-masse for the given message range. Implementations are 
+     * expected to retrieve these items for the given message range in
+     * a efficient manner. Note that this method is just a hint to the
+     * implementation to prefetch the desired items. <p>
+     *
+     * An example is a client filling its header-view window with
+     * the Subject, From and X-mailer headers for all messages in the 
+     * folder.
+     * <blockquote><pre>
+     *
+     *  Message[] msgs = folder.getMessages();
+     *
+     *  FetchProfile fp = new FetchProfile();
+     *  fp.add(FetchProfile.Item.ENVELOPE);
+     *  fp.add("X-mailer");
+     *  folder.fetch(msgs, fp);
+     *  
+     *  for (int i = 0; i &lt; folder.getMessageCount(); i++) {
+     *      display(msg[i].getFrom());
+     *      display(msg[i].getSubject());
+     *      display(msg[i].getHeader("X-mailer"));
+     *  }
+     *
+     * </pre></blockquote><p>
+     *
+     * The implementation provided here just returns without
+     * doing anything useful. Providers wanting to provide a real 
+     * implementation for this method should override this method.
+     *
+     * @param msgs	fetch items for these messages
+     * @param fp	the FetchProfile
+     * @exception	IllegalStateException if this folder is not opened
+     * @exception	MessagingException for other failures
+     */
+    public void fetch(Message[] msgs, FetchProfile fp)
+			throws MessagingException {
+	return;
+    }
+
+    /**
+     * Set the specified flags on the messages specified in the array.
+     * This will result in appropriate MessageChangedEvents being
+     * delivered to any MessageChangedListener registered on this
+     * Message's containing folder. <p>
+     *
+     * Note that the specified Message objects <strong>must</strong> 
+     * belong to this folder. Certain Folder implementations can
+     * optimize the operation of setting Flags for a group of messages,
+     * so clients might want to use this method, rather than invoking
+     * <code>Message.setFlags</code> for each Message. <p>
+     *
+     * This implementation degenerates to invoking <code>setFlags()</code>
+     * on each Message object. Specific Folder implementations that can 
+     * optimize this case should do so. 
+     * Also, an implementation must not abort the operation if a Message 
+     * in the array turns out to be an expunged Message.
+     *
+     * @param msgs	the array of message objects
+     * @param flag	Flags object containing the flags to be set
+     * @param value	set the flags to this boolean value
+     * @exception	IllegalStateException if this folder is not opened
+     *			or if it has been opened READ_ONLY.
+     * @exception 	MessagingException for other failures
+     * @see		Message#setFlags
+     * @see		javax.mail.event.MessageChangedEvent
+     */
+    public synchronized void setFlags(Message[] msgs,
+			Flags flag, boolean value) throws  MessagingException {
+	for (int i = 0; i < msgs.length; i++) {
+	    try {
+		msgs[i].setFlags(flag, value);
+	    } catch (MessageRemovedException me) {
+		// This message is expunged, skip 
+	    }
+	}
+    }
+
+    /**
+     * Set the specified flags on the messages numbered from start
+     * through end, both start and end inclusive. Note that message 
+     * numbers start at 1, not 0.
+     * This will result in appropriate MessageChangedEvents being
+     * delivered to any MessageChangedListener registered on this
+     * Message's containing folder. <p>
+     *
+     * Certain Folder implementations can
+     * optimize the operation of setting Flags for a group of messages,
+     * so clients might want to use this method, rather than invoking
+     * <code>Message.setFlags</code> for each Message. <p>
+     *
+     * The default implementation uses <code>getMessage(int)</code> to
+     * get each <code>Message</code> object and then invokes
+     * <code>setFlags</code> on that object to set the flags.
+     * Specific Folder implementations that can optimize this case should do so.
+     * Also, an implementation must not abort the operation if a message 
+     * number refers to an expunged message.
+     *
+     * @param start	the number of the first message
+     * @param end	the number of the last message
+     * @param flag	Flags object containing the flags to be set
+     * @param value	set the flags to this boolean value
+     * @exception	IllegalStateException if this folder is not opened
+     *			or if it has been opened READ_ONLY.
+     * @exception	IndexOutOfBoundsException if the start or end
+     *			message numbers are out of range.
+     * @exception 	MessagingException for other failures
+     * @see		Message#setFlags
+     * @see		javax.mail.event.MessageChangedEvent
+     */
+    public synchronized void setFlags(int start, int end,
+			Flags flag, boolean value) throws MessagingException {
+	for (int i = start; i <= end; i++) {
+	    try {
+		Message msg = getMessage(i);
+		msg.setFlags(flag, value);
+	    } catch (MessageRemovedException me) {
+		// This message is expunged, skip 
+	    }
+	}
+    }
+
+    /**
+     * Set the specified flags on the messages whose message numbers
+     * are in the array.
+     * This will result in appropriate MessageChangedEvents being
+     * delivered to any MessageChangedListener registered on this
+     * Message's containing folder. <p>
+     *
+     * Certain Folder implementations can
+     * optimize the operation of setting Flags for a group of messages,
+     * so clients might want to use this method, rather than invoking
+     * <code>Message.setFlags</code> for each Message. <p>
+     *
+     * The default implementation uses <code>getMessage(int)</code> to
+     * get each <code>Message</code> object and then invokes
+     * <code>setFlags</code> on that object to set the flags.
+     * Specific Folder implementations that can optimize this case should do so.
+     * Also, an implementation must not abort the operation if a message 
+     * number refers to an expunged message.
+     *
+     * @param msgnums	the array of message numbers
+     * @param flag	Flags object containing the flags to be set
+     * @param value	set the flags to this boolean value
+     * @exception	IllegalStateException if this folder is not opened
+     *			or if it has been opened READ_ONLY.
+     * @exception	IndexOutOfBoundsException if any message number
+     *			in the given array is out of range.
+     * @exception 	MessagingException for other failures
+     * @see		Message#setFlags
+     * @see		javax.mail.event.MessageChangedEvent
+     */
+    public synchronized void setFlags(int[] msgnums,
+			Flags flag, boolean value) throws MessagingException {
+	for (int i = 0; i < msgnums.length; i++) {
+	    try {
+		Message msg = getMessage(msgnums[i]);
+		msg.setFlags(flag, value);
+	    } catch (MessageRemovedException me) {
+		// This message is expunged, skip 
+	    }
+	}
+    }
+
+    /**
+     * Copy the specified Messages from this Folder into another 
+     * Folder. This operation appends these Messages to the 
+     * destination Folder. The destination Folder does not have to 
+     * be opened.  An appropriate MessageCountEvent 
+     * is delivered to any MessageCountListener registered on the 
+     * destination folder when the messages arrive in the folder. <p>
+     *
+     * Note that the specified Message objects <strong>must</strong> 
+     * belong to this folder. Folder implementations might be able
+     * to optimize this method by doing server-side copies. <p>
+     *
+     * This implementation just invokes <code>appendMessages()</code>
+     * on the destination folder to append the given Messages. Specific
+     * folder implementations that support server-side copies should
+     * do so, if the destination folder's Store is the same as this
+     * folder's Store. 
+     * Also, an implementation must not abort the operation if a 
+     * Message in the array turns out to be an expunged Message.
+     *
+     * @param msgs	the array of message objects
+     * @param folder	the folder to copy the messages to
+     * @exception	FolderNotFoundException if the destination
+     *			folder does not exist.
+     * @exception	IllegalStateException if this folder is not opened.
+     * @exception	MessagingException for other failures
+     * @see		#appendMessages
+     */
+    public void copyMessages(Message[] msgs, Folder folder)
+				throws MessagingException {
+	if (!folder.exists())
+	    throw new FolderNotFoundException(
+			folder.getFullName() + " does not exist",
+			folder);
+
+	folder.appendMessages(msgs);
+    }
+
+    /**
+     * Expunge (permanently remove) messages marked DELETED. Returns an
+     * array containing the expunged message objects.  The
+     * <code>getMessageNumber</code> method
+     * on each of these message objects returns that Message's original
+     * (that is, prior to the expunge) sequence number. A MessageCountEvent 
+     * containing the expunged messages is delivered to any 
+     * MessageCountListeners registered on the folder. <p>
+     *
+     * Expunge causes the renumbering of Message objects subsequent to
+     * the expunged messages. Clients that use message numbers as 
+     * references to messages should be aware of this and should be 
+     * prepared to deal with the situation (probably by flushing out 
+     * existing message number caches and reloading them). Because of 
+     * this complexity, it is better for clients to use Message objects
+     * as references to messages, rather than message numbers. Any 
+     * expunged Messages objects still have to be pruned, but other 
+     * Messages in that folder are not affected by the expunge. <p>
+     *
+     * After a message is expunged, only the <code>isExpunged</code> and 
+     * <code>getMessageNumber</code> methods are still valid on the
+     * corresponding Message object; other methods may throw
+     * <code>MessageRemovedException</code>
+     *
+     * @return		array of expunged Message objects
+     * @exception	FolderNotFoundException if this folder does not
+     *			exist
+     * @exception	IllegalStateException if this folder is not opened.
+     * @exception       MessagingException for other failures
+     * @see		Message#isExpunged
+     * @see		javax.mail.event.MessageCountEvent
+     */
+    public abstract Message[] expunge() throws MessagingException;
+
+    /**
+     * Search this Folder for messages matching the specified
+     * search criterion. Returns an array containing the matching
+     * messages . Returns an empty array if no matches were found. <p>
+     *
+     * This implementation invokes 
+     * <code>search(term, getMessages())</code>, to apply the search 
+     * over all the messages in this folder. Providers that can implement
+     * server-side searching might want to override this method to provide
+     * a more efficient implementation.
+     *
+     * @param term	the search criterion
+     * @return 		array of matching messages 
+     * @exception       javax.mail.search.SearchException if the search 
+     *			term is too complex for the implementation to handle.
+     * @exception	FolderNotFoundException if this folder does 
+     *			not exist.
+     * @exception	IllegalStateException if this folder is not opened.
+     * @exception       MessagingException for other failures
+     * @see		javax.mail.search.SearchTerm
+     */
+    public Message[] search(SearchTerm term) throws MessagingException {
+	return search(term, getMessages());
+    }
+
+    /**
+     * Search the given array of messages for those that match the 
+     * specified search criterion. Returns an array containing the 
+     * matching messages. Returns an empty array if no matches were 
+     * found. <p>
+     *
+     * Note that the specified Message objects <strong>must</strong> 
+     * belong to this folder. <p>
+     *
+     * This implementation iterates through the given array of messages,
+     * and applies the search criterion on each message by calling
+     * its <code>match()</code> method with the given term. The
+     * messages that succeed in the match are returned. Providers
+     * that can implement server-side searching might want to override
+     * this method to provide a more efficient implementation. If the
+     * search term is too complex or contains user-defined terms that
+     * cannot be executed on the server, providers may elect to either
+     * throw a SearchException or degenerate to client-side searching by
+     * calling <code>super.search()</code> to invoke this implementation.
+     *
+     * @param term	the search criterion
+     * @param msgs 	the messages to be searched
+     * @return 		array of matching messages 
+     * @exception       javax.mail.search.SearchException if the search 
+     *			term is too complex for the implementation to handle.
+     * @exception	IllegalStateException if this folder is not opened
+     * @exception       MessagingException for other failures
+     * @see		javax.mail.search.SearchTerm
+     */
+    public Message[] search(SearchTerm term, Message[] msgs)
+				throws MessagingException {
+	List<Message> matchedMsgs = new ArrayList<>();
+
+	// Run thru the given messages
+	for (Message msg : msgs) {
+	    try {
+		if (msg.match(term)) // matched
+		    matchedMsgs.add(msg); // add it
+	    } catch(MessageRemovedException mrex) { }
+	}
+
+	return matchedMsgs.toArray(new Message[matchedMsgs.size()]);
+    }
+
+    /*
+     * The set of listeners are stored in Vectors appropriate to their
+     * type.  We mark all listener Vectors as "volatile" because, while
+     * we initialize them inside this folder's synchronization lock,
+     * they are accessed (checked for null) in the "notify" methods,
+     * which can't be synchronized due to lock ordering constraints.
+     * Since the listener fields (the handles on the Vector objects)
+     * are only ever set, and are never cleared, we believe this is
+     * safe.  The code that dispatches the notifications will either
+     * see the null and assume there are no listeners or will see the
+     * Vector and will process the listeners.  There's an inherent race
+     * between adding a listener and notifying the listeners; the lack
+     * of synchronization during notification does not make the race
+     * condition significantly worse.  If one thread is setting a
+     * listener at the "same" time an event is being dispatched, the
+     * dispatch code might not see the listener right away.  The
+     * dispatch code doesn't have to worry about the Vector handle
+     * being set to null, and thus using an out-of-date set of
+     * listeners, because we never set the field to null.
+     */
+
+    // Vector of connection listeners.
+    private volatile Vector<ConnectionListener> connectionListeners = null;
+
+    /**
+     * Add a listener for Connection events on this Folder. <p>
+     *
+     * The implementation provided here adds this listener
+     * to an internal list of ConnectionListeners.
+     *
+     * @param l 	the Listener for Connection events
+     * @see		javax.mail.event.ConnectionEvent
+     */
+    public synchronized void
+    addConnectionListener(ConnectionListener l) { 
+   	if (connectionListeners == null) 
+	    connectionListeners = new Vector<>();
+	connectionListeners.addElement(l);
+    }
+
+    /**
+     * Remove a Connection event listener. <p>
+     *
+     * The implementation provided here removes this listener
+     * from the internal list of ConnectionListeners.
+     *
+     * @param l 	the listener
+     * @see		#addConnectionListener
+     */
+    public synchronized void
+    removeConnectionListener(ConnectionListener l) { 
+   	if (connectionListeners != null) 
+	    connectionListeners.removeElement(l);
+    }
+
+    /**
+     * Notify all ConnectionListeners. Folder implementations are
+     * expected to use this method to broadcast connection events. <p>
+     *
+     * The provided implementation queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the registered
+     * ConnectionListeners. Note that the event dispatching occurs
+     * in a separate thread, thus avoiding potential deadlock problems.
+     *
+     * @param type	the ConnectionEvent type
+     * @see		javax.mail.event.ConnectionEvent
+     */
+    protected void notifyConnectionListeners(int type) {
+   	if (connectionListeners != null) {
+	    ConnectionEvent e = new ConnectionEvent(this, type);
+	    queueEvent(e, connectionListeners);
+	}
+
+	/* Fix for broken JDK1.1.x Garbage collector :
+	 *  The 'conservative' GC in JDK1.1.x occasionally fails to
+	 *  garbage-collect Threads which are in the wait state.
+	 *  This would result in thread (and consequently memory) leaks.
+	 * 
+	 * We attempt to fix this by sending a 'terminator' event
+	 * to the queue, after we've sent the CLOSED event. The
+	 * terminator event causes the event-dispatching thread to
+	 * self destruct.
+	 */
+	if (type == ConnectionEvent.CLOSED)
+	    q.terminateQueue();
+    }
+
+    // Vector of folder listeners
+    private volatile Vector<FolderListener> folderListeners = null;
+
+    /**
+     * Add a listener for Folder events on this Folder. <p>
+     *
+     * The implementation provided here adds this listener
+     * to an internal list of FolderListeners.
+     *
+     * @param l 	the Listener for Folder events
+     * @see		javax.mail.event.FolderEvent
+     */
+    public synchronized void addFolderListener(FolderListener l) { 
+   	if (folderListeners == null)
+	    folderListeners = new Vector<>();
+	folderListeners.addElement(l);
+    }
+
+    /**
+     * Remove a Folder event listener. <p>
+     *
+     * The implementation provided here removes this listener
+     * from the internal list of FolderListeners.
+     *
+     * @param l 	the listener
+     * @see		#addFolderListener
+     */
+    public synchronized void removeFolderListener(FolderListener l) {
+	if (folderListeners != null)
+	    folderListeners.removeElement(l);
+    }
+
+    /**
+     * Notify all FolderListeners registered on this Folder and
+     * this folder's Store. Folder implementations are expected
+     * to use this method to broadcast Folder events. <p>
+     *
+     * The implementation provided here queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the 
+     * FolderListeners registered on this folder. The implementation
+     * also invokes <code>notifyFolderListeners</code> on this folder's
+     * Store to notify any FolderListeners registered on the store.
+     *
+     * @param type	type of FolderEvent
+     * @see		#notifyFolderRenamedListeners
+     */
+    protected void notifyFolderListeners(int type) { 
+   	if (folderListeners != null) {
+	    FolderEvent e = new FolderEvent(this, this, type);
+	    queueEvent(e, folderListeners);
+	}
+	store.notifyFolderListeners(type, this);
+    }
+
+    /**
+     * Notify all FolderListeners registered on this Folder and
+     * this folder's Store about the renaming of this folder.
+     * Folder implementations are expected to use this method to
+     * broadcast Folder events indicating the renaming of folders. <p>
+     *
+     * The implementation provided here queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the 
+     * FolderListeners registered on this folder. The implementation
+     * also invokes <code>notifyFolderRenamedListeners</code> on this 
+     * folder's Store to notify any FolderListeners registered on the store.
+     *
+     * @param	folder	Folder representing the new name.
+     * @see		#notifyFolderListeners
+     * @since		JavaMail 1.1
+     */
+    protected void notifyFolderRenamedListeners(Folder folder) {
+   	if (folderListeners != null) {
+	    FolderEvent e = new FolderEvent(this, this, folder,
+					    FolderEvent.RENAMED);
+	    queueEvent(e, folderListeners);
+	}
+	store.notifyFolderRenamedListeners(this, folder);
+    }
+
+    // Vector of MessageCount listeners
+    private volatile Vector<MessageCountListener> messageCountListeners = null;
+
+    /**
+     * Add a listener for MessageCount events on this Folder. <p>
+     *
+     * The implementation provided here adds this listener
+     * to an internal list of MessageCountListeners.
+     *
+     * @param l 	the Listener for MessageCount events
+     * @see		javax.mail.event.MessageCountEvent
+     */
+    public synchronized void addMessageCountListener(MessageCountListener l) { 
+   	if (messageCountListeners == null)
+	    messageCountListeners = new Vector<>();
+	messageCountListeners.addElement(l);
+    }
+
+    /**
+     * Remove a MessageCount listener. <p>
+     *
+     * The implementation provided here removes this listener
+     * from the internal list of MessageCountListeners.
+     *
+     * @param l 	the listener
+     * @see		#addMessageCountListener
+     */
+    public synchronized void
+			removeMessageCountListener(MessageCountListener l) { 
+   	if (messageCountListeners != null) 
+	    messageCountListeners.removeElement(l); 
+    }
+
+    /**
+     * Notify all MessageCountListeners about the addition of messages
+     * into this folder. Folder implementations are expected to use this 
+     * method to broadcast MessageCount events for indicating arrival of
+     * new messages. <p>
+     *
+     * The provided implementation queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the registered
+     * MessageCountListeners. Note that the event dispatching occurs
+     * in a separate thread, thus avoiding potential deadlock problems.
+     *
+     * @param	msgs	the messages that were added
+     */
+    protected void notifyMessageAddedListeners(Message[] msgs) { 
+   	if (messageCountListeners == null)
+	    return;
+
+	MessageCountEvent e = new MessageCountEvent(
+					this, 
+					MessageCountEvent.ADDED, 
+					false,
+					msgs);
+
+   	queueEvent(e, messageCountListeners); 
+    }
+
+    /**
+     * Notify all MessageCountListeners about the removal of messages
+     * from this Folder. Folder implementations are expected to use this 
+     * method to broadcast MessageCount events indicating removal of
+     * messages. <p>
+     *
+     * The provided implementation queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the registered
+     * MessageCountListeners. Note that the event dispatching occurs
+     * in a separate thread, thus avoiding potential deadlock problems.
+     *
+     * @param	removed	was the message removed by this client?
+     * @param	msgs	the messages that were removed
+     */
+    protected void notifyMessageRemovedListeners(boolean removed, 
+						 Message[] msgs) { 
+   	if (messageCountListeners == null)
+	    return;
+
+	MessageCountEvent e = new MessageCountEvent(
+					this, 
+					MessageCountEvent.REMOVED, 
+					removed,
+					msgs);
+   	queueEvent(e, messageCountListeners); 
+    }
+
+    // Vector of MessageChanged listeners.
+    private volatile Vector<MessageChangedListener> messageChangedListeners
+	    = null;
+
+    /**
+     * Add a listener for MessageChanged events on this Folder. <p>
+     *
+     * The implementation provided here adds this listener
+     * to an internal list of MessageChangedListeners.
+     *
+     * @param l 	the Listener for MessageChanged events
+     * @see		javax.mail.event.MessageChangedEvent
+     */
+    public synchronized void
+			addMessageChangedListener(MessageChangedListener l) { 
+   	if (messageChangedListeners == null)
+	    messageChangedListeners = new Vector<>();
+	messageChangedListeners.addElement(l);
+    }
+
+    /**
+     * Remove a MessageChanged listener. <p>
+     *
+     * The implementation provided here removes this listener
+     * from the internal list of MessageChangedListeners.
+     *
+     * @param l 	the listener
+     * @see		#addMessageChangedListener
+     */
+    public synchronized void
+		removeMessageChangedListener(MessageChangedListener l) { 
+   	if (messageChangedListeners != null) 
+	    messageChangedListeners.removeElement(l);
+    }
+
+    /**
+     * Notify all MessageChangedListeners. Folder implementations are
+     * expected to use this method to broadcast MessageChanged events. <p>
+     *
+     * The provided implementation queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to registered
+     * MessageChangedListeners. Note that the event dispatching occurs
+     * in a separate thread, thus avoiding potential deadlock problems.
+     *
+     * @param	type	the MessageChangedEvent type
+     * @param	msg	the message that changed
+     */
+    protected void notifyMessageChangedListeners(int type, Message msg) {
+	if (messageChangedListeners == null)
+	    return;
+	
+	MessageChangedEvent e = new MessageChangedEvent(this, type, msg);
+	queueEvent(e, messageChangedListeners);
+    }
+
+    /*
+     * Add the event and vector of listeners to the queue to be delivered.
+     */
+    @SuppressWarnings("unchecked")
+    private void queueEvent(MailEvent event,
+	    Vector<? extends EventListener> vector) {
+	/*
+         * Copy the vector in order to freeze the state of the set
+         * of EventListeners the event should be delivered to prior
+         * to delivery.  This ensures that any changes made to the
+         * Vector from a target listener's method during the delivery
+         * of this event will not take effect until after the event is
+         * delivered.
+         */
+	Vector<? extends EventListener> v = (Vector)vector.clone();
+	q.enqueue(event, v);
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+	try {
+	    q.terminateQueue();
+	} finally {
+	    super.finalize();
+	}
+    }
+
+    /**
+     * override the default toString(), it will return the String
+     * from Folder.getFullName() or if that is null, it will use
+     * the default toString() behavior.
+     */
+
+    @Override
+    public String toString() {
+	String s = getFullName();
+	if (s != null)
+	    return s;
+	else
+	    return super.toString();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/FolderClosedException.java b/mail/src/main/java/javax/mail/FolderClosedException.java
new file mode 100644
index 0000000..7e8a4b4
--- /dev/null
+++ b/mail/src/main/java/javax/mail/FolderClosedException.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This exception is thrown when a method is invoked on a Messaging object
+ * and the Folder that owns that object has died due to some reason. <p>
+ *
+ * Following the exception, the Folder is reset to the "closed" state. 
+ * All messaging objects owned by the Folder should be considered invalid. 
+ * The Folder can be reopened using the "open" method to reestablish the 
+ * lost connection. <p>
+ *
+ * The getMessage() method returns more detailed information about the
+ * error that caused this exception. <p>
+ *
+ * @author John Mani
+ */
+
+public class FolderClosedException extends MessagingException {
+    transient private Folder folder;
+
+    private static final long serialVersionUID = 1687879213433302315L;
+    
+    /**
+     * Constructs a FolderClosedException.
+     *
+     * @param folder	The Folder
+     */
+    public FolderClosedException(Folder folder) {
+	this(folder, null);
+    }
+
+    /**
+     * Constructs a FolderClosedException with the specified
+     * detail message.
+     *
+     * @param folder 	The Folder
+     * @param message	The detailed error message
+     */
+    public FolderClosedException(Folder folder, String message) {
+	super(message);
+	this.folder = folder;
+    }
+
+    /**
+     * Constructs a FolderClosedException with the specified
+     * detail message and embedded exception.  The exception is chained
+     * to this exception.
+     *
+     * @param folder 	The Folder
+     * @param message	The detailed error message
+     * @param e		The embedded exception
+     * @since		JavaMail 1.5
+     */
+    public FolderClosedException(Folder folder, String message, Exception e) {
+	super(message, e);
+	this.folder = folder;
+    }
+
+    /**
+     * Returns the dead Folder object
+     *
+     * @return	the dead Folder object
+     */
+    public Folder getFolder() {
+	return folder;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/FolderNotFoundException.java b/mail/src/main/java/javax/mail/FolderNotFoundException.java
new file mode 100644
index 0000000..0ea6745
--- /dev/null
+++ b/mail/src/main/java/javax/mail/FolderNotFoundException.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.lang.*;
+
+/**
+ * This exception is thrown by Folder methods, when those
+ * methods are invoked on a non existent folder.
+ *
+ * @author John Mani
+ */
+
+public class FolderNotFoundException extends MessagingException {
+    transient private Folder folder;
+
+    private static final long serialVersionUID = 472612108891249403L;
+
+    /**
+     * Constructs a FolderNotFoundException with no detail message.
+     */
+    public FolderNotFoundException() {
+	super();
+    }
+
+    /**
+     * Constructs a FolderNotFoundException.
+     *
+     * @param folder	The Folder
+     * @since		JavaMail 1.2 
+     */
+    public FolderNotFoundException(Folder folder) {
+	super();
+        this.folder = folder;
+    }
+
+    /**
+     * Constructs a FolderNotFoundException with the specified
+     * detail message.
+     *
+     * @param folder	The Folder
+     * @param s		The detailed error message
+     * @since		JavaMail 1.2
+     */
+    public FolderNotFoundException(Folder folder, String s) {
+	super(s);
+	this.folder = folder;
+    }
+
+    /**
+     * Constructs a FolderNotFoundException with the specified
+     * detail message and embedded exception.  The exception is chained
+     * to this exception.
+     *
+     * @param folder	The Folder
+     * @param s		The detailed error message
+     * @param e		The embedded exception
+     * @since		JavaMail 1.5
+     */
+    public FolderNotFoundException(Folder folder, String s, Exception e) {
+	super(s, e);
+	this.folder = folder;
+    }
+
+    /**
+     * Constructs a FolderNotFoundException with the specified detail message
+     * and the specified folder.
+     *
+     * @param s		The detail message
+     * @param folder	The Folder
+     */
+    public FolderNotFoundException(String s, Folder folder) {
+	super(s);
+	this.folder = folder;
+    }
+
+    /**
+     * Returns the offending Folder object.
+     *
+     * @return	the Folder object. Note that the returned value can be
+     * 		<code>null</code>.
+     */
+    public Folder getFolder() {
+	return folder;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Header.java b/mail/src/main/java/javax/mail/Header.java
new file mode 100644
index 0000000..924f84c
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Header.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+
+/**
+ * The Header class stores a name/value pair to represent headers.
+ *
+ * @author John Mani
+ */
+
+public class Header {
+
+    /**
+     * The name of the header.
+     *
+     * @since	JavaMail 1.4
+     */
+    protected String name;
+
+    /**
+     * The value of the header.
+     *
+     * @since	JavaMail 1.4
+     */
+    protected String value;
+
+    /**
+     * Construct a Header object.
+     *
+     * @param name	name of the header
+     * @param value	value of the header
+     */
+    public Header(String name, String value) {
+	this.name = name;
+	this.value = value;
+    }
+
+    /**
+     * Returns the name of this header.
+     *
+     * @return 		name of the header
+     */
+    public String getName() {
+	return name;
+    }
+
+    /**
+     * Returns the value of this header.
+     *
+     * @return 		value of the header
+     */
+    public String getValue() {
+	return value;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/IllegalWriteException.java b/mail/src/main/java/javax/mail/IllegalWriteException.java
new file mode 100644
index 0000000..8011c4c
--- /dev/null
+++ b/mail/src/main/java/javax/mail/IllegalWriteException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+
+/**
+ * The exception thrown when a write is attempted on a read-only attribute
+ * of any Messaging object. 
+ *
+ * @author John Mani
+ */
+
+public class IllegalWriteException extends MessagingException {
+
+    private static final long serialVersionUID = 3974370223328268013L;
+
+    /**
+     * Constructs an IllegalWriteException with no detail message.
+     */
+    public IllegalWriteException() {
+	super();
+    }
+
+    /**
+     * Constructs an IllegalWriteException with the specified
+     * detail message.
+     *
+     * @param s		The detailed error message
+     */
+    public IllegalWriteException(String s) {
+	super(s);
+    }
+
+    /**
+     * Constructs an IllegalWriteException with the specified
+     * detail message and embedded exception.  The exception is chained
+     * to this exception.
+     *
+     * @param s		The detailed error message
+     * @param e		The embedded exception
+     * @since		JavaMail 1.5
+     */
+    public IllegalWriteException(String s, Exception e) {
+	super(s, e);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/MailSessionDefinition.java b/mail/src/main/java/javax/mail/MailSessionDefinition.java
new file mode 100644
index 0000000..788fa4e
--- /dev/null
+++ b/mail/src/main/java/javax/mail/MailSessionDefinition.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.lang.annotation.Repeatable;
+
+/**
+ * Annotation used by Java EE applications to define a <code>MailSession</code>
+ * to be registered with JNDI.  The <code>MailSession</code> may be configured
+ * by setting the annotation elements for commonly used <code>Session</code>
+ * properties.  Additional standard and vendor-specific properties may be
+ * specified using the <code>properties</code> element.
+ * <p>
+ * The session will be registered under the name specified in the
+ * <code>name</code> element.  It may be defined to be in any valid
+ * <code>Java EE</code> namespace, and will determine the accessibility of
+ * the session from other components.
+ *
+ * @since JavaMail 1.5
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(MailSessionDefinitions.class)
+public @interface MailSessionDefinition {
+
+    /**
+     * Description of this mail session.
+     *
+     * @return	the description
+     */
+    String description() default "";
+
+    /**
+     * JNDI name by which the mail session will be registered.
+     *
+     * @return	the JNDI name
+     */
+    String name();
+
+    /**
+     * Store protocol name.
+     *
+     * @return	the store protocol name
+     */
+    String storeProtocol() default "";
+
+    /**
+     * Transport protocol name.
+     *
+     * @return	the transport protocol name
+     */
+    String transportProtocol() default "";
+
+    /**
+     * Host name for the mail server.
+     *
+     * @return	the host name
+     */
+    String host() default "";
+
+    /**
+     * User name to use for authentication.
+     *
+     * @return	the user name
+     */
+    String user() default "";
+
+    /**
+     * Password to use for authentication.
+     *
+     * @return	the password
+     */
+    String password() default "";
+
+    /**
+     * From address for the user.
+     *
+     * @return	the from address
+     */
+    String from() default "";
+
+    /**
+     * Properties to include in the Session.
+     * Properties are specified using the format:
+     * <i>propertyName=propertyValue</i> with one property per array element.
+     *
+     * @return	the properties
+     */
+    String[] properties() default {};
+}
diff --git a/mail/src/main/java/javax/mail/MailSessionDefinitions.java b/mail/src/main/java/javax/mail/MailSessionDefinitions.java
new file mode 100644
index 0000000..b97462b
--- /dev/null
+++ b/mail/src/main/java/javax/mail/MailSessionDefinitions.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.lang.annotation.Target;
+import java.lang.annotation.Retention;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Declares one or more <code>MailSessionDefinition</code> annotations.
+ *
+ * @see MailSessionDefinition
+ * @since JavaMail 1.5
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface MailSessionDefinitions {
+    MailSessionDefinition[] value();
+}
diff --git a/mail/src/main/java/javax/mail/Message.java b/mail/src/main/java/javax/mail/Message.java
new file mode 100644
index 0000000..370fd28
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Message.java
@@ -0,0 +1,704 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.util.Date;
+import java.io.*;
+import javax.mail.search.SearchTerm;
+
+/**
+ * This class models an email message. This is an abstract class.  
+ * Subclasses provide actual implementations. <p>
+ *
+ * Message implements the Part interface. Message contains a set of
+ * attributes and a "content". Messages within a folder also have a 
+ * set of flags that describe its state within the folder.<p>
+ *
+ * Message defines some new attributes in addition to those defined
+ * in the <code>Part</code> interface. These attributes specify meta-data
+ * for the message - i.e., addressing  and descriptive information about 
+ * the message. <p>
+ *
+ * Message objects are obtained either from a Folder or by constructing
+ * a new Message object of the appropriate subclass. Messages that have
+ * been received are normally retrieved from a folder named "INBOX". <p>
+ *
+ * A Message object obtained from a folder is just a lightweight
+ * reference to the actual message. The Message is 'lazily' filled 
+ * up (on demand) when each item is requested from the message. Note
+ * that certain folder implementations may return Message objects that
+ * are pre-filled with certain user-specified items.
+ 
+ * To send a message, an appropriate subclass of Message (e.g., 
+ * MimeMessage) is instantiated, the attributes and content are 
+ * filled in, and the message is sent using the <code>Transport.send</code> 
+ * method. <p>
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ * @author Max Spivak
+ * @see	   javax.mail.Part
+ */
+
+public abstract class Message implements Part {
+
+    /**
+     * The number of this message within its folder, or zero if
+     * the message was not retrieved from a folder.
+     */
+    protected int msgnum = 0;
+
+    /**
+     * True if this message has been expunged.
+     */
+    protected boolean expunged 	= false;
+
+    /**
+     * The containing folder, if this message is obtained from a folder
+     */
+    protected Folder folder	= null;
+
+    /**
+     * The Session object for this Message
+     */
+    protected Session session	= null;
+
+    /**
+     * No-arg version of the constructor.
+     */
+    protected Message() { }
+
+    /**
+     * Constructor that takes a Folder and a message number. 
+     * Used by Folder implementations.
+     *
+     * @param	folder	containing folder
+     * @param	msgnum	this message's sequence number within this folder
+     */
+    protected Message(Folder folder, int msgnum) {
+	this.folder = folder;
+	this.msgnum = msgnum;
+	session = folder.store.session;
+    }
+
+    /**
+     * Constructor that takes a Session. Used for client created
+     * Message objects.
+     *
+     * @param	session	A Session object
+     */
+    protected Message(Session session) {
+	this.session = session;
+    }
+
+    /**
+     * Return the Session used when this message was created.
+     *
+     * @return		the message's Session
+     * @since		JavaMail 1.5
+     */
+    public Session getSession() {
+	return session;
+    }
+
+    /**
+     * Returns the "From" attribute. The "From" attribute contains
+     * the identity of the person(s) who wished this message to 
+     * be sent. <p>
+     * 
+     * In certain implementations, this may be different
+     * from the entity that actually sent the message. <p>
+     *
+     * This method returns <code>null</code> if this attribute
+     * is not present in this message. Returns an empty array if
+     * this attribute is present, but contains no addresses.
+     *
+     * @return          array of Address objects
+     * @exception       MessagingException for failures
+     */
+    public abstract Address[] getFrom() throws MessagingException;
+
+    /**
+     * Set the "From" attribute in this Message. The value of this
+     * attribute is obtained from the property "mail.user". If this
+     * property is absent, the system property "user.name" is used.
+     *
+     * @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.
+     * @exception       MessagingException for other failures
+     */
+    public abstract void setFrom() throws MessagingException;
+
+    /**
+     * Set the "From" attribute in this Message.
+     *
+     * @param address   the sender
+     * @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.
+     * @exception       MessagingException for other failures
+     */
+    public abstract void setFrom(Address address) 
+			throws MessagingException;
+
+    /**
+     * Add these addresses to the existing "From" attribute 
+     *
+     * @param addresses	the senders
+     * @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.
+     * @exception       MessagingException for other failures
+     */
+    public abstract void addFrom(Address[] addresses) 
+			throws MessagingException;
+
+    /**
+     * This inner class defines the types of recipients allowed by
+     * the Message class. The currently defined types are TO,
+     * CC and BCC.
+     *
+     * Note that this class only has a protected constructor, thereby
+     * restricting new Recipient types to either this class or subclasses.
+     * This effectively implements an enumeration of the allowed Recipient
+     * types.
+     *
+     * The following code sample shows how to use this class to obtain
+     * the "TO" recipients from a message.
+     * <blockquote><pre>
+     *
+     * Message msg = folder.getMessages(1);
+     * Address[] a = m.getRecipients(Message.RecipientType.TO);
+     *
+     * </pre></blockquote>
+     *
+     * @see javax.mail.Message#getRecipients
+     * @see javax.mail.Message#setRecipients
+     * @see javax.mail.Message#addRecipients
+     */
+    public static class RecipientType implements Serializable {
+	/**
+	 * The "To" (primary) recipients.
+	 */
+	public static final RecipientType TO = new RecipientType("To");
+	/**
+	 * The "Cc" (carbon copy) recipients.
+	 */
+	public static final RecipientType CC = new RecipientType("Cc");
+	/**
+	 * The "Bcc" (blind carbon copy) recipients.
+	 */
+	public static final RecipientType BCC = new RecipientType("Bcc");
+
+	/**
+	 * The type of recipient, usually the name of a corresponding
+	 * Internet standard header.
+	 *
+	 * @serial
+	 */
+	protected String type;
+
+	private static final long serialVersionUID = -7479791750606340008L;
+
+	/**
+	 * Constructor for use by subclasses.
+	 *
+	 * @param	type	the recipient type
+	 */
+	protected RecipientType(String type) {
+	    this.type = type;
+	}
+
+	/**
+	 * 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
+	 * <code>readResolve</code> method that checks for their known
+	 * instances before calling this super method.
+	 *
+	 * @return	the RecipientType object instance
+	 * @exception	ObjectStreamException for object stream errors
+	 */
+	protected Object readResolve() throws ObjectStreamException {
+	    if (type.equals("To"))
+		return TO;
+	    else if (type.equals("Cc"))
+		return CC;
+	    else if (type.equals("Bcc"))
+		return BCC;
+	    else
+		throw new InvalidObjectException(
+		    "Attempt to resolve unknown RecipientType: " + type);
+	}
+
+	@Override
+	public String toString() {
+	    return type;
+	}
+    }
+
+    /**
+     * Get all the recipient addresses of the given type. <p>
+     *
+     * This method returns <code>null</code> if no recipients of
+     * the given type are present in this message. It may return an 
+     * empty array if the header is present, but contains no addresses.
+     *
+     * @param type      the recipient type
+     * @return          array of Address objects
+     * @exception       MessagingException for failures
+     * @see Message.RecipientType#TO
+     * @see Message.RecipientType#CC
+     * @see Message.RecipientType#BCC
+     */
+    public abstract Address[] getRecipients(RecipientType type)
+                                throws MessagingException;
+
+    /**
+     * Get all the recipient addresses for the message.
+     * The default implementation extracts the TO, CC, and BCC
+     * recipients using the <code>getRecipients</code> method. <p>
+     *
+     * This method returns <code>null</code> if none of the recipient
+     * headers are present in this message.  It may Return an empty array
+     * if any recipient header is present, but contains no addresses.
+     *
+     * @return          array of Address objects
+     * @exception       MessagingException for failures
+     * @see Message.RecipientType#TO
+     * @see Message.RecipientType#CC
+     * @see Message.RecipientType#BCC
+     * @see #getRecipients
+     */
+    public Address[] getAllRecipients() throws MessagingException {
+	Address[] to = getRecipients(RecipientType.TO);
+	Address[] cc = getRecipients(RecipientType.CC);
+	Address[] bcc = getRecipients(RecipientType.BCC);
+
+	if (cc == null && bcc == null)
+	    return to;		// a common case
+
+	int numRecip =
+	    (to != null ? to.length : 0) +
+	    (cc != null ? cc.length : 0) +
+	    (bcc != null ? bcc.length : 0);
+	Address[] addresses = new Address[numRecip];
+	int pos = 0;
+	if (to != null) {
+	    System.arraycopy(to, 0, addresses, pos, to.length);
+	    pos += to.length;
+	}
+	if (cc != null) {
+	    System.arraycopy(cc, 0, addresses, pos, cc.length);
+	    pos += cc.length;
+	}
+	if (bcc != null) {
+	    System.arraycopy(bcc, 0, addresses, pos, bcc.length);
+	    // pos += bcc.length;
+	}
+	return addresses;
+    }
+
+    /**
+     * Set the recipient addresses.  All addresses of the specified
+     * type are replaced by the addresses parameter.
+     *
+     * @param type      the recipient type
+     * @param addresses the 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.
+     * @exception       MessagingException for other failures
+     */
+    public abstract void setRecipients(RecipientType type, Address[] addresses)
+                                throws MessagingException;
+
+    /**
+     * Set the recipient address.  All addresses of the specified
+     * type are replaced by the address parameter. <p>
+     *
+     * The default implementation uses the <code>setRecipients</code> method.
+     *
+     * @param type      the recipient type
+     * @param address	the address
+     * @exception	IllegalWriteException if the underlying 
+     *			implementation does not support modification 
+     *			of existing values
+     * @exception       MessagingException for other failures
+     */
+    public void setRecipient(RecipientType type, Address address)
+                                throws MessagingException {
+	if (address == null)
+	    setRecipients(type, null);
+	else {
+	    Address[] a = new Address[1];
+	    a[0] = address;
+	    setRecipients(type, a);
+	}
+    }
+
+    /**
+     * Add these recipient addresses to the existing ones of the given type.
+     *
+     * @param type      the recipient type
+     * @param addresses the 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.
+     * @exception       MessagingException for other failures
+     */
+    public abstract void addRecipients(RecipientType type, Address[] addresses)
+                                throws MessagingException;
+
+    /**
+     * Add this recipient address to the existing ones of the given type. <p>
+     *
+     * The default implementation uses the <code>addRecipients</code> method.
+     *
+     * @param type      the recipient type
+     * @param address	the address
+     * @exception	IllegalWriteException if the underlying 
+     *			implementation does not support modification 
+     *			of existing values
+     * @exception       MessagingException for other failures
+     */
+    public void addRecipient(RecipientType type, Address address)
+                                throws MessagingException {
+	Address[] a = new Address[1];
+	a[0] = address;
+	addRecipients(type, a);
+    }
+
+    /**
+     * Get the addresses to which replies should be directed.
+     * This will usually be the sender of the message, but
+     * some messages may direct replies to a different address. <p>
+     *
+     * The default implementation simply calls the <code>getFrom</code>
+     * method. <p>
+     *
+     * This method returns <code>null</code> if the corresponding
+     * header is not present. Returns an empty array if the header
+     * is present, but contains no addresses.
+     *
+     * @return          addresses to which replies should be directed
+     * @exception       MessagingException for failures
+     * @see		#getFrom
+     */
+    public Address[] getReplyTo() throws MessagingException {
+        return getFrom();
+    }
+
+    /**
+     * Set the addresses to which replies should be directed.
+     * (Normally only a single address will be specified.)
+     * Not all message types allow this to be specified separately
+     * from the sender of the message. <p>
+     *
+     * The default implementation provided here just throws the
+     * MethodNotSupportedException.
+     *
+     * @param addresses addresses to which replies should be directed
+     * @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.
+     * @exception	MethodNotSupportedException if the underlying 
+     *			implementation does not support setting this
+     *			attribute
+     * @exception       MessagingException for other failures
+     */
+    public void setReplyTo(Address[] addresses) throws MessagingException {
+	throw new MethodNotSupportedException("setReplyTo not supported");
+    }
+
+    /**
+     * Get the subject of this message.
+     *
+     * @return          the subject
+     * @exception       MessagingException for failures
+     */
+    public abstract String getSubject() throws MessagingException;
+
+    /**
+     * Set the subject of this message.
+     *
+     * @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.
+     * @exception       MessagingException for other failures
+     */
+    public abstract void setSubject(String subject) 
+			throws MessagingException;
+
+    /**
+     * Get the date this message was sent.
+     *
+     * @return          the date this message was sent
+     * @exception       MessagingException for failures
+     */
+    public abstract Date getSentDate() throws MessagingException;
+
+    /**
+     * Set the sent date of this message.
+     *
+     * @param date      the sent date 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.
+     * @exception       MessagingException for other failures
+     */
+    public abstract void setSentDate(Date date) throws MessagingException;
+
+    /**
+     * Get the date this message was received.
+     *
+     * @return          the date this message was received
+     * @exception       MessagingException for failures
+     */
+    public abstract Date getReceivedDate() throws MessagingException;
+
+    /**
+     * Returns a <code>Flags</code> object containing the flags for 
+     * this message. <p>
+     *
+     * Modifying any of the flags in this returned Flags object will 
+     * not affect the flags of this message. Use <code>setFlags()</code>
+     * to do that. <p>
+     *
+     * @return		Flags object containing the flags for this message
+     * @see 		javax.mail.Flags
+     * @see 		#setFlags
+     * @exception       MessagingException for failures
+     */
+    public abstract Flags getFlags() throws MessagingException;
+
+    /**
+     * Check whether the flag specified in the <code>flag</code>
+     * argument is set in this message. <p>
+     *
+     * The default implementation uses <code>getFlags</code>.
+     *
+     * @param flag	the flag
+     * @return		value of the specified flag for this message
+     * @see 		javax.mail.Flags.Flag
+     * @see		javax.mail.Flags.Flag#ANSWERED
+     * @see		javax.mail.Flags.Flag#DELETED
+     * @see		javax.mail.Flags.Flag#DRAFT
+     * @see		javax.mail.Flags.Flag#FLAGGED
+     * @see		javax.mail.Flags.Flag#RECENT
+     * @see		javax.mail.Flags.Flag#SEEN
+     * @exception       MessagingException for failures
+     */
+    public boolean isSet(Flags.Flag flag) throws MessagingException {
+	return getFlags().contains(flag);
+    }
+
+    /**
+     * Set the specified flags on this message to the specified value.
+     * Note that any flags in this message that are not specified in
+     * the given <code>Flags</code> object are unaffected. <p>
+     *
+     * This will result in a <code>MessageChangedEvent</code> being 
+     * delivered to any MessageChangedListener registered on this 
+     * Message's containing folder.
+     *
+     * @param flag	Flags object containing the flags to be set
+     * @param set	the value to be 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.
+     * @exception       MessagingException for other failures
+     * @see		javax.mail.event.MessageChangedEvent
+     */
+    public abstract void setFlags(Flags flag, boolean set)
+				throws MessagingException;
+
+    /**
+     * Set the specified flag on this message to the specified value.
+     *
+     * This will result in a <code>MessageChangedEvent</code> being 
+     * delivered to any MessageChangedListener registered on this 
+     * Message's containing folder. <p>
+     *
+     * The default implementation uses the <code>setFlags</code> method.
+     *
+     * @param flag	Flags.Flag object containing the flag to be set
+     * @param set	the value to be 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.
+     * @exception       MessagingException for other failures
+     * @see		javax.mail.event.MessageChangedEvent
+     */
+    public void setFlag(Flags.Flag flag, boolean set)
+				throws MessagingException {
+	Flags f = new Flags(flag);
+	setFlags(f, set);
+    }
+
+    /**
+     * Get the Message number for this Message.
+     * A Message object's message number is the relative
+     * position of this Message in its Folder. Note that the message
+     * number for a particular Message can change during a session
+     * if other messages in the Folder are deleted and expunged. <p>
+     *
+     * Valid message numbers start at 1. Messages that do not belong
+     * to any folder (like newly composed or derived messages) have 0
+     * as their message number.
+     *
+     * @return	the message number
+     */
+    public int getMessageNumber() {
+	return msgnum;
+    }
+
+    /**
+     * Set the Message number for this Message. This method is
+     * invoked only by the implementation classes.
+     *
+     * @param	msgnum	the message number
+     */
+    protected void setMessageNumber(int msgnum) {
+	this.msgnum = msgnum;
+    }
+
+    /**
+     * Get the folder from which this message was obtained. If
+     * this is a new message or nested message, this method returns
+     * null.
+     *
+     * @return	the containing folder
+     */
+    public Folder getFolder() {
+	return folder;
+    }
+
+    /**
+     * Checks whether this message is expunged. All other methods except
+     * <code>getMessageNumber()</code> are invalid on an expunged 
+     * Message object. <p>
+     *
+     * Messages that are expunged due to an explict <code>expunge()</code>
+     * request on the containing Folder are removed from the Folder 
+     * immediately. Messages that are externally expunged by another source
+     * are marked "expunged" and return true for the isExpunged() method, 
+     * but they are not removed from the Folder until an explicit 
+     * <code>expunge()</code> is done on the Folder. <p>
+     * 
+     * See the description of <code>expunge()</code> for more details on
+     * expunge handling.
+     *
+     * @return	true if the message is expunged
+     * @see	Folder#expunge
+     */
+    public boolean isExpunged() {
+	return expunged;
+    }
+
+    /**
+     * Sets the expunged flag for this Message. This method is to
+     * be used only by the implementation classes.
+     *
+     * @param expunged	the expunged flag
+     */
+    protected void setExpunged(boolean expunged) {
+	this.expunged = expunged;
+    }
+
+    /**
+     * 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, that is, it will <strong>not</strong> 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:"). <p>
+     *
+     * The reply message will use the same session as this message.
+     *
+     * @param	replyToAll	reply should be sent to all recipients
+     *				of this message
+     * @return		the reply Message
+     * @exception	MessagingException for failures
+     */
+    public abstract Message reply(boolean replyToAll) throws MessagingException;
+
+    /**
+     * Save any changes made to this message into the message-store
+     * when the containing folder is closed, if the message is contained
+     * in a folder.  (Some implementations may save the changes
+     * immediately.)  Update any header fields to be consistent with the
+     * changed message contents.  If any part of a message's headers or
+     * contents are changed, saveChanges must be called to ensure that
+     * those changes are permanent.  If saveChanges is not called, any
+     * such modifications may or may not be saved, depending on the
+     * message store and folder implementation. <p>
+     *
+     * Messages obtained from folders opened READ_ONLY should not be
+     * modified and saveChanges should not be called on such messages.
+     *
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception	IllegalWriteException if the underlying 
+     *			implementation does not support modification 
+     *			of existing values.
+     * @exception       MessagingException for other failures
+     */
+    public abstract void saveChanges() throws MessagingException;
+
+    /**
+     * Apply the specified Search criterion to this message.
+     *
+     * @param term	the Search criterion
+     * @return		true if the Message matches this search
+     *			criterion, false otherwise.
+     * @exception       MessagingException for failures
+     * @see		javax.mail.search.SearchTerm
+     */
+    public boolean match(SearchTerm term) throws MessagingException {
+	return term.match(this);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/MessageAware.java b/mail/src/main/java/javax/mail/MessageAware.java
new file mode 100644
index 0000000..7657fcc
--- /dev/null
+++ b/mail/src/main/java/javax/mail/MessageAware.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * An interface optionally implemented by <code>DataSources</code> to
+ * supply information to a <code>DataContentHandler</code> about the
+ * message context in which the data content object is operating.
+ *
+ * @see javax.mail.MessageContext
+ * @see javax.activation.DataSource
+ * @see javax.activation.DataContentHandler
+ * @since	JavaMail 1.1
+ */
+public interface MessageAware {
+    /**
+     * Return the message context.
+     *
+     * @return	the message context
+     */
+    public MessageContext getMessageContext();
+}
diff --git a/mail/src/main/java/javax/mail/MessageContext.java b/mail/src/main/java/javax/mail/MessageContext.java
new file mode 100644
index 0000000..a1365cb
--- /dev/null
+++ b/mail/src/main/java/javax/mail/MessageContext.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * The context in which a piece of Message content is contained.  A
+ * <code>MessageContext</code> object is returned by the
+ * <code>getMessageContext</code> method of the
+ * <code>MessageAware</code> interface.  <code>MessageAware</code> is
+ * typically implemented by <code>DataSources</code> to allow a
+ * <code>DataContentHandler</code> to pass on information about the
+ * context in which a data content object is operating.
+ *
+ * @see javax.mail.MessageAware
+ * @see javax.activation.DataSource
+ * @see javax.activation.DataContentHandler
+ * @since	JavaMail 1.1
+ */
+public class MessageContext {
+    private Part part;
+
+    /**
+     * Create a MessageContext object describing the context of the given Part.
+     *
+     * @param	part	the Part
+     */
+    public MessageContext(Part part) {
+	this.part = part;
+    }
+
+    /**
+     * Return the Part that contains the content.
+     *
+     * @return	the containing Part, or null if not known
+     */
+    public Part getPart() {
+	return part;
+    }
+
+    /**
+     * Return the Message that contains the content.
+     * Follows the parent chain up through containing Multipart
+     * objects until it comes to a Message object, or null.
+     *
+     * @return	the containing Message, or null if not known
+     */
+    public Message getMessage() {
+	try {
+	    return getMessage(part);
+	} catch (MessagingException ex) {
+	    return null;
+	}
+    }
+
+    /**
+     * Return the Message containing an arbitrary Part.
+     * Follows the parent chain up through containing Multipart
+     * objects until it comes to a Message object, or null.
+     *
+     * @return	the containing Message, or null if none
+     * @see javax.mail.BodyPart#getParent
+     * @see javax.mail.Multipart#getParent
+     */
+    private static Message getMessage(Part p) throws MessagingException {
+	while (p != null) {
+	    if (p instanceof Message)
+		return (Message)p;
+	    BodyPart bp = (BodyPart)p;
+	    Multipart mp = bp.getParent();
+	    if (mp == null)	// MimeBodyPart might not be in a MimeMultipart
+		return null;
+	    p = mp.getParent();
+	}
+	return null;
+    }
+
+    /**
+     * Return the Session we're operating in.
+     *
+     * @return	the Session, or null if not known
+     */
+    public Session getSession() {
+	Message msg = getMessage();
+	return msg != null ? msg.getSession() : null;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/MessageRemovedException.java b/mail/src/main/java/javax/mail/MessageRemovedException.java
new file mode 100644
index 0000000..28cde68
--- /dev/null
+++ b/mail/src/main/java/javax/mail/MessageRemovedException.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * The exception thrown when an invalid method is invoked on an expunged
+ * Message. The only valid methods on an expunged Message are
+ * <code>isExpunged()</code> and <code>getMessageNumber()</code>.
+ *
+ * @see	   javax.mail.Message#isExpunged()
+ * @see	   javax.mail.Message#getMessageNumber()
+ * @author John Mani
+ */
+
+public class MessageRemovedException extends MessagingException {
+
+    private static final long serialVersionUID = 1951292550679528690L;
+
+    /**
+     * Constructs a MessageRemovedException with no detail message.
+     */
+    public MessageRemovedException() {
+	super();
+    }
+
+    /**
+     * Constructs a MessageRemovedException with the specified
+     * detail message.
+     *
+     * @param s		The detailed error message
+     */
+    public MessageRemovedException(String s) {
+	super(s);
+    }
+
+    /**
+     * Constructs a MessageRemovedException with the specified
+     * detail message and embedded exception.  The exception is chained
+     * to this exception.
+     *
+     * @param s		The detailed error message
+     * @param e		The embedded exception
+     * @since		JavaMail 1.5
+     */
+    public MessageRemovedException(String s, Exception e) {
+	super(s, e);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/MessagingException.java b/mail/src/main/java/javax/mail/MessagingException.java
new file mode 100644
index 0000000..803a4af
--- /dev/null
+++ b/mail/src/main/java/javax/mail/MessagingException.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.lang.*;
+
+/**
+ * The base class for all exceptions thrown by the Messaging classes
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class MessagingException extends Exception {
+
+    /**
+     * The next exception in the chain.
+     *
+     * @serial
+     */
+    private Exception next;
+
+    private static final long serialVersionUID = -7569192289819959253L;
+
+    /**
+     * Constructs a MessagingException with no detail message.
+     */
+    public MessagingException() {
+	super();
+	initCause(null);	// prevent anyone else from setting it
+    }
+
+    /**
+     * Constructs a MessagingException with the specified detail message.
+     *
+     * @param s		the detail message
+     */
+    public MessagingException(String s) {
+	super(s);
+	initCause(null);	// prevent anyone else from setting it
+    }
+
+    /**
+     * Constructs a MessagingException with the specified 
+     * Exception and detail message. The specified exception is chained
+     * to this exception.
+     *
+     * @param s		the detail message
+     * @param e		the embedded exception
+     * @see	#getNextException
+     * @see	#setNextException
+     * @see	#getCause
+     */
+    public MessagingException(String s, Exception e) {
+	super(s);
+	next = e;
+	initCause(null);	// prevent anyone else from setting it
+    }
+
+    /**
+     * Get the next exception chained to this one. If the
+     * next exception is a MessagingException, the chain
+     * may extend further.
+     *
+     * @return	next Exception, null if none.
+     */
+    public synchronized Exception getNextException() {
+	return next;
+    }
+
+    /**
+     * Overrides the <code>getCause</code> method of <code>Throwable</code>
+     * to return the next exception in the chain of nested exceptions.
+     *
+     * @return	next Exception, null if none.
+     */
+    @Override
+    public synchronized Throwable getCause() {
+	return next;
+    }
+
+    /**
+     * Add an exception to the end of the chain. If the end
+     * is <strong>not</strong> a MessagingException, this 
+     * exception cannot be added to the end.
+     *
+     * @param	ex	the new end of the Exception chain
+     * @return		<code>true</code> if this Exception
+     *			was added, <code>false</code> otherwise.
+     */
+    public synchronized boolean setNextException(Exception ex) {
+	Exception theEnd = this;
+	while (theEnd instanceof MessagingException &&
+	       ((MessagingException)theEnd).next != null) {
+	    theEnd = ((MessagingException)theEnd).next;
+	}
+	// If the end is a MessagingException, we can add this 
+	// exception to the chain.
+	if (theEnd instanceof MessagingException) {
+	    ((MessagingException)theEnd).next = ex;
+	    return true;
+	} else
+	    return false;
+    }
+
+    /**
+     * Override toString method to provide information on
+     * nested exceptions.
+     */
+    @Override
+    public synchronized String toString() {
+	String s = super.toString();
+	Exception n = next;
+	if (n == null)
+	    return s;
+	StringBuilder sb = new StringBuilder(s == null ? "" : s);
+	while (n != null) {
+	    sb.append(";\n  nested exception is:\n\t");
+	    if (n instanceof MessagingException) {
+		MessagingException mex = (MessagingException)n;
+		sb.append(mex.superToString());
+		n = mex.next;
+	    } else {
+		sb.append(n.toString());
+		n = null;
+	    }
+	}
+	return sb.toString();
+    }
+
+    /**
+     * Return the "toString" information for this exception,
+     * without any information on nested exceptions.
+     */
+    private final String superToString() {
+	return super.toString();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/MethodNotSupportedException.java b/mail/src/main/java/javax/mail/MethodNotSupportedException.java
new file mode 100644
index 0000000..bf7289f
--- /dev/null
+++ b/mail/src/main/java/javax/mail/MethodNotSupportedException.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+
+/**
+ * The exception thrown when a method is not supported by the 
+ * implementation
+ *
+ * @author John Mani
+ */
+
+public class MethodNotSupportedException extends MessagingException {
+
+    private static final long serialVersionUID = -3757386618726131322L;
+
+    /**
+     * Constructs a MethodNotSupportedException with no detail message.
+     */
+    public MethodNotSupportedException() {
+	super();
+    }
+
+    /**
+     * Constructs a MethodNotSupportedException with the specified
+     * detail message.
+     *
+     * @param s		The detailed error message
+     */
+    public MethodNotSupportedException(String s) {
+	super(s);
+    }
+
+    /**
+     * Constructs a MethodNotSupportedException with the specified
+     * detail message and embedded exception.  The exception is chained
+     * to this exception.
+     *
+     * @param s		The detailed error message
+     * @param e		The embedded exception
+     * @since		JavaMail 1.5
+     */
+    public MethodNotSupportedException(String s, Exception e) {
+	super(s, e);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Multipart.java b/mail/src/main/java/javax/mail/Multipart.java
new file mode 100644
index 0000000..d1bcc27
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Multipart.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.util.Vector;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+import javax.activation.DataSource;
+
+/**
+ * Multipart is a container that holds multiple body parts. Multipart
+ * provides methods to retrieve and set its subparts. <p>
+ * 
+ * Multipart also acts as the base class for the content object returned 
+ * by most Multipart DataContentHandlers. For example, invoking getContent()
+ * on a DataHandler whose source is a "multipart/signed" data source may
+ * return an appropriate subclass of Multipart. <p>
+ *
+ * Some messaging systems provide different subtypes of Multiparts. For
+ * example, MIME specifies a set of subtypes that include "alternative", 
+ * "mixed", "related", "parallel", "signed", etc. <p>
+ *
+ * Multipart is an abstract class.  Subclasses provide actual implementations.
+ *
+ * @author John Mani
+ */
+
+public abstract class Multipart {
+
+    /**
+     * Vector of BodyPart objects.
+     */
+    protected Vector<BodyPart> parts = new Vector<>(); // Holds BodyParts
+
+    /**
+     * This field specifies the content-type of this multipart
+     * object. It defaults to "multipart/mixed".
+     */
+    protected String contentType = "multipart/mixed"; // Content-Type
+
+    /**
+     * The <code>Part</code> containing this <code>Multipart</code>,
+     * if known.
+     * @since	JavaMail 1.1
+     */
+    protected Part parent;
+
+    /** 
+     * Default constructor. An empty Multipart object is created.
+     */
+    protected Multipart() { }
+
+    /**
+     * Setup this Multipart object from the given MultipartDataSource. <p>
+     *
+     * The method adds the MultipartDataSource's BodyPart 
+     * objects into this Multipart. This Multipart's contentType is
+     * set to that of the MultipartDataSource. <p>
+     *
+     * This method is typically used in those cases where one 
+     * has a multipart data source that has already been pre-parsed into
+     * the individual body parts (for example, an IMAP datasource), but 
+     * needs to create an appropriate Multipart subclass that represents
+     * a specific multipart subtype. 
+     * 
+     * @param	mp	Multipart datasource
+     * @exception       MessagingException for failures
+     */
+    protected synchronized void setMultipartDataSource(MultipartDataSource mp)
+			throws MessagingException {
+	contentType = mp.getContentType();
+
+	int count = mp.getCount();
+	for (int i = 0; i < count; i++)
+	    addBodyPart(mp.getBodyPart(i));
+    }
+
+    /**
+     * Return the content-type of this Multipart. <p>
+     *
+     * This implementation just returns the value of the
+     * <code>contentType</code> field.
+     *
+     * @return 	content-type
+     * @see	#contentType
+     */
+    public synchronized String getContentType() {
+	return contentType;
+    }
+
+    /**
+     * Return the number of enclosed BodyPart objects. <p>
+     *
+     * @return		number of parts
+     * @exception       MessagingException for failures
+     * @see		#parts
+     */
+    public synchronized int getCount() throws MessagingException {
+	if (parts == null)
+	    return 0;
+
+	return parts.size();
+    }
+
+    /**
+     * Get the specified Part.  Parts are numbered starting at 0.
+     *
+     * @param index	the index of the desired Part
+     * @return		the Part
+     * @exception       IndexOutOfBoundsException if the given index
+     *			is out of range.
+     * @exception       MessagingException for other failures
+     */
+    public synchronized BodyPart getBodyPart(int index)
+				throws MessagingException {
+	if (parts == null)
+	    throw new IndexOutOfBoundsException("No such BodyPart");
+
+	return parts.elementAt(index);
+    }
+
+    /**
+     * Remove the specified part from the multipart message.
+     * Shifts all the parts after the removed part down one.
+     *
+     * @param   part	The part to remove
+     * @return		true if part removed, false otherwise
+     * @exception	MessagingException if no such Part exists
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     *			of existing values
+     */
+    public synchronized boolean removeBodyPart(BodyPart part)
+				throws MessagingException {
+	if (parts == null)
+	    throw new MessagingException("No such body part");
+
+	boolean ret = parts.removeElement(part);
+	part.setParent(null);
+	return ret;
+    }
+
+    /**
+     * Remove the part at specified location (starting from 0).
+     * Shifts all the parts after the removed part down one.
+     *
+     * @param   index	Index of the part to remove
+     * @exception       IndexOutOfBoundsException if the given index
+     *			is out of range.
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     *			of existing values
+     * @exception	MessagingException for other failures
+     */
+    public synchronized void removeBodyPart(int index)
+				throws MessagingException {
+	if (parts == null)
+	    throw new IndexOutOfBoundsException("No such BodyPart");
+
+	BodyPart part = parts.elementAt(index);
+	parts.removeElementAt(index);
+	part.setParent(null);
+    }
+
+    /**
+     * Adds a Part to the multipart.  The BodyPart is appended to 
+     * the list of existing Parts.
+     *
+     * @param  part  The Part to be appended
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     *			of existing values
+     * @exception       MessagingException for other failures
+     */
+    public synchronized void addBodyPart(BodyPart part) 
+		throws MessagingException {
+	if (parts == null)
+	    parts = new Vector<>();
+
+	parts.addElement(part);
+	part.setParent(this);
+    }
+
+    /**
+     * Adds a BodyPart at position <code>index</code>.
+     * If <code>index</code> is not the last one in the list,
+     * the subsequent parts are shifted up. If <code>index</code>
+     * is larger than the number of parts present, the
+     * BodyPart is appended to the end.
+     *
+     * @param  part  The BodyPart to be inserted
+     * @param  index Location where to insert the part
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     *			of existing values
+     * @exception       MessagingException for other failures
+     */
+    public synchronized void addBodyPart(BodyPart part, int index) 
+				throws MessagingException {
+	if (parts == null)
+	    parts = new Vector<>();
+
+	parts.insertElementAt(part, index);
+	part.setParent(this);
+    }
+
+    /**
+     * Output an appropriately encoded bytestream to the given
+     * OutputStream. The implementation subclass decides the
+     * appropriate encoding algorithm to be used. The bytestream
+     * is typically used for sending.
+     * 
+     * @param	os	the stream to write to
+     * @exception       IOException if an IO related exception occurs
+     * @exception       MessagingException for other failures
+     */
+    public abstract void writeTo(OutputStream os) 
+		throws IOException, MessagingException;
+
+    /**
+     * Return the <code>Part</code> that contains this <code>Multipart</code>
+     * object, or <code>null</code> if not known.
+     *
+     * @return	the parent Part
+     * @since	JavaMail 1.1
+     */
+    public synchronized Part getParent() {
+	return parent;
+    }
+
+    /**
+     * Set the parent of this <code>Multipart</code> to be the specified
+     * <code>Part</code>.  Normally called by the <code>Message</code>
+     * or <code>BodyPart</code> <code>setContent(Multipart)</code> method.
+     * <code>parent</code> may be <code>null</code> if the
+     * <code>Multipart</code> is being removed from its containing
+     * <code>Part</code>.
+     *
+     * @param	parent	the parent Part
+     * @since	JavaMail 1.1
+     */
+    public synchronized void setParent(Part parent) {
+	this.parent = parent;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/MultipartDataSource.java b/mail/src/main/java/javax/mail/MultipartDataSource.java
new file mode 100644
index 0000000..0d68363
--- /dev/null
+++ b/mail/src/main/java/javax/mail/MultipartDataSource.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import javax.activation.DataSource;
+
+/**
+ * MultipartDataSource is a <code>DataSource</code> that contains body
+ * parts.  This allows "mail aware" <code>DataContentHandlers</code> to
+ * be implemented more efficiently by being aware of such
+ * <code>DataSources</code> and using the appropriate methods to access
+ * <code>BodyParts</code>. <p>
+ *
+ * Note that the data of a MultipartDataSource is also available as
+ * an input stream. <p>
+ *
+ * This interface will typically be implemented by providers that
+ * preparse multipart bodies, for example an IMAP provider.
+ *
+ * @author	John Mani
+ * @see		javax.activation.DataSource
+ */
+
+public interface MultipartDataSource extends DataSource {
+
+    /**
+     * Return the number of enclosed BodyPart objects.
+     *
+     * @return          number of parts
+     */
+    public int getCount();
+
+    /**
+     * Get the specified Part.  Parts are numbered starting at 0.
+     *
+     * @param index     the index of the desired Part
+     * @return          the Part
+     * @exception       IndexOutOfBoundsException if the given index
+     *			is out of range.
+     * @exception       MessagingException for other failures
+     */
+    public BodyPart getBodyPart(int index) throws MessagingException;
+
+}
diff --git a/mail/src/main/java/javax/mail/NoSuchProviderException.java b/mail/src/main/java/javax/mail/NoSuchProviderException.java
new file mode 100644
index 0000000..f94527d
--- /dev/null
+++ b/mail/src/main/java/javax/mail/NoSuchProviderException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This exception is thrown when Session attempts to instantiate a  
+ * Provider that doesn't exist.
+ * 
+ * @author Max Spivak
+ */
+
+public class NoSuchProviderException extends MessagingException {
+
+    private static final long serialVersionUID = 8058319293154708827L;
+    
+    /**
+     * Constructs a NoSuchProviderException with no detail message.
+     */
+    public NoSuchProviderException() {
+	super();
+    }
+
+    /**
+     * Constructs a NoSuchProviderException with the specified
+     * detail message.
+     *
+     * @param message	The detailed error message
+     */
+    public NoSuchProviderException(String message) {
+	super(message);
+    }
+
+    /**
+     * Constructs a NoSuchProviderException with the specified
+     * detail message and embedded exception.  The exception is chained
+     * to this exception.
+     *
+     * @param message	The detailed error message
+     * @param e		The embedded exception
+     * @since		JavaMail 1.5
+     */
+    public NoSuchProviderException(String message, Exception e) {
+	super(message, e);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Part.java b/mail/src/main/java/javax/mail/Part.java
new file mode 100644
index 0000000..c8e5fe3
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Part.java
@@ -0,0 +1,451 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.io.*;
+import java.util.Enumeration;
+import javax.activation.DataHandler;
+
+/**
+ * The <code>Part</code> interface is the common base interface for 
+ * Messages and BodyParts. <p>
+ *
+ * Part consists of a set of attributes and a "Content".<p>
+ *
+ * <strong> Attributes: </strong> <p>
+ *
+ * The JavaMail API defines a set of standard Part attributes that are
+ * considered to be common to most existing Mail systems. These
+ * attributes have their own settor and gettor methods. Mail systems 
+ * may support other Part attributes as well, these are represented as 
+ * name-value pairs where both the name and value are Strings.<p>
+ *
+ * <strong> Content: </strong> <p>
+ *
+ * The <strong>data type</strong> of the "content" is returned by
+ * the <code>getContentType()</code> method. The MIME typing system
+ * is used to name data types. <p>
+ *
+ * The "content" of a Part is available in various formats:
+ * <ul>
+ * <li> As a DataHandler - using the <code>getDataHandler()</code> method.
+ * The "content" of a Part is also available through a 
+ * <code>javax.activation.DataHandler</code> object. The DataHandler 
+ * object allows clients to discover the operations available on the
+ * content, and to instantiate the appropriate component to perform
+ * those operations. 
+ *
+ * <li> As an input stream - using the <code>getInputStream()</code> method.
+ * Any mail-specific encodings are decoded before this stream is returned.
+ *
+ * <li> As a Java object - using the <code>getContent()</code> method.
+ * This method returns the "content" as a Java object.
+ * The returned object is of course dependent on the content
+ * itself. In particular, a "multipart" Part's content is always a 
+ * Multipart or subclass thereof.  That is, <code>getContent()</code> on a 
+ * "multipart" type Part will always return a Multipart (or subclass) object.
+ * </ul>
+ *
+ * Part provides the <code>writeTo()</code> method that streams
+ * out its bytestream in mail-safe form suitable for transmission. 
+ * This bytestream is typically an aggregation of the Part attributes
+ * and its content's bytestream. <p>
+ *
+ * Message and BodyPart implement the Part interface. Note that in
+ * MIME parlance, Part models an Entity (RFC 2045, Section 2.4).
+ *
+ * @author John Mani
+ */
+
+public interface Part {
+
+    /**
+     * Return the size of the content of this part in bytes.
+     * Return -1 if the size cannot be determined. <p>
+     *
+     * Note that the size may not be an exact measure of the content
+     * size and may or may not account for any transfer encoding
+     * of the content. The size is appropriate for display in a 
+     * user interface to give the user a rough idea of the size
+     * of this part.
+     *
+     * @return		size of content in bytes
+     * @exception	MessagingException for failures
+     */
+    public int getSize() throws MessagingException;
+
+    /**
+     * Return the number of lines in the content of this part. 
+     * Return -1 if the number cannot be determined.
+     *
+     * 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. 
+     *
+     * @return		number of lines in the content.
+     * @exception	MessagingException for failures
+     */
+    public int getLineCount() throws MessagingException;
+
+    /**
+     * Returns the Content-Type of the content of this part.
+     * Returns null if the Content-Type could not be determined. <p>
+     *
+     * The MIME typing system is used to name Content-types.
+     *
+     * @return		The ContentType of this part
+     * @exception	MessagingException for failures
+     * @see		javax.activation.DataHandler
+     */
+    public String getContentType() throws MessagingException;
+
+    /**
+     * Is this Part of the specified MIME type?  This method
+     * compares <strong>only the <code>primaryType</code> and 
+     * <code>subType</code></strong>.
+     * The parameters of the content types are ignored. <p>
+     *
+     * For example, this method will return <code>true</code> when
+     * comparing a Part of content type <strong>"text/plain"</strong>
+     * with <strong>"text/plain; charset=foobar"</strong>. <p>
+     *
+     * If the <code>subType</code> of <code>mimeType</code> is the
+     * special character '*', then the subtype is ignored during the
+     * comparison.
+     *
+     * @param	mimeType	the MIME type to test
+     * @return	true if this part is of the specified type
+     * @exception	MessagingException for failures
+     */
+    public boolean isMimeType(String mimeType) throws MessagingException;
+
+    /**
+     * This part should be presented as an attachment.
+     * @see #getDisposition
+     * @see #setDisposition
+     */
+    public static final String ATTACHMENT = "attachment";
+
+    /**
+     * This part should be presented inline.
+     * @see #getDisposition
+     * @see #setDisposition
+     */
+    public static final String INLINE = "inline";
+
+    /**
+     * Return the disposition of this part.  The disposition
+     * describes how the part should be presented to the user.
+     * (See RFC 2183.)  The return value should be considered
+     * without regard to case.  For example:
+     * <blockquote><pre>
+     * String disp = part.getDisposition();
+     * if (disp == null || disp.equalsIgnoreCase(Part.ATTACHMENT))
+     *	// treat as attachment if not first part
+     * </pre></blockquote>
+     *
+     * @return		disposition of this part, or null if unknown
+     * @exception	MessagingException for failures
+     * @see #ATTACHMENT
+     * @see #INLINE
+     * @see #getFileName
+     */
+    public String getDisposition() throws MessagingException;
+
+    /**
+     * Set the disposition of this part.
+     *
+     * @param	disposition	disposition of this part
+     * @exception	IllegalWriteException if the underlying implementation
+     *			does not support modification of this header
+     * @exception	IllegalStateException if this Part is obtained
+     *			from a READ_ONLY folder
+     * @exception	MessagingException for other failures
+     * @see #ATTACHMENT
+     * @see #INLINE
+     * @see #setFileName
+     */
+    public void setDisposition(String disposition) throws MessagingException;
+
+    /**
+     * Return a description String for this part. This typically
+     * associates some descriptive information with this part.
+     * Returns null if none is available.
+     *
+     * @return		description of this part
+     * @exception	MessagingException for failures
+     */
+    public String getDescription() throws MessagingException;
+
+    /**
+     * Set a description String for this part. This typically
+     * associates some descriptive information with this part.
+     *
+     * @param	description	description of this part
+     * @exception	IllegalWriteException if the underlying implementation
+     *			does not support modification of this header
+     * @exception	IllegalStateException if this Part is obtained
+     *			from a READ_ONLY folder
+     * @exception	MessagingException for other failures
+     */
+    public void setDescription(String description) throws MessagingException;
+
+    /**
+     * Get the filename associated with this part, if possible.
+     * Useful if this part represents an "attachment" that was
+     * loaded from a file.  The filename will usually be a simple
+     * name, not including directory components.
+     *
+     * @return	Filename to associate with this part
+     * @exception	MessagingException for failures
+     */
+    public String getFileName() throws MessagingException;
+
+    /**
+     * Set the filename associated with this part, if possible.
+     * Useful if this part represents an "attachment" that was
+     * loaded from a file.  The filename will usually be a simple
+     * name, not including directory components.
+     *
+     * @param	filename	Filename to associate with this part
+     * @exception	IllegalWriteException if the underlying implementation
+     *			does not support modification of this header
+     * @exception	IllegalStateException if this Part is obtained
+     *			from a READ_ONLY folder
+     * @exception	MessagingException for other failures
+     */
+    public void setFileName(String filename) throws MessagingException;
+
+    /**
+     * Return an input stream for this part's "content". Any 
+     * mail-specific transfer encodings will be decoded before the 
+     * input stream is provided. <p>
+     *
+     * This is typically a convenience method that just invokes
+     * the DataHandler's <code>getInputStream()</code> method.
+     *
+     * @return an InputStream
+     * @exception	IOException this is typically thrown by the 
+     *			DataHandler. Refer to the documentation for 
+     *			javax.activation.DataHandler for more details.
+     * @exception	MessagingException for other failures
+     * @see #getDataHandler
+     * @see javax.activation.DataHandler#getInputStream
+     */
+    public InputStream getInputStream() 
+		throws IOException, MessagingException;
+    
+    /**
+     * Return a DataHandler for the content within this part. The
+     * DataHandler allows clients to operate on as well as retrieve
+     * the content.
+     *
+     * @return		DataHandler for the content
+     * @exception 	MessagingException for failures
+     */
+    public DataHandler getDataHandler() throws MessagingException;
+
+    /**
+     * Return the content as a Java object. The type of the returned 
+     * object is of course dependent on the content itself. For example,
+     * the object returned for "text/plain" content is usually a String 
+     * object. The object returned for a "multipart" content 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 is a convenience method that just invokes the DataHandler's
+     * getContent() method
+     *
+     * @return		Object
+     * @exception	IOException this is typically thrown by the 
+     *			DataHandler. Refer to the documentation for 
+     *			javax.activation.DataHandler for more details.
+     * @exception 	MessagingException for other failures
+     *
+     * @see javax.activation.DataHandler#getContent
+     */
+    public Object getContent() throws IOException, MessagingException;
+
+    /**
+     * This method provides the mechanism to set this part's content.
+     * The DataHandler wraps around 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 Part is obtained
+     *			from a READ_ONLY folder
+     * @exception 	MessagingException for other failures
+     */
+    public void setDataHandler(DataHandler dh) throws MessagingException;
+
+    /**
+     * A convenience method for setting this part's content.  The part
+     * internally wraps the content in a DataHandler. <p>
+     *
+     * 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	obj	A java object.
+     * @param	type	MIME type of this object.
+     * @exception	IllegalWriteException if the underlying implementation
+     *			does not support modification of existing values
+     * @exception	IllegalStateException if this Part is obtained
+     *			from a READ_ONLY folder
+     * @exception 	MessagingException for other failures
+     */
+    public void setContent(Object obj, String type) 
+			throws MessagingException;
+
+    /**
+     * A convenience method that sets the given String as this
+     * part's content with a MIME type of "text/plain". 
+     *
+     * @param  text    	The text that is the Message's content.
+     * @exception	IllegalWriteException if the underlying 
+     *			implementation does not support modification of 
+     *			existing values
+     * @exception	IllegalStateException if this Part is obtained
+     *			from a READ_ONLY folder
+     * @exception 	MessagingException for other failures
+     */
+    public void setText(String text) throws MessagingException;
+
+    /**
+     * This method sets the given Multipart object as this message's
+     * content.
+     *
+     * @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 Part is obtained
+     *			from a READ_ONLY folder
+     * @exception 	MessagingException for other failures
+     */
+    public void setContent(Multipart mp) throws MessagingException;
+
+    /**
+     * Output a bytestream for this Part. This bytestream is
+     * typically an aggregration of the Part attributes and
+     * an appropriately encoded bytestream from its 'content'. <p>
+     *
+     * Classes that implement the Part interface decide on
+     * the appropriate encoding algorithm to be used. <p>
+     *
+     * The bytestream is typically used for sending.
+     *
+     * @param	os	the stream to write to
+     * @exception IOException		if an error occurs writing to the 
+     *					stream or if an error is generated
+     *					by the javax.activation layer.
+     * @exception MessagingException	if an error occurs fetching the
+     *					data to be written
+     *
+     * @see javax.activation.DataHandler#writeTo
+     */
+    public void writeTo(OutputStream os) throws IOException, MessagingException;
+
+    /**
+     * Get all the headers for this header name. Returns <code>null</code>
+     * if no headers for this header name are available.
+     *
+     * @param header_name       the name of this header
+     * @return                  the value fields for all headers with 
+     *				this name
+     * @exception       	MessagingException for failures
+     */
+    public String[] getHeader(String header_name)
+				throws MessagingException;
+    
+    /**
+     * Set the value for this header_name. Replaces all existing
+     * header values with this new value.
+     *
+     * @param header_name       the name of this header
+     * @param header_value      the value for this header
+     * @exception		IllegalWriteException if the underlying 
+     *				implementation does not support modification 
+     *				of existing values
+     * @exception		IllegalStateException if this Part is 
+     *				obtained from a READ_ONLY folder
+     * @exception       	MessagingException for other failures
+     */
+    public void setHeader(String header_name, String header_value)
+				throws MessagingException;
+    /**
+     * Add this value to the existing values for this header_name.
+     *
+     * @param header_name       the name of this header
+     * @param header_value      the value for this header
+     * @exception		IllegalWriteException if the underlying 
+     *				implementation does not support modification 
+     *				of existing values
+     * @exception		IllegalStateException if this Part is 
+     *				obtained from a READ_ONLY folder
+     * @exception       	MessagingException for other failures
+     */
+    public void addHeader(String header_name, String header_value)
+				throws MessagingException;
+    /**
+     * Remove all headers with this name.
+     *
+     * @param header_name       the name of this header
+     * @exception		IllegalWriteException if the underlying 
+     *				implementation does not support modification 
+     *				of existing values
+     * @exception		IllegalStateException if this Part is 
+     *				obtained from a READ_ONLY folder
+     * @exception       	MessagingException for other failures
+     */
+    public void removeHeader(String header_name)
+				throws MessagingException;
+
+    /**
+     * Return all the headers from this part as an Enumeration of
+     * Header objects.
+     *
+     * @return  enumeration of Header objects
+     * @exception       MessagingException for failures
+     */
+    public Enumeration<Header> getAllHeaders() throws MessagingException;
+
+    /**
+     * Return matching headers from this part as an Enumeration of
+     * Header objects.
+     *
+     * @param	header_names	the headers to match
+     * @return  enumeration of Header objects
+     * @exception       MessagingException for failures
+     */
+    public Enumeration<Header> getMatchingHeaders(String[] header_names)
+				throws MessagingException;
+
+    /**
+     * Return non-matching headers from this envelope as an Enumeration
+     * of Header objects.
+     *
+     * @param	header_names	the headers to not match
+     * @return  enumeration of Header objects
+     * @exception       MessagingException for failures
+     */
+    public Enumeration<Header> getNonMatchingHeaders(String[] header_names) 
+				throws MessagingException;
+}
diff --git a/mail/src/main/java/javax/mail/PasswordAuthentication.java b/mail/src/main/java/javax/mail/PasswordAuthentication.java
new file mode 100644
index 0000000..8654ee0
--- /dev/null
+++ b/mail/src/main/java/javax/mail/PasswordAuthentication.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+
+/**
+ * The class PasswordAuthentication is a data holder that is used by
+ * Authenticator.  It is simply a repository for a user name and a password.
+ *
+ * @see java.net.PasswordAuthentication
+ * @see javax.mail.Authenticator
+ * @see javax.mail.Authenticator#getPasswordAuthentication()
+ *
+ * @author  Bill Foote
+ */
+
+public final class PasswordAuthentication {
+
+    private final String userName;
+    private final String password;
+
+    /** 
+     * Initialize a new PasswordAuthentication
+     * @param userName the user name
+     * @param password The user's password
+     */
+    public PasswordAuthentication(String userName, String password) {
+	this.userName = userName;
+	this.password = password;
+    }
+
+    /**
+     * @return the user name
+     */
+    public String getUserName() {
+	return userName;
+    }
+
+    /**
+     * @return the password
+     */
+    public String getPassword() {
+	return password;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Provider.java b/mail/src/main/java/javax/mail/Provider.java
new file mode 100644
index 0000000..6889875
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Provider.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * The Provider is a class that describes a protocol 
+ * implementation.  The values typically come from the
+ * javamail.providers and javamail.default.providers
+ * resource files.  An application may also create and
+ * register a Provider object to dynamically add support
+ * for a new provider.
+ *
+ * @author Max Spivak
+ * @author Bill Shannon
+ */
+public class Provider {
+
+    /**
+     * This inner class defines the Provider type.
+     * Currently, STORE and TRANSPORT are the only two provider types 
+     * supported.
+     */
+
+    public static class Type {
+	public static final Type STORE     = new Type("STORE");
+	public static final Type TRANSPORT = new Type("TRANSPORT");
+
+	private String type;
+
+	private Type(String type) {
+	    this.type = type;
+	}
+
+	@Override
+	public String toString() {
+	    return type;
+	}
+    }
+
+    private Type type;
+    private String protocol, className, vendor, version;
+
+    /**
+     * Create a new provider of the specified type for the specified
+     * protocol.  The specified class implements the provider.
+     *
+     * @param type      Type.STORE or Type.TRANSPORT
+     * @param protocol  valid protocol for the type
+     * @param classname class name that implements this protocol
+     * @param vendor    optional string identifying the vendor (may be null)
+     * @param version   optional implementation version string (may be null)
+     * @since JavaMail 1.4
+     */
+    public Provider(Type type, String protocol, String classname, 
+	     String vendor, String version) {
+	this.type = type;
+	this.protocol = protocol;
+	this.className = classname;
+	this.vendor = vendor;
+	this.version = version;
+    }
+
+    /**
+     * Returns the type of this Provider.
+     *
+     * @return	the provider type
+     */
+    public Type getType() {
+	return type;
+    }
+
+    /**
+     * Returns the protocol supported by this Provider.
+     *
+     * @return	the protocol
+     */
+    public String getProtocol() {
+	return protocol;
+    }
+
+    /**
+     * Returns the name of the class that implements the protocol.
+     *
+     * @return	the class name
+     */
+    public String getClassName() {
+	return className;
+    }
+
+    /**
+     * Returns the name of the vendor associated with this implementation
+     * or null.
+     *
+     * @return	the vendor
+     */
+    public String getVendor() {
+	return vendor;
+    }
+
+    /**
+     * Returns the version of this implementation or null if no version.
+     *
+     * @return	the version
+     */
+    public String getVersion() {
+	return version;
+    }
+
+    /** Overrides Object.toString() */
+    @Override
+    public String toString() {
+	String s = "javax.mail.Provider[" + type + "," +
+		    protocol + "," + className;
+
+	if (vendor != null)
+	    s += "," + vendor;
+
+	if (version != null)
+	    s += "," + version;
+
+	s += "]";
+	return s;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Quota.java b/mail/src/main/java/javax/mail/Quota.java
new file mode 100644
index 0000000..13da8b9
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Quota.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This class represents a set of quotas for a given quota root.
+ * Each quota root has a set of resources, represented by the
+ * <code>Quota.Resource</code> class.  Each resource has a name
+ * (for example, "STORAGE"), a current usage, and a usage limit.
+ * See RFC 2087.
+ *
+ * @since JavaMail 1.4
+ * @author  Bill Shannon
+ */
+
+public class Quota {
+
+    /**
+     * An individual resource in a quota root.
+     *
+     * @since JavaMail 1.4
+     */
+    public static class Resource {
+	/** The name of the resource. */
+	public String name;
+	/** The current usage of the resource. */
+	public long usage;
+	/** The usage limit for the resource. */
+	public long limit;
+
+	/**
+	 * Construct a Resource object with the given name,
+	 * usage, and limit.
+	 *
+	 * @param	name	the resource name
+	 * @param	usage	the current usage of the resource
+	 * @param	limit	the usage limit for the resource
+	 */
+	public Resource(String name, long usage, long limit) {
+	    this.name = name;
+	    this.usage = usage;
+	    this.limit = limit;
+	}
+    }
+
+    /**
+     * The name of the quota root.
+     */
+    public String quotaRoot;
+
+    /**
+     * The set of resources associated with this quota root.
+     */
+    public Quota.Resource[] resources;
+
+    /**
+     * Create a Quota object for the named quotaroot with no associated
+     * resources.
+     *
+     * @param	quotaRoot	the name of the quota root
+     */
+    public Quota(String quotaRoot) {
+	this.quotaRoot = quotaRoot;
+    }
+
+    /**
+     * Set a resource limit for this quota root.
+     *
+     * @param	name	the name of the resource
+     * @param	limit	the resource limit
+     */
+    public void setResourceLimit(String name, long limit) {
+	if (resources == null) {
+	    resources = new Quota.Resource[1];
+	    resources[0] = new Quota.Resource(name, 0, limit);
+	    return;
+	}
+	for (int i = 0; i < resources.length; i++) {
+	    if (resources[i].name.equalsIgnoreCase(name)) {
+		resources[i].limit = limit;
+		return;
+	    }
+	}
+	Quota.Resource[] ra = new Quota.Resource[resources.length + 1];
+	System.arraycopy(resources, 0, ra, 0, resources.length);
+	ra[ra.length - 1] = new Quota.Resource(name, 0, limit);
+	resources = ra;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/QuotaAwareStore.java b/mail/src/main/java/javax/mail/QuotaAwareStore.java
new file mode 100644
index 0000000..a6f566d
--- /dev/null
+++ b/mail/src/main/java/javax/mail/QuotaAwareStore.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * An interface implemented by Stores that support quotas.
+ * The {@link #getQuota getQuota} and {@link #setQuota setQuota} methods
+ * support the quota model defined by the IMAP QUOTA extension.
+ * Refer to <A HREF="http://www.ietf.org/rfc/rfc2087.txt">RFC 2087</A>
+ * for more information. <p>
+ *
+ * @since JavaMail 1.4
+ */
+public interface QuotaAwareStore {
+    /**
+     * Get the quotas for the named folder.
+     * Quotas are controlled on the basis of a quota root, not
+     * (necessarily) a folder.  The relationship between folders
+     * and quota roots depends on the server.  Some servers
+     * might implement a single quota root for all folders owned by
+     * a user.  Other servers might implement a separate quota root
+     * for each folder.  A single folder can even have multiple
+     * quota roots, perhaps controlling quotas for different
+     * resources.
+     *
+     * @param	folder	the name of the folder
+     * @return		array of Quota objects
+     * @exception MessagingException	if the server doesn't support the
+     *					QUOTA extension
+     */
+    Quota[] getQuota(String folder) throws MessagingException;
+
+    /**
+     * Set the quotas for the quota root specified in the quota argument.
+     * Typically this will be one of the quota roots obtained from the
+     * <code>getQuota</code> method, but it need not be.
+     *
+     * @param	quota	the quota to set
+     * @exception MessagingException	if the server doesn't support the
+     *					QUOTA extension
+     */
+    void setQuota(Quota quota) throws MessagingException;
+}
diff --git a/mail/src/main/java/javax/mail/ReadOnlyFolderException.java b/mail/src/main/java/javax/mail/ReadOnlyFolderException.java
new file mode 100644
index 0000000..f658b5c
--- /dev/null
+++ b/mail/src/main/java/javax/mail/ReadOnlyFolderException.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This exception is thrown when an attempt is made to open a folder
+ * read-write access when the folder is marked read-only. <p>
+ *
+ * The getMessage() method returns more detailed information about the
+ * error that caused this exception. <p>
+ *
+ * @author Jim Glennon
+ */
+
+public class ReadOnlyFolderException extends MessagingException {
+    transient private Folder folder;
+
+    private static final long serialVersionUID = 5711829372799039325L;
+    
+    /**
+     * Constructs a ReadOnlyFolderException with the specified
+     * folder and no detail message.
+     *
+     * @param folder	the Folder
+     * @since 		JavaMail 1.2
+     */
+    public ReadOnlyFolderException(Folder folder) {
+	this(folder, null);
+    }
+
+    /**
+     * Constructs a ReadOnlyFolderException with the specified
+     * detail message.
+     *
+     * @param folder 	The Folder
+     * @param message	The detailed error message
+     * @since 		JavaMail 1.2
+     */
+    public ReadOnlyFolderException(Folder folder, String message) {
+	super(message);
+	this.folder = folder;
+    }
+
+    /**
+     * Constructs a ReadOnlyFolderException with the specified
+     * detail message and embedded exception.  The exception is chained
+     * to this exception.
+     *
+     * @param folder 	The Folder
+     * @param message	The detailed error message
+     * @param e		The embedded exception
+     * @since		JavaMail 1.5
+     */
+    public ReadOnlyFolderException(Folder folder, String message, Exception e) {
+	super(message, e);
+	this.folder = folder;
+    }
+
+    /**
+     * Returns the Folder object.
+     *
+     * @return	the Folder
+     * @since 		JavaMail 1.2
+     */
+    public Folder getFolder() {
+	return folder;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/SendFailedException.java b/mail/src/main/java/javax/mail/SendFailedException.java
new file mode 100644
index 0000000..ed73480
--- /dev/null
+++ b/mail/src/main/java/javax/mail/SendFailedException.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This exception is thrown when the message cannot be sent.<p>
+ * 
+ * The exception includes those addresses to which the message could not be
+ * sent as well as the valid addresses to which the message was sent and
+ * valid addresses to which the message was not sent.
+ *
+ * @see	javax.mail.Transport#send
+ * @see	javax.mail.Transport#sendMessage
+ * @see	javax.mail.event.TransportEvent
+ *
+ * @author John Mani
+ * @author Max Spivak
+ */
+
+public class SendFailedException extends MessagingException {
+    transient protected Address[] invalid;
+    transient protected Address[] validSent;
+    transient protected Address[] validUnsent;
+
+    private static final long serialVersionUID = -6457531621682372913L;
+
+    /**
+     * Constructs a SendFailedException with no detail message.
+     */
+    public SendFailedException() {
+	super();
+    }
+
+    /**
+     * Constructs a SendFailedException with the specified detail message.
+     * @param s		the detail message
+     */
+    public SendFailedException(String s) {
+	super(s);
+    }
+
+    /**
+     * Constructs a SendFailedException with the specified 
+     * Exception and detail message. The specified exception is chained
+     * to this exception.
+     * @param s		the detail message
+     * @param e		the embedded exception
+     * @see	#getNextException
+     * @see	#setNextException
+     */
+    public SendFailedException(String s, Exception e) {
+	super(s, e);
+    }
+
+
+    /**
+     * Constructs a SendFailedException with the specified string
+     * and the specified address objects.
+     *
+     * @param msg	the detail message
+     * @param ex        the embedded exception
+     * @param validSent valid addresses to which message was sent
+     * @param validUnsent valid addresses to which message was not sent
+     * @param invalid 	the invalid addresses
+     * @see	#getNextException
+     * @see	#setNextException
+     */
+    public SendFailedException(String msg, Exception ex, Address[] validSent, 
+			       Address[] validUnsent, Address[] invalid) {
+	super(msg, ex);
+	this.validSent = validSent;
+	this.validUnsent = validUnsent;
+	this.invalid = invalid;
+    }
+
+    /**
+     * Return the addresses to which this message was sent succesfully.
+     * @return Addresses to which the message was sent successfully or null
+     */
+    public Address[] getValidSentAddresses() {
+	return validSent;
+    }
+
+    /**
+     * Return the addresses that are valid but to which this message 
+     * was not sent.
+     * @return Addresses that are valid but to which the message was 
+     *         not sent successfully or null
+     */
+    public Address[] getValidUnsentAddresses() {
+	return validUnsent;
+    }
+
+    /**
+     * Return the addresses to which this message could not be sent.
+     *
+     * @return Addresses to which the message sending failed or null;
+     */
+    public Address[] getInvalidAddresses() {
+	return invalid;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Service.java b/mail/src/main/java/javax/mail/Service.java
new file mode 100644
index 0000000..5c9e20c
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Service.java
@@ -0,0 +1,657 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.Executor;
+import javax.mail.event.*;
+
+/**
+ * An abstract class that contains the functionality
+ * common to messaging services, such as stores and transports. <p>
+ * A messaging service is created from a <code>Session</code> and is
+ * named using a <code>URLName</code>.  A service must be connected
+ * before it can be used.  Connection events are sent to reflect
+ * its connection status.
+ *
+ * @author Christopher Cotton
+ * @author Bill Shannon
+ * @author Kanwar Oberoi
+ */
+
+public abstract class Service implements AutoCloseable {
+
+    /**
+     * The session from which this service was created.
+     */
+    protected Session	session;
+
+    /**
+     * The <code>URLName</code> of this service.
+     */
+    protected volatile URLName	url = null;
+
+    /**
+     * Debug flag for this service.  Set from the session's debug
+     * flag when this service is created.
+     */
+    protected boolean	debug = false;
+
+    private boolean	connected = false;
+
+    /*
+     * connectionListeners is a Vector, initialized here,
+     * because we depend on it always existing and depend
+     * on the synchronization that Vector provides.
+     * (Sychronizing on the Service object itself can cause
+     * deadlocks when notifying listeners.)
+     */
+    private final Vector<ConnectionListener> connectionListeners
+	    = new Vector<>();
+
+    /**
+     * The queue of events to be delivered.
+     */
+    private final EventQueue q;
+
+    /**
+     * Constructor.
+     *
+     * @param	session Session object for this service
+     * @param	urlname	URLName object to be used for this service
+     */
+    protected Service(Session session, URLName urlname) {
+	this.session = session;
+	debug = session.getDebug();
+	url = urlname;
+
+	/*
+	 * Initialize the URLName with default values.
+	 * The URLName will be updated when connect is called.
+	 */
+	String protocol = null;
+	String host = null;
+	int port = -1;
+	String user = null;
+	String password = null;
+	String file = null;
+
+	// get whatever information we can from the URL
+	// XXX - url should always be non-null here, Session
+	//       passes it into the constructor
+	if (url != null) {
+	    protocol = url.getProtocol();
+	    host = url.getHost();
+	    port = url.getPort();
+	    user = url.getUsername();
+	    password = url.getPassword();
+	    file = url.getFile();
+	}
+
+	// try to get protocol-specific default properties
+	if (protocol != null) {
+	    if (host == null)
+		host = session.getProperty("mail." + protocol + ".host");
+	    if (user == null)
+		user = session.getProperty("mail." + protocol + ".user");
+	}
+
+	// try to get mail-wide default properties
+	if (host == null)
+	    host = session.getProperty("mail.host");
+
+	if (user == null)
+	    user = session.getProperty("mail.user");
+
+	// try using the system username
+	if (user == null) {
+	    try {
+		user = System.getProperty("user.name");
+	    } catch (SecurityException sex) {
+		// XXX - it's not worth creating a MailLogger just for this
+		//logger.log(Level.CONFIG, "Can't get user.name property", sex);
+	    }
+	}
+
+	url = new URLName(protocol, host, port, file, user, password);
+
+	// create or choose the appropriate event queue
+	String scope =
+	    session.getProperties().getProperty("mail.event.scope", "folder");
+	Executor executor =
+		(Executor)session.getProperties().get("mail.event.executor");
+	if (scope.equalsIgnoreCase("application"))
+	    q = EventQueue.getApplicationEventQueue(executor);
+	else if (scope.equalsIgnoreCase("session"))
+	    q = session.getEventQueue();
+	else // if (scope.equalsIgnoreCase("store") ||
+	     //     scope.equalsIgnoreCase("folder"))
+	    q = new EventQueue(executor);
+    }
+
+    /**
+     * A generic connect method that takes no parameters. Subclasses
+     * can implement the appropriate authentication schemes. Subclasses
+     * that need additional information might want to use some properties
+     * or might get it interactively using a popup window. <p>
+     *
+     * If the connection is successful, an "open" <code>ConnectionEvent</code>
+     * is delivered to any <code>ConnectionListeners</code> on this service. <p>
+     *
+     * Most clients should just call this method to connect to the service.<p>
+     *
+     * It is an error to connect to an already connected service. <p>
+     *
+     * The implementation provided here simply calls the following
+     * <code>connect(String, String, String)</code> method with nulls.
+     *
+     * @exception AuthenticationFailedException	for authentication failures
+     * @exception MessagingException	for other failures
+     * @exception IllegalStateException	if the service is already connected
+     *
+     * @see javax.mail.event.ConnectionEvent
+     */
+    public void connect() throws MessagingException {
+	connect(null, null, null);
+    }
+
+    /**
+     * Connect to the specified address. This method provides a simple
+     * authentication scheme that requires a username and password. <p>
+     *
+     * If the connection is successful, an "open" <code>ConnectionEvent</code>
+     * is delivered to any <code>ConnectionListeners</code> on this service. <p>
+     *
+     * It is an error to connect to an already connected service. <p>
+     *
+     * The implementation in the Service class will collect defaults
+     * for the host, user, and password from the session, from the
+     * <code>URLName</code> for this service, and from the supplied
+     * parameters and then call the <code>protocolConnect</code> method.
+     * If the <code>protocolConnect</code> method returns <code>false</code>,
+     * the user will be prompted for any missing information and the
+     * <code>protocolConnect</code> method will be called again.  The
+     * subclass should override the <code>protocolConnect</code> method.
+     * The subclass should also implement the <code>getURLName</code>
+     * method, or use the implementation in this class. <p>
+     *
+     * On a successful connection, the <code>setURLName</code> method is
+     * called with a URLName that includes the information used to make
+     * the connection, including the password. <p>
+     *
+     * If the username passed in is null, a default value will be chosen
+     * as described above.
+     *
+     * If the password passed in is null and this is the first successful
+     * connection to this service, the user name and the password
+     * collected from the user will be saved as defaults for subsequent
+     * connection attempts to this same service when using other Service object
+     * instances (the connection information is typically always saved within
+     * a particular Service object instance).  The password is saved using the
+     * Session method <code>setPasswordAuthentication</code>.  If the
+     * password passed in is not null, it is not saved, on the assumption
+     * that the application is managing passwords explicitly.
+     *
+     * @param host 	the host to connect to
+     * @param user	the user name
+     * @param password	this user's password
+     * @exception AuthenticationFailedException	for authentication failures
+     * @exception MessagingException		for other failures
+     * @exception IllegalStateException	if the service is already connected
+     * @see javax.mail.event.ConnectionEvent
+     * @see javax.mail.Session#setPasswordAuthentication
+     */
+    public void connect(String host, String user, String password)
+			throws MessagingException {
+	connect(host, -1, user, password);
+    }
+
+    /**
+     * Connect to the current host using the specified username
+     * and password.  This method is equivalent to calling the
+     * <code>connect(host, user, password)</code> method with null
+     * for the host name.
+     *
+     * @param user      the user name
+     * @param password  this user's password
+     * @exception AuthenticationFailedException for authentication failures
+     * @exception MessagingException            for other failures
+     * @exception IllegalStateException if the service is already connected
+     * @see javax.mail.event.ConnectionEvent
+     * @see javax.mail.Session#setPasswordAuthentication
+     * @see #connect(java.lang.String, java.lang.String, java.lang.String)
+     * @since           JavaMail 1.4
+     */
+    public void connect(String user, String password)
+	    throws MessagingException {
+        connect(null, user, password);
+    }
+
+    /**
+     * Similar to connect(host, user, password) except a specific port
+     * can be specified.
+     *
+     * @param host 	the host to connect to
+     * @param port	the port to connect to (-1 means the default port)
+     * @param user	the user name
+     * @param password	this user's password
+     * @exception AuthenticationFailedException	for authentication failures
+     * @exception MessagingException		for other failures
+     * @exception IllegalStateException	if the service is already connected
+     * @see #connect(java.lang.String, java.lang.String, java.lang.String)
+     * @see javax.mail.event.ConnectionEvent
+     */
+    public synchronized void connect(String host, int port,
+		String user, String password) throws MessagingException {
+
+	// see if the service is already connected
+	if (isConnected())
+	    throw new IllegalStateException("already connected");
+
+	PasswordAuthentication pw;
+	boolean connected = false;
+	boolean save = false;
+	String protocol = null;
+	String file = null;
+
+	// get whatever information we can from the URL
+	// XXX - url should always be non-null here, Session
+	//       passes it into the constructor
+	if (url != null) {
+	    protocol = url.getProtocol();
+	    if (host == null)
+		host = url.getHost();
+	    if (port == -1)
+		port = url.getPort();
+
+	    if (user == null) {
+		user = url.getUsername();
+		if (password == null)	// get password too if we need it
+		    password = url.getPassword();
+	    } else {
+		if (password == null && user.equals(url.getUsername()))
+		    // only get the password if it matches the username
+		    password = url.getPassword();
+	    }
+
+	    file = url.getFile();
+	}
+
+	// try to get protocol-specific default properties
+	if (protocol != null) {
+	    if (host == null)
+		host = session.getProperty("mail." + protocol + ".host");
+	    if (user == null)
+		user = session.getProperty("mail." + protocol + ".user");
+	}
+
+	// try to get mail-wide default properties
+	if (host == null)
+	    host = session.getProperty("mail.host");
+
+	if (user == null)
+	    user = session.getProperty("mail.user");
+
+	// try using the system username
+	if (user == null) {
+	    try {
+		user = System.getProperty("user.name");
+	    } catch (SecurityException sex) {
+		// XXX - it's not worth creating a MailLogger just for this
+		//logger.log(Level.CONFIG, "Can't get user.name property", sex);
+	    }
+	}
+
+	// if we don't have a password, look for saved authentication info
+	if (password == null && url != null) {
+	    // canonicalize the URLName
+	    setURLName(new URLName(protocol, host, port, file, user, null));
+	    pw = session.getPasswordAuthentication(getURLName());
+	    if (pw != null) {
+		if (user == null) {
+		    user = pw.getUserName();
+		    password = pw.getPassword();
+		} else if (user.equals(pw.getUserName())) {
+		    password = pw.getPassword();
+		}
+	    } else
+		save = true;
+	}
+
+	// try connecting, if the protocol needs some missing
+	// information (user, password) it will not connect.
+	// if it tries to connect and fails, remember why for later.
+	AuthenticationFailedException authEx = null;
+	try {
+	    connected = protocolConnect(host, port, user, password);
+	} catch (AuthenticationFailedException ex) {
+	    authEx = ex;
+	}
+
+	// if not connected, ask the user and try again
+	if (!connected) {
+	    InetAddress addr;
+	    try {
+		addr = InetAddress.getByName(host);
+	    } catch (UnknownHostException e) {
+		addr = null;
+	    }
+	    pw = session.requestPasswordAuthentication(
+			    addr, port,
+			    protocol,
+			    null, user);
+	    if (pw != null) {
+		user = pw.getUserName();
+		password = pw.getPassword();
+
+		// have the service connect again
+		connected = protocolConnect(host, port, user, password);
+	    }
+	}
+
+	// if we're not connected by now, we give up
+	if (!connected) {
+	    if (authEx != null)
+		throw authEx;
+	    else if (user == null)
+		throw new AuthenticationFailedException(
+			"failed to connect, no user name specified?");
+	    else if (password == null)
+		throw new AuthenticationFailedException(
+			"failed to connect, no password specified?");
+	    else
+		throw new AuthenticationFailedException("failed to connect");
+	}
+
+	setURLName(new URLName(protocol, host, port, file, user, password));
+
+	if (save)
+	    session.setPasswordAuthentication(getURLName(),
+			    new PasswordAuthentication(user, password));
+
+	// set our connected state
+	setConnected(true);
+
+	// finally, deliver the connection event
+	notifyConnectionListeners(ConnectionEvent.OPENED);
+    }
+
+
+    /**
+     * The service implementation should override this method to
+     * perform the actual protocol-specific connection attempt.
+     * The default implementation of the <code>connect</code> method
+     * calls this method as needed. <p>
+     *
+     * The <code>protocolConnect</code> method should return
+     * <code>false</code> if a user name or password is required
+     * for authentication but the corresponding parameter is null;
+     * the <code>connect</code> method will prompt the user when
+     * needed to supply missing information.  This method may
+     * also return <code>false</code> if authentication fails for
+     * the supplied user name or password.  Alternatively, this method
+     * may throw an AuthenticationFailedException when authentication
+     * fails.  This exception may include a String message with more
+     * detail about the failure. <p>
+     *
+     * The <code>protocolConnect</code> method should throw an
+     * exception to report failures not related to authentication,
+     * such as an invalid host name or port number, loss of a
+     * connection during the authentication process, unavailability
+     * of the server, etc.
+     *
+     * @param	host		the name of the host to connect to
+     * @param	port		the port to use (-1 means use default port)
+     * @param	user		the name of the user to login as
+     * @param	password	the user's password
+     * @return	true if connection successful, false if authentication failed
+     * @exception AuthenticationFailedException	for authentication failures
+     * @exception MessagingException	for non-authentication failures
+     */
+    protected boolean protocolConnect(String host, int port, String user,
+				String password) throws MessagingException {
+	return false;
+    }
+
+    /**
+     * Is this service currently connected? <p>
+     *
+     * This implementation uses a private boolean field to 
+     * store the connection state. This method returns the value
+     * of that field. <p>
+     *
+     * Subclasses may want to override this method to verify that any
+     * connection to the message store is still alive.
+     *
+     * @return	true if the service is connected, false if it is not connected
+     */
+    public synchronized boolean isConnected() {
+	return connected;
+    }
+
+    /**
+     * Set the connection state of this service.  The connection state
+     * will automatically be set by the service implementation during the
+     * <code>connect</code> and <code>close</code> methods.
+     * Subclasses will need to call this method to set the state
+     * if the service was automatically disconnected. <p>
+     *
+     * The implementation in this class merely sets the private field
+     * returned by the <code>isConnected</code> method.
+     *
+     * @param connected true if the service is connected,
+     *                  false if it is not connected
+     */
+    protected synchronized void setConnected(boolean connected) {
+	this.connected = connected;
+    }
+
+    /**
+     * Close this service and terminate its connection. A close
+     * ConnectionEvent is delivered to any ConnectionListeners. Any
+     * Messaging components (Folders, Messages, etc.) belonging to this
+     * service are invalid after this service is closed. Note that the service
+     * is closed even if this method terminates abnormally by throwing
+     * a MessagingException. <p>
+     *
+     * This implementation uses <code>setConnected(false)</code> to set
+     * this service's connected state to <code>false</code>. It will then
+     * send a close ConnectionEvent to any registered ConnectionListeners.
+     * Subclasses overriding this method to do implementation specific
+     * cleanup should call this method as a last step to insure event
+     * notification, probably by including a call to <code>super.close()</code>
+     * in a <code>finally</code> clause.
+     *
+     * @see javax.mail.event.ConnectionEvent
+     * @throws	MessagingException	for errors while closing
+     */
+    public synchronized void close() throws MessagingException {
+	setConnected(false);
+	notifyConnectionListeners(ConnectionEvent.CLOSED);
+    }
+
+    /**
+     * Return a URLName representing this service.  The returned URLName
+     * does <em>not</em> include the password field.  <p>
+     *
+     * Subclasses should only override this method if their
+     * URLName does not follow the standard format. <p>
+     *
+     * The implementation in the Service class returns (usually a copy of)
+     * the <code>url</code> field with the password and file information
+     * stripped out.
+     *
+     * @return	the URLName representing this service
+     * @see	URLName
+     */
+    public URLName getURLName() {
+	URLName url = this.url;	// snapshot
+	if (url != null && (url.getPassword() != null || url.getFile() != null))
+	    return new URLName(url.getProtocol(), url.getHost(),
+			url.getPort(), null /* no file */,
+			url.getUsername(), null /* no password */);
+	else
+	    return url;
+    }
+
+    /**
+     * Set the URLName representing this service.
+     * Normally used to update the <code>url</code> field
+     * after a service has successfully connected. <p>
+     *
+     * Subclasses should only override this method if their
+     * URL does not follow the standard format.  In particular,
+     * subclasses should override this method if their URL
+     * does not require all the possible fields supported by
+     * <code>URLName</code>; a new <code>URLName</code> should
+     * be constructed with any unneeded fields removed. <p>
+     *
+     * The implementation in the Service class simply sets the
+     * <code>url</code> field.
+     *
+     * @param	url	the URLName
+     * @see URLName
+     */
+    protected void setURLName(URLName url) {
+	this.url = url;
+    }
+
+    /**
+     * Add a listener for Connection events on this service. <p>
+     *
+     * The default implementation provided here adds this listener
+     * to an internal list of ConnectionListeners.
+     *
+     * @param l         the Listener for Connection events
+     * @see             javax.mail.event.ConnectionEvent
+     */
+    public void addConnectionListener(ConnectionListener l) {
+	connectionListeners.addElement(l);
+    }
+
+    /**
+     * Remove a Connection event listener. <p>
+     *
+     * The default implementation provided here removes this listener
+     * from the internal list of ConnectionListeners.
+     *
+     * @param l         the listener
+     * @see             #addConnectionListener
+     */
+    public void removeConnectionListener(ConnectionListener l) {
+	connectionListeners.removeElement(l);
+    }
+
+    /**
+     * Notify all ConnectionListeners. Service implementations are
+     * expected to use this method to broadcast connection events. <p>
+     *
+     * The provided default implementation queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the registered
+     * ConnectionListeners. Note that the event dispatching occurs
+     * in a separate thread, thus avoiding potential deadlock problems.
+     *
+     * @param	type	the ConnectionEvent type
+     */
+    protected void notifyConnectionListeners(int type) {
+	/*
+	 * Don't bother queuing an event if there's no listeners.
+	 * Yes, listeners could be removed after checking, which
+	 * just makes this an expensive no-op.
+	 */
+	if (connectionListeners.size() > 0) {
+	    ConnectionEvent e = new ConnectionEvent(this, type);
+	    queueEvent(e, connectionListeners);
+	}
+
+        /* Fix for broken JDK1.1.x Garbage collector :
+         *  The 'conservative' GC in JDK1.1.x occasionally fails to
+         *  garbage-collect Threads which are in the wait state.
+         *  This would result in thread (and consequently memory) leaks.
+         *
+         * We attempt to fix this by sending a 'terminator' event
+         * to the queue, after we've sent the CLOSED event. The
+         * terminator event causes the event-dispatching thread to
+         * self destruct.
+         */
+        if (type == ConnectionEvent.CLOSED)
+            q.terminateQueue();
+    }
+
+    /**
+     * Return <code>getURLName.toString()</code> if this service has a URLName,
+     * otherwise it will return the default <code>toString</code>.
+     */
+    @Override
+    public String toString() {
+	URLName url = getURLName();
+	if (url != null)
+	    return url.toString();
+	else
+	    return super.toString();
+    }
+
+    /**
+     * Add the event and vector of listeners to the queue to be delivered.
+     *
+     * @param	event	the event
+     * @param	vector	the vector of listeners
+     */
+    protected void queueEvent(MailEvent event,
+	    Vector<? extends EventListener> vector) {
+	/*
+         * Copy the vector in order to freeze the state of the set
+         * of EventListeners the event should be delivered to prior
+         * to delivery.  This ensures that any changes made to the
+         * Vector from a target listener's method during the delivery
+         * of this event will not take effect until after the event is
+         * delivered.
+         */
+	@SuppressWarnings("unchecked")
+	Vector<? extends EventListener> v = (Vector)vector.clone();
+	q.enqueue(event, v);
+    }
+
+    /**
+     * Stop the event dispatcher thread so the queue can be garbage collected.
+     */
+    @Override
+    protected void finalize() throws Throwable {
+	try {
+	    q.terminateQueue();
+	} finally {
+	    super.finalize();
+	}
+    }
+
+    /**
+     * Package private method to allow Folder to get the Session for a Store.
+     */
+    Session getSession() {
+	return session;
+    }
+
+    /**
+     * Package private method to allow Folder to get the EventQueue for a Store.
+     */
+    EventQueue getEventQueue() {
+	return q;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Session.java b/mail/src/main/java/javax/mail/Session.java
new file mode 100644
index 0000000..534143e
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Session.java
@@ -0,0 +1,1421 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.lang.reflect.*;
+import java.io.*;
+import java.net.*;
+import java.security.*;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.ServiceLoader;
+import java.util.logging.Level;
+import java.util.concurrent.Executor;
+
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.MailLogger;
+
+/**
+ * The Session class represents a mail session and is not subclassed.
+ * It collects together properties and defaults used by the mail API's.
+ * A single default session can be shared by multiple applications on the
+ * desktop.  Unshared sessions can also be created. <p>
+ *
+ * The Session class provides access to the protocol providers that
+ * implement the <code>Store</code>, <code>Transport</code>, and related
+ * classes.  The protocol providers are configured using the following files:
+ * <ul>
+ *  <li> <code>javamail.providers</code> and
+ * 	<code>javamail.default.providers</code> </li>
+ *  <li> <code>javamail.address.map</code> and
+ * 	<code>javamail.default.address.map</code> </li>
+ * </ul>
+ * <p>
+ * Each <code>javamail.</code><i>X</i> resource file is searched for using
+ * three methods in the following order:
+ * <ol>
+ *  <li> <code><i>java.home</i>/<i>conf</i>/javamail.</code><i>X</i> </li>
+ *  <li> <code>META-INF/javamail.</code><i>X</i> </li>
+ *  <li> <code>META-INF/javamail.default.</code><i>X</i> </li>
+ * </ol>
+ * <p>
+ * (Where <i>java.home</i> is the value of the "java.home" System property
+ * and <i>conf</i> is the directory named "conf" if it exists,
+ * otherwise the directory named "lib"; the "conf" directory was
+ * introduced in JDK 1.9.)
+ * <p>
+ * The first method allows the user to include their own version of the
+ * resource file by placing it in the <i>conf</i> directory where the
+ * <code>java.home</code> property points.  The second method allows an
+ * application that uses the JavaMail APIs to include their own resource
+ * files in their application's or jar file's <code>META-INF</code>
+ * directory.  The <code>javamail.default.</code><i>X</i> default files
+ * are part of the JavaMail <code>mail.jar</code> file and should not be
+ * supplied by users. <p>
+ *
+ * File location depends upon how the <code>ClassLoader</code> method
+ * <code>getResource</code> is implemented.  Usually, the
+ * <code>getResource</code> method searches through CLASSPATH until it
+ * finds the requested file and then stops. <p>
+ *
+ * The ordering of entries in the resource files matters.  If multiple
+ * entries exist, the first entries take precedence over the later
+ * entries.  For example, the first IMAP provider found will be set as the
+ * default IMAP implementation until explicitly changed by the
+ * application.  The user- or system-supplied resource files augment, they
+ * do not override, the default files included with the JavaMail APIs.
+ * This means that all entries in all files loaded will be available. <p>
+ *
+ * <b><code>javamail.providers</code></b> and
+ * <b><code>javamail.default.providers</code></b><p>
+ *
+ * These resource files specify the stores and transports that are
+ * available on the system, allowing an application to "discover" what
+ * store and transport implementations are available.  The protocol
+ * implementations are listed one per line.  The file format defines four
+ * attributes that describe a protocol implementation.  Each attribute is
+ * an "="-separated name-value pair with the name in lowercase. Each
+ * name-value pair is semi-colon (";") separated.  The following names
+ * are defined.
+ *
+ * <table border=1>
+ * <caption>
+ * Attribute Names in Providers Files
+ * </caption>
+ * <tr>
+ * <th>Name</th><th>Description</th>
+ * </tr>
+ * <tr>
+ * <td>protocol</td>
+ * <td>Name assigned to protocol.
+ * For example, <code>smtp</code> for Transport.</td>
+ * </tr>
+ * <tr>
+ * <td>type</td>
+ * <td>Valid entries are <code>store</code> and <code>transport</code>.</td>
+ * </tr>
+ * <tr>
+ * <td>class</td>
+ * <td>Class name that implements this protocol.</td>
+ * </tr>
+ * <tr>
+ * <td>vendor</td>
+ * <td>Optional string identifying the vendor.</td>
+ * </tr>
+ * <tr>
+ * <td>version</td>
+ * <td>Optional string identifying the version.</td>
+ * </tr>
+ * </table><p>
+ *
+ * Here's an example of <code>META-INF/javamail.default.providers</code>
+ * file contents:
+ * <pre>
+ * protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Oracle;
+ * protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=Oracle;
+ * </pre><p>
+ *
+ * The current implementation also supports configuring providers using
+ * the Java SE {@link java.util.ServiceLoader ServiceLoader} mechanism.
+ * When creating your own provider, create a {@link Provider} subclass,
+ * for example:
+ * <pre>
+ * package com.example;
+ *
+ * import javax.mail.Provider;
+ *
+ * public class MyProvider extends Provider {
+ *     public MyProvider() {
+ *         super(Provider.Type.STORE, "myprot", MyStore.class.getName(),
+ *             "Example", null);
+ *     }
+ * }
+ * </pre>
+ * Then include a file named <code>META-INF/services/javax.mail.Provider</code>
+ * in your jar file that lists the name of your Provider class:
+ * <pre>
+ * com.example.MyProvider
+ * </pre>
+ * <p>
+ *
+ * <b><code>javamail.address.map</code></b> and
+ * <b><code>javamail.default.address.map</code></b><p>
+ *
+ * These resource files map transport address types to the transport
+ * protocol.  The <code>getType</code> method of
+ * <code>javax.mail.Address</code> returns the address type.  The
+ * <code>javamail.address.map</code> file maps the transport type to the
+ * protocol.  The file format is a series of name-value pairs.  Each key
+ * name should correspond to an address type that is currently installed
+ * on the system; there should also be an entry for each
+ * <code>javax.mail.Address</code> implementation that is present if it is
+ * to be used.  For example, the
+ * <code>javax.mail.internet.InternetAddress</code> method
+ * <code>getType</code> returns "rfc822". Each referenced protocol should
+ * be installed on the system.  For the case of <code>news</code>, below,
+ * the client should install a Transport provider supporting the nntp
+ * protocol. <p>
+ *
+ * Here are the typical contents of a <code>javamail.address.map</code> file:
+ * <pre>
+ * rfc822=smtp
+ * news=nntp
+ * </pre>
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ * @author Max Spivak
+ */
+
+public final class Session {
+
+    private final Properties props;
+    private final Authenticator authenticator;
+    private final Hashtable<URLName, PasswordAuthentication> authTable
+	    = new Hashtable<>();
+    private boolean debug = false;
+    private PrintStream out;			// debug output stream
+    private MailLogger logger;
+    private List<Provider> providers;
+    private final Map<String, Provider> providersByProtocol = new HashMap<>();
+    private final Map<String, Provider> providersByClassName = new HashMap<>();
+    private final Properties addressMap = new Properties();
+						// maps type to protocol
+    private boolean loadedProviders;	// javamail.[default.]providers loaded?
+    // the queue of events to be delivered, if mail.event.scope===session
+    private final EventQueue q;
+
+    // The default session.
+    private static Session defaultSession = null;
+
+    private static final String confDir;
+
+    static {
+	String dir = null;
+	try {
+	    dir = AccessController.doPrivileged(
+		new PrivilegedAction<String>() {
+		    @Override
+		    public String run() {
+			String home = System.getProperty("java.home");
+			String newdir = home + File.separator + "conf";
+			File conf = new File(newdir);
+			if (conf.exists())
+			    return newdir + File.separator;
+			else
+			    return home + File.separator +
+				    "lib" + File.separator;
+		    }
+		});
+	} catch (Exception ex) {
+	    // ignore any exceptions
+	}
+	confDir = dir;
+    }
+
+    // Constructor is not public
+    private Session(Properties props, Authenticator authenticator) {
+	this.props = props;
+	this.authenticator = authenticator;
+
+	if (Boolean.valueOf(props.getProperty("mail.debug")).booleanValue())
+	    debug = true;
+
+	initLogger();
+	logger.log(Level.CONFIG, "JavaMail version {0}", Version.version);
+
+	// get the Class associated with the Authenticator
+	Class<?> cl;
+	if (authenticator != null)
+	    cl = authenticator.getClass();
+	else
+	    cl = this.getClass();
+	// load the resources
+	loadAddressMap(cl);
+	q = new EventQueue((Executor)props.get("mail.event.executor"));
+    }
+
+    private final synchronized void initLogger() {
+	logger = new MailLogger(this.getClass(), "DEBUG", debug, getDebugOut());
+    }
+
+    /**
+     * Get a new Session object.
+     *
+     * @param	props	Properties object that hold relevant properties.<br>
+     *                  It is expected that the client supplies values
+     *                  for the properties listed in Appendix A of the
+     *                  JavaMail spec (particularly  mail.store.protocol, 
+     *                  mail.transport.protocol, mail.host, mail.user, 
+     *                  and mail.from) as the defaults are unlikely to 
+     *                  work in all cases.
+     * @param	authenticator Authenticator object used to call back to
+     *			the application when a user name and password is
+     *			needed.
+     * @return		a new Session object
+     * @see	javax.mail.Authenticator
+     */
+    public static Session getInstance(Properties props,
+					Authenticator authenticator) {
+	return new Session(props, authenticator);
+    }
+
+    /**
+     * Get a new Session object.
+     *
+     * @param	props	Properties object that hold relevant properties.<br>
+     *                  It is expected that the client supplies values
+     *                  for the properties listed in Appendix A of the
+     *                  JavaMail spec (particularly  mail.store.protocol, 
+     *                  mail.transport.protocol, mail.host, mail.user, 
+     *                  and mail.from) as the defaults are unlikely to 
+     *                  work in all cases.
+     * @return		a new Session object
+     * @since		JavaMail 1.2
+     */
+    public static Session getInstance(Properties props) {
+	return new Session(props, null);
+    }
+
+    /**
+     * Get the default Session object. If a default has not yet been
+     * setup, a new Session object is created and installed as the 
+     * default. <p>
+     *
+     * Since the default session is potentially available to all
+     * code executing in the same Java virtual machine, and the session
+     * can contain security sensitive information such as user names
+     * and passwords, access to the default session is restricted.
+     * The Authenticator object, which must be created by the caller,
+     * is used indirectly to check access permission.  The Authenticator
+     * object passed in when the session is created is compared with
+     * the Authenticator object passed in to subsequent requests to
+     * get the default session.  If both objects are the same, or are
+     * from the same ClassLoader, the request is allowed.  Otherwise,
+     * it is denied.  <p>
+     *
+     * Note that if the Authenticator object used to create the session
+     * is null, anyone can get the default session by passing in null.  <p>
+     *
+     * Note also that the Properties object is used only the first time
+     * this method is called, when a new Session object is created.
+     * Subsequent calls return the Session object that was created by the
+     * first call, and ignore the passed Properties object.  Use the
+     * <code>getInstance</code> method to get a new Session object every
+     * time the method is called. <p>
+     *
+     * Additional security Permission objects may be used to
+     * control access to the default session. <p>
+     *
+     * In the current implementation, if a SecurityManager is set, the
+     * caller must have the <code>RuntimePermission("setFactory")</code>
+     * permission.
+     *
+     * @param	props	Properties object. Used only if a new Session
+     *			object is created.<br>
+     *                  It is expected that the client supplies values
+     *                  for the properties listed in Appendix A of the
+     *                  JavaMail spec (particularly  mail.store.protocol, 
+     *                  mail.transport.protocol, mail.host, mail.user, 
+     *                  and mail.from) as the defaults are unlikely to 
+     *                  work in all cases.
+     * @param	authenticator Authenticator object.  Used only if a
+     *			new Session object is created.  Otherwise, 
+     *			it must match the Authenticator used to create
+     *			the Session.
+     * @return		the default Session object
+     */
+    public static synchronized Session getDefaultInstance(Properties props,
+					Authenticator authenticator) {
+	if (defaultSession == null) {
+	    SecurityManager security = System.getSecurityManager();
+	    if (security != null)
+		security.checkSetFactory();
+	    defaultSession = new Session(props, authenticator);
+	} else {
+	    // have to check whether caller is allowed to see default session
+	    if (defaultSession.authenticator == authenticator)
+		;	// either same object or both null, either way OK
+	    else if (defaultSession.authenticator != null &&
+		    authenticator != null &&
+		    defaultSession.authenticator.getClass().getClassLoader() ==
+			authenticator.getClass().getClassLoader())
+		;	// both objects came from the same class loader, OK
+	    else
+		// anything else is not allowed
+		throw new SecurityException("Access to default session denied");
+	}
+
+	return defaultSession;
+    }
+
+    /**
+     * Get the default Session object. If a default has not yet been
+     * setup, a new Session object is created and installed as the 
+     * default. <p>
+     *
+     * Note that a default session created with no Authenticator is
+     * available to all code executing in the same Java virtual
+     * machine, and the session can contain security sensitive
+     * information such as user names and passwords.
+     *
+     * @param	props	Properties object. Used only if a new Session
+     *			object is created.<br>
+     *                  It is expected that the client supplies values
+     *                  for the properties listed in Appendix A of the
+     *                  JavaMail spec (particularly  mail.store.protocol, 
+     *                  mail.transport.protocol, mail.host, mail.user, 
+     *                  and mail.from) as the defaults are unlikely to 
+     *                  work in all cases.
+     * @return		the default Session object
+     * @since		JavaMail 1.2
+     */
+    public static Session getDefaultInstance(Properties props) {
+        return getDefaultInstance(props, null);
+    }
+
+    /**
+     * Set the debug setting for this Session.
+     * <p>
+     * Since the debug setting can be turned on only after the Session
+     * has been created, to turn on debugging in the Session
+     * constructor, set the property <code>mail.debug</code> in the
+     * Properties object passed in to the constructor to true.  The
+     * value of the <code>mail.debug</code> property is used to
+     * initialize the per-Session debugging flag.  Subsequent calls to
+     * the <code>setDebug</code> method manipulate the per-Session
+     * debugging flag and have no affect on the <code>mail.debug</code>
+     * property.
+     *
+     * @param debug	Debug setting
+     */
+    public synchronized void setDebug(boolean debug) {
+	this.debug = debug;
+	initLogger();
+	logger.log(Level.CONFIG, "setDebug: JavaMail version {0}",
+				    Version.version);
+    }
+
+    /**
+     * Get the debug setting for this Session.
+     *
+     * @return current debug setting
+     */
+    public synchronized boolean getDebug() {
+	return debug;
+    }
+
+    /**
+     * Set the stream to be used for debugging output for this session.
+     * If <code>out</code> is null, <code>System.out</code> will be used.
+     * Note that debugging output that occurs before any session is created,
+     * as a result of setting the <code>mail.debug</code> system property,
+     * will always be sent to <code>System.out</code>.
+     *
+     * @param	out	the PrintStream to use for debugging output
+     * @since		JavaMail 1.3
+     */
+    public synchronized void setDebugOut(PrintStream out) {
+	this.out = out;
+	initLogger();
+    }
+
+    /**
+     * Returns the stream to be used for debugging output.  If no stream
+     * has been set, <code>System.out</code> is returned.
+     *
+     * @return		the PrintStream to use for debugging output
+     * @since		JavaMail 1.3
+     */
+    public synchronized PrintStream getDebugOut() {
+	if (out == null)
+	    return System.out;
+	else
+	    return out;
+    }
+
+    /**
+     * This method returns an array of all the implementations installed 
+     * via the javamail.[default.]providers files that can
+     * be loaded using the ClassLoader available to this application.
+     *
+     * @return Array of configured providers
+     */
+    public synchronized Provider[] getProviders() {
+	List<Provider> plist = new ArrayList<Provider>();
+	boolean needFallback = true;
+	// first, add all the services
+	ServiceLoader<Provider> loader = ServiceLoader.load(Provider.class);
+	for (Provider p : loader) {
+	    plist.add(p);
+	    needFallback = false;
+	}
+	// then, add all the providers from config files
+	if (!loadedProviders)
+	    loadProviders(needFallback);
+	if (providers != null)
+	    plist.addAll(providers);
+	Provider[] _providers = new Provider[plist.size()];
+	plist.toArray(_providers);
+	return _providers;
+    }
+
+    /**
+     * Returns the default Provider for the protocol
+     * specified. Checks mail.&lt;protocol&gt;.class property
+     * first and if it exists, returns the Provider
+     * associated with this implementation. If it doesn't exist, 
+     * returns the Provider that appeared first in the 
+     * configuration files. If an implementation for the protocol 
+     * isn't found, throws NoSuchProviderException
+     *
+     * @param  protocol  Configured protocol (i.e. smtp, imap, etc)
+     * @return Currently configured Provider for the specified protocol
+     * @exception	NoSuchProviderException If a provider for the given
+     *			protocol is not found.
+     */
+    public synchronized Provider getProvider(String protocol)
+	                                throws NoSuchProviderException {
+
+	if (protocol == null || protocol.length() <= 0) {
+	    throw new NoSuchProviderException("Invalid protocol: null");
+	}
+
+	Provider _provider = null;
+
+	// check if the mail.<protocol>.class property exists
+	String _className = props.getProperty("mail."+protocol+".class");
+	if (_className != null) {
+	    if (logger.isLoggable(Level.FINE)) {
+		logger.fine("mail."+protocol+
+				   ".class property exists and points to " + 
+				   _className);
+	    }
+	    _provider = getProviderByClassName(_className);
+	} 
+
+	if (_provider == null)
+	    _provider = getProviderByProtocol(protocol);
+
+	if (_provider == null) {
+	    throw new NoSuchProviderException("No provider for " + protocol);
+	} else {
+	    if (logger.isLoggable(Level.FINE)) {
+		logger.fine("getProvider() returning " + _provider.toString());
+	    }
+	    return _provider;
+	}
+    }
+
+    /**
+     * Set the passed Provider to be the default implementation
+     * for the protocol in Provider.protocol overriding any previous values.
+     *
+     * @param provider Currently configured Provider which will be 
+     * set as the default for the protocol
+     * @exception	NoSuchProviderException If the provider passed in
+     *			is invalid.
+     */
+    public synchronized void setProvider(Provider provider)
+				throws NoSuchProviderException {
+	if (provider == null) {
+	    throw new NoSuchProviderException("Can't set null provider");
+	}
+	providersByProtocol.put(provider.getProtocol(), provider);
+	providersByClassName.put(provider.getClassName(), provider);
+	props.put("mail." + provider.getProtocol() + ".class", 
+		  provider.getClassName());
+    }
+
+
+    /**
+     * Get a Store object that implements this user's desired Store
+     * protocol. The <code>mail.store.protocol</code> property specifies the
+     * desired protocol. If an appropriate Store object is not obtained, 
+     * NoSuchProviderException is thrown
+     *
+     * @return 		a Store object 
+     * @exception	NoSuchProviderException If a provider for the given
+     *			protocol is not found.
+     */
+    public Store getStore() throws NoSuchProviderException {
+	return getStore(getProperty("mail.store.protocol"));
+    }
+
+    /**
+     * Get a Store object that implements the specified protocol. If an
+     * appropriate Store object cannot be obtained, 
+     * NoSuchProviderException is thrown.
+     *
+     * @param	        protocol	the Store protocol
+     * @return		a Store object 
+     * @exception	NoSuchProviderException If a provider for the given
+     *			protocol is not found.
+     */
+    public Store getStore(String protocol) throws NoSuchProviderException {
+	return getStore(new URLName(protocol, null, -1, null, null, null));
+    }
+
+
+    /**
+     * Get a Store object for the given URLName. If the requested Store
+     * object cannot be obtained, NoSuchProviderException is thrown.
+     *
+     * The "scheme" part of the URL string (Refer RFC 1738) is used 
+     * to locate the Store protocol. <p>
+     *
+     * @param	url	URLName that represents the desired Store
+     * @return		a closed Store object
+     * @see		#getFolder(URLName)
+     * @see		javax.mail.URLName
+     * @exception	NoSuchProviderException If a provider for the given
+     *			URLName is not found.
+     */
+    public Store getStore(URLName url) throws NoSuchProviderException {
+	String protocol = url.getProtocol();
+	Provider p = getProvider(protocol);
+	return getStore(p, url);
+    }
+
+    /**
+     * Get an instance of the store specified by Provider. Instantiates
+     * the store and returns it.
+     * 
+     * @param provider Store Provider that will be instantiated
+     * @return Instantiated Store
+     * @exception	NoSuchProviderException If a provider for the given
+     *			Provider is not found.
+     */
+    public Store getStore(Provider provider) throws NoSuchProviderException {
+	return getStore(provider, null);
+    }
+
+
+    /**
+     * Get an instance of the store specified by Provider. If the URLName
+     * is not null, uses it, otherwise creates a new one. Instantiates
+     * the store and returns it. This is a private method used by
+     * getStore(Provider) and getStore(URLName)
+     * 
+     * @param provider Store Provider that will be instantiated
+     * @param url      URLName used to instantiate the Store
+     * @return Instantiated Store
+     * @exception	NoSuchProviderException If a provider for the given
+     *			Provider/URLName is not found.
+     */
+    private Store getStore(Provider provider, URLName url) 
+	throws NoSuchProviderException {
+
+	// make sure we have the correct type of provider
+	if (provider == null || provider.getType() != Provider.Type.STORE ) {
+	    throw new NoSuchProviderException("invalid provider");
+	}
+
+	return getService(provider, url, Store.class);
+    }
+
+    /**
+     * Get a closed Folder object for the given URLName. If the requested
+     * Folder object cannot be obtained, null is returned. <p>
+     *
+     * The "scheme" part of the URL string (Refer RFC 1738) is used
+     * to locate the Store protocol. The rest of the URL string (that is,
+     * the "schemepart", as per RFC 1738) is used by that Store
+     * in a protocol dependent manner to locate and instantiate the
+     * appropriate Folder object. <p>
+     *
+     * Note that RFC 1738 also specifies the syntax for the 
+     * "schemepart" for IP-based protocols (IMAP4, POP3, etc.).
+     * Providers of IP-based mail Stores should implement that
+     * syntax for referring to Folders. <p>
+     *
+     * @param	url	URLName that represents the desired folder
+     * @return		Folder
+     * @see		#getStore(URLName)
+     * @see		javax.mail.URLName
+     * @exception	NoSuchProviderException If a provider for the given
+     *			URLName is not found.
+     * @exception	MessagingException if the Folder could not be 
+     *			located or created.
+     */
+    public Folder getFolder(URLName url)
+		throws MessagingException {
+	// First get the Store
+	Store store = getStore(url);
+	store.connect();
+	return store.getFolder(url);
+    }
+
+    /**
+     * Get a Transport object that implements this user's desired 
+     * Transport protcol. The <code>mail.transport.protocol</code> property 
+     * specifies the desired protocol. If an appropriate Transport 
+     * object cannot be obtained, MessagingException is thrown.
+     *
+     * @return 		a Transport object 
+     * @exception	NoSuchProviderException If the provider is not found.
+     */
+    public Transport getTransport() throws NoSuchProviderException {
+	String prot = getProperty("mail.transport.protocol");
+	if (prot != null)
+	    return getTransport(prot);
+	// if the property isn't set, use the protocol for "rfc822"
+	prot = (String)addressMap.get("rfc822");
+	if (prot != null)
+	    return getTransport(prot);
+	return getTransport("smtp");	// if all else fails
+    }
+
+    /**
+     * Get a Transport object that implements the specified protocol.
+     * If an appropriate Transport object cannot be obtained, null is
+     * returned.
+     *
+     * @param	protocol the Transport protocol
+     * @return 		a Transport object 
+     * @exception	NoSuchProviderException If provider for the given
+     *			protocol is not found.
+     */
+    public Transport getTransport(String protocol)
+				throws NoSuchProviderException {
+	return getTransport(new URLName(protocol, null, -1, null, null, null));
+    }
+
+
+    /**
+     * Get a Transport object for the given URLName. If the requested 
+     * Transport object cannot be obtained, NoSuchProviderException is thrown.
+     *
+     * The "scheme" part of the URL string (Refer RFC 1738) is used 
+     * to locate the Transport protocol. <p>
+     *
+     * @param	url	URLName that represents the desired Transport
+     * @return		a closed Transport object
+     * @see		javax.mail.URLName
+     * @exception	NoSuchProviderException If a provider for the given
+     *			URLName is not found.
+     */
+    public Transport getTransport(URLName url) throws NoSuchProviderException {
+	String protocol = url.getProtocol();
+	Provider p = getProvider(protocol);
+	return getTransport(p, url);
+    }
+
+    /**
+     * Get an instance of the transport specified in the Provider. Instantiates
+     * the transport and returns it.
+     * 
+     * @param provider Transport Provider that will be instantiated
+     * @return Instantiated Transport
+     * @exception	NoSuchProviderException If provider for the given
+     *			provider is not found.
+     */
+    public Transport getTransport(Provider provider) 
+	                                     throws NoSuchProviderException {
+	return getTransport(provider, null);
+    }
+
+    /**
+     * Get a Transport object that can transport a Message of the
+     * specified address type.
+     *
+     * @param	address	an address for which a Transport is needed
+     * @return	A Transport object
+     * @see javax.mail.Address
+     * @exception	NoSuchProviderException If provider for the 
+     *			Address type is not found
+     */
+    public Transport getTransport(Address address) 
+	                                     throws NoSuchProviderException {
+
+	String transportProtocol;
+	transportProtocol =
+	    getProperty("mail.transport.protocol." + address.getType());
+	if (transportProtocol != null)
+	    return getTransport(transportProtocol);
+	transportProtocol = (String)addressMap.get(address.getType());
+	if (transportProtocol != null)
+	    return getTransport(transportProtocol);
+	throw new NoSuchProviderException("No provider for Address type: "+
+						address.getType());
+    }
+
+    /**
+     * Get a Transport object using the given provider and urlname.
+     *
+     * @param	provider	the provider to use
+     * @param	url		urlname to use (can be null)
+     * @return A Transport object
+     * @exception	NoSuchProviderException	If no provider or the provider
+     *			was the wrong class.	
+     */
+
+    private Transport getTransport(Provider provider, URLName url)
+					throws NoSuchProviderException {
+	// make sure we have the correct type of provider
+	if (provider == null || provider.getType() != Provider.Type.TRANSPORT) {
+	    throw new NoSuchProviderException("invalid provider");
+	}
+
+	return getService(provider, url, Transport.class);
+    }
+
+    /**
+     * Get a Service object.  Needs a provider object, but will
+     * create a URLName if needed.  It attempts to instantiate
+     * the correct class.
+     *
+     * @param provider	which provider to use
+     * @param url	which URLName to use (can be null)
+     * @param type	the service type (class)
+     * @exception	NoSuchProviderException	thrown when the class cannot be
+     *			found or when it does not have the correct constructor
+     *			(Session, URLName), or if it is not derived from
+     *			Service.
+     */
+    private <T extends Service> T getService(Provider provider, URLName url,
+					Class<T> type)
+					throws NoSuchProviderException {
+	// need a provider and url
+	if (provider == null) {
+	    throw new NoSuchProviderException("null");
+	}
+
+	// create a url if needed
+	if (url == null) {
+	    url = new URLName(provider.getProtocol(), null, -1, 
+			      null, null, null);
+	}
+
+	Object service = null;
+	
+	// get the ClassLoader associated with the Authenticator
+	ClassLoader cl;
+	if (authenticator != null)
+	    cl = authenticator.getClass().getClassLoader();
+	else
+	    cl = this.getClass().getClassLoader();
+
+	// now load the class
+	Class<?> serviceClass = null;
+	try {
+	    // First try the "application's" class loader.
+	    ClassLoader ccl = getContextClassLoader();
+	    if (ccl != null)
+		try {
+		    serviceClass =
+			Class.forName(provider.getClassName(), false, ccl);
+		} catch (ClassNotFoundException ex) {
+		    // ignore it
+		}
+	    if (serviceClass == null || !type.isAssignableFrom(serviceClass))
+		serviceClass =
+		    Class.forName(provider.getClassName(), false, cl);
+            
+	    if (!type.isAssignableFrom(serviceClass))
+		throw new ClassCastException(
+				type.getName() + " " + serviceClass.getName());
+	} catch (Exception ex1) {
+	    // That didn't work, now try the "system" class loader.
+	    // (Need both of these because JDK 1.1 class loaders
+	    // may not delegate to their parent class loader.)
+	    try {
+		serviceClass = Class.forName(provider.getClassName());
+		if (!type.isAssignableFrom(serviceClass))
+		    throw new ClassCastException(
+				type.getName() + " " + serviceClass.getName());
+	    } catch (Exception ex) {
+		// Nothing worked, give up.
+		logger.log(Level.FINE, "Exception loading provider", ex);
+		throw new NoSuchProviderException(provider.getProtocol());
+	    }
+	}
+
+	// construct an instance of the class
+	try {
+	    Class<?>[] c = {javax.mail.Session.class, javax.mail.URLName.class};
+	    Constructor<?> cons = serviceClass.getConstructor(c);
+
+	    Object[] o = {this, url};
+	    service = cons.newInstance(o);
+
+	} catch (Exception ex) {
+	    logger.log(Level.FINE, "Exception loading provider", ex);
+	    throw new NoSuchProviderException(provider.getProtocol());
+	}
+
+	return type.cast(service);
+    }
+
+    /**
+     * Save a PasswordAuthentication for this (store or transport) URLName.
+     * If pw is null the entry corresponding to the URLName is removed.
+     * <p>
+     * This is normally used only by the store or transport implementations
+     * to allow authentication information to be shared among multiple
+     * uses of a session.
+     *
+     * @param	url	the URLName
+     * @param	pw	the PasswordAuthentication to save
+     */
+    public void setPasswordAuthentication(URLName url,
+					  PasswordAuthentication pw) {
+	if (pw == null)
+	    authTable.remove(url);
+	else
+	    authTable.put(url, pw);
+    }
+
+    /**
+     * Return any saved PasswordAuthentication for this (store or transport)
+     * URLName.  Normally used only by store or transport implementations.
+     *
+     * @param	url	the URLName
+     * @return	the PasswordAuthentication corresponding to the URLName
+     */
+    public PasswordAuthentication getPasswordAuthentication(URLName url) {
+	return authTable.get(url);
+    }
+
+    /**
+     * Call back to the application to get the needed user name and password.
+     * The application should put up a dialog something like:
+     * <pre>
+     * Connecting to &lt;protocol&gt; mail service on host &lt;addr&gt;, port &lt;port&gt;.
+     * &lt;prompt&gt;
+     *
+     * User Name: &lt;defaultUserName&gt;
+     * Password:
+     * </pre>
+     *
+     * @param	addr		InetAddress of the host.  may be null.
+     * @param	port		the port on the host
+     * @param	protocol	protocol scheme (e.g. imap, pop3, etc.)
+     * @param	prompt		any additional String to show as part of
+     *                          the prompt; may be null.
+     * @param	defaultUserName	the default username. may be null.
+     * @return	the authentication which was collected by the authenticator; 
+     *          may be null.
+     */
+    public PasswordAuthentication requestPasswordAuthentication(
+	InetAddress addr, int port,
+	String protocol, String prompt, String defaultUserName) {
+
+	if (authenticator != null) {
+	    return authenticator.requestPasswordAuthentication(
+		addr, port, protocol, prompt, defaultUserName);
+	} else {
+	    return null;
+	}
+    }
+
+    /**
+     * Returns the Properties object associated with this Session
+     *
+     * @return		Properties object
+     */
+    public Properties getProperties() { 
+   	return props; 
+    }
+
+    /**
+     * Returns the value of the specified property. Returns null
+     * if this property does not exist.
+     *
+     * @param	name	the property name
+     * @return		String that is the property value
+     */
+    public String getProperty(String name) { 
+   	return props.getProperty(name); 
+    }
+
+    /**
+     * Get the Provider that uses the specified class name.
+     *
+     * @param	className	the class name
+     * @return		the Provider
+     */
+    private Provider getProviderByClassName(String className) {
+	// first, try our local list of providers
+	Provider p = providersByClassName.get(className);
+	if (p != null)
+	    return p;
+
+	// now, try services
+	ServiceLoader<Provider> loader = ServiceLoader.load(Provider.class);
+	for (Provider pp : loader) {
+	    if (className.equals(pp.getClassName()))
+		return pp;
+	}
+
+	// finally, if we haven't loaded our config, load it and try again
+	if (!loadedProviders) {
+	    loadProviders(true);
+	    p = providersByClassName.get(className);
+	}
+	return p;
+    }
+
+    /**
+     * Get the Provider for the specified protocol.
+     *
+     * @param	protocol	the protocol
+     * @return		the Provider
+     */
+    private Provider getProviderByProtocol(String protocol) {
+	// first, try our local list of providers
+	Provider p = providersByProtocol.get(protocol);
+	if (p != null)
+	    return p;
+
+	// now, try services
+	ServiceLoader<Provider> loader = ServiceLoader.load(Provider.class);
+	for (Provider pp : loader) {
+	    if (protocol.equals(pp.getProtocol()))
+		return pp;
+	}
+
+	// finally, if we haven't loaded our config, load it and try again
+	if (!loadedProviders) {
+	    loadProviders(true);
+	    p = providersByProtocol.get(protocol);
+	}
+	return p;
+    }
+
+    /**
+     * Load the protocol providers config files.
+     * If fallback is true, provide built in defaults if nothing is loaded.
+     */
+    private void loadProviders(boolean fallback) {
+	StreamLoader loader = new StreamLoader() {
+	    @Override
+	    public void load(InputStream is) throws IOException {
+		loadProvidersFromStream(is);
+	    }
+	};
+
+	// load system-wide javamail.providers from the
+	// <java.home>/{conf,lib} directory
+	try {
+	    if (confDir != null)
+		loadFile(confDir + "javamail.providers", loader);
+	} catch (SecurityException ex) {}
+
+	// get the Class associated with the Authenticator
+	Class<?> cl;
+	if (authenticator != null)
+	    cl = authenticator.getClass();
+	else
+	    cl = this.getClass();
+
+	// load the META-INF/javamail.providers file supplied by an application
+	loadAllResources("META-INF/javamail.providers", cl, loader);
+
+	// load default META-INF/javamail.default.providers from mail.jar file
+	loadResource("/META-INF/javamail.default.providers", cl, loader, false);
+
+	/*
+	 * If we haven't loaded any providers and the fallback configuration
+	 * is needed, fake it.
+	 */
+	if ((providers == null || providers.size() == 0) && fallback) {
+	    logger.config("failed to load any providers, using defaults");
+	    // failed to load any providers, initialize with our defaults
+	    addProvider(new Provider(Provider.Type.STORE,
+			"imap", "com.sun.mail.imap.IMAPStore",
+			"Oracle", Version.version));
+	    addProvider(new Provider(Provider.Type.STORE,
+			"imaps", "com.sun.mail.imap.IMAPSSLStore",
+			"Oracle", Version.version));
+	    addProvider(new Provider(Provider.Type.STORE,
+			"pop3", "com.sun.mail.pop3.POP3Store",
+			"Oracle", Version.version));
+	    addProvider(new Provider(Provider.Type.STORE,
+			"pop3s", "com.sun.mail.pop3.POP3SSLStore",
+			"Oracle", Version.version));
+	    addProvider(new Provider(Provider.Type.TRANSPORT,
+			"smtp", "com.sun.mail.smtp.SMTPTransport",
+			"Oracle", Version.version));
+	    addProvider(new Provider(Provider.Type.TRANSPORT,
+			"smtps", "com.sun.mail.smtp.SMTPSSLTransport",
+			"Oracle", Version.version));
+	}
+
+	if (logger.isLoggable(Level.CONFIG)) {
+	    // dump the output of the tables for debugging
+	    logger.config("Tables of loaded providers from javamail.providers");
+	    logger.config("Providers Listed By Class Name: " + 
+	       providersByClassName.toString());
+	    logger.config("Providers Listed By Protocol: " + 
+	       providersByProtocol.toString());
+	}
+	loadedProviders = true;
+    }
+
+    private void loadProvidersFromStream(InputStream is) 
+				throws IOException {
+	if (is != null) {
+	    LineInputStream lis = new LineInputStream(is);
+	    String currLine;
+
+	    // load and process one line at a time using LineInputStream
+	    while ((currLine = lis.readLine()) != null) {
+
+		if (currLine.startsWith("#"))
+		    continue;
+		if (currLine.trim().length() == 0)
+		    continue;	// skip blank line
+		Provider.Type type = null;
+		String protocol = null, className = null;
+		String vendor = null, version = null;
+		    
+		// separate line into key-value tuples
+		StringTokenizer tuples = new StringTokenizer(currLine,";");
+		while (tuples.hasMoreTokens()) {
+		    String currTuple = tuples.nextToken().trim();
+			
+		    // set the value of each attribute based on its key
+		    int sep = currTuple.indexOf("=");
+		    if (currTuple.startsWith("protocol=")) {
+			protocol = currTuple.substring(sep+1);
+		    } else if (currTuple.startsWith("type=")) {
+			String strType = currTuple.substring(sep+1);
+			if (strType.equalsIgnoreCase("store")) {
+			    type = Provider.Type.STORE;
+			} else if (strType.equalsIgnoreCase("transport")) {
+			    type = Provider.Type.TRANSPORT;
+		    	}
+		    } else if (currTuple.startsWith("class=")) {
+			className = currTuple.substring(sep+1);
+		    } else if (currTuple.startsWith("vendor=")) {
+			vendor = currTuple.substring(sep+1);
+		    } else if (currTuple.startsWith("version=")) {
+			version = currTuple.substring(sep+1);
+		    }
+		}
+
+		// check if a valid Provider; else, continue
+		if (type == null || protocol == null || className == null 
+		    || protocol.length() <= 0 || className.length() <= 0) {
+			
+		    logger.log(Level.CONFIG, "Bad provider entry: {0}",
+						currLine);
+		    continue;
+		}
+		Provider provider = new Provider(type, protocol, className,
+					         vendor, version);
+
+		// add the newly-created Provider to the lookup tables
+		addProvider(provider);
+	    }
+	}
+    }
+
+    /**
+     * Add a provider to the session.
+     *
+     * @param	provider	the provider to add
+     * @since	JavaMail 1.4
+     */
+    public synchronized void addProvider(Provider provider) {
+	if (providers == null)
+	    providers = new ArrayList<Provider>();
+	providers.add(provider);
+	providersByClassName.put(provider.getClassName(), provider);
+	if (!providersByProtocol.containsKey(provider.getProtocol()))
+	    providersByProtocol.put(provider.getProtocol(), provider);
+    }
+
+    // load maps in reverse order of preference so that the preferred
+    // map is loaded last since its entries will override the previous ones
+    private void loadAddressMap(Class<?> cl) {
+	StreamLoader loader = new StreamLoader() {
+	    @Override
+	    public void load(InputStream is) throws IOException {
+		addressMap.load(is);
+	    }
+	};
+
+	// load default META-INF/javamail.default.address.map from mail.jar
+	loadResource("/META-INF/javamail.default.address.map", cl, loader, true);
+
+	// load the META-INF/javamail.address.map file supplied by an app
+	loadAllResources("META-INF/javamail.address.map", cl, loader);
+
+	// load system-wide javamail.address.map from the
+	// <java.home>/{conf,lib} directory
+	try {
+	    if (confDir != null)
+		loadFile(confDir + "javamail.address.map", loader);
+	} catch (SecurityException ex) {}
+
+	if (addressMap.isEmpty()) {
+	    logger.config("failed to load address map, using defaults");
+	    addressMap.put("rfc822", "smtp");
+	}
+    }
+
+    /**
+     * Set the default transport protocol to use for addresses of
+     * the specified type.  Normally the default is set by the
+     * <code>javamail.default.address.map</code> or
+     * <code>javamail.address.map</code> files or resources.
+     *
+     * @param	addresstype	type of address
+     * @param	protocol	name of protocol
+     * @see #getTransport(Address)
+     * @since	JavaMail 1.4
+     */
+    public synchronized void setProtocolForAddress(String addresstype,
+				String protocol) {
+	if (protocol == null)
+	    addressMap.remove(addresstype);
+	else
+	    addressMap.put(addresstype, protocol);
+    }
+
+    /**
+     * Load from the named file.
+     */
+    private void loadFile(String name, StreamLoader loader) {
+	InputStream clis = null;
+	try {
+	    clis = new BufferedInputStream(new FileInputStream(name));
+	    loader.load(clis);
+	    logger.log(Level.CONFIG, "successfully loaded file: {0}", name);
+	} catch (FileNotFoundException fex) {
+	    // ignore it
+	} catch (IOException e) {
+	    if (logger.isLoggable(Level.CONFIG))
+		logger.log(Level.CONFIG, "not loading file: " + name, e);
+	} catch (SecurityException sex) {
+	    if (logger.isLoggable(Level.CONFIG))
+		logger.log(Level.CONFIG, "not loading file: " + name, sex);
+	} finally {
+	    try {
+		if (clis != null)
+		    clis.close();
+	    } catch (IOException ex) { }	// ignore it
+	}
+    }
+
+    /**
+     * Load from the named resource.
+     */
+    private void loadResource(String name, Class<?> cl, StreamLoader loader,
+				boolean expected) {
+	InputStream clis = null;
+	try {
+	    clis = getResourceAsStream(cl, name);
+	    if (clis != null) {
+		loader.load(clis);
+		logger.log(Level.CONFIG, "successfully loaded resource: {0}",
+					    name);
+	    } else {
+		if (expected)
+		    logger.log(Level.WARNING,
+				    "expected resource not found: {0}", name);
+	    }
+	} catch (IOException e) {
+	    logger.log(Level.CONFIG, "Exception loading resource", e);
+	} catch (SecurityException sex) {
+	    logger.log(Level.CONFIG, "Exception loading resource", sex);
+	} finally {
+	    try {
+		if (clis != null)
+		    clis.close();
+	    } catch (IOException ex) { }	// ignore it
+	}
+    }
+
+    /**
+     * Load all of the named resource.
+     */
+    private void loadAllResources(String name, Class<?> cl,
+	    StreamLoader loader) {
+	boolean anyLoaded = false;
+	try {
+	    URL[] urls;
+	    ClassLoader cld = null;
+	    // First try the "application's" class loader.
+	    cld = getContextClassLoader();
+	    if (cld == null)
+		cld = cl.getClassLoader();
+	    if (cld != null)
+		urls = getResources(cld, name);
+	    else
+		urls = getSystemResources(name);
+	    if (urls != null) {
+		for (int i = 0; i < urls.length; i++) {
+		    URL url = urls[i];
+		    InputStream clis = null;
+		    logger.log(Level.CONFIG, "URL {0}", url);
+		    try {
+			clis = openStream(url);
+			if (clis != null) {
+			    loader.load(clis);
+			    anyLoaded = true;
+			    logger.log(Level.CONFIG,
+				"successfully loaded resource: {0}", url);
+			} else {
+			    logger.log(Level.CONFIG,
+				"not loading resource: {0}", url);
+			}
+		    } catch (FileNotFoundException fex) {
+			// ignore it
+		    } catch (IOException ioex) {
+			logger.log(Level.CONFIG, "Exception loading resource",
+				    ioex);
+		    } catch (SecurityException sex) {
+			logger.log(Level.CONFIG, "Exception loading resource",
+				    sex);
+		    } finally {
+			try {
+			    if (clis != null)
+				clis.close();
+			} catch (IOException cex) { }
+		    }
+		}
+	    }
+	} catch (Exception ex) {
+	    logger.log(Level.CONFIG, "Exception loading resource", ex);
+	}
+
+	// if failed to load anything, fall back to old technique, just in case
+	if (!anyLoaded) {
+	    /*
+	    logger.config("!anyLoaded");
+	    */
+	    loadResource("/" + name, cl, loader, false);
+	}
+    }
+
+    /*
+     * Following are security related methods that work on JDK 1.2 or newer.
+     */
+
+    static ClassLoader getContextClassLoader() {
+	return AccessController.doPrivileged(
+		new PrivilegedAction<ClassLoader>() {
+		    @Override
+		    public ClassLoader run() {
+			ClassLoader cl = null;
+			try {
+			    cl = Thread.currentThread().getContextClassLoader();
+			} catch (SecurityException ex) {
+			}
+			return cl;
+		    }
+		}
+	);
+    }
+
+    private static InputStream getResourceAsStream(final Class<?> c,
+				final String name) throws IOException {
+	try {
+	    return AccessController.doPrivileged(
+		    new PrivilegedExceptionAction<InputStream>() {
+			@Override
+			public InputStream run() throws IOException {
+			    try {
+				return c.getResourceAsStream(name);
+			    } catch (RuntimeException e) {
+				// gracefully handle ClassLoader bugs (Tomcat)
+				IOException ioex = new IOException(
+				    "ClassLoader.getResourceAsStream failed");
+				ioex.initCause(e);
+				throw ioex;
+			    }
+			}
+		    }
+	    );
+	} catch (PrivilegedActionException e) {
+	    throw (IOException)e.getException();
+	}
+    }
+
+    private static URL[] getResources(final ClassLoader cl, final String name) {
+	return AccessController.doPrivileged(new PrivilegedAction<URL[]>() {
+	    @Override
+	    public URL[] run() {
+		URL[] ret = null;
+		try {
+		    List<URL> v = Collections.list(cl.getResources(name));
+		    if (!v.isEmpty()) {
+			ret = new URL[v.size()];
+			v.toArray(ret);
+		    }
+		} catch (IOException ioex) {
+		} catch (SecurityException ex) { }
+		return ret;
+	    }
+	});
+    }
+
+    private static URL[] getSystemResources(final String name) {
+	return AccessController.doPrivileged(new PrivilegedAction<URL[]>() {
+	    @Override
+	    public URL[] run() {
+		URL[] ret = null;
+		try {
+		    List<URL> v = Collections.list(
+			    ClassLoader.getSystemResources(name));
+		    if (!v.isEmpty()) {
+			ret = new URL[v.size()];
+			v.toArray(ret);
+		    }
+		} catch (IOException ioex) {
+		} catch (SecurityException ex) { }
+		return ret;
+	    }
+	});
+    }
+
+    private static InputStream openStream(final URL url) throws IOException {
+	try {
+	    return AccessController.doPrivileged(
+		    new PrivilegedExceptionAction<InputStream>() {
+			@Override
+			public InputStream run() throws IOException {
+			    return url.openStream();
+			}
+		    }
+	    );
+	} catch (PrivilegedActionException e) {
+	    throw (IOException)e.getException();
+	}
+    }
+
+    EventQueue getEventQueue() {
+	return q;
+    }
+}
+
+/**
+ * Support interface to generalize
+ * code that loads resources from stream.
+ */
+interface StreamLoader {
+    public void load(InputStream is) throws IOException;
+}
diff --git a/mail/src/main/java/javax/mail/Store.java b/mail/src/main/java/javax/mail/Store.java
new file mode 100644
index 0000000..2678a09
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Store.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import javax.mail.event.*;
+
+/**
+ * An abstract class that models a message store and its
+ * access protocol, for storing and retrieving messages. 
+ * Subclasses provide actual implementations. <p>
+ *
+ * Note that <code>Store</code> extends the <code>Service</code>
+ * class, which provides many common methods for naming stores,
+ * connecting to stores, and listening to connection events.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ *
+ * @see javax.mail.Service
+ * @see javax.mail.event.ConnectionEvent
+ * @see javax.mail.event.StoreEvent
+ */
+
+public abstract class Store extends Service {
+
+    /**
+     * Constructor.
+     *
+     * @param	session Session object for this Store.
+     * @param	urlname	URLName object to be used for this Store
+     */
+    protected Store(Session session, URLName urlname) {
+	super(session, urlname);
+    }
+
+    /**
+     * Returns a Folder object that represents the 'root' of
+     * the default namespace presented to the user by the Store.
+     *
+     * @return the root Folder
+     * @exception	IllegalStateException if this Store is not connected.
+     * @exception 	MessagingException for other failures
+     */
+    public abstract Folder getDefaultFolder() throws MessagingException;
+
+    /**
+     * Return the Folder object corresponding to the given name. Note
+     * that a Folder object is returned even if the named folder does
+     * not physically exist on the Store. The <code>exists()</code> 
+     * method on the folder object indicates whether this folder really
+     * exists. <p>
+     *
+     * Folder objects are not cached by the Store, so invoking this
+     * method on the same name multiple times will return that many
+     * distinct Folder objects.
+     *
+     * @param name 	The name of the Folder. In some Stores, name can
+     *			be an absolute path if it starts with the
+     *			hierarchy delimiter. Else it is interpreted
+     *			relative to the 'root' of this namespace.
+     * @return		Folder object
+     * @exception 	IllegalStateException if this Store is not connected.
+     * @exception 	MessagingException for other failures
+     * @see 		Folder#exists
+     * @see		Folder#create
+     */
+    public abstract Folder getFolder(String name)
+			throws MessagingException;
+
+    /**
+     * Return a closed Folder object, corresponding to the given 
+     * URLName. The store specified in the given URLName should
+     * refer to this Store object. <p>
+     *
+     * Implementations of this method may obtain the name of the
+     * actual folder using the <code>getFile()</code> method on
+     * URLName, and use that name to create the folder.
+     * 
+     * @param url	URLName that denotes a folder
+     * @return		Folder object
+     * @exception 	IllegalStateException if this Store is not connected.
+     * @exception 	MessagingException for other failures
+     * @see 		URLName
+     */
+    public abstract Folder getFolder(URLName url)
+			throws MessagingException;
+
+    /**
+     * Return a set of folders representing the <i>personal</i> namespaces
+     * for the current user.  A personal namespace is a set of names that
+     * is considered within the personal scope of the authenticated user.
+     * Typically, only the authenticated user has access to mail folders
+     * in their personal namespace.  If an INBOX exists for a user, it
+     * must appear within the user's personal namespace.  In the
+     * typical case, there should be only one personal namespace for each
+     * user in each Store. <p>
+     *
+     * This implementation returns an array with a single entry containing
+     * the return value of the <code>getDefaultFolder</code> method.
+     * Subclasses should override this method to return appropriate information.
+     *
+     * @return		array of Folder objects
+     * @exception 	IllegalStateException if this Store is not connected.
+     * @exception 	MessagingException for other failures
+     * @since		JavaMail 1.2
+     */
+    public Folder[] getPersonalNamespaces() throws MessagingException {
+	return new Folder[] { getDefaultFolder() };
+    }
+
+    /**
+     * Return a set of folders representing the namespaces for
+     * <code>user</code>.  The namespaces returned represent the
+     * personal namespaces for the user.  To access mail folders in the
+     * other user's namespace, the currently authenticated user must be
+     * explicitly granted access rights.  For example, it is common for
+     * a manager to grant to their secretary access rights to their
+     * mail folders. <p>
+     *
+     * This implementation returns an empty array.  Subclasses should
+     * override this method to return appropriate information.
+     *
+     * @param	user	the user name
+     * @return		array of Folder objects
+     * @exception 	IllegalStateException if this Store is not connected.
+     * @exception 	MessagingException for other failures
+     * @since		JavaMail 1.2
+     */
+    public Folder[] getUserNamespaces(String user)
+				throws MessagingException {
+	return new Folder[0];
+    }
+
+    /**
+     * Return a set of folders representing the <i>shared</i> namespaces.
+     * A shared namespace is a namespace that consists of mail folders
+     * that are intended to be shared amongst users and do not exist
+     * within a user's personal namespace. <p>
+     *
+     * This implementation returns an empty array.  Subclasses should
+     * override this method to return appropriate information.
+     *
+     * @exception 	IllegalStateException if this Store is not connected.
+     * @exception 	MessagingException for other failures
+     * @return		array of Folder objects
+     * @since		JavaMail 1.2
+     */
+    public Folder[] getSharedNamespaces() throws MessagingException {
+	return new Folder[0];
+    }
+
+    // Vector of Store listeners
+    private volatile Vector<StoreListener> storeListeners = null;
+
+    /**
+     * Add a listener for StoreEvents on this Store. <p>
+     *
+     * The default implementation provided here adds this listener
+     * to an internal list of StoreListeners.
+     *
+     * @param l         the Listener for Store events
+     * @see             javax.mail.event.StoreEvent
+     */
+    public synchronized void addStoreListener(StoreListener l) {
+	if (storeListeners == null)
+	    storeListeners = new Vector<>();
+	storeListeners.addElement(l);
+    }
+
+    /**
+     * Remove a listener for Store events. <p>
+     *
+     * The default implementation provided here removes this listener
+     * from the internal list of StoreListeners.
+     *
+     * @param l         the listener
+     * @see             #addStoreListener
+     */
+    public synchronized void removeStoreListener(StoreListener l) {
+	if (storeListeners != null)
+	    storeListeners.removeElement(l);
+    }
+
+    /**
+     * Notify all StoreListeners. Store implementations are
+     * expected to use this method to broadcast StoreEvents. <p>
+     *
+     * The provided default implementation queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the registered
+     * StoreListeners. Note that the event dispatching occurs
+     * in a separate thread, thus avoiding potential deadlock problems.
+     *
+     * @param	type	the StoreEvent type
+     * @param	message	a message for the StoreEvent
+     */
+    protected void notifyStoreListeners(int type, String message) {
+   	if (storeListeners == null)
+	    return;
+	
+	StoreEvent e = new StoreEvent(this, type, message);
+	queueEvent(e, storeListeners);
+    }
+
+    // Vector of folder listeners
+    private volatile Vector<FolderListener> folderListeners = null;
+
+    /**
+     * Add a listener for Folder events on any Folder object 
+     * obtained from this Store. FolderEvents are delivered to
+     * FolderListeners on the affected Folder as well as to 
+     * FolderListeners on the containing Store. <p>
+     *
+     * The default implementation provided here adds this listener
+     * to an internal list of FolderListeners.
+     *
+     * @param l         the Listener for Folder events
+     * @see             javax.mail.event.FolderEvent
+     */
+    public synchronized void addFolderListener(FolderListener l) {
+   	if (folderListeners == null)
+	    folderListeners = new Vector<>();
+	folderListeners.addElement(l);
+    }
+
+    /**
+     * Remove a listener for Folder events. <p>
+     *
+     * The default implementation provided here removes this listener
+     * from the internal list of FolderListeners.
+     *
+     * @param l         the listener
+     * @see             #addFolderListener
+     */
+    public synchronized void removeFolderListener(FolderListener l) {
+   	if (folderListeners != null)
+	    folderListeners.removeElement(l);
+    }
+
+    /**
+     * Notify all FolderListeners. Store implementations are
+     * expected to use this method to broadcast Folder events. <p>
+     *
+     * The provided default implementation queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the registered
+     * FolderListeners. Note that the event dispatching occurs
+     * in a separate thread, thus avoiding potential deadlock problems.
+     *
+     * @param	type	type of FolderEvent
+     * @param	folder	affected Folder
+     * @see		#notifyFolderRenamedListeners
+     */
+    protected void notifyFolderListeners(int type, Folder folder) {
+   	if (folderListeners == null) 
+	    return;
+	
+	FolderEvent e = new FolderEvent(this, folder, type);
+	queueEvent(e, folderListeners);
+    }
+
+    /**
+     * Notify all FolderListeners about the renaming of a folder.
+     * Store implementations are expected to use this method to broadcast 
+     * Folder events indicating the renaming of folders. <p>
+     *
+     * The provided default implementation queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the registered
+     * FolderListeners. Note that the event dispatching occurs
+     * in a separate thread, thus avoiding potential deadlock problems.
+     *
+     * @param	oldF	the folder being renamed
+     * @param	newF	the folder representing the new name.
+     * @since	JavaMail 1.1
+     */
+    protected void notifyFolderRenamedListeners(Folder oldF, Folder newF) {
+   	if (folderListeners == null) 
+	    return;
+	
+	FolderEvent e = new FolderEvent(this, oldF, newF,FolderEvent.RENAMED);
+	queueEvent(e, folderListeners);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/StoreClosedException.java b/mail/src/main/java/javax/mail/StoreClosedException.java
new file mode 100644
index 0000000..29a0bbb
--- /dev/null
+++ b/mail/src/main/java/javax/mail/StoreClosedException.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * This exception is thrown when a method is invoked on a Messaging object
+ * and the Store that owns that object has died due to some reason.
+ * This exception should be treated as a fatal error; in particular any
+ * messaging object belonging to that Store must be considered invalid. <p>
+ *
+ * The connect method may be invoked on the dead Store object to 
+ * revive it. <p>
+ *
+ * The getMessage() method returns more detailed information about the
+ * error that caused this exception. <p>
+ *
+ * @author John Mani
+ */
+
+public class StoreClosedException extends MessagingException {
+    transient private Store store;
+
+    private static final long serialVersionUID = -3145392336120082655L;
+
+    /**
+     * Constructs a StoreClosedException with no detail message.
+     *
+     * @param store	The dead Store object
+     */
+    public StoreClosedException(Store store) {
+	this(store, null);
+    }
+
+    /**
+     * Constructs a StoreClosedException with the specified
+     * detail message.
+     *
+     * @param store	The dead Store object
+     * @param message	The detailed error message
+     */
+    public StoreClosedException(Store store, String message) {
+	super(message);
+	this.store = store;
+    }
+
+    /**
+     * Constructs a StoreClosedException with the specified
+     * detail message and embedded exception.  The exception is chained
+     * to this exception.
+     *
+     * @param store	The dead Store object
+     * @param message	The detailed error message
+     * @param e		The embedded exception
+     * @since		JavaMail 1.5
+     */
+    public StoreClosedException(Store store, String message, Exception e) {
+	super(message, e);
+	this.store = store;
+    }
+
+    /**
+     * Returns the dead Store object.
+     *
+     * @return	the dead Store object
+     */
+    public Store getStore() {
+	return store;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/Transport.java b/mail/src/main/java/javax/mail/Transport.java
new file mode 100644
index 0000000..e9b3029
--- /dev/null
+++ b/mail/src/main/java/javax/mail/Transport.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Vector;
+import javax.mail.event.*;
+
+/**
+ * An abstract class that models a message transport.
+ * Subclasses provide actual implementations. <p>
+ *
+ * Note that <code>Transport</code> extends the <code>Service</code>
+ * class, which provides many common methods for naming transports,
+ * connecting to transports, and listening to connection events.
+ *
+ * @author John Mani
+ * @author Max Spivak
+ * @author Bill Shannon
+ * 
+ * @see javax.mail.Service
+ * @see javax.mail.event.ConnectionEvent
+ * @see javax.mail.event.TransportEvent
+ */
+
+public abstract class Transport extends Service {
+
+    /**
+     * Constructor.
+     *
+     * @param	session Session object for this Transport.
+     * @param	urlname	URLName object to be used for this Transport
+     */
+    public Transport(Session session, URLName urlname) {
+	super(session, urlname);
+    }
+
+    /**
+     * Send a message.  The message will be sent to all recipient
+     * addresses specified in the message (as returned from the
+     * <code>Message</code> method <code>getAllRecipients</code>),
+     * using message transports appropriate to each address.  The
+     * <code>send</code> method calls the <code>saveChanges</code>
+     * method on the message before sending it. <p>
+     *
+     * If any of the recipient addresses is detected to be invalid by
+     * the Transport during message submission, a SendFailedException
+     * is thrown.  Clients can get more detail about the failure by examining
+     * the exception.  Whether or not the message is still sent successfully
+     * to any valid addresses depends on the Transport implementation.  See 
+     * SendFailedException for more details.  Note also that success does 
+     * not imply that the message was delivered to the ultimate recipient,
+     * as failures may occur in later stages of delivery.  Once a Transport 
+     * accepts a message for delivery to a recipient, failures that occur later
+     * should be reported to the user via another mechanism, such as
+     * returning the undeliverable message. <p>
+     *
+     * In typical usage, a SendFailedException reflects an error detected
+     * by the server.  The details of the SendFailedException will usually
+     * contain the error message from the server (such as an SMTP error
+     * message).  An address may be detected as invalid for a variety of
+     * reasons - the address may not exist, the address may have invalid
+     * syntax, the address may have exceeded its quota, etc. <p>
+     *
+     * Note that <code>send</code> is a static method that creates and
+     * manages its own connection.  Any connection associated with any
+     * Transport instance used to invoke this method is ignored and not
+     * used.  This method should only be invoked using the form
+     * <code>Transport.send(msg);</code>, and should never be invoked
+     * using an instance variable.
+     *
+     * @param	msg	the message to send
+     * @exception	SendFailedException if the message could not
+     *			be sent to some or any of the recipients.
+     * @exception	MessagingException for other failures
+     * @see		Message#saveChanges
+     * @see		Message#getAllRecipients
+     * @see		#send(Message, Address[])
+     * @see		javax.mail.SendFailedException
+     */
+    public static void send(Message msg) throws MessagingException {
+	msg.saveChanges(); // do this first
+	send0(msg, msg.getAllRecipients(), null, null);
+    }
+
+    /**
+     * Send the message to the specified addresses, ignoring any
+     * recipients specified in the message itself. The
+     * <code>send</code> method calls the <code>saveChanges</code>
+     * method on the message before sending it. <p>
+     *
+     * @param	msg	the message to send
+     * @param	addresses the addresses to which to send the message
+     * @exception	SendFailedException if the message could not
+     *			be sent to some or any of the recipients.
+     * @exception	MessagingException for other failures
+     * @see		Message#saveChanges
+     * @see             #send(Message)
+     * @see		javax.mail.SendFailedException
+     */
+    public static void send(Message msg, Address[] addresses) 
+		throws MessagingException {
+
+	msg.saveChanges();
+	send0(msg, addresses, null, null);
+    }
+
+    /**
+     * Send a message.  The message will be sent to all recipient
+     * addresses specified in the message (as returned from the
+     * <code>Message</code> method <code>getAllRecipients</code>).
+     * The <code>send</code> method calls the <code>saveChanges</code>
+     * method on the message before sending it. <p>
+     *
+     * Use the specified user name and password to authenticate to
+     * the mail server.
+     *
+     * @param	msg	the message to send
+     * @param	user	the user name
+     * @param	password this user's password
+     * @exception	SendFailedException if the message could not
+     *			be sent to some or any of the recipients.
+     * @exception	MessagingException for other failures
+     * @see		Message#saveChanges
+     * @see             #send(Message)
+     * @see		javax.mail.SendFailedException
+     * @since		JavaMail 1.5
+     */
+    public static void send(Message msg,
+		String user, String password) throws MessagingException {
+
+	msg.saveChanges();
+	send0(msg, msg.getAllRecipients(), user, password);
+    }
+
+    /**
+     * Send the message to the specified addresses, ignoring any
+     * recipients specified in the message itself. The
+     * <code>send</code> method calls the <code>saveChanges</code>
+     * method on the message before sending it. <p>
+     *
+     * Use the specified user name and password to authenticate to
+     * the mail server.
+     *
+     * @param	msg	the message to send
+     * @param	addresses the addresses to which to send the message
+     * @param	user	the user name
+     * @param	password this user's password
+     * @exception	SendFailedException if the message could not
+     *			be sent to some or any of the recipients.
+     * @exception	MessagingException for other failures
+     * @see		Message#saveChanges
+     * @see             #send(Message)
+     * @see		javax.mail.SendFailedException
+     * @since		JavaMail 1.5
+     */
+    public static void send(Message msg, Address[] addresses,
+		String user, String password) throws MessagingException {
+
+	msg.saveChanges();
+	send0(msg, addresses, user, password);
+    }
+
+    // send, but without the saveChanges
+    private static void send0(Message msg, Address[] addresses,
+		String user, String password) throws MessagingException {
+
+	if (addresses == null || addresses.length == 0)
+	    throw new SendFailedException("No recipient addresses");
+
+	/*
+	 * protocols is a map containing the addresses
+	 * indexed by address type
+	 */
+	Map<String, List<Address>> protocols
+		= new HashMap<>();
+
+	// Lists of addresses
+	List<Address> invalid = new ArrayList<>();
+	List<Address> validSent = new ArrayList<>();
+	List<Address> validUnsent = new ArrayList<>();
+
+	for (int i = 0; i < addresses.length; i++) {
+	    // is this address type already in the map?
+	    if (protocols.containsKey(addresses[i].getType())) {
+		List<Address> v = protocols.get(addresses[i].getType());
+		v.add(addresses[i]);
+	    } else {
+		// need to add a new protocol
+		List<Address> w = new ArrayList<>();
+		w.add(addresses[i]);
+		protocols.put(addresses[i].getType(), w);
+	    }
+	}
+
+	int dsize = protocols.size();
+	if (dsize == 0)
+	    throw new SendFailedException("No recipient addresses");
+
+	Session s = (msg.session != null) ? msg.session :
+		     Session.getDefaultInstance(System.getProperties(), null);
+	Transport transport;
+
+	/*
+	 * Optimize the case of a single protocol.
+	 */
+	if (dsize == 1) {
+	    transport = s.getTransport(addresses[0]);
+	    try {
+		if (user != null)
+		    transport.connect(user, password);
+		else
+		    transport.connect();
+		transport.sendMessage(msg, addresses);
+	    } finally {
+		transport.close();
+	    }
+	    return;
+	}
+
+	/*
+	 * More than one protocol.  Have to do them one at a time
+	 * and collect addresses and chain exceptions.
+	 */
+	MessagingException chainedEx = null;
+	boolean sendFailed = false;
+
+	for(List<Address> v : protocols.values()) {
+	    Address[] protaddresses = new Address[v.size()];
+	    v.toArray(protaddresses);
+
+	    // Get a Transport that can handle this address type.
+	    if ((transport = s.getTransport(protaddresses[0])) == null) {
+		// Could not find an appropriate Transport ..
+		// Mark these addresses invalid.
+		for (int j = 0; j < protaddresses.length; j++)
+		    invalid.add(protaddresses[j]);
+		continue;
+	    }
+	    try {
+		transport.connect();
+		transport.sendMessage(msg, protaddresses);
+	    } catch (SendFailedException sex) {
+		sendFailed = true;
+		// chain the exception we're catching to any previous ones
+		if (chainedEx == null)
+		    chainedEx = sex;
+		else
+		    chainedEx.setNextException(sex);
+
+		// retrieve invalid addresses
+		Address[] a = sex.getInvalidAddresses();
+		if (a != null)
+		    for (int j = 0; j < a.length; j++) 
+			invalid.add(a[j]);
+
+		// retrieve validSent addresses
+		a = sex.getValidSentAddresses();
+		if (a != null)
+		    for (int k = 0; k < a.length; k++) 
+			validSent.add(a[k]);
+
+		// retrieve validUnsent addresses
+		Address[] c = sex.getValidUnsentAddresses();
+		if (c != null)
+		    for (int l = 0; l < c.length; l++) 
+			validUnsent.add(c[l]);
+	    } catch (MessagingException mex) {
+		sendFailed = true;
+		// chain the exception we're catching to any previous ones
+		if (chainedEx == null)
+		    chainedEx = mex;
+		else
+		    chainedEx.setNextException(mex);
+	    } finally {
+		transport.close();
+	    }
+	}
+
+	// done with all protocols. throw exception if something failed
+	if (sendFailed || invalid.size() != 0 || validUnsent.size() != 0) { 
+	    Address[] a = null, b = null, c = null;
+
+	    // copy address lists into arrays
+	    if (validSent.size() > 0) {
+		a = new Address[validSent.size()];
+		validSent.toArray(a);
+	    }
+	    if (validUnsent.size() > 0) {
+		b = new Address[validUnsent.size()];
+		validUnsent.toArray(b);
+	    }
+	    if (invalid.size() > 0) {
+		c = new Address[invalid.size()];
+		invalid.toArray(c);
+	    }
+	    throw new SendFailedException("Sending failed", chainedEx, 
+					  a, b, c);
+	}
+    }
+
+    /**
+     * Send the Message to the specified list of addresses. An appropriate
+     * TransportEvent indicating the delivery status is delivered to any 
+     * TransportListener registered on this Transport. Also, if any of
+     * the addresses is invalid, a SendFailedException is thrown.
+     * Whether or not the message is still sent succesfully to
+     * any valid addresses depends on the Transport implementation. <p>
+     *
+     * Unlike the static <code>send</code> method, the <code>sendMessage</code>
+     * method does <em>not</em> call the <code>saveChanges</code> method on
+     * the message; the caller should do so.
+     *
+     * @param msg	The Message to be sent
+     * @param addresses	array of addresses to send this message to
+     * @see 		javax.mail.event.TransportEvent
+     * @exception SendFailedException if the send failed because of
+     *			invalid addresses.
+     * @exception MessagingException if the connection is dead or not in the 
+     * 				connected state
+     */
+    public abstract void sendMessage(Message msg, Address[] addresses) 
+				throws MessagingException;
+
+    // Vector of Transport listeners
+    private volatile Vector<TransportListener> transportListeners = null;
+
+    /**
+     * Add a listener for Transport events. <p>
+     *
+     * The default implementation provided here adds this listener
+     * to an internal list of TransportListeners.
+     *
+     * @param l         the Listener for Transport events
+     * @see             javax.mail.event.TransportEvent
+     */
+    public synchronized void addTransportListener(TransportListener l) {
+	if (transportListeners == null)
+	    transportListeners = new Vector<>();
+	transportListeners.addElement(l);
+    }
+
+    /**
+     * Remove a listener for Transport events. <p>
+     *
+     * The default implementation provided here removes this listener
+     * from the internal list of TransportListeners.
+     *
+     * @param l         the listener
+     * @see             #addTransportListener
+     */
+    public synchronized void removeTransportListener(TransportListener l) {
+	if (transportListeners != null)
+	    transportListeners.removeElement(l);
+    }
+
+    /**
+     * Notify all TransportListeners. Transport implementations are
+     * expected to use this method to broadcast TransportEvents.<p>
+     *
+     * The provided default implementation queues the event into
+     * an internal event queue. An event dispatcher thread dequeues
+     * events from the queue and dispatches them to the registered
+     * TransportListeners. Note that the event dispatching occurs
+     * in a separate thread, thus avoiding potential deadlock problems.
+     *
+     * @param	type	the TransportEvent type
+     * @param	validSent valid addresses to which message was sent
+     * @param	validUnsent valid addresses to which message was not sent
+     * @param	invalid the invalid addresses
+     * @param	msg	the message
+     */
+    protected void notifyTransportListeners(int type, Address[] validSent,
+					    Address[] validUnsent,
+					    Address[] invalid, Message msg) {
+	if (transportListeners == null)
+	    return;
+	
+	TransportEvent e = new TransportEvent(this, type, validSent, 
+					      validUnsent, invalid, msg);
+	queueEvent(e, transportListeners);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/UIDFolder.java b/mail/src/main/java/javax/mail/UIDFolder.java
new file mode 100644
index 0000000..2a8b97c
--- /dev/null
+++ b/mail/src/main/java/javax/mail/UIDFolder.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.util.NoSuchElementException;
+
+/**
+ * The <code>UIDFolder</code> interface is implemented by Folders 
+ * that can support the "disconnected" mode of operation, by providing 
+ * unique-ids for messages in the folder. This interface is based on
+ * the IMAP model for supporting disconnected operation. <p>
+ *
+ * A Unique identifier (UID) is a positive long value, assigned to
+ * each message in a specific folder. Unique identifiers are assigned
+ * in a strictly <strong>ascending</strong> fashion in the mailbox. 
+ * That is, as each message is added to the mailbox it is assigned a 
+ * higher UID than the message(s) which were added previously. Unique
+ * identifiers persist across sessions. This permits a client to 
+ * resynchronize its state from a previous session with the server. <p>
+ *
+ * Associated with every mailbox is a unique identifier validity value.
+ * If unique identifiers from an earlier session fail to persist to 
+ * this session, the unique identifier validity value 
+ * <strong>must</strong> be greater than the one used in the earlier
+ * session. <p>
+ *
+ * Refer to <A HREF="http://www.ietf.org/rfc/rfc2060.txt">RFC 2060</A>
+ * for more information.
+ *
+ * All the Folder objects returned by the default IMAP provider implement
+ * the UIDFolder interface.  Use it as follows:
+ * <blockquote><pre>
+ *
+ * 	Folder f = store.getFolder("whatever");
+ *	UIDFolder uf = (UIDFolder)f;
+ *	long uid = uf.getUID(msg);
+ *
+ * </pre></blockquote><p>
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+
+public interface UIDFolder {
+
+    /**
+     * A fetch profile item for fetching UIDs.
+     * This inner class extends the <code>FetchProfile.Item</code>
+     * class to add new FetchProfile item types, specific to UIDFolders.
+     * The only item currently defined here is the <code>UID</code> item.
+     *
+     * @see FetchProfile
+     */
+    public static class FetchProfileItem extends FetchProfile.Item {
+	protected FetchProfileItem(String name) {
+	    super(name);
+	}
+
+	/**
+	 * UID is a fetch profile item that can be included in a
+	 * <code>FetchProfile</code> during a fetch request to a Folder.
+	 * This item indicates that the UIDs for messages in the specified 
+	 * range are desired to be prefetched. <p>
+	 * 
+	 * An example of how a client uses this is below:
+	 * <blockquote><pre>
+	 *
+	 * 	FetchProfile fp = new FetchProfile();
+	 *	fp.add(UIDFolder.FetchProfileItem.UID);
+	 *	folder.fetch(msgs, fp);
+	 *
+	 * </pre></blockquote>
+	 */ 
+	public static final FetchProfileItem UID = 
+		new FetchProfileItem("UID");
+    }
+
+    /**
+     * This is a special value that can be used as the <code>end</code>
+     * parameter in <code>getMessagesByUID(start, end)</code>, to denote the
+     * UID of the last message in the folder.
+     *
+     * @see #getMessagesByUID
+     */ 
+    public static final long LASTUID = -1;
+
+    /**
+     * The largest value possible for a UID, a 32-bit unsigned integer.
+     * This can be used to fetch all new messages by keeping track of the
+     * last UID that was seen and using:
+     * <blockquote><pre>
+     *
+     * 	Folder f = store.getFolder("whatever");
+     *	UIDFolder uf = (UIDFolder)f;
+     *	Message[] newMsgs =
+     *		uf.getMessagesByUID(lastSeenUID + 1, UIDFolder.MAXUID);
+     *
+     * </pre></blockquote><p>
+     *
+     * @since JavaMail 1.6
+     */
+    public static final long MAXUID = 0xffffffffL; // max 32-bit unsigned int
+
+    /**
+     * Returns the UIDValidity value associated with this folder. <p>
+     * 
+     * Clients typically compare this value against a UIDValidity
+     * value saved from a previous session to insure that any cached 
+     * UIDs are not stale.
+     *
+     * @return UIDValidity
+     * @exception	MessagingException for failures
+     */
+    public long getUIDValidity() throws MessagingException;
+
+    /**
+     * Get the Message corresponding to the given UID. If no such 
+     * message exists, <code>null</code> is returned.
+     *
+     * @param uid	UID for the desired message
+     * @return		the Message object. <code>null</code> is returned
+     *			if no message corresponding to this UID is obtained.
+     * @exception	MessagingException for failures
+     */
+    public Message getMessageByUID(long uid) throws MessagingException;
+
+    /**
+     * Get the Messages specified by the given range. The special
+     * value LASTUID can be used for the <code>end</code> parameter
+     * to indicate the UID of the last message in the folder. <p>
+     *
+     * Note that <code>end</code> need not be greater than <code>start</code>;
+     * the order of the range doesn't matter.
+     * Note also that, unless the folder is empty, use of LASTUID ensures
+     * that at least one message will be returned - the last message in the
+     * folder.
+     *
+     * @param start	start UID
+     * @param end	end UID
+     * @return		array of Message objects
+     * @exception	MessagingException for failures
+     * @see 		#LASTUID
+     */
+    public Message[] getMessagesByUID(long start, long end)
+				throws MessagingException;
+
+    /**
+     * Get the Messages specified by the given array of UIDs. If any UID is 
+     * invalid, <code>null</code> is returned for that entry. <p>
+     *
+     * Note that the returned array will be of the same size as the specified
+     * array of UIDs, and <code>null</code> entries may be present in the
+     * array to indicate invalid UIDs.
+     *
+     * @param uids	array of UIDs
+     * @return		array of Message objects
+     * @exception	MessagingException for failures
+     */
+    public Message[] getMessagesByUID(long[] uids) 
+				throws MessagingException;
+
+    /**
+     * Get the UID for the specified message. Note that the message
+     * <strong>must</strong> belong to this folder. Otherwise
+     * java.util.NoSuchElementException is thrown.
+     *
+     * @param message	Message from this folder
+     * @return		UID for this message
+     * @exception	NoSuchElementException if the given Message
+     *			is not in this Folder.
+     * @exception	MessagingException for other failures
+     */
+    public long getUID(Message message) throws MessagingException;
+
+    /**
+     * Returns the predicted UID that will be assigned to the
+     * next message that is appended to this folder.
+     * Messages might be appended to the folder after this value
+     * is retrieved, causing this value to be out of date.
+     * This value might only be updated when a folder is first opened.
+     * Note that messages may have been appended to the folder
+     * while it was open and thus this value may be out of
+     * date. <p>
+     *
+     * If the value is unknown, -1 is returned.  <p>
+     *
+     * @return		the UIDNEXT value, or -1 if unknown
+     * @exception	MessagingException for failures
+     * @since		JavaMail 1.6
+     */
+    public long getUIDNext() throws MessagingException;
+}
diff --git a/mail/src/main/java/javax/mail/URLName.java b/mail/src/main/java/javax/mail/URLName.java
new file mode 100644
index 0000000..e26955c
--- /dev/null
+++ b/mail/src/main/java/javax/mail/URLName.java
@@ -0,0 +1,786 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.net.*;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStreamWriter;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.BitSet;
+import java.util.Locale;
+
+
+/**
+ * The name of a URL. This class represents a URL name and also 
+ * provides the basic parsing functionality to parse most internet 
+ * standard URL schemes. <p>
+ *
+ * Note that this class differs from <code>java.net.URL</code> 
+ * in that this class just represents the name of a URL, it does 
+ * not model the connection to a URL.
+ *
+ * @author	Christopher Cotton
+ * @author	Bill Shannon
+ */
+
+public class URLName {
+
+    /**
+     * The full version of the URL
+     */
+    protected String fullURL;
+
+    /** 
+     * The protocol to use (ftp, http, nntp, imap, pop3 ... etc.) . 
+     */
+    private String protocol;
+
+    /** 
+     * The username to use when connecting
+     */
+    private String username;
+
+    /** 
+     * The password to use when connecting.
+     */
+    private String password;
+
+    /** 
+     * The host name to which to connect. 
+     */
+    private String host;
+
+    /**
+     * The host's IP address, used in equals and hashCode.
+     * Computed on demand.
+     */
+    private InetAddress hostAddress;
+    private boolean hostAddressKnown = false;
+
+    /** 
+     * The protocol port to connect to. 
+     */
+    private int port = -1;
+
+    /** 
+     * The specified file name on that host. 
+     */
+    private String file;
+
+    /** 
+     * # reference. 
+     */
+    private String ref;
+
+    /**
+     * Our hash code.
+     */
+    private int hashCode = 0;
+
+    /**
+     * A way to turn off encoding, just in case...
+     */
+    private static boolean doEncode = true;
+
+    static {
+	try {
+	    doEncode = !Boolean.getBoolean("mail.URLName.dontencode");
+	} catch (Exception ex) {
+	    // ignore any errors
+	}
+    }
+
+    /**
+     * Creates a URLName object from the specified protocol,
+     * host, port number, file, username, and password. Specifying a port
+     * number of -1 indicates that the URL should use the default port for
+     * the protocol.
+     *
+     * @param	protocol	the protocol
+     * @param	host		the host name
+     * @param	port		the port number
+     * @param	file		the file
+     * @param	username	the user name
+     * @param	password	the password
+     */
+    public URLName(
+	String protocol,
+	String host,
+	int port,
+	String file,
+	String username,
+	String password
+	)
+    {
+	this.protocol = protocol;
+	this.host = host;
+	this.port = port;
+	int refStart;
+	if (file != null && (refStart = file.indexOf('#')) != -1) {
+	    this.file = file.substring(0, refStart);
+	    this.ref = file.substring(refStart + 1);
+	} else {
+	    this.file = file;
+	    this.ref = null;
+	}
+	this.username = doEncode ? encode(username) : username;
+	this.password = doEncode ? encode(password) : password;
+    }
+
+    /**
+     * Construct a URLName from a java.net.URL object.
+     *
+     * @param	url	the URL
+     */
+    public URLName(URL url) {
+	this(url.toString());
+    }
+
+    /**
+     * Construct a URLName from the string.  Parses out all the possible
+     * information (protocol, host, port, file, username, password).
+     *
+     * @param	url	the URL string
+     */
+    public URLName(String url) {
+	parseString(url);
+    }
+
+    /**
+     * Constructs a string representation of this URLName.
+     */
+    @Override
+    public String toString() {
+	if (fullURL == null) {
+	    // add the "protocol:"
+	    StringBuilder tempURL = new StringBuilder();
+	    if (protocol != null) {
+		tempURL.append(protocol);
+		tempURL.append(":");
+	    }
+
+	    if (username != null || host != null) {
+		// add the "//"
+		tempURL.append("//");
+		
+		// add the user:password@
+		// XXX - can you just have a password? without a username?
+		if (username != null) {
+		    tempURL.append(username);
+		
+		    if (password != null){
+			tempURL.append(":");
+			tempURL.append(password);
+		    }
+		
+		    tempURL.append("@");
+		}
+	    
+		// add host
+		if (host != null) {
+		    tempURL.append(host);
+		}
+	    
+		// add port (if needed)
+		if (port != -1) {
+		    tempURL.append(":");
+		    tempURL.append(Integer.toString(port));
+		}
+		if (file != null)
+		    tempURL.append("/");
+	    }
+	    
+	    // add the file
+	    if (file != null) {
+		tempURL.append(file);
+	    }
+	    
+	    // add the ref
+	    if (ref != null) {
+		tempURL.append("#");
+		tempURL.append(ref);
+	    }
+
+	    // create the fullURL now
+	    fullURL = tempURL.toString();
+	}
+
+	return fullURL;
+    }
+
+    /**
+     * Method which does all of the work of parsing the string.
+     *
+     * @param	url	the URL string to parse
+     */
+    protected void parseString(String url) {
+	// initialize everything in case called from subclass
+	// (URLName really should be a final class)
+	protocol = file = ref = host = username = password = null;
+	port = -1;
+
+	int len = url.length();
+
+	// find the protocol
+	// XXX - should check for only legal characters before the colon
+	// (legal: a-z, A-Z, 0-9, "+", ".", "-")
+	int protocolEnd = url.indexOf(':');
+        if (protocolEnd != -1)
+	    protocol = url.substring(0, protocolEnd);
+
+	// is this an Internet standard URL that contains a host name?
+	if (url.regionMatches(protocolEnd + 1, "//", 0, 2)) {
+	    // find where the file starts
+	    String fullhost = null;
+	    int fileStart = url.indexOf('/', protocolEnd + 3);
+	    if (fileStart != -1) {
+		fullhost = url.substring(protocolEnd + 3, fileStart);
+		if (fileStart + 1 < len)
+		    file = url.substring(fileStart + 1);
+		else
+		    file = "";
+	    } else
+		fullhost = url.substring(protocolEnd + 3);
+
+	    // examine the fullhost, for username password etc.
+	    int i = fullhost.indexOf('@');
+	    if (i != -1) {
+		String fulluserpass = fullhost.substring(0, i);
+		fullhost = fullhost.substring(i + 1);
+
+		// get user and password
+		int passindex = fulluserpass.indexOf(':');
+		if (passindex != -1) {
+		    username = fulluserpass.substring(0, passindex);
+		    password = fulluserpass.substring(passindex + 1);
+		} else {
+		    username = fulluserpass;
+		}
+	    }
+	    
+	    // get the port (if there)
+	    int portindex;
+	    if (fullhost.length() > 0 && fullhost.charAt(0) == '[') {
+		// an IPv6 address?
+		portindex = fullhost.indexOf(':', fullhost.indexOf(']'));
+	    } else {
+		portindex = fullhost.indexOf(':');
+	    }
+	    if (portindex != -1) {
+		String portstring = fullhost.substring(portindex + 1);
+		if (portstring.length() > 0) {
+		    try {
+			port = Integer.parseInt(portstring);
+		    } catch (NumberFormatException nfex) {
+			port = -1;
+		    }
+		}
+		
+		host = fullhost.substring(0, portindex);
+	    } else {
+		host = fullhost;
+	    }
+	} else {
+	    if (protocolEnd + 1 < len)
+		file = url.substring(protocolEnd + 1);
+	}
+
+	// extract the reference from the file name, if any
+	int refStart;
+	if (file != null && (refStart = file.indexOf('#')) != -1) {
+	    ref = file.substring(refStart + 1);
+	    file = file.substring(0, refStart);
+	}
+    }
+    
+    /**
+     * Returns the port number of this URLName.
+     * Returns -1 if the port is not set. 
+     *
+     * @return	the port number
+     */
+    public int getPort() {
+	return port;
+    }
+
+    /**
+     * Returns the protocol of this URLName.
+     * Returns null if this URLName has no protocol.
+     *
+     * @return	the protocol
+     */
+    public String getProtocol() {
+	return protocol;
+    }
+
+    /**
+     * Returns the file name of this URLName.
+     * Returns null if this URLName has no file name.
+     *
+     * @return	the file name of this URLName
+     */
+    public String getFile() {
+	return file;
+    }
+
+    /**
+     * Returns the reference of this URLName.
+     * Returns null if this URLName has no reference.
+     *
+     * @return	the reference part of the URLName
+     */
+    public String getRef() {
+	return ref;
+    }
+
+    /**
+     * Returns the host of this URLName.
+     * Returns null if this URLName has no host.
+     *
+     * @return	the host name
+     */
+    public String getHost() {
+	return host;
+    }
+
+    /**
+     * Returns the user name of this URLName.
+     * Returns null if this URLName has no user name.
+     *
+     * @return	the user name
+     */
+    public String getUsername() {
+	return doEncode ? decode(username) : username;
+    }
+
+    /**
+     * Returns the password of this URLName.
+     * Returns null if this URLName has no password.
+     *
+     * @return	the password
+     */
+    public String getPassword() {
+	return doEncode ? decode(password) : password;
+    }
+
+    /**
+     * Constructs a URL from the URLName.
+     *
+     * @return	the URL
+     * @exception	MalformedURLException if the URL is malformed
+     */
+    public URL getURL() throws MalformedURLException {
+	// URL expects the file to include the separating "/"
+	String f = getFile();
+	if (f == null)
+	    f = "";
+	else
+	    f = "/" + f;
+        return new URL(getProtocol(), getHost(), getPort(), f);
+    }
+
+    /**
+     * Compares two URLNames. The result is true if and only if the
+     * argument is not null and is a URLName object that represents the
+     * same URLName as this object. Two URLName objects are equal if
+     * they have the same protocol and the same host,
+     * the same port number on the host, the same username,
+     * and the same file on the host. The fields (host, username,
+     * file) are also considered the same if they are both
+     * null.  <p>
+     *
+     * Hosts are considered equal if the names are equal (case independent)
+     * or if host name lookups for them both succeed and they both reference
+     * the same IP address. <p>
+     *
+     * Note that URLName has no knowledge of default port numbers for
+     * particular protocols, so "imap://host" and "imap://host:143"
+     * would not compare as equal. <p>
+     *
+     * Note also that the password field is not included in the comparison,
+     * nor is any reference field appended to the filename.
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof URLName))
+	    return false;
+	URLName u2 = (URLName)obj;
+
+	// compare protocols
+	if (!(protocol == u2.protocol ||
+		(protocol != null && protocol.equals(u2.protocol))))
+	    return false;
+
+	// compare hosts
+	InetAddress a1 = getHostAddress(), a2 = u2.getHostAddress();
+	// if we have internet address for both, and they're not the same, fail
+	if (a1 != null && a2 != null) {
+	    if (!a1.equals(a2))
+		return false;
+	// else, if we have host names for both, and they're not the same, fail
+	} else if (host != null && u2.host != null) {
+	    if (!host.equalsIgnoreCase(u2.host))
+		return false;
+	// else, if not both null
+	} else if (host != u2.host) {
+	    return false;
+	}
+	// at this point, hosts match
+
+	// compare usernames
+	if (!(username == u2.username ||
+		(username != null && username.equals(u2.username))))
+	    return false;
+
+	// Forget about password since it doesn't
+	// really denote a different store.
+
+	// compare files
+	String f1 = file == null ? "" : file;
+	String f2 = u2.file == null ? "" : u2.file;
+
+	if (!f1.equals(f2))
+	    return false;
+
+	// compare ports
+	if (port != u2.port)
+	    return false;
+
+	// all comparisons succeeded, they're equal
+        return true;
+    }
+
+    /**
+     * Compute the hash code for this URLName.
+     */
+    @Override
+    public int hashCode() {
+	if (hashCode != 0)
+	    return hashCode;
+	if (protocol != null)
+	    hashCode += protocol.hashCode();
+	InetAddress addr = getHostAddress();
+	if (addr != null)
+	    hashCode += addr.hashCode();
+	else if (host != null)
+	    hashCode += host.toLowerCase(Locale.ENGLISH).hashCode();
+	if (username != null)
+	    hashCode += username.hashCode();
+	if (file != null)
+	    hashCode += file.hashCode();
+	hashCode += port;
+	return hashCode;
+    }
+
+    /**
+     * Get the IP address of our host.  Look up the
+     * name the first time and remember that we've done
+     * so, whether the lookup fails or not.
+     */
+    private synchronized InetAddress getHostAddress() {
+	if (hostAddressKnown)
+	    return hostAddress;
+	if (host == null)
+	    return null;
+	try {
+	    hostAddress = InetAddress.getByName(host);
+	} catch (UnknownHostException ex) {
+	    hostAddress = null;
+	}
+	hostAddressKnown = true;
+	return hostAddress;
+    }
+
+    /**
+     * The class contains a utility method for converting a
+     * <code>String</code> into a MIME format called
+     * "<code>x-www-form-urlencoded</code>" format.
+     * <p>
+     * To convert a <code>String</code>, each character is examined in turn:
+     * <ul>
+     * <li>The ASCII characters '<code>a</code>' through '<code>z</code>',
+     *     '<code>A</code>' through '<code>Z</code>', '<code>0</code>'
+     *     through '<code>9</code>', and &quot;.&quot;, &quot;-&quot;, 
+     * &quot;*&quot;, &quot;_&quot; remain the same.
+     * <li>The space character '<code>&nbsp;</code>' is converted into a
+     *     plus sign '<code>+</code>'.
+     * <li>All other characters are converted into the 3-character string
+     *     "<code>%<i>xy</i></code>", where <i>xy</i> is the two-digit
+     *     hexadecimal representation of the lower 8-bits of the character.
+     * </ul>
+     *
+     * @author  Herb Jellinek
+     * @since   JDK1.0
+     */
+    static BitSet dontNeedEncoding;
+    static final int caseDiff = ('a' - 'A');
+
+    /* The list of characters that are not encoded have been determined by
+       referencing O'Reilly's "HTML: The Definitive Guide" (page 164). */
+
+    static {
+	dontNeedEncoding = new BitSet(256);
+	int i;
+	for (i = 'a'; i <= 'z'; i++) {
+	    dontNeedEncoding.set(i);
+	}
+	for (i = 'A'; i <= 'Z'; i++) {
+	    dontNeedEncoding.set(i);
+	}
+	for (i = '0'; i <= '9'; i++) {
+	    dontNeedEncoding.set(i);
+	}
+	/* encoding a space to a + is done in the encode() method */
+	dontNeedEncoding.set(' ');
+	dontNeedEncoding.set('-');
+	dontNeedEncoding.set('_');
+	dontNeedEncoding.set('.');
+	dontNeedEncoding.set('*');
+    }
+
+    /**
+     * Translates a string into <code>x-www-form-urlencoded</code> format.
+     *
+     * @param   s   <code>String</code> to be translated.
+     * @return  the translated <code>String</code>.
+     */
+    static String encode(String s) {
+	if (s == null)
+	    return null;
+	// the common case is no encoding is needed
+	for (int i = 0; i < s.length(); i++) {
+	    int c = (int)s.charAt(i);
+	    if (c == ' ' || !dontNeedEncoding.get(c))
+		return _encode(s);
+	}
+	return s;
+    }
+
+    private static String _encode(String s) {
+	int maxBytesPerChar = 10;
+        StringBuilder out = new StringBuilder(s.length());
+	ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
+	OutputStreamWriter writer = new OutputStreamWriter(buf);
+
+	for (int i = 0; i < s.length(); i++) {
+	    int c = (int)s.charAt(i);
+	    if (dontNeedEncoding.get(c)) {
+		if (c == ' ') {
+		    c = '+';
+		}
+		out.append((char)c);
+	    } else {
+		// convert to external encoding before hex conversion
+		try {
+		    writer.write(c);
+		    writer.flush();
+		} catch(IOException e) {
+		    buf.reset();
+		    continue;
+		}
+		byte[] ba = buf.toByteArray();
+		for (int j = 0; j < ba.length; j++) {
+		    out.append('%');
+		    char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);
+		    // converting to use uppercase letter as part of
+		    // the hex value if ch is a letter.
+		    if (Character.isLetter(ch)) {
+			ch -= caseDiff;
+		    }
+		    out.append(ch);
+		    ch = Character.forDigit(ba[j] & 0xF, 16);
+		    if (Character.isLetter(ch)) {
+			ch -= caseDiff;
+		    }
+		    out.append(ch);
+		}
+		buf.reset();
+	    }
+	}
+
+	return out.toString();
+    }
+
+
+    /**
+     * The class contains a utility method for converting from
+     * a MIME format called "<code>x-www-form-urlencoded</code>"
+     * to a <code>String</code>
+     * <p>
+     * To convert to a <code>String</code>, each character is examined in turn:
+     * <ul>
+     * <li>The ASCII characters '<code>a</code>' through '<code>z</code>',
+     * '<code>A</code>' through '<code>Z</code>', and '<code>0</code>'
+     * through '<code>9</code>' remain the same.
+     * <li>The plus sign '<code>+</code>'is converted into a
+     * space character '<code>&nbsp;</code>'.
+     * <li>The remaining characters are represented by 3-character
+     * strings which begin with the percent sign,
+     * "<code>%<i>xy</i></code>", where <i>xy</i> is the two-digit
+     * hexadecimal representation of the lower 8-bits of the character.
+     * </ul>
+     *
+     * @author  Mark Chamness
+     * @author  Michael McCloskey
+     * @since   1.2
+     */
+
+    /**
+     * Decodes a &quot;x-www-form-urlencoded&quot; 
+     * to a <tt>String</tt>.
+     * @param s the <code>String</code> to decode
+     * @return the newly decoded <code>String</code>
+     */
+    static String decode(String s) {
+	if (s == null)
+	    return null;
+	if (indexOfAny(s, "+%") == -1)
+	    return s;		// the common case
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+            switch (c) {
+                case '+':
+                    sb.append(' ');
+                    break;
+                case '%':
+                    try {
+                        sb.append((char)Integer.parseInt(
+                                        s.substring(i+1,i+3),16));
+                    } catch (NumberFormatException e) {
+                        throw new IllegalArgumentException(
+			    "Illegal URL encoded value: " +
+			    s.substring(i,i+3));
+                    }
+                    i += 2;
+                    break;
+                default:
+                    sb.append(c);
+                    break;
+            }
+        }
+        // Undo conversion to external encoding
+        String result = sb.toString();
+        try {
+            byte[] inputBytes = result.getBytes("8859_1");
+            result = new String(inputBytes);
+        } catch (UnsupportedEncodingException e) {
+            // The system should always have 8859_1
+        }
+        return result;
+    }
+
+    /**
+     * Return the first index of any of the characters in "any" in "s",
+     * or -1 if none are found.
+     *
+     * This should be a method on String.
+     */
+    private static int indexOfAny(String s, String any) {
+	return indexOfAny(s, any, 0);
+    }
+
+    private static int indexOfAny(String s, String any, int start) {
+	try {
+	    int len = s.length();
+	    for (int i = start; i < len; i++) {
+		if (any.indexOf(s.charAt(i)) >= 0)
+		    return i;
+	    }
+	    return -1;
+	} catch (StringIndexOutOfBoundsException e) {
+	    return -1;
+	}
+    }
+
+    /*
+    // Do not remove, this is needed when testing new URL cases
+    public static void main(String[] argv) {
+	String [] testURLNames = {
+	    "protocol://userid:password@host:119/file",
+	    "http://funny/folder/file.html",
+	    "http://funny/folder/file.html#ref",
+	    "http://funny/folder/file.html#",
+	    "http://funny/#ref",
+	    "imap://jmr:secret@labyrinth//var/mail/jmr",
+	    "nntp://fred@labyrinth:143/save/it/now.mbox",
+	    "imap://jmr@labyrinth/INBOX",
+	    "imap://labryrinth",
+	    "imap://labryrinth/",
+	    "file:",
+	    "file:INBOX",
+	    "file:/home/shannon/mail/foo",
+	    "/tmp/foo",
+	    "//host/tmp/foo",
+	    ":/tmp/foo",
+	    "/really/weird:/tmp/foo#bar",
+	    ""
+	};
+
+	URLName url =
+	    new URLName("protocol", "host", 119, "file", "userid", "password");
+	System.out.println("Test URL: " + url.toString());
+	if (argv.length == 0) {
+	    for (int i = 0; i < testURLNames.length; i++) {
+		print(testURLNames[i]);
+		System.out.println();
+	    }
+	} else {
+	    for (int i = 0; i < argv.length; i++) {
+		print(argv[i]);
+		System.out.println();
+	    }
+	    if (argv.length == 2) {
+		URLName u1 = new URLName(argv[0]);
+		URLName u2 = new URLName(argv[1]);
+		System.out.println("URL1 hash code: " + u1.hashCode());
+		System.out.println("URL2 hash code: " + u2.hashCode());
+		if (u1.equals(u2))
+		    System.out.println("success, equal");
+		else
+		    System.out.println("fail, not equal");
+		if (u2.equals(u1))
+		    System.out.println("success, equal");
+		else
+		    System.out.println("fail, not equal");
+		if (u1.hashCode() == u2.hashCode())
+		    System.out.println("success, hashCodes equal");
+		else
+		    System.out.println("fail, hashCodes not equal");
+	    }
+	}
+    }
+
+    private static void print(String name) {
+	URLName url = new URLName(name);
+	System.out.println("Original URL: " + name);
+	System.out.println("The fullUrl : " + url.toString());
+	if (!name.equals(url.toString()))
+	    System.out.println("            : NOT EQUAL!");
+	System.out.println("The protocol is: " + url.getProtocol());
+	System.out.println("The host is: " + url.getHost());
+	System.out.println("The port is: " + url.getPort());
+	System.out.println("The user is: " + url.getUsername());
+	System.out.println("The password is: " + url.getPassword());
+	System.out.println("The file is: " + url.getFile());
+	System.out.println("The ref is: " + url.getRef());
+    }
+    */
+}
diff --git a/mail/src/main/java/javax/mail/event/ConnectionAdapter.java b/mail/src/main/java/javax/mail/event/ConnectionAdapter.java
new file mode 100644
index 0000000..a07e151
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/ConnectionAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+/**
+ * The adapter which receives connection events.
+ * The methods in this class are empty;  this class is provided as a
+ * convenience for easily creating listeners by extending this class
+ * and overriding only the methods of interest.
+ *
+ * @author John Mani
+ */
+public abstract class ConnectionAdapter implements ConnectionListener {
+    @Override
+    public void opened(ConnectionEvent e) {}
+    @Override
+    public void disconnected(ConnectionEvent e) {}
+    @Override
+    public void closed(ConnectionEvent e) {}
+}
diff --git a/mail/src/main/java/javax/mail/event/ConnectionEvent.java b/mail/src/main/java/javax/mail/event/ConnectionEvent.java
new file mode 100644
index 0000000..c89a003
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/ConnectionEvent.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+import javax.mail.*;
+
+/**
+ * This class models Connection events.
+ *
+ * @author John Mani
+ */
+
+public class ConnectionEvent extends MailEvent  {
+
+    /** A connection was opened. */
+    public static final int OPENED 		= 1;
+    /** A connection was disconnected (not currently used). */
+    public static final int DISCONNECTED 	= 2;
+    /** A connection was closed. */
+    public static final int CLOSED 		= 3;
+
+    /**
+     * The event type.
+     *
+     * @serial
+     */
+    protected int type;
+
+    private static final long serialVersionUID = -1855480171284792957L;
+
+    /**
+     * Construct a ConnectionEvent.
+     *
+     * @param	source  The source object
+     * @param	type	the event type
+     */
+    public ConnectionEvent(Object source, int type) {
+	super(source);
+	this.type = type;
+    }
+
+    /**
+     * Return the type of this event
+     * @return  type
+     */
+    public int getType() {
+	return type;
+    }
+
+    /**
+     * Invokes the appropriate ConnectionListener method
+     */
+    @Override
+    public void dispatch(Object listener) {
+	if (type == OPENED)
+	    ((ConnectionListener)listener).opened(this);
+	else if (type == DISCONNECTED)
+	    ((ConnectionListener)listener).disconnected(this);
+	else if (type == CLOSED)
+	    ((ConnectionListener)listener).closed(this);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/event/ConnectionListener.java b/mail/src/main/java/javax/mail/event/ConnectionListener.java
new file mode 100644
index 0000000..64721df
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/ConnectionListener.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+
+/**
+ * This is the Listener interface for Connection events.
+ *
+ * @author John Mani
+ */
+
+public interface ConnectionListener extends java.util.EventListener {
+
+    /**
+     * Invoked when a Store/Folder/Transport is opened.
+     *
+     * @param	e	the ConnectionEvent
+     */
+    public void opened(ConnectionEvent e);
+
+    /**
+     * Invoked when a Store is disconnected. Note that a folder
+     * cannot be disconnected, so a folder will not fire this event
+     *
+     * @param	e	the ConnectionEvent
+     */
+    public void disconnected(ConnectionEvent e);
+
+    /**
+     * Invoked when a Store/Folder/Transport is closed.
+     *
+     * @param	e	the ConnectionEvent
+     */
+    public void closed(ConnectionEvent e);
+}
diff --git a/mail/src/main/java/javax/mail/event/FolderAdapter.java b/mail/src/main/java/javax/mail/event/FolderAdapter.java
new file mode 100644
index 0000000..7bdd009
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/FolderAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+/**
+ * The adapter which receives Folder events.
+ * The methods in this class are empty;  this class is provided as a
+ * convenience for easily creating listeners by extending this class
+ * and overriding only the methods of interest.
+ *
+ * @author John Mani
+ */
+public abstract class FolderAdapter implements FolderListener {
+    @Override
+    public void folderCreated(FolderEvent e) {}
+    @Override
+    public void folderRenamed(FolderEvent e) {}
+    @Override
+    public void folderDeleted(FolderEvent e) {}
+}
diff --git a/mail/src/main/java/javax/mail/event/FolderEvent.java b/mail/src/main/java/javax/mail/event/FolderEvent.java
new file mode 100644
index 0000000..4c89ae6
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/FolderEvent.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+import javax.mail.*;
+
+/**
+ * This class models Folder <em>existence</em> events. FolderEvents are
+ * delivered to FolderListeners registered on the affected Folder as
+ * well as the containing Store. <p>
+ *
+ * Service providers vary widely in their ability to notify clients of
+ * these events.  At a minimum, service providers must notify listeners
+ * registered on the same Store or Folder object on which the operation
+ * occurs.  Service providers may also notify listeners when changes
+ * are made through operations on other objects in the same virtual
+ * machine, or by other clients in the same or other hosts.  Such
+ * notifications are not required and are typically not supported
+ * by mail protocols (including IMAP).
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class FolderEvent extends MailEvent {
+
+    /** The folder was created. */
+    public static final int CREATED 		= 1;
+    /** The folder was deleted. */
+    public static final int DELETED 		= 2;
+    /** The folder was renamed. */
+    public static final int RENAMED 		= 3;
+
+    /**
+     * The event type.
+     *
+     * @serial
+     */
+    protected int type;
+
+    /**
+     * The folder the event occurred on.
+     */
+    transient protected Folder folder;
+
+    /**
+     * The folder that represents the new name, in case of a RENAMED event.
+     *
+     * @since	JavaMail 1.1
+     */
+    transient protected Folder newFolder;
+
+    private static final long serialVersionUID = 5278131310563694307L;
+
+    /**
+     * Constructor. <p>
+     *
+     * @param source  	The source of the event
+     * @param folder	The affected folder
+     * @param type	The event type
+     */
+    public FolderEvent(Object source, Folder folder, int type) {
+	this(source, folder, folder, type);
+    }
+
+    /**
+     * Constructor. Use for RENAMED events.
+     *
+     * @param source  	The source of the event
+     * @param oldFolder	The folder that is renamed
+     * @param newFolder	The folder that represents the new name
+     * @param type	The event type
+     * @since		JavaMail 1.1
+     */
+    public FolderEvent(Object source, Folder oldFolder, 
+		       Folder newFolder, int type) {
+	super(source);
+	this.folder = oldFolder;
+	this.newFolder = newFolder;
+	this.type = type;
+    }
+
+    /**
+     * Return the type of this event.
+     *
+     * @return  type
+     */
+    public int getType() {
+	return type;
+    }
+
+    /**
+     * Return the affected folder.
+     *
+     * @return 		the affected folder
+     * @see 		#getNewFolder
+     */
+    public Folder getFolder() {
+	return folder;
+    }
+
+    /**
+     * If this event indicates that a folder is renamed, (i.e, the event type
+     * is RENAMED), then this method returns the Folder object representing the
+     * new name. <p>
+     *
+     * The <code>getFolder()</code> method returns the folder that is renamed.
+     *
+     * @return		Folder representing the new name.
+     * @see		#getFolder
+     * @since		JavaMail 1.1
+     */
+    public Folder getNewFolder() {
+	return newFolder;
+    }
+
+    /**
+     * Invokes the appropriate FolderListener method
+     */
+    @Override
+    public void dispatch(Object listener) {
+	if (type == CREATED)
+	    ((FolderListener)listener).folderCreated(this);
+	else if (type == DELETED)
+	    ((FolderListener)listener).folderDeleted(this);
+	else if (type == RENAMED)
+	    ((FolderListener)listener).folderRenamed(this);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/event/FolderListener.java b/mail/src/main/java/javax/mail/event/FolderListener.java
new file mode 100644
index 0000000..5c652a3
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/FolderListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+
+/**
+ * This is the Listener interface for Folder events.
+ *
+ * @author John Mani
+ */
+
+public interface FolderListener extends java.util.EventListener {
+    /**
+     * Invoked when a Folder is created.
+     *
+     * @param	e	the FolderEvent
+     */
+    public void folderCreated(FolderEvent e);
+
+    /**
+     * Invoked when a folder is deleted.
+     *
+     * @param	e	the FolderEvent
+     */
+    public void folderDeleted(FolderEvent e);
+
+    /**
+     * Invoked when a folder is renamed.
+     *
+     * @param	e	the FolderEvent
+     */
+    public void folderRenamed(FolderEvent e);
+}
diff --git a/mail/src/main/java/javax/mail/event/MailEvent.java b/mail/src/main/java/javax/mail/event/MailEvent.java
new file mode 100644
index 0000000..97e4191
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/MailEvent.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.EventObject;
+
+/**
+ * Common base class for mail events, defining the dispatch method.
+ *
+ * @author Bill Shannon
+ */
+
+public abstract class MailEvent extends EventObject {
+    private static final long serialVersionUID = 1846275636325456631L;
+
+    /**
+     * Construct a MailEvent referring to the given source.
+     *
+     * @param	source	the source of the event
+     */
+    public MailEvent(Object source) {
+        super(source);
+    }
+
+    /**
+     * This method invokes the appropriate method on a listener for
+     * this event. Subclasses provide the implementation.
+     *
+     * @param	listener	the listener to invoke on
+     */
+    public abstract void dispatch(Object listener);
+}
diff --git a/mail/src/main/java/javax/mail/event/MessageChangedEvent.java b/mail/src/main/java/javax/mail/event/MessageChangedEvent.java
new file mode 100644
index 0000000..0099592
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/MessageChangedEvent.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+import javax.mail.*;
+
+/**
+ * This class models Message change events.
+ *
+ * @author John Mani
+ */
+
+public class MessageChangedEvent extends MailEvent {
+
+    /** The message's flags changed. */
+    public static final int FLAGS_CHANGED 	= 1;
+    /** The message's envelope (headers, but not body) changed. */
+    public static final int ENVELOPE_CHANGED 	= 2;
+
+    /**
+     * The event type.
+     *
+     * @serial
+     */
+    protected int type;
+
+    /**
+     * The message that changed.
+     */
+    transient protected Message msg;
+
+    private static final long serialVersionUID = -4974972972105535108L;
+
+    /**
+     * Constructor.
+     * @param source  	The folder that owns the message
+     * @param type	The change type
+     * @param msg	The changed message 
+     */
+    public MessageChangedEvent(Object source, int type, Message msg) {
+	super(source);
+	this.msg = msg;
+	this.type = type;
+    }
+
+    /**
+     * Return the type of this event.
+     * @return  type
+     */
+    public int getMessageChangeType() {
+	return type;
+    }
+
+    /**
+     * Return the changed Message.
+     * @return  the message
+     */
+    public Message getMessage() {
+	return msg;
+    }
+
+    /**
+     * Invokes the appropriate MessageChangedListener method.
+     */
+    @Override
+    public void dispatch(Object listener) {
+	((MessageChangedListener)listener).messageChanged(this);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/event/MessageChangedListener.java b/mail/src/main/java/javax/mail/event/MessageChangedListener.java
new file mode 100644
index 0000000..c4eac48
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/MessageChangedListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+
+/**
+ * This is the Listener interface for MessageChanged events
+ *
+ * @author John Mani
+ */
+
+public interface MessageChangedListener extends java.util.EventListener {
+    /**
+     * Invoked when a message is changed. The change-type specifies
+     * what changed.
+     *
+     * @param	e	the MessageChangedEvent
+     * @see MessageChangedEvent#FLAGS_CHANGED
+     * @see MessageChangedEvent#ENVELOPE_CHANGED
+     */
+    public void messageChanged(MessageChangedEvent e);
+}
diff --git a/mail/src/main/java/javax/mail/event/MessageCountAdapter.java b/mail/src/main/java/javax/mail/event/MessageCountAdapter.java
new file mode 100644
index 0000000..694069f
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/MessageCountAdapter.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+/**
+ * The adapter which receives MessageCount events.
+ * The methods in this class are empty;  this class is provided as a
+ * convenience for easily creating listeners by extending this class
+ * and overriding only the methods of interest.
+ *
+ * @author John Mani
+ */
+public abstract class MessageCountAdapter implements MessageCountListener {
+    @Override
+    public void messagesAdded(MessageCountEvent e) {}
+    @Override
+    public void messagesRemoved(MessageCountEvent e) {}
+}
diff --git a/mail/src/main/java/javax/mail/event/MessageCountEvent.java b/mail/src/main/java/javax/mail/event/MessageCountEvent.java
new file mode 100644
index 0000000..24d38a8
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/MessageCountEvent.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+import javax.mail.*;
+
+/**
+ * This class notifies changes in the number of messages in a folder. <p>
+ *
+ * Note that some folder types may only deliver MessageCountEvents at
+ * certain times or after certain operations.  IMAP in particular will
+ * only notify the client of MessageCountEvents when a client issues a
+ * new command.  Refer to
+ * <A HREF="http://www.ietf.org/rfc/rfc3501.txt" TARGET="_top">RFC 3501</A>
+ * for details.
+ * A client may want to "poll" the folder by occasionally calling the
+ * {@link javax.mail.Folder#getMessageCount getMessageCount} or
+ * {@link javax.mail.Folder#isOpen isOpen} methods
+ * to solicit any such notifications.
+ *
+ * @author John Mani
+ */
+
+public class MessageCountEvent extends MailEvent {
+
+    /** The messages were added to their folder */
+    public static final int ADDED 		= 1;
+    /** The messages were removed from their folder */
+    public static final int REMOVED 		= 2;
+
+    /**
+     * The event type.
+     *
+     * @serial
+     */
+    protected int type;
+
+    /**
+     * If true, this event is the result of an explicit
+     * expunge by this client, and the messages in this 
+     * folder have been renumbered to account for this.
+     * If false, this event is the result of an expunge
+     * by external sources.
+     *
+     * @serial
+     */
+    protected boolean removed;
+
+    /**
+     * The messages.
+     */
+    transient protected Message[] msgs;
+
+    private static final long serialVersionUID = -7447022340837897369L;
+
+    /**
+     * Constructor.
+     * @param folder  	The containing folder
+     * @param type	The event type
+     * @param removed	If true, this event is the result of an explicit
+     *			expunge by this client, and the messages in this 
+     *			folder have been renumbered to account for this.
+     *			If false, this event is the result of an expunge
+     *			by external sources.
+     *
+     * @param msgs	The messages added/removed
+     */
+    public MessageCountEvent(Folder folder, int type, 
+			     boolean removed, Message[] msgs) {
+	super(folder);
+	this.type = type;
+	this.removed = removed;
+	this.msgs = msgs;
+    }
+
+    /**
+     * Return the type of this event.
+     * @return  type
+     */
+    public int getType() {
+	return type;
+    }
+
+    /**
+     * Indicates whether this event is the result of an explicit
+     * expunge by this client, or due to an expunge from external
+     * sources. If <code>true</code>, this event is due to an
+     * explicit expunge and hence all remaining messages in this
+     * folder have been renumbered. If <code>false</code>, this event
+     * is due to an external expunge. <p>
+     *
+     * Note that this method is valid only if the type of this event
+     * is <code>REMOVED</code>
+     *
+     * @return	true if the message has been removed
+     */
+    public boolean isRemoved() {
+	return removed;
+    }
+
+    /**
+     * Return the array of messages added or removed.
+     * @return array of messages
+     */
+    public Message[] getMessages() {
+	return msgs;
+    }
+
+    /**
+     * Invokes the appropriate MessageCountListener method.
+     */
+    @Override
+    public void dispatch(Object listener) {
+	if (type == ADDED)
+	    ((MessageCountListener)listener).messagesAdded(this);
+	else // REMOVED
+	    ((MessageCountListener)listener).messagesRemoved(this);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/event/MessageCountListener.java b/mail/src/main/java/javax/mail/event/MessageCountListener.java
new file mode 100644
index 0000000..5793fe0
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/MessageCountListener.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+
+/**
+ * This is the Listener interface for MessageCount events.
+ *
+ * @author John Mani
+ */
+
+public interface MessageCountListener extends java.util.EventListener {
+    /**
+     * Invoked when messages are added into a folder.
+     *
+     * @param	e	the MessageCountEvent
+     */
+    public void messagesAdded(MessageCountEvent e);
+
+    /**
+     * Invoked when messages are removed (expunged) from a folder.
+     *
+     * @param	e	the MessageCountEvent
+     */
+    public void messagesRemoved(MessageCountEvent e);
+}
diff --git a/mail/src/main/java/javax/mail/event/StoreEvent.java b/mail/src/main/java/javax/mail/event/StoreEvent.java
new file mode 100644
index 0000000..b433283
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/StoreEvent.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+import javax.mail.*;
+
+/**
+ * This class models notifications from the Store connection. These
+ * notifications can be ALERTS or NOTICES. ALERTS must be presented
+ * to the user in a fashion that calls the user's attention to the
+ * message.
+ *
+ * @author John Mani
+ */
+
+public class StoreEvent extends MailEvent {
+
+    /**
+     * Indicates that this message is an ALERT.
+     */
+    public static final int ALERT 		= 1;
+
+    /**
+     * Indicates that this message is a NOTICE.
+     */
+    public static final int NOTICE 		= 2;
+
+    /**
+     * The event type.
+     *
+     * @serial
+     */
+    protected int type;
+
+    /**
+     * The message text to be presented to the user.
+     *
+     * @serial
+     */
+    protected String message;
+
+    private static final long serialVersionUID = 1938704919992515330L;
+
+    /**
+     * Construct a StoreEvent.
+     *
+     * @param	store	the source Store
+     * @param	type	the event type
+     * @param	message	a message assoicated with the event
+     */
+    public StoreEvent(Store store, int type, String message) {
+	super(store);
+	this.type = type;
+	this.message = message;
+    }
+
+    /**
+     * Return the type of this event.
+     *
+     * @return  type
+     * @see #ALERT
+     * @see #NOTICE
+     */
+    public int getMessageType() {
+	return type;
+    }
+
+    /**
+     * Get the message from the Store.
+     *
+     * @return message from the Store
+     */
+    public String getMessage() {
+	return message;
+    }
+
+    /**
+     * Invokes the appropriate StoreListener method.
+     */
+    @Override
+    public void dispatch(Object listener) {
+	((StoreListener)listener).notification(this);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/event/StoreListener.java b/mail/src/main/java/javax/mail/event/StoreListener.java
new file mode 100644
index 0000000..3c00ab8
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/StoreListener.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+
+/**
+ * This is the Listener interface for Store Notifications.
+ *
+ * @author John Mani
+ */
+
+public interface StoreListener extends java.util.EventListener {
+
+   /**
+    * Invoked when the Store generates a notification event.
+    *
+    * @param	e	the StoreEvent
+    * @see StoreEvent#ALERT
+    * @see StoreEvent#NOTICE
+    */
+   public void notification(StoreEvent e);
+}
diff --git a/mail/src/main/java/javax/mail/event/TransportAdapter.java b/mail/src/main/java/javax/mail/event/TransportAdapter.java
new file mode 100644
index 0000000..798cef7
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/TransportAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+/**
+ * The adapter which receives Transport events.
+ * The methods in this class are empty;  this class is provided as a
+ * convenience for easily creating listeners by extending this class
+ * and overriding only the methods of interest.
+ *
+ * @author John Mani
+ */
+public abstract class TransportAdapter implements TransportListener {
+    @Override
+    public void messageDelivered(TransportEvent e) {}
+    @Override
+    public void messageNotDelivered(TransportEvent e) {}
+    @Override
+    public void messagePartiallyDelivered(TransportEvent e) {}
+}
diff --git a/mail/src/main/java/javax/mail/event/TransportEvent.java b/mail/src/main/java/javax/mail/event/TransportEvent.java
new file mode 100644
index 0000000..2f754ca
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/TransportEvent.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+import javax.mail.*;
+
+/**
+ * This class models Transport events.
+ *
+ * @author John Mani
+ * @author Max Spivak
+ * 
+ * @see javax.mail.Transport
+ * @see javax.mail.event.TransportListener
+ */
+
+public class TransportEvent extends MailEvent {
+
+    /**
+     * Message has been	successfully delivered to all recipients by the
+     * transport firing this event. validSent[] contains all the addresses
+     * this transport sent to successfully. validUnsent[] and invalid[] 
+     * should be null,
+     */
+    public static final int MESSAGE_DELIVERED	  = 1;
+
+    /**
+     * Message was not sent for some reason. validSent[] should be null. 
+     * validUnsent[] may have addresses that are valid (but the message
+     * wasn't sent to them). invalid[] should likely contain invalid addresses.
+     */
+    public static final int MESSAGE_NOT_DELIVERED = 2;
+
+    /**
+     * Message was successfully sent to some recipients but not to all. 
+     * validSent[] holds addresses of recipients to whom the message was sent.
+     * validUnsent[] holds valid addresses to which the message was not sent.
+     * invalid[] holds invalid addresses, if any.
+     */
+    public static final int MESSAGE_PARTIALLY_DELIVERED = 3;
+
+
+    /**
+     * The event type.
+     *
+     * @serial
+     */
+    protected int type;
+
+    /** The valid address to which the message was sent. */
+    transient protected Address[] validSent;
+    /** The valid address to which the message was not sent. */
+    transient protected Address[] validUnsent;
+    /** The invalid addresses. */
+    transient protected Address[] invalid;
+    /** The Message to which this event applies. */
+    transient protected Message msg;
+
+    private static final long serialVersionUID = -4729852364684273073L;
+
+    /**
+     * Constructor.
+     *
+     * @param	transport The Transport object
+     * @param	type	the event type (MESSAGE_DELIVERED, etc.)
+     * @param	validSent the valid addresses to which the message was sent
+     * @param	validUnsent the valid addresses to which the message was
+     *				not sent
+     * @param	invalid	the invalid addresses
+     * @param	msg	the message being sent
+     */
+    public TransportEvent(Transport transport, int type, Address[] validSent,
+			  Address[] validUnsent, Address[] invalid,
+			  Message msg) {
+	super(transport);
+	this.type = type;
+	this.validSent = validSent;
+	this.validUnsent = validUnsent;
+	this.invalid = invalid;
+	this.msg = msg;
+    }
+
+    /**
+     * Return the type of this event.
+     * @return  type
+     */
+    public int getType() {
+	return type;
+    }
+
+    /**
+     * Return the addresses to which this message was sent succesfully.
+     * @return Addresses to which the message was sent successfully or null
+     */
+    public Address[] getValidSentAddresses() {
+	return validSent;
+    }
+
+    /**
+     * Return the addresses that are valid but to which this message 
+     * was not sent.
+     * @return Addresses that are valid but to which the message was 
+     *         not sent successfully or null
+     */
+    public Address[] getValidUnsentAddresses() {
+	return validUnsent;
+    }
+
+    /**
+     * Return the addresses to which this message could not be sent.
+     * @return Addresses to which the message sending failed or null
+     */
+    public Address[] getInvalidAddresses() {
+	return invalid;
+    }
+
+    /**
+     * Get the Message object associated with this Transport Event.
+     *   
+     * @return          the Message object
+     * @since		JavaMail 1.2
+     */  
+    public Message getMessage() {
+        return msg;
+    }
+
+    /**
+     * Invokes the appropriate TransportListener method.
+     */
+    @Override
+    public void dispatch(Object listener) {
+	if (type == MESSAGE_DELIVERED)	
+	    ((TransportListener)listener).messageDelivered(this);
+	else if (type == MESSAGE_NOT_DELIVERED)
+	    ((TransportListener)listener).messageNotDelivered(this);
+	else // MESSAGE_PARTIALLY_DELIVERED
+	    ((TransportListener)listener).messagePartiallyDelivered(this);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/event/TransportListener.java b/mail/src/main/java/javax/mail/event/TransportListener.java
new file mode 100644
index 0000000..3fc030d
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/TransportListener.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.event;
+
+import java.util.*;
+
+/**
+ * This is the Listener interface for Transport events
+ *
+ * @author John Mani
+ * @author Max Spivak
+ *
+ * @see javax.mail.Transport
+ * @see javax.mail.event.TransportEvent
+ */
+
+public interface TransportListener extends java.util.EventListener {
+
+    /**
+     * Invoked when a Message is succesfully delivered.
+     * @param	e TransportEvent
+     */
+    public void messageDelivered(TransportEvent e);
+
+    /**
+     * Invoked when a Message is not delivered.
+     * @param	e TransportEvent
+     * @see TransportEvent
+     */
+    public void messageNotDelivered(TransportEvent e);
+
+    /**
+     * Invoked when a Message is partially delivered.
+     * @param	e TransportEvent
+     * @see TransportEvent
+     */
+    public void messagePartiallyDelivered(TransportEvent e);
+}
diff --git a/mail/src/main/java/javax/mail/event/package.html b/mail/src/main/java/javax/mail/event/package.html
new file mode 100644
index 0000000..d22c00b
--- /dev/null
+++ b/mail/src/main/java/javax/mail/event/package.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+</HEAD>
+<BODY BGCOLOR="white">
+
+Listeners and events for the JavaMail API.
+This package defines listener classes and event classes used by the classes
+defined in the <code>javax.mail</code> package.
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/javax/mail/internet/AddressException.java b/mail/src/main/java/javax/mail/internet/AddressException.java
new file mode 100644
index 0000000..b91e1ce
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/AddressException.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+/**
+ * The exception thrown when a wrongly formatted address is encountered.
+ *
+ * @author Bill Shannon
+ * @author Max Spivak
+ */
+
+public class AddressException extends ParseException {
+    /**
+     * The string being parsed.
+     *
+     * @serial
+     */
+    protected String ref = null;
+
+    /**
+     * The index in the string where the error occurred, or -1 if not known.
+     *
+     * @serial
+     */
+    protected int pos = -1;
+
+    private static final long serialVersionUID = 9134583443539323120L;
+
+    /**
+     * Constructs an AddressException with no detail message.
+     */
+    public AddressException() {
+	super();
+    }
+
+    /**
+     * Constructs an AddressException with the specified detail message.
+     * @param s		the detail message
+     */
+    public AddressException(String s) {
+	super(s);
+    }
+
+    /**
+     * Constructs an AddressException with the specified detail message
+     * and reference info.
+     *
+     * @param	s	the detail message
+     * @param	ref	the string being parsed
+     */
+    public AddressException(String s, String ref) {
+	super(s);
+	this.ref = ref;
+    }
+
+    /**
+     * Constructs an AddressException with the specified detail message
+     * and reference info.
+     *
+     * @param	s	the detail message
+     * @param	ref	the string being parsed
+     * @param	pos	the position of the error
+     */
+    public AddressException(String s, String ref, int pos) {
+	super(s);
+	this.ref = ref;
+	this.pos = pos;
+    }
+
+    /**
+     * Get the string that was being parsed when the error was detected
+     * (null if not relevant).
+     *
+     * @return	the string that was being parsed
+     */
+    public String getRef() {
+	return ref;
+    }
+
+    /**
+     * Get the position with the reference string where the error was
+     * detected (-1 if not relevant).
+     *
+     * @return	the position within the string of the error
+     */
+    public int getPos() {
+	return pos;
+    }
+
+    @Override
+    public String toString() {
+	String s = super.toString();
+	if (ref == null)
+	    return s;
+	s += " in string ``" + ref + "''";
+	if (pos < 0)
+	    return s;
+	return s + " at position " + pos;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/ContentDisposition.java b/mail/src/main/java/javax/mail/internet/ContentDisposition.java
new file mode 100644
index 0000000..123a1ef
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/ContentDisposition.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.*;
+import java.util.*;
+import java.io.*;
+import com.sun.mail.util.PropUtil;
+
+/**
+ * This class represents a MIME ContentDisposition value. It provides
+ * methods to parse a ContentDisposition string into individual components
+ * and to generate a MIME style ContentDisposition string.
+ *
+ * @author  John Mani
+ */
+
+public class ContentDisposition {
+
+    private static final boolean contentDispositionStrict =
+        PropUtil.getBooleanSystemProperty("mail.mime.contentdisposition.strict", true);
+
+    private String disposition; // disposition
+    private ParameterList list;	// parameter list
+
+    /**
+     * No-arg Constructor.
+     */
+    public ContentDisposition() { }
+
+    /**
+     * Constructor.
+     *
+     * @param	disposition	disposition
+     * @param	list	ParameterList
+     * @since		JavaMail 1.2
+     */
+    public ContentDisposition(String disposition, ParameterList list) {
+	this.disposition = disposition;
+	this.list = list;
+    }
+
+    /**
+     * Constructor that takes a ContentDisposition string. The String
+     * is parsed into its constituents: dispostion and parameters. 
+     * A ParseException is thrown if the parse fails. 
+     *
+     * @param	s	the ContentDisposition string.
+     * @exception	ParseException if the parse fails.
+     * @since		JavaMail 1.2
+     */
+    public ContentDisposition(String s) throws ParseException {
+	HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
+	HeaderTokenizer.Token tk;
+
+	// First "disposition" ..
+	tk = h.next();
+	if (tk.getType() != HeaderTokenizer.Token.ATOM) {
+            if (contentDispositionStrict) {
+	        throw new ParseException("Expected disposition, got " +
+				    tk.getValue());
+            }
+        } else {
+	    disposition = tk.getValue();
+        }
+
+	// Then parameters ..
+	String rem = h.getRemainder();
+	if (rem != null) {
+            try {
+                list = new ParameterList(rem);
+            } catch (ParseException px) {
+                if (contentDispositionStrict) {
+                    throw px;
+                }
+            }
+        }
+    }
+
+    /**
+     * Return the disposition value.
+     * @return the disposition
+     * @since		JavaMail 1.2
+     */
+    public String getDisposition() {
+	return disposition;
+    }
+
+    /**
+     * Return the specified parameter value. Returns <code>null</code>
+     * if this parameter is absent.
+     *
+     * @param	name	the parameter name
+     * @return	parameter value
+     * @since		JavaMail 1.2
+     */
+    public String getParameter(String name) {
+	if (list == null)
+	    return null;
+
+	return list.get(name);
+    }
+
+    /**
+     * Return a ParameterList object that holds all the available 
+     * parameters. Returns null if no parameters are available.
+     *
+     * @return	ParameterList
+     * @since		JavaMail 1.2
+     */
+    public ParameterList getParameterList() {
+	return list;
+    }
+
+    /**
+     * Set the disposition.  Replaces the existing disposition.
+     * @param	disposition	the disposition
+     * @since		JavaMail 1.2
+     */
+    public void setDisposition(String disposition) {
+	this.disposition = disposition;
+    }
+
+    /**
+     * Set the specified parameter. If this parameter already exists,
+     * it is replaced by this new value.
+     *
+     * @param	name	parameter name
+     * @param	value	parameter value
+     * @since		JavaMail 1.2
+     */
+    public void setParameter(String name, String value) {
+	if (list == null)
+	    list = new ParameterList();
+
+	list.set(name, value);
+    }
+
+    /**
+     * Set a new ParameterList.
+     * @param	list	ParameterList
+     * @since		JavaMail 1.2
+     */
+    public void setParameterList(ParameterList list) {
+	this.list = list;
+    }
+
+    /**
+     * Retrieve a RFC2045 style string representation of
+     * this ContentDisposition. Returns an empty string if
+     * the conversion failed.
+     *
+     * @return	RFC2045 style string
+     * @since		JavaMail 1.2
+     */
+    @Override
+    public String toString() {
+	if (disposition == null)
+	    return "";
+
+	if (list == null)
+	    return disposition;
+
+	StringBuilder sb = new StringBuilder(disposition);
+
+        // append the parameter list  
+        // use the length of the string buffer + the length of 
+        // the header name formatted as follows "Content-Disposition: "
+	sb.append(list.toString(sb.length() + 21));
+	return sb.toString();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/ContentType.java b/mail/src/main/java/javax/mail/internet/ContentType.java
new file mode 100644
index 0000000..2005d2f
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/ContentType.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.*;
+import java.util.*;
+import java.io.*;
+
+/**
+ * This class represents a MIME Content-Type value. It provides
+ * methods to parse a Content-Type string into individual components
+ * and to generate a MIME style Content-Type string.
+ *
+ * @author  John Mani
+ */
+
+public class ContentType {
+
+    private String primaryType;	// primary type
+    private String subType;	// subtype
+    private ParameterList list;	// parameter list
+
+    /**
+     * No-arg Constructor.
+     */
+    public ContentType() { }
+
+    /**
+     * Constructor.
+     *
+     * @param	primaryType	primary type
+     * @param	subType	subType
+     * @param	list	ParameterList
+     */
+    public ContentType(String primaryType, String subType, 
+			ParameterList list) {
+	this.primaryType = primaryType;
+	this.subType = subType;
+	this.list = list;
+    }
+
+    /**
+     * Constructor that takes a Content-Type string. The String
+     * is parsed into its constituents: primaryType, subType
+     * and parameters. A ParseException is thrown if the parse fails. 
+     *
+     * @param	s	the Content-Type string.
+     * @exception	ParseException if the parse fails.
+     */
+    public ContentType(String s) throws ParseException {
+	HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
+	HeaderTokenizer.Token tk;
+
+	// First "type" ..
+	tk = h.next();
+	if (tk.getType() != HeaderTokenizer.Token.ATOM)
+	    throw new ParseException("In Content-Type string <" + s + ">" +
+					", expected MIME type, got " +
+					tk.getValue());
+	primaryType = tk.getValue();
+
+	// The '/' separator ..
+	tk = h.next();
+	if ((char)tk.getType() != '/')
+	    throw new ParseException("In Content-Type string <" + s + ">" +
+				", expected '/', got " + tk.getValue());
+
+	// Then "subType" ..
+	tk = h.next();
+	if (tk.getType() != HeaderTokenizer.Token.ATOM)
+	    throw new ParseException("In Content-Type string <" + s + ">" +
+					", expected MIME subtype, got " +
+					tk.getValue());
+	subType = tk.getValue();
+
+	// Finally parameters ..
+	String rem = h.getRemainder();
+	if (rem != null)
+	    list = new ParameterList(rem);
+    }
+
+    /**
+     * Return the primary type.
+     * @return the primary type
+     */
+    public String getPrimaryType() {
+	return primaryType;
+    }
+
+    /**
+     * Return the subType.
+     * @return the subType
+     */
+    public String getSubType() {
+	return subType;
+    }
+
+    /**
+     * Return the MIME type string, without the parameters.
+     * The returned value is basically the concatenation of
+     * the primaryType, the '/' character and the secondaryType.
+     *
+     * @return the type
+     */
+    public String getBaseType() {
+	if (primaryType == null || subType == null)
+	    return "";
+	return primaryType + '/' + subType;
+    }
+
+    /**
+     * Return the specified parameter value. Returns <code>null</code>
+     * if this parameter is absent.
+     *
+     * @param	name	the parameter name
+     * @return	parameter value
+     */
+    public String getParameter(String name) {
+	if (list == null)
+	    return null;
+
+	return list.get(name);
+    }
+
+    /**
+     * Return a ParameterList object that holds all the available 
+     * parameters. Returns null if no parameters are available.
+     *
+     * @return	ParameterList
+     */
+    public ParameterList getParameterList() {
+	return list;
+    }
+
+    /**
+     * Set the primary type. Overrides existing primary type.
+     * @param	primaryType	primary type
+     */
+    public void setPrimaryType(String primaryType) {
+	this.primaryType = primaryType;
+    }
+
+    /**
+     * Set the subType.  Replaces the existing subType.
+     * @param	subType	the subType
+     */
+    public void setSubType(String subType) {
+	this.subType = subType;
+    }
+
+    /**
+     * Set the specified parameter. If this parameter already exists,
+     * it is replaced by this new value.
+     *
+     * @param	name	parameter name
+     * @param	value	parameter value
+     */
+    public void setParameter(String name, String value) {
+	if (list == null)
+	    list = new ParameterList();
+
+	list.set(name, value);
+    }
+
+    /**
+     * Set a new ParameterList.
+     * @param	list	ParameterList
+     */
+    public void setParameterList(ParameterList list) {
+	this.list = list;
+    }
+
+    /**
+     * Retrieve a RFC2045 style string representation of
+     * this Content-Type. Returns an empty string if
+     * the conversion failed.
+     *
+     * @return	RFC2045 style string
+     */
+    @Override
+    public String toString() {
+	if (primaryType == null || subType == null) // need both
+	    return "";
+
+	StringBuilder sb = new StringBuilder();
+	sb.append(primaryType).append('/').append(subType);
+	if (list != null)
+            // append the parameter list 
+            // use the length of the string buffer + the length of
+            // the header name formatted as follows "Content-Type: "
+	    sb.append(list.toString(sb.length() + 14));
+	
+	return sb.toString();
+    }
+
+    /**
+     * Match with the specified ContentType object. This method
+     * compares <strong>only the <code>primaryType</code> and 
+     * <code>subType</code> </strong>. The parameters of both operands
+     * are ignored. <p>
+     *
+     * For example, this method will return <code>true</code> when
+     * comparing the ContentTypes for <strong>"text/plain"</strong>
+     * and <strong>"text/plain; charset=foobar"</strong>.
+     *
+     * If the <code>subType</code> of either operand is the special
+     * character '*', then the subtype is ignored during the match. 
+     * For example, this method will return <code>true</code> when 
+     * comparing the ContentTypes for <strong>"text/plain"</strong> 
+     * and <strong>"text/*" </strong>
+     *
+     * @param   cType	ContentType to compare this against
+     * @return	true if it matches
+     */
+    public boolean match(ContentType cType) {
+	// Match primaryType
+	if (!((primaryType == null && cType.getPrimaryType() == null) ||
+		(primaryType != null &&
+		    primaryType.equalsIgnoreCase(cType.getPrimaryType()))))
+	    return false;
+	
+	String sType = cType.getSubType();
+
+	// If either one of the subTypes is wildcarded, return true
+	if ((subType != null && subType.startsWith("*")) ||
+	    (sType != null && sType.startsWith("*")))
+	    return true;
+	
+	// Match subType
+	return (subType == null && sType == null) ||
+	    (subType != null && subType.equalsIgnoreCase(sType));
+    }
+
+    /**
+     * Match with the specified content-type string. This method
+     * compares <strong>only the <code>primaryType</code> and 
+     * <code>subType</code> </strong>.
+     * The parameters of both operands are ignored. <p>
+     *
+     * For example, this method will return <code>true</code> when
+     * comparing the ContentType for <strong>"text/plain"</strong>
+     * with <strong>"text/plain; charset=foobar"</strong>.
+     *
+     * If the <code>subType</code> of either operand is the special 
+     * character '*', then the subtype is ignored during the match. 
+     * For example, this method will return <code>true</code> when 
+     * comparing the ContentType for <strong>"text/plain"</strong> 
+     * with <strong>"text/*" </strong>
+     *
+     * @param	s	the content-type string to match
+     * @return	true if it matches
+     */
+    public boolean match(String s) {
+	try {
+	    return match(new ContentType(s));
+	} catch (ParseException pex) {
+	    return false;
+	}
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/HeaderTokenizer.java b/mail/src/main/java/javax/mail/internet/HeaderTokenizer.java
new file mode 100644
index 0000000..e5bafe3
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/HeaderTokenizer.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.util.*;
+
+/**
+ * This class tokenizes RFC822 and MIME headers into the basic
+ * symbols specified by RFC822 and MIME. <p>
+ *
+ * This class handles folded headers (ie headers with embedded
+ * CRLF SPACE sequences). The folds are removed in the returned
+ * tokens. 
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class HeaderTokenizer {
+
+    /**
+     * The Token class represents tokens returned by the 
+     * HeaderTokenizer.
+     */
+    public static class Token {
+
+	private int type;
+	private String value;
+
+	/**
+	 * Token type indicating an ATOM.
+	 */
+	public static final int ATOM 		= -1;
+
+	/**
+	 * Token type indicating a quoted string. The value 
+	 * field contains the string without the quotes.
+ 	 */
+	public static final int QUOTEDSTRING 	= -2;
+
+	/**
+	 * Token type indicating a comment. The value field 
+	 * contains the comment string without the comment 
+	 * start and end symbols.
+	 */
+	public static final int COMMENT		= -3;
+
+	/**
+	 * Token type indicating end of input.
+	 */
+	public static final int  EOF 		= -4;
+
+	/**
+	 * Constructor.
+	 * @param	type	Token type
+	 * @param	value	Token value
+	 */
+	public Token(int type, String value) {
+	     this.type = type;
+	     this.value = value;
+	}
+
+	/**
+	 * Return the type of the token. If the token represents a
+	 * delimiter or a control character, the type is that character
+	 * itself, converted to an integer. Otherwise, it's value is 
+	 * one of the following:
+	 * <ul>
+	 * <li><code>ATOM</code> A sequence of ASCII characters 
+	 *	delimited by either SPACE, CTL, "(", &lt;"&gt; or the 
+	 *	specified SPECIALS
+	 * <li><code>QUOTEDSTRING</code> A sequence of ASCII characters
+	 *	within quotes
+	 * <li><code>COMMENT</code> A sequence of ASCII characters 
+	 *	within "(" and ")".
+	 * <li><code>EOF</code> End of header
+	 * </ul>
+	 *
+	 * @return	the token type
+	 */
+	public int getType() {
+	    return type;
+	}
+
+	/**
+	 * Returns the value of the token just read. When the current
+	 * token is a quoted string, this field contains the body of the
+	 * string, without the quotes. When the current token is a comment,
+	 * this field contains the body of the comment.
+	 *
+	 * @return	token value
+	 */
+	public String getValue() {
+	    return value;
+	}
+    }
+
+    private String string; // the string to be tokenized
+    private boolean skipComments; // should comments be skipped ?
+    private String delimiters; // delimiter string
+    private int currentPos; // current parse position
+    private int maxPos; // string length
+    private int nextPos; // track start of next Token for next()
+    private int peekPos; // track start of next Token for peek()
+
+    /**
+     * RFC822 specials
+     */
+    public final static String RFC822 = "()<>@,;:\\\"\t .[]";
+
+    /**
+     * MIME specials
+     */
+    public final static String MIME = "()<>@,;:\\\"\t []/?=";
+
+    // The EOF Token
+    private final static Token EOFToken = new Token(Token.EOF, null);
+
+    /**
+     * Constructor that takes a rfc822 style header.
+     *
+     * @param	header	The rfc822 header to be tokenized
+     * @param	delimiters      Set of delimiter characters 
+     *				to be used to delimit ATOMS. These
+     *				are usually <code>RFC822</code> or 
+     *				<code>MIME</code>
+     * @param   skipComments  If true, comments are skipped and
+     *				not returned as tokens
+     */
+    public HeaderTokenizer(String header, String delimiters,
+    			   boolean skipComments) {
+	string = (header == null) ? "" : header; // paranoia ?!
+	this.skipComments = skipComments;
+	this.delimiters = delimiters;
+	currentPos = nextPos = peekPos = 0;
+	maxPos = string.length();
+    }
+
+    /**
+     * Constructor. Comments are ignored and not returned as tokens
+     *
+     * @param	header  The header that is tokenized
+     * @param	delimiters  The delimiters to be used
+     */
+    public HeaderTokenizer(String header, String delimiters) {
+	this(header, delimiters, true);
+    }
+
+    /**
+     * Constructor. The RFC822 defined delimiters - RFC822 - are
+     * used to delimit ATOMS. Also comments are skipped and not
+     * returned as tokens
+     *
+     * @param	header	the header string
+     */
+    public HeaderTokenizer(String header)  {
+	this(header, RFC822);
+    }
+
+    /**
+     * Parses the next token from this String. <p>
+     *
+     * Clients sit in a loop calling next() to parse successive
+     * tokens until an EOF Token is returned.
+     *
+     * @return		the next Token
+     * @exception	ParseException if the parse fails
+     */
+    public Token next() throws ParseException { 
+	return next('\0', false);
+    }
+
+    /**
+     * Parses the next token from this String.
+     * If endOfAtom is not NUL, the token extends until the
+     * endOfAtom character is seen, or to the end of the header.
+     * This method is useful when parsing headers that don't
+     * obey the MIME specification, e.g., by failing to quote
+     * parameter values that contain spaces.
+     *
+     * @param	endOfAtom	if not NUL, character marking end of token
+     * @return		the next Token
+     * @exception	ParseException if the parse fails
+     * @since		JavaMail 1.5
+     */
+    public Token next(char endOfAtom) throws ParseException { 
+	return next(endOfAtom, false);
+    }
+
+    /**
+     * Parses the next token from this String.
+     * endOfAtom is handled as above.  If keepEscapes is true,
+     * any backslash escapes are preserved in the returned string.
+     * This method is useful when parsing headers that don't
+     * obey the MIME specification, e.g., by failing to escape
+     * backslashes in the filename parameter.
+     *
+     * @param	endOfAtom	if not NUL, character marking end of token
+     * @param	keepEscapes	keep all backslashes in returned string?
+     * @return		the next Token
+     * @exception	ParseException if the parse fails
+     * @since		JavaMail 1.5
+     */
+    public Token next(char endOfAtom, boolean keepEscapes)
+				throws ParseException { 
+	Token tk;
+
+	currentPos = nextPos; // setup currentPos
+	tk = getNext(endOfAtom, keepEscapes);
+	nextPos = peekPos = currentPos; // update currentPos and peekPos
+	return tk;
+    }
+
+    /**
+     * Peek at the next token, without actually removing the token
+     * from the parse stream. Invoking this method multiple times
+     * will return successive tokens, until <code>next()</code> is
+     * called. <p>
+     *
+     * @return		the next Token
+     * @exception	ParseException if the parse fails
+     */
+    public Token peek() throws ParseException {
+	Token tk;
+
+	currentPos = peekPos; // setup currentPos
+	tk = getNext('\0', false);
+	peekPos = currentPos; // update peekPos
+	return tk;
+    }
+
+    /**
+     * Return the rest of the Header.
+     *
+     * @return String	rest of header. null is returned if we are
+     *			already at end of header
+     */
+    public String getRemainder() {
+	if (nextPos >= string.length())
+	    return null;
+	return string.substring(nextPos);
+    }
+
+    /*
+     * Return the next token starting from 'currentPos'. After the
+     * parse, 'currentPos' is updated to point to the start of the 
+     * next token.
+     */
+    private Token getNext(char endOfAtom, boolean keepEscapes)
+				throws ParseException {
+	// If we're already at end of string, return EOF
+	if (currentPos >= maxPos)
+	    return EOFToken;
+
+	// Skip white-space, position currentPos beyond the space
+	if (skipWhiteSpace() == Token.EOF)
+	    return EOFToken;
+
+	char c; 
+	int start; 
+	boolean filter = false;
+	
+	c = string.charAt(currentPos);
+
+	// Check or Skip comments and position currentPos
+	// beyond the comment
+	while (c == '(') {
+	    // Parsing comment ..
+	    int nesting;
+	    for (start = ++currentPos, nesting = 1; 
+		 nesting > 0 && currentPos < maxPos;
+		 currentPos++) {
+		c = string.charAt(currentPos);
+		if (c == '\\') {  // Escape sequence
+		    currentPos++; // skip the escaped character
+		    filter = true;
+		} else if (c == '\r')
+		    filter = true;
+		else if (c == '(')
+		    nesting++;
+		else if (c == ')')
+		    nesting--;
+	    }
+	    if (nesting != 0)
+		throw new ParseException("Unbalanced comments");
+
+	    if (!skipComments) {
+		// Return the comment, if we are asked to.
+		// Note that the comment start & end markers are ignored.
+		String s;
+		if (filter) // need to go thru the token again.
+		    s = filterToken(string, start, currentPos-1, keepEscapes);
+		else
+		    s = string.substring(start,currentPos-1);
+
+		return new Token(Token.COMMENT, s);
+	    }
+
+	    // Skip any whitespace after the comment.
+	    if (skipWhiteSpace() == Token.EOF)
+		return EOFToken;
+	    c = string.charAt(currentPos);
+	}
+
+	// Check for quoted-string and position currentPos 
+	//  beyond the terminating quote
+	if (c == '"') {
+	    currentPos++;	// skip initial quote
+	    return collectString('"', keepEscapes);
+	}
+	
+	// Check for SPECIAL or CTL
+	if (c < 040 || c >= 0177 || delimiters.indexOf(c) >= 0) {
+	    if (endOfAtom > 0 && c != endOfAtom) {
+		// not expecting a special character here,
+		// pretend it's a quoted string
+		return collectString(endOfAtom, keepEscapes);
+	    }
+	    currentPos++; // re-position currentPos
+	    char ch[] = new char[1];
+	    ch[0] = c;
+	    return new Token((int)c, new String(ch));
+	}
+
+	// Check for ATOM
+	for (start = currentPos; currentPos < maxPos; currentPos++) {
+	    c = string.charAt(currentPos);
+	    // ATOM is delimited by either SPACE, CTL, "(", <"> 
+	    // or the specified SPECIALS
+	    if (c < 040 || c >= 0177 || c == '(' || c == ' ' ||
+			c == '"' || delimiters.indexOf(c) >= 0) {
+		if (endOfAtom > 0 && c != endOfAtom) {
+		    // not the expected atom after all;
+		    // back up and pretend it's a quoted string
+		    currentPos = start;
+		    return collectString(endOfAtom, keepEscapes);
+		}
+		break;
+	    }
+	}
+	return new Token(Token.ATOM, string.substring(start, currentPos));
+    }
+
+    private Token collectString(char eos, boolean keepEscapes)
+				throws ParseException {
+	int start;
+	boolean filter = false;
+	for (start = currentPos; currentPos < maxPos; currentPos++) {
+	    char c = string.charAt(currentPos);
+	    if (c == '\\') { // Escape sequence
+		currentPos++;
+		filter = true;
+	    } else if (c == '\r')
+		filter = true;
+	    else if (c == eos) {
+		currentPos++;
+		String s;
+
+		if (filter)
+		    s = filterToken(string, start, currentPos-1, keepEscapes);
+		else
+		    s = string.substring(start, currentPos-1);
+
+		if (c != '"') {		// not a real quoted string
+		    s = trimWhiteSpace(s);
+		    currentPos--;	// back up before the eos char
+		}
+
+		return new Token(Token.QUOTEDSTRING, s);
+	    }
+	}
+
+	// ran off the end of the string
+
+	// if we're looking for a matching quote, that's an error
+	if (eos == '"')
+	    throw new ParseException("Unbalanced quoted string");
+
+	// otherwise, just return whatever's left
+	String s;
+	if (filter)
+	    s = filterToken(string, start, currentPos, keepEscapes);
+	else
+	    s = string.substring(start, currentPos);
+	s = trimWhiteSpace(s);
+	return new Token(Token.QUOTEDSTRING, s);
+    }
+
+    // Skip SPACE, HT, CR and NL
+    private int skipWhiteSpace() {
+	char c;
+	for (; currentPos < maxPos; currentPos++)
+	    if (((c = string.charAt(currentPos)) != ' ') && 
+		(c != '\t') && (c != '\r') && (c != '\n'))
+		return currentPos;
+	return Token.EOF;
+    }
+
+    // Trim SPACE, HT, CR and NL from end of string
+    private static String trimWhiteSpace(String s) {
+	char c;
+	int i;
+	for (i = s.length() - 1; i >= 0; i--) {
+	    if (((c = s.charAt(i)) != ' ') && 
+		(c != '\t') && (c != '\r') && (c != '\n'))
+		break;
+	}
+	if (i <= 0)
+	    return "";
+	else
+	    return s.substring(0, i + 1);
+    }
+
+    /* Process escape sequences and embedded LWSPs from a comment or
+     * quoted string.
+     */
+    private static String filterToken(String s, int start, int end,
+				boolean keepEscapes) {
+	StringBuilder sb = new StringBuilder();
+	char c;
+	boolean gotEscape = false;
+	boolean gotCR = false;
+
+	for (int i = start; i < end; i++) {
+	    c = s.charAt(i);
+	    if (c == '\n' && gotCR) {
+		// This LF is part of an unescaped 
+		// CRLF sequence (i.e, LWSP). Skip it.
+		gotCR = false;
+		continue;
+	    }
+
+	    gotCR = false;
+	    if (!gotEscape) {
+		// Previous character was NOT '\'
+		if (c == '\\') // skip this character
+		    gotEscape = true;
+		else if (c == '\r') // skip this character
+		    gotCR = true;
+		else // append this character
+		    sb.append(c);
+	    } else {
+		// Previous character was '\'. So no need to 
+		// bother with any special processing, just 
+		// append this character.  If keepEscapes is
+		// set, keep the backslash.  IE6 fails to escape
+		// backslashes in quoted strings in HTTP headers,
+		// e.g., in the filename parameter.
+		if (keepEscapes)
+		    sb.append('\\');
+		sb.append(c);
+		gotEscape = false;
+	    }
+	}
+	return sb.toString();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/InternetAddress.java b/mail/src/main/java/javax/mail/internet/InternetAddress.java
new file mode 100644
index 0000000..97ceb7e
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/InternetAddress.java
@@ -0,0 +1,1523 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.UnsupportedEncodingException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+import java.util.Locale;
+import java.nio.charset.StandardCharsets;
+import javax.mail.*;
+import com.sun.mail.util.PropUtil;
+
+/**
+ * This class represents an Internet email address using the syntax
+ * of <a href="http://www.ietf.org/rfc/rfc822.txt" target="_top">RFC822</a>.
+ * Typical address syntax is of the form "user@host.domain" or
+ * "Personal Name &lt;user@host.domain&gt;".
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+
+public class InternetAddress extends Address implements Cloneable {
+
+    protected String address; // email address
+
+    /**
+     * The personal name.
+     */
+    protected String personal;
+
+    /**
+     * The RFC 2047 encoded version of the personal name. <p>
+     *
+     * This field and the <code>personal</code> field track each
+     * other, so if a subclass sets one of these fields directly, it
+     * should set the other to <code>null</code>, so that it is
+     * suitably recomputed.
+     */
+    protected String encodedPersonal;
+
+    private static final long serialVersionUID = -7507595530758302903L;
+
+    private static final boolean ignoreBogusGroupName =
+	PropUtil.getBooleanSystemProperty(
+			    "mail.mime.address.ignorebogusgroupname", true);
+
+    private static final boolean useCanonicalHostName =
+	PropUtil.getBooleanSystemProperty(
+			    "mail.mime.address.usecanonicalhostname", true);
+
+    private static final boolean allowUtf8 =
+	PropUtil.getBooleanSystemProperty("mail.mime.allowutf8", false);
+
+    /**
+     * Default constructor.
+     */
+    public InternetAddress() { }
+
+    /**
+     * Constructor. <p>
+     *
+     * Parse the given string and create an InternetAddress.
+     * See the <code>parse</code> method for details of the parsing.
+     * The address is parsed using "strict" parsing.
+     * This constructor does <b>not</b> perform the additional
+     * syntax checks that the
+     * <code>InternetAddress(String address, boolean strict)</code>
+     * constructor does when <code>strict</code> is <code>true</code>.
+     * This constructor is equivalent to
+     * <code>InternetAddress(address, false)</code>.
+     *
+     * @param address	the address in RFC822 format
+     * @exception	AddressException if the parse failed
+     */
+    public InternetAddress(String address) throws AddressException {
+	// use our address parsing utility routine to parse the string
+	InternetAddress a[] = parse(address, true);
+	// if we got back anything other than a single address, it's an error
+	if (a.length != 1)
+	    throw new AddressException("Illegal address", address);
+
+	/*
+	 * Now copy the contents of the single address we parsed
+	 * into the current object, which will be returned from the
+	 * constructor.
+	 * XXX - this sure is a round-about way of getting this done.
+	 */
+	this.address = a[0].address;
+	this.personal = a[0].personal;
+	this.encodedPersonal = a[0].encodedPersonal;
+    }
+
+    /**
+     * Parse the given string and create an InternetAddress.
+     * If <code>strict</code> is false, the detailed syntax of the
+     * address isn't checked.
+     *
+     * @param	address		the address in RFC822 format
+     * @param	strict		enforce RFC822 syntax
+     * @exception		AddressException if the parse failed
+     * @since			JavaMail 1.3
+     */
+    public InternetAddress(String address, boolean strict)
+						throws AddressException {
+	this(address);
+	if (strict) {
+	    if (isGroup())
+		getGroup(true);	// throw away the result
+	    else
+		checkAddress(this.address, true, true);
+	}
+    }
+
+    /**
+     * Construct an InternetAddress given the address and personal name.
+     * The address is assumed to be a syntactically valid RFC822 address.
+     *
+     * @param address	the address in RFC822 format
+     * @param personal	the personal name
+     * @exception	UnsupportedEncodingException if the personal name
+     *			can't be encoded in the given charset
+     */
+    public InternetAddress(String address, String personal)
+				throws UnsupportedEncodingException {
+	this(address, personal, null);
+    }
+
+    /**
+     * Construct an InternetAddress given the address and personal name.
+     * The address is assumed to be a syntactically valid RFC822 address.
+     *
+     * @param address	the address in RFC822 format
+     * @param personal	the personal name
+     * @param charset	the MIME charset for the name
+     * @exception	UnsupportedEncodingException if the personal name
+     *			can't be encoded in the given charset
+     */
+    public InternetAddress(String address, String personal, String charset)
+				throws UnsupportedEncodingException {
+	this.address = address;
+	setPersonal(personal, charset);
+    }
+
+    /**
+     * Return a copy of this InternetAddress object.
+     * @since		JavaMail 1.2
+     */
+    @Override
+    public Object clone() {
+	InternetAddress a = null;
+	try {
+	    a = (InternetAddress)super.clone();
+	} catch (CloneNotSupportedException e) {} // Won't happen
+	return a;
+    }
+
+    /**
+     * Return the type of this address. The type of an InternetAddress
+     * is "rfc822".
+     */
+    @Override
+    public String getType() {
+	return "rfc822";
+    }
+
+    /**
+     * Set the email address.
+     *
+     * @param	address email address
+     */
+    public void setAddress(String address) {
+	this.address = address;
+    }
+
+    /**
+     * Set the personal name. If the name contains non US-ASCII
+     * characters, then the name will be encoded using the specified
+     * charset as per RFC 2047. If the name contains only US-ASCII
+     * characters, no encoding is done and the name is used as is. <p>
+     *
+     * @param	name 	personal name
+     * @param	charset	MIME charset to be used to encode the name as 
+     *			per RFC 2047
+     * @see 	#setPersonal(String)
+     * @exception UnsupportedEncodingException if the charset encoding
+     *		  fails.
+     */
+    public void setPersonal(String name, String charset)
+				throws UnsupportedEncodingException {
+	personal = name;
+	if (name != null)
+	    encodedPersonal = MimeUtility.encodeWord(name, charset, null);
+	else
+	    encodedPersonal = null;
+    }
+
+    /**
+     * Set the personal name. If the name contains non US-ASCII
+     * characters, then the name will be encoded using the platform's 
+     * default charset. If the name contains only US-ASCII characters,
+     * no encoding is done and the name is used as is. <p>
+     *
+     * @param	name 	personal name
+     * @see 	#setPersonal(String name, String charset)
+     * @exception UnsupportedEncodingException if the charset encoding
+     *		  fails.
+     */
+    public void setPersonal(String name) 
+		throws UnsupportedEncodingException {
+	personal = name;
+	if (name != null)
+	    encodedPersonal = MimeUtility.encodeWord(name);
+	else
+	    encodedPersonal = null;
+    }
+
+    /**
+     * Get the email address.
+     * @return	email address
+     */
+    public String getAddress() {
+	return address;
+    }
+
+    /**
+     * Get the personal name. If the name 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.
+     *
+     * @return	personal name
+     */
+    public String getPersonal() {
+	if (personal != null)
+	    return personal;
+	
+	if (encodedPersonal != null) {
+	    try {
+		personal = MimeUtility.decodeText(encodedPersonal);
+		return personal;
+	    } catch (Exception ex) {
+		// 1. ParseException: either its an unencoded string or
+		//	it can't be parsed
+		// 2. UnsupportedEncodingException: can't decode it.
+		return encodedPersonal;
+	    }
+	}
+	// No personal or encodedPersonal, return null
+	return null;
+    }
+
+    /**
+     * Convert this address into a RFC 822 / RFC 2047 encoded address.
+     * The resulting string contains only US-ASCII characters, and
+     * hence is mail-safe.
+     *
+     * @return		possibly encoded address string
+     */
+    @Override
+    public String toString() {
+	String a = address == null ? "" : address;
+	if (encodedPersonal == null && personal != null)
+	    try {
+		encodedPersonal = MimeUtility.encodeWord(personal);
+	    } catch (UnsupportedEncodingException ex) { }
+	
+	if (encodedPersonal != null)
+	    return quotePhrase(encodedPersonal) + " <" + a + ">";
+	else if (isGroup() || isSimple())
+	    return a;
+	else
+	    return "<" + a + ">";
+    }
+
+    /**
+     * Returns a properly formatted address (RFC 822 syntax) of
+     * Unicode characters.
+     *   
+     * @return          Unicode address string
+     * @since           JavaMail 1.2
+     */  
+    public String toUnicodeString() {
+	String p = getPersonal();
+        if (p != null)
+            return quotePhrase(p) + " <" + address + ">";
+        else if (isGroup() || isSimple())
+            return address;
+        else
+            return "<" + address + ">";
+    }
+
+    /*
+     * quotePhrase() quotes the words within a RFC822 phrase.
+     *
+     * This is tricky, since a phrase is defined as 1 or more
+     * RFC822 words, separated by LWSP. Now, a word that contains
+     * LWSP is supposed to be quoted, and this is exactly what the 
+     * MimeUtility.quote() method does. However, when dealing with
+     * a phrase, any LWSP encountered can be construed to be the
+     * separator between words, and not part of the words themselves.
+     * To deal with this funkiness, we have the below variant of
+     * MimeUtility.quote(), which essentially ignores LWSP when
+     * deciding whether to quote a word.
+     *
+     * It aint pretty, but it gets the job done :)
+     */
+
+    private static final String rfc822phrase =
+	HeaderTokenizer.RFC822.replace(' ', '\0').replace('\t', '\0');
+
+    private static String quotePhrase(String phrase) {
+        int len = phrase.length();
+        boolean needQuoting = false;
+
+        for (int i = 0; i < len; i++) {
+            char c = phrase.charAt(i);
+            if (c == '"' || c == '\\') { 
+                // need to escape them and then quote the whole string
+                StringBuilder sb = new StringBuilder(len + 3);
+                sb.append('"');
+                for (int j = 0; j < len; j++) {
+                    char cc = phrase.charAt(j);
+                    if (cc == '"' || cc == '\\')
+                        // Escape the character
+                        sb.append('\\');
+                    sb.append(cc);
+                }
+                sb.append('"');
+                return sb.toString();
+            } else if ((c < 040 && c != '\r' && c != '\n' && c != '\t') || 
+		    (c >= 0177 && !allowUtf8) || rfc822phrase.indexOf(c) >= 0)
+		// These characters cause the string to be quoted
+                needQuoting = true;
+        }
+
+        if (needQuoting) {
+            StringBuilder sb = new StringBuilder(len + 2);
+            sb.append('"').append(phrase).append('"');
+            return sb.toString();
+        } else
+            return phrase;
+    }
+
+    private static String unquote(String s) {
+	if (s.startsWith("\"") && s.endsWith("\"") && s.length() > 1) {
+	    s = s.substring(1, s.length() - 1);
+	    // check for any escaped characters
+	    if (s.indexOf('\\') >= 0) {
+		StringBuilder sb = new StringBuilder(s.length());	// approx
+		for (int i = 0; i < s.length(); i++) {
+		    char c = s.charAt(i);
+		    if (c == '\\' && i < s.length() - 1)
+			c = s.charAt(++i);
+		    sb.append(c);
+		}
+		s = sb.toString();
+	    }
+	}
+	return s;
+    }
+
+    /**
+     * The equality operator.
+     */
+    @Override
+    public boolean equals(Object a) {
+	if (!(a instanceof InternetAddress))
+	    return false;
+
+	String s = ((InternetAddress)a).getAddress();
+	if (s == address)
+	    return true;
+	if (address != null && address.equalsIgnoreCase(s))
+	    return true;
+
+	return false;
+    }
+
+    /**
+     * Compute a hash code for the address.
+     */
+    @Override
+    public int hashCode() {
+	if (address == null)
+	    return 0;
+	else
+	    return address.toLowerCase(Locale.ENGLISH).hashCode();
+    }
+
+    /**
+     * Convert the given array of InternetAddress objects into
+     * a comma separated sequence of address strings. The
+     * resulting string contains only US-ASCII characters, and
+     * hence is mail-safe. <p>
+     *
+     * @param addresses	array of InternetAddress objects
+     * @exception 	ClassCastException if any address object in the 
+     *			given array is not an InternetAddress object. Note
+     *			that this is a RuntimeException.
+     * @return		comma separated string of addresses
+     */
+    public static String toString(Address[] addresses) {
+	return toString(addresses, 0);
+    }
+
+    /**
+     * Convert the given array of InternetAddress objects into
+     * a comma separated sequence of address strings. The
+     * resulting string contains Unicode characters. <p>
+     *
+     * @param addresses	array of InternetAddress objects
+     * @exception 	ClassCastException if any address object in the 
+     *			given array is not an InternetAddress object. Note
+     *			that this is a RuntimeException.
+     * @return		comma separated string of addresses
+     * @since		JavaMail 1.6
+     */
+    public static String toUnicodeString(Address[] addresses) {
+	return toUnicodeString(addresses, 0);
+    }
+
+    /**
+     * Convert the given array of InternetAddress objects into
+     * a comma separated sequence of address strings. The
+     * resulting string contains only US-ASCII characters, and
+     * hence is mail-safe. <p>
+     *
+     * The 'used' parameter specifies the number of character positions
+     * already taken up in the field into which the resulting address 
+     * sequence string is to be inserted. It is used to determine the 
+     * line-break positions in the resulting address sequence string.
+     *
+     * @param addresses	array of InternetAddress objects
+     * @param used	number of character positions already used, in
+     *			the field into which the address string is to
+     *			be inserted.
+     * @exception 	ClassCastException if any address object in the 
+     *			given array is not an InternetAddress object. Note
+     *			that this is a RuntimeException.
+     * @return		comma separated string of addresses
+     */
+    public static String toString(Address[] addresses, int used) {
+	if (addresses == null || addresses.length == 0)
+	    return null;
+
+	StringBuilder sb = new StringBuilder();
+
+	for (int i = 0; i < addresses.length; i++) {
+	    if (i != 0) { // need to append comma
+		sb.append(", ");
+		used += 2;
+	    }
+
+	    // prefer not to split a single address across lines so used=0 below
+	    String s = MimeUtility.fold(0, addresses[i].toString());
+	    int len = lengthOfFirstSegment(s); // length till CRLF
+	    if (used + len > 76) { // overflows ...
+		// smash trailing space from ", " above
+		int curlen = sb.length();
+		if (curlen > 0 && sb.charAt(curlen - 1) == ' ')
+		    sb.setLength(curlen - 1);
+		sb.append("\r\n\t"); // .. start new continuation line
+		used = 8; // account for the starting <tab> char
+	    }
+	    sb.append(s);
+	    used = lengthOfLastSegment(s, used);
+	}
+
+	return sb.toString();
+    }
+
+    /**
+     * Convert the given array of InternetAddress objects into
+     * a comma separated sequence of address strings. The
+     * resulting string contains Unicode characters. <p>
+     *
+     * The 'used' parameter specifies the number of character positions
+     * already taken up in the field into which the resulting address 
+     * sequence string is to be inserted. It is used to determine the 
+     * line-break positions in the resulting address sequence string.
+     *
+     * @param addresses	array of InternetAddress objects
+     * @param used	number of character positions already used, in
+     *			the field into which the address string is to
+     *			be inserted.
+     * @exception 	ClassCastException if any address object in the 
+     *			given array is not an InternetAddress object. Note
+     *			that this is a RuntimeException.
+     * @return		comma separated string of addresses
+     * @since		JavaMail 1.6
+     */
+    /*
+     * XXX - This is exactly the same as the above, except it uses
+     *	     toUnicodeString instead of toString.
+     * XXX - Since the line length restrictions are in bytes, not characters,
+     *	     we convert all non-ASCII addresses to UTF-8 byte strings,
+     *	     which we then convert to ISO-8859-1 Strings where every
+     *	     character respresents one UTF-8 byte.  At the end we reverse
+     *	     the conversion to get back to a correct Unicode string.
+     *	     This is a hack to allow all the other character-based methods
+     *	     to work properly with UTF-8 bytes.
+     */
+    public static String toUnicodeString(Address[] addresses, int used) {
+	if (addresses == null || addresses.length == 0)
+	    return null;
+
+	StringBuilder sb = new StringBuilder();
+
+	boolean sawNonAscii = false;
+	for (int i = 0; i < addresses.length; i++) {
+	    if (i != 0) { // need to append comma
+		sb.append(", ");
+		used += 2;
+	    }
+
+	    // prefer not to split a single address across lines so used=0 below
+	    String as = ((InternetAddress)addresses[i]).toUnicodeString();
+	    if (MimeUtility.checkAscii(as) != MimeUtility.ALL_ASCII) {
+		sawNonAscii = true;
+		as = new String(as.getBytes(StandardCharsets.UTF_8),
+	    			StandardCharsets.ISO_8859_1);
+	    }
+	    String s = MimeUtility.fold(0, as);
+	    int len = lengthOfFirstSegment(s); // length till CRLF
+	    if (used + len > 76) { // overflows ...
+		// smash trailing space from ", " above
+		int curlen = sb.length();
+		if (curlen > 0 && sb.charAt(curlen - 1) == ' ')
+		    sb.setLength(curlen - 1);
+		sb.append("\r\n\t"); // .. start new continuation line
+		used = 8; // account for the starting <tab> char
+	    }
+	    sb.append(s);
+	    used = lengthOfLastSegment(s, used);
+	}
+
+	String ret = sb.toString();
+	if (sawNonAscii)
+	    ret = new String(ret.getBytes(StandardCharsets.ISO_8859_1),
+				StandardCharsets.UTF_8);
+    	return ret;
+    }
+
+    /*
+     * Return the length of the first segment within this string.
+     * If no segments exist, the length of the whole line is returned.
+     */
+    private static int lengthOfFirstSegment(String s) {
+	int pos;
+	if ((pos = s.indexOf("\r\n")) != -1)
+	    return pos;
+	else
+	    return s.length();
+    }
+
+    /*
+     * Return the length of the last segment within this string.
+     * If no segments exist, the length of the whole line plus
+     * <code>used</code> is returned.
+     */
+    private static int lengthOfLastSegment(String s, int used) {
+	int pos;
+	if ((pos = s.lastIndexOf("\r\n")) != -1)
+	    return s.length() - pos - 2;
+	else 
+	    return s.length() + used;
+    }
+
+    /**
+     * Return an InternetAddress object representing the current user.
+     * The entire email address may be specified in the "mail.from"
+     * property.  If not set, the "mail.user" and "mail.host" properties
+     * are tried.  If those are not set, the "user.name" property and
+     * <code>InetAddress.getLocalHost</code> method are tried.
+     * Security exceptions that may occur while accessing this information
+     * are ignored.  If it is not possible to determine an email address,
+     * null is returned.
+     *
+     * @param	session		Session object used for property lookup
+     * @return			current user's email address
+     */
+    public static InternetAddress getLocalAddress(Session session) {
+	try {
+	    return _getLocalAddress(session);
+	} catch (SecurityException sex) {	// ignore it
+	} catch (AddressException ex) {		// ignore it
+	} catch (UnknownHostException ex) { }	// ignore it
+	return null;
+    }
+
+    /**
+     * A package-private version of getLocalAddress that doesn't swallow
+     * the exception.  Used by MimeMessage.setFrom() to report the reason
+     * for the failure.
+     */
+    // package-private
+    static InternetAddress _getLocalAddress(Session session)
+	    throws SecurityException, AddressException, UnknownHostException {
+	String user = null, host = null, address = null;
+	if (session == null) {
+	    user = System.getProperty("user.name");
+	    host = getLocalHostName();
+	} else {
+	    address = session.getProperty("mail.from");
+	    if (address == null) {
+		user = session.getProperty("mail.user");
+		if (user == null || user.length() == 0)
+		    user = session.getProperty("user.name");
+		if (user == null || user.length() == 0)
+		    user = System.getProperty("user.name");
+		host = session.getProperty("mail.host");
+		if (host == null || host.length() == 0)
+		    host = getLocalHostName();
+	    }
+	}
+
+	if (address == null && user != null && user.length() != 0 &&
+		host != null && host.length() != 0)
+	    address = MimeUtility.quote(user.trim(), specialsNoDot + "\t ") +
+							    "@" + host;
+
+	if (address == null)
+	    return null;
+
+	return new InternetAddress(address);
+    }
+
+    /**
+     * Get the local host name from InetAddress and return it in a form
+     * suitable for use in an email address.
+     */
+    private static String getLocalHostName() throws UnknownHostException {
+	String host = null;
+	InetAddress me = InetAddress.getLocalHost();
+	if (me != null) {
+	    // try canonical host name first
+	    if (useCanonicalHostName)
+		host = me.getCanonicalHostName();
+	    if (host == null)
+		host = me.getHostName();
+	    // if we can't get our name, use local address literal
+	    if (host == null)
+		host = me.getHostAddress();
+	    if (host != null && host.length() > 0 && isInetAddressLiteral(host))
+		host = '[' + host + ']';
+	}
+	return host;
+    }
+
+    /**
+     * Is the address an IPv4 or IPv6 address literal, which needs to
+     * be enclosed in "[]" in an email address?  IPv4 literals contain
+     * decimal digits and dots, IPv6 literals contain hex digits, dots,
+     * and colons.  We're lazy and don't check the exact syntax, just
+     * the allowed characters; strings that have only the allowed
+     * characters in a literal but don't meet the syntax requirements
+     * for a literal definitely can't be a host name and thus will fail
+     * later when used as an address literal.
+     */
+    private static boolean isInetAddressLiteral(String addr) {
+	boolean sawHex = false, sawColon = false;
+	for (int i = 0; i < addr.length(); i++) {
+	    char c = addr.charAt(i);
+	    if (c >= '0' && c <= '9')
+		;	// digits always ok
+	    else if (c == '.')
+		;	// dot always ok
+	    else if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
+		sawHex = true;	// need to see a colon too
+	    else if (c == ':')
+		sawColon = true;
+	    else
+		return false;	// anything else, definitely not a literal
+	}
+	return !sawHex || sawColon;
+    }
+
+    /**
+     * Parse the given comma separated sequence of addresses into
+     * InternetAddress objects.  Addresses must follow RFC822 syntax.
+     *
+     * @param addresslist	comma separated address strings
+     * @return			array of InternetAddress objects
+     * @exception		AddressException if the parse failed
+     */
+    public static InternetAddress[] parse(String addresslist) 
+				throws AddressException {
+	return parse(addresslist, true);
+    }
+
+    /**
+     * Parse the given sequence of addresses into InternetAddress
+     * objects.  If <code>strict</code> is false, simple email addresses
+     * separated by spaces are also allowed.  If <code>strict</code> is
+     * true, many (but not all) of the RFC822 syntax rules are enforced.
+     * In particular, even if <code>strict</code> is true, addresses
+     * composed of simple names (with no "@domain" part) are allowed.
+     * Such "illegal" addresses are not uncommon in real messages. <p>
+     *
+     * Non-strict parsing is typically used when parsing a list of
+     * mail addresses entered by a human.  Strict parsing is typically
+     * used when parsing address headers in mail messages.
+     *
+     * @param	addresslist	comma separated address strings
+     * @param	strict		enforce RFC822 syntax
+     * @return			array of InternetAddress objects
+     * @exception		AddressException if the parse failed
+     */
+    public static InternetAddress[] parse(String addresslist, boolean strict)
+					    throws AddressException {
+	return parse(addresslist, strict, false);
+    }
+
+    /**
+     * Parse the given sequence of addresses into InternetAddress
+     * objects.  If <code>strict</code> is false, the full syntax rules for
+     * individual addresses are not enforced.  If <code>strict</code> is
+     * true, many (but not all) of the RFC822 syntax rules are enforced. <p>
+     *
+     * To better support the range of "invalid" addresses seen in real
+     * messages, this method enforces fewer syntax rules than the
+     * <code>parse</code> method when the strict flag is false
+     * and enforces more rules when the strict flag is true.  If the
+     * strict flag is false and the parse is successful in separating out an
+     * email address or addresses, the syntax of the addresses themselves
+     * is not checked.
+     *
+     * @param	addresslist	comma separated address strings
+     * @param	strict		enforce RFC822 syntax
+     * @return			array of InternetAddress objects
+     * @exception		AddressException if the parse failed
+     * @since			JavaMail 1.3
+     */
+    public static InternetAddress[] parseHeader(String addresslist,
+				boolean strict) throws AddressException {
+	return parse(MimeUtility.unfold(addresslist), strict, true);
+    }
+
+    /*
+     * RFC822 Address parser.
+     *
+     * XXX - This is complex enough that it ought to be a real parser,
+     *       not this ad-hoc mess, and because of that, this is not perfect.
+     *
+     * XXX - Deal with encoded Headers too.
+     */
+    @SuppressWarnings("fallthrough")
+    private static InternetAddress[] parse(String s, boolean strict,
+				    boolean parseHdr) throws AddressException {
+	int start, end, index, nesting;
+	int start_personal = -1, end_personal = -1;
+	int length = s.length();
+	boolean ignoreErrors = parseHdr && !strict;
+	boolean in_group = false;	// we're processing a group term
+	boolean route_addr = false;	// address came from route-addr term
+	boolean rfc822 = false;		// looks like an RFC822 address
+	char c;
+	List<InternetAddress> v = new ArrayList<>();
+	InternetAddress ma;
+
+	for (start = end = -1, index = 0; index < length; index++) {
+    	    c = s.charAt(index);
+
+	    switch (c) {
+	    case '(': // We are parsing a Comment. Ignore everything inside.
+		// XXX - comment fields should be parsed as whitespace,
+		//	 more than one allowed per address
+		rfc822 = true;
+		if (start >= 0 && end == -1)
+		    end = index;
+		int pindex = index;
+		for (index++, nesting = 1; index < length && nesting > 0;
+		  index++) {
+		    c = s.charAt(index);
+		    switch (c) {
+		    case '\\':
+			index++; // skip both '\' and the escaped char
+			break;
+		    case '(':
+			nesting++;
+			break;
+		    case ')':
+			nesting--;
+			break;
+		    default:
+			break;
+		    }
+		}
+		if (nesting > 0) {
+		    if (!ignoreErrors)
+			throw new AddressException("Missing ')'", s, index);
+		    // pretend the first paren was a regular character and
+		    // continue parsing after it
+		    index = pindex + 1;
+		    break;
+		}
+		index--;	// point to closing paren
+		if (start_personal == -1)
+		    start_personal = pindex + 1;
+		if (end_personal == -1)
+		    end_personal = index;
+		break;
+
+	    case ')':
+		if (!ignoreErrors)
+		    throw new AddressException("Missing '('", s, index);
+		// pretend the left paren was a regular character and
+		// continue parsing
+		if (start == -1)
+		    start = index;
+		break;
+
+	    case '<':
+		rfc822 = true;
+		if (route_addr) {
+		    if (!ignoreErrors)
+			throw new AddressException(
+						"Extra route-addr", s, index);
+
+		    // assume missing comma between addresses
+		    if (start == -1) {
+			route_addr = false;
+			rfc822 = false;
+			start = end = -1;
+			break;	// nope, nothing there
+		    }
+		    if (!in_group) {
+			// got a token, add this to our InternetAddress list
+			if (end == -1)	// should never happen
+			    end = index;
+			String addr = s.substring(start, end).trim();
+
+			ma = new InternetAddress();
+			ma.setAddress(addr);
+			if (start_personal >= 0) {
+			    ma.encodedPersonal = unquote(
+				s.substring(start_personal, end_personal).
+								trim());
+			}
+			v.add(ma);
+
+			route_addr = false;
+			rfc822 = false;
+			start = end = -1;
+			start_personal = end_personal = -1;
+			// continue processing this new address...
+		    }
+		}
+
+		int rindex = index;
+		boolean inquote = false;
+	      outf:
+		for (index++; index < length; index++) {
+		    c = s.charAt(index);
+		    switch (c) {
+		    case '\\':	// XXX - is this needed?
+			index++; // skip both '\' and the escaped char
+			break;
+		    case '"':
+			inquote = !inquote;
+			break;
+		    case '>':
+			if (inquote)
+			    continue;
+			break outf; // out of for loop
+		    default:
+			break;
+		    }
+		}
+
+		// did we find a matching quote?
+		if (inquote) {
+		    if (!ignoreErrors)
+			throw new AddressException("Missing '\"'", s, index);
+		    // didn't find matching quote, try again ignoring quotes
+		    // (e.g., ``<"@foo.com>'')
+		  outq:
+		    for (index = rindex + 1; index < length; index++) {
+			c = s.charAt(index);
+			if (c == '\\')	// XXX - is this needed?
+			    index++;	// skip both '\' and the escaped char
+			else if (c == '>')
+			    break;
+		    }
+		}
+
+		// did we find a terminating '>'?
+		if (index >= length) {
+		    if (!ignoreErrors)
+			throw new AddressException("Missing '>'", s, index);
+		    // pretend the "<" was a regular character and
+		    // continue parsing after it (e.g., ``<@foo.com'')
+		    index = rindex + 1;
+		    if (start == -1)
+			start = rindex;	// back up to include "<"
+		    break;
+		}
+
+		if (!in_group) {
+		    if (start >= 0) {
+			// seen some characters?  use them as the personal name
+			start_personal = start;
+			end_personal = rindex;
+		    }
+		    start = rindex + 1;
+		}
+		route_addr = true;
+		end = index;
+		break;
+
+	    case '>':
+		if (!ignoreErrors)
+		    throw new AddressException("Missing '<'", s, index);
+		// pretend the ">" was a regular character and
+		// continue parsing (e.g., ``>@foo.com'')
+		if (start == -1)
+		    start = index;
+		break;
+
+	    case '"':	// parse quoted string
+		int qindex = index;
+		rfc822 = true;
+		if (start == -1)
+		    start = index;
+	      outq:
+		for (index++; index < length; index++) {
+		    c = s.charAt(index);
+		    switch (c) {
+		    case '\\':
+			index++; // skip both '\' and the escaped char
+			break;
+		    case '"':
+			break outq; // out of for loop
+		    default:
+			break;
+		    }
+		}
+		if (index >= length) {
+		    if (!ignoreErrors)
+			throw new AddressException("Missing '\"'", s, index);
+		    // pretend the quote was a regular character and
+		    // continue parsing after it (e.g., ``"@foo.com'')
+		    index = qindex + 1;
+		}
+		break;
+
+	    case '[':	// a domain-literal, probably
+		int lindex = index;
+		rfc822 = true;
+		if (start == -1)
+		    start = index;
+	      outb:
+		for (index++; index < length; index++) {
+		    c = s.charAt(index);
+		    switch (c) {
+		    case '\\':
+			index++; // skip both '\' and the escaped char
+			break;
+		    case ']':
+			break outb; // out of for loop
+		    default:
+			break;
+		    }
+		}
+		if (index >= length) {
+		    if (!ignoreErrors)
+			throw new AddressException("Missing ']'", s, index);
+		    // pretend the "[" was a regular character and
+		    // continue parsing after it (e.g., ``[@foo.com'')
+		    index = lindex + 1;
+		}
+		break;
+
+	    case ';':
+		if (start == -1) {
+		    route_addr = false;
+		    rfc822 = false;
+		    start = end = -1;
+		    break;	// nope, nothing there
+		}
+		if (in_group) {
+		    in_group = false;
+		    /*
+		     * If parsing headers, but not strictly, peek ahead.
+		     * If next char is "@", treat the group name
+		     * like the local part of the address, e.g.,
+		     * "Undisclosed-Recipient:;@java.sun.com".
+		     */
+		    if (parseHdr && !strict &&
+			    index + 1 < length && s.charAt(index + 1) == '@')
+			break;
+		    ma = new InternetAddress();
+		    end = index + 1;
+		    ma.setAddress(s.substring(start, end).trim());
+		    v.add(ma);
+
+		    route_addr = false;
+		    rfc822 = false;
+		    start = end = -1;
+		    start_personal = end_personal = -1;
+		    break;
+		}
+		if (!ignoreErrors)
+		    throw new AddressException(
+			    "Illegal semicolon, not in group", s, index);
+
+		// otherwise, parsing a header; treat semicolon like comma
+		// fall through to comma case...
+
+	    case ',':	// end of an address, probably
+		if (start == -1) {
+		    route_addr = false;
+		    rfc822 = false;
+		    start = end = -1;
+		    break;	// nope, nothing there
+		}
+		if (in_group) {
+		    route_addr = false;
+		    break;
+		}
+		// got a token, add this to our InternetAddress list
+		if (end == -1)
+		    end = index;
+
+		String addr = s.substring(start, end).trim();
+		String pers = null;
+		if (rfc822 && start_personal >= 0) {
+		    pers = unquote(
+			    s.substring(start_personal, end_personal).trim());
+		    if (pers.trim().length() == 0)
+			pers = null;
+		}
+
+		/*
+		 * If the personal name field has an "@" and the address
+		 * field does not, assume they were reversed, e.g.,
+		 * ``"joe doe" (john.doe@example.com)''.
+		 */
+		if (parseHdr && !strict && pers != null &&
+			pers.indexOf('@') >= 0 &&
+			addr.indexOf('@') < 0 && addr.indexOf('!') < 0) {
+		    String tmp = addr;
+		    addr = pers;
+		    pers = tmp;
+		}
+		if (rfc822 || strict || parseHdr) {
+		    if (!ignoreErrors)
+			checkAddress(addr, route_addr, false);
+		    ma = new InternetAddress();
+		    ma.setAddress(addr);
+		    if (pers != null)
+			ma.encodedPersonal = pers;
+		    v.add(ma);
+		} else {
+		    // maybe we passed over more than one space-separated addr
+		    StringTokenizer st = new StringTokenizer(addr);
+		    while (st.hasMoreTokens()) {
+			String a = st.nextToken();
+			checkAddress(a, false, false);
+			ma = new InternetAddress();
+			ma.setAddress(a);
+			v.add(ma);
+		    }
+		}
+
+		route_addr = false;
+		rfc822 = false;
+		start = end = -1;
+		start_personal = end_personal = -1;
+		break;
+
+	    case ':':
+		rfc822 = true;
+		if (in_group)
+		    if (!ignoreErrors)
+			throw new AddressException("Nested group", s, index);
+		if (start == -1)
+		    start = index;
+		if (parseHdr && !strict) {
+		    /*
+		     * If next char is a special character that can't occur at
+		     * the start of a valid address, treat the group name
+		     * as the entire address, e.g., "Date:, Tue", "Re:@foo".
+		     */
+		    if (index + 1 < length) {
+			String addressSpecials = ")>[]:@\\,.";
+			char nc = s.charAt(index + 1);
+			if (addressSpecials.indexOf(nc) >= 0) {
+			    if (nc != '@')
+				break;	// don't change in_group
+			    /*
+			     * Handle a common error:
+			     * ``Undisclosed-Recipient:@example.com;''
+			     *
+			     * Scan ahead.  If we find a semicolon before
+			     * one of these other special characters,
+			     * consider it to be a group after all.
+			     */
+			    for (int i = index + 2; i < length; i++) {
+				nc = s.charAt(i);
+				if (nc == ';')
+				    break;
+				if (addressSpecials.indexOf(nc) >= 0)
+				    break;
+			    }
+			    if (nc == ';')
+				break;	// don't change in_group
+			}
+		    }
+
+		    // ignore bogus "mailto:" prefix in front of an address,
+		    // or bogus mail header name included in the address field
+		    String gname = s.substring(start, index);
+		    if (ignoreBogusGroupName &&
+			(gname.equalsIgnoreCase("mailto") ||
+			gname.equalsIgnoreCase("From") ||
+			gname.equalsIgnoreCase("To") ||
+			gname.equalsIgnoreCase("Cc") ||
+			gname.equalsIgnoreCase("Subject") ||
+			gname.equalsIgnoreCase("Re")))
+			start = -1;	// we're not really in a group
+		    else
+			in_group = true;
+		} else
+		    in_group = true;
+		break;
+
+	    // Ignore whitespace
+	    case ' ':
+	    case '\t':
+	    case '\r':
+	    case '\n':
+		break;
+
+	    default:
+		if (start == -1)
+		    start = index;
+		break;
+	     }
+	}
+
+	if (start >= 0) {
+	    /*
+	     * The last token, add this to our InternetAddress list.
+	     * Note that this block of code should be identical to the
+	     * block above for "case ','".
+	     */
+	    if (end == -1)
+		end = length;
+
+	    String addr = s.substring(start, end).trim();
+	    String pers = null;
+	    if (rfc822 && start_personal >= 0) {
+		pers = unquote(
+			s.substring(start_personal, end_personal).trim());
+		if (pers.trim().length() == 0)
+		    pers = null;
+	    }
+
+	    /*
+	     * If the personal name field has an "@" and the address
+	     * field does not, assume they were reversed, e.g.,
+	     * ``"joe doe" (john.doe@example.com)''.
+	     */
+	    if (parseHdr && !strict &&
+		    pers != null && pers.indexOf('@') >= 0 &&
+		    addr.indexOf('@') < 0 && addr.indexOf('!') < 0) {
+		String tmp = addr;
+		addr = pers;
+		pers = tmp;
+	    }
+	    if (rfc822 || strict || parseHdr) {
+		if (!ignoreErrors)
+		    checkAddress(addr, route_addr, false);
+		ma = new InternetAddress();
+		ma.setAddress(addr);
+		if (pers != null)
+		    ma.encodedPersonal = pers;
+		v.add(ma);
+	    } else {
+		// maybe we passed over more than one space-separated addr
+		StringTokenizer st = new StringTokenizer(addr);
+		while (st.hasMoreTokens()) {
+		    String a = st.nextToken();
+		    checkAddress(a, false, false);
+		    ma = new InternetAddress();
+		    ma.setAddress(a);
+		    v.add(ma);
+		}
+	    }
+	}
+
+	InternetAddress[] a = new InternetAddress[v.size()];
+	v.toArray(a);
+	return a;
+    }
+
+    /**
+     * Validate that this address conforms to the syntax rules of
+     * RFC 822.  The current implementation checks many, but not
+     * all, syntax rules.  Note that even though the syntax of
+     * the address may be correct, there's no guarantee that a
+     * mailbox of that name exists.
+     *
+     * @exception	AddressException if the address isn't valid.
+     * @since		JavaMail 1.3
+     */
+    public void validate() throws AddressException {
+	if (isGroup())
+	    getGroup(true);	// throw away the result
+	else
+	    checkAddress(getAddress(), true, true);
+    }
+
+    private static final String specialsNoDotNoAt = "()<>,;:\\\"[]";
+    private static final String specialsNoDot = specialsNoDotNoAt + "@";
+
+    /**
+     * Check that the address is a valid "mailbox" per RFC822.
+     * (We also allow simple names.)
+     *
+     * XXX - much more to check
+     * XXX - doesn't handle domain-literals properly (but no one uses them)
+     */
+    private static void checkAddress(String addr,
+				boolean routeAddr, boolean validate)
+				throws AddressException {
+	int i, start = 0;
+
+	if (addr == null)
+	    throw new AddressException("Address is null");
+	int len = addr.length();
+	if (len == 0)
+	    throw new AddressException("Empty address", addr);
+
+	/*
+	 * routeAddr indicates that the address is allowed
+	 * to have an RFC 822 "route".
+	 */
+	if (routeAddr && addr.charAt(0) == '@') {
+	    /*
+	     * Check for a legal "route-addr":
+	     *		[@domain[,@domain ...]:]local@domain
+	     */
+	    for (start = 0; (i = indexOfAny(addr, ",:", start)) >= 0;
+		    start = i+1) {
+		if (addr.charAt(start) != '@')
+		    throw new AddressException("Illegal route-addr", addr);
+		if (addr.charAt(i) == ':') {
+		    // end of route-addr
+		    start = i + 1;
+		    break;
+		}
+	    }
+	}
+
+	/*
+	 * The rest should be "local@domain", but we allow simply "local"
+	 * unless called from validate.
+	 *
+	 * local-part must follow RFC 822 - no specials except '.'
+	 * unless quoted.
+	 */
+
+	char c = (char)-1;
+	char lastc = (char)-1;
+	boolean inquote = false;
+	for (i = start; i < len; i++) {
+	    lastc = c;
+	    c = addr.charAt(i);
+	    // a quoted-pair is only supposed to occur inside a quoted string,
+	    // but some people use it outside so we're more lenient
+	    if (c == '\\' || lastc == '\\')
+		continue;
+	    if (c == '"') {
+		if (inquote) {
+		    // peek ahead, next char must be "@"
+		    if (validate && i + 1 < len && addr.charAt(i + 1) != '@')
+			throw new AddressException(
+				"Quote not at end of local address", addr);
+		    inquote = false;
+		} else {
+		    if (validate && i != 0)
+			throw new AddressException(
+				"Quote not at start of local address", addr);
+		    inquote = true;
+		}
+		continue;
+	    } else if (c == '\r') {
+		// peek ahead, next char must be LF
+		if (i + 1 < len && addr.charAt(i + 1) != '\n')
+		    throw new AddressException(
+			"Quoted local address contains CR without LF", addr);
+	    } else if (c == '\n') {
+		/*
+		 * CRLF followed by whitespace is allowed in a quoted string.
+		 * We allowed naked LF, but ensure LF is always followed by
+		 * whitespace to prevent spoofing the end of the header.
+		 */
+		if (i + 1 < len && addr.charAt(i + 1) != ' ' &&
+				    addr.charAt(i + 1) != '\t')
+		    throw new AddressException(
+		     "Quoted local address contains newline without whitespace",
+			addr);
+	    } else if (c == '.') {
+		if (i == start)
+		    throw new AddressException(
+			"Local address starts with dot", addr);
+		if (lastc == '.')
+		    throw new AddressException(
+			"Local address contains dot-dot", addr);
+	    }
+	    if (inquote)
+		continue;
+	    if (c == '@') {
+		if (i == 0)
+		    throw new AddressException("Missing local name", addr);
+		if (lastc == '.')
+		    throw new AddressException(
+			"Local address ends with dot", addr);
+		break;		// done with local part
+	    }
+	    if (c <= 040 || c == 0177)
+		throw new AddressException(
+			"Local address contains control or whitespace", addr);
+	    if (specialsNoDot.indexOf(c) >= 0)
+		throw new AddressException(
+			"Local address contains illegal character", addr);
+	}
+	if (inquote)
+	    throw new AddressException("Unterminated quote", addr);
+
+	/*
+	 * Done with local part, now check domain.
+	 *
+	 * Note that the MimeMessage class doesn't remember addresses
+	 * as separate objects; it writes them out as headers and then
+	 * parses the headers when the addresses are requested.
+	 * In order to support the case where a "simple" address is used,
+	 * but the address also has a personal name and thus looks like
+	 * it should be a valid RFC822 address when parsed, we only check
+	 * this if we're explicitly called from the validate method.
+	 */
+
+	if (c != '@') {
+	    if (validate)
+		throw new AddressException("Missing final '@domain'", addr);
+	    return;
+	}
+
+	// check for illegal chars in the domain, but ignore domain literals
+
+	start = i + 1;
+	if (start >= len)
+	    throw new AddressException("Missing domain", addr);
+
+	if (addr.charAt(start) == '.')
+	    throw new AddressException("Domain starts with dot", addr);
+	boolean inliteral = false;
+	for (i = start; i < len; i++) {
+	    c = addr.charAt(i);
+	    if (c == '[') {
+		if (i != start)
+		    throw new AddressException(
+				"Domain literal not at start of domain", addr);
+		inliteral = true;	// domain literal, don't validate
+	    } else if (c == ']') {
+		if (i != len - 1)
+		    throw new AddressException(
+			    "Domain literal end not at end of domain", addr);
+		inliteral = false;
+	    } else if (c <= 040 || c == 0177) {
+		throw new AddressException(
+				"Domain contains control or whitespace", addr);
+	    } else {
+		// RFC 2822 rule
+		//if (specialsNoDot.indexOf(c) >= 0)
+		/*
+		 * RFC 1034 rule is more strict
+		 * the full rule is:
+		 * 
+		 * <domain> ::= <subdomain> | " "
+		 * <subdomain> ::= <label> | <subdomain> "." <label>
+		 * <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
+		 * <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
+		 * <let-dig-hyp> ::= <let-dig> | "-"
+		 * <let-dig> ::= <letter> | <digit>
+		 */
+		if (!inliteral) {
+		    if (!(Character.isLetterOrDigit(c) || c == '-' || c == '.'))
+			throw new AddressException(
+				    "Domain contains illegal character", addr);
+		    if (c == '.' && lastc == '.')
+			throw new AddressException(
+					"Domain contains dot-dot", addr);
+		}
+	    }
+	    lastc = c;
+	}
+	if (lastc == '.')
+	    throw new AddressException("Domain ends with dot", addr);
+    }
+
+    /**
+     * Is this a "simple" address?  Simple addresses don't contain quotes
+     * or any RFC822 special characters other than '@' and '.'.
+     */
+    private boolean isSimple() {
+	return address == null || indexOfAny(address, specialsNoDotNoAt) < 0;
+    }
+
+    /**
+     * Indicates whether this address is an RFC 822 group address.
+     * Note that a group address is different than the mailing
+     * list addresses supported by most mail servers.  Group addresses
+     * are rarely used; see RFC 822 for details.
+     *
+     * @return		true if this address represents a group
+     * @since		JavaMail 1.3
+     */
+    public boolean isGroup() {
+	// quick and dirty check
+	return address != null &&
+	    address.endsWith(";") && address.indexOf(':') > 0;
+    }
+
+    /**
+     * Return the members of a group address.  A group may have zero,
+     * one, or more members.  If this address is not a group, null
+     * is returned.  The <code>strict</code> parameter controls whether
+     * the group list is parsed using strict RFC 822 rules or not.
+     * The parsing is done using the <code>parseHeader</code> method.
+     *
+     * @param	strict	use strict RFC 822 rules?
+     * @return		array of InternetAddress objects, or null
+     * @exception	AddressException if the group list can't be parsed
+     * @since		JavaMail 1.3
+     */
+    public InternetAddress[] getGroup(boolean strict) throws AddressException {
+	String addr = getAddress();
+	if (addr == null)
+	    return null;
+	// groups are of the form "name:addr,addr,...;"
+	if (!addr.endsWith(";"))
+	    return null;
+	int ix = addr.indexOf(':');
+	if (ix < 0)
+	    return null;
+	// extract the list
+	String list = addr.substring(ix + 1, addr.length() - 1);
+	// parse it and return the individual addresses
+	return InternetAddress.parseHeader(list, strict);
+    }
+
+    /**
+     * Return the first index of any of the characters in "any" in "s",
+     * or -1 if none are found.
+     *
+     * This should be a method on String.
+     */
+    private static int indexOfAny(String s, String any) {
+	return indexOfAny(s, any, 0);
+    }
+
+    private static int indexOfAny(String s, String any, int start) {
+	try {
+	    int len = s.length();
+	    for (int i = start; i < len; i++) {
+		if (any.indexOf(s.charAt(i)) >= 0)
+		    return i;
+	    }
+	    return -1;
+	} catch (StringIndexOutOfBoundsException e) {
+	    return -1;
+	}
+    }
+
+    /*
+    public static void main(String argv[]) throws Exception {
+	for (int i = 0; i < argv.length; i++) {
+	    InternetAddress[] a = InternetAddress.parse(argv[i]);
+	    for (int j = 0; j < a.length; j++) {
+		System.out.println("arg " + i + " address " + j + ": " + a[j]);
+		System.out.println("\tAddress: " + a[j].getAddress() +
+				    "\tPersonal: " + a[j].getPersonal());
+	    }
+	    if (a.length > 1) {
+		System.out.println("address 0 hash code: " + a[0].hashCode());
+		System.out.println("address 1 hash code: " + a[1].hashCode());
+		if (a[0].hashCode() == a[1].hashCode())
+		    System.out.println("success, hashcodes equal");
+		else
+		    System.out.println("fail, hashcodes not equal");
+		if (a[0].equals(a[1]))
+		    System.out.println("success, addresses equal");
+		else
+		    System.out.println("fail, addresses not equal");
+		if (a[1].equals(a[0]))
+		    System.out.println("success, addresses equal");
+		else
+		    System.out.println("fail, addresses not equal");
+	    }
+	}
+    }
+    */
+}
diff --git a/mail/src/main/java/javax/mail/internet/InternetHeaders.java b/mail/src/main/java/javax/mail/internet/InternetHeaders.java
new file mode 100644
index 0000000..3df0cca
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/InternetHeaders.java
@@ -0,0 +1,683 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.*;
+import java.util.*;
+import javax.mail.*;
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.PropUtil;
+
+/**
+ * InternetHeaders is a utility class that manages RFC822 style
+ * headers. Given an RFC822 format message stream, it reads lines
+ * until the blank line that indicates end of header. The input stream
+ * is positioned at the start of the body. The lines are stored 
+ * within the object and can be extracted as either Strings or
+ * {@link javax.mail.Header} objects. <p>
+ *
+ * This class is mostly intended for service providers. MimeMessage
+ * and MimeBody use this class for holding their headers.
+ * 
+ * <hr> <strong>A note on RFC822 and MIME headers</strong><p>
+ *
+ * RFC822 and MIME header fields <strong>must</strong> contain only 
+ * US-ASCII characters. If a header contains non US-ASCII characters,
+ * it must be encoded as per the rules in RFC 2047. 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. <p>
+ *
+ * The current implementation supports the System property
+ * <code>mail.mime.ignorewhitespacelines</code>, which if set to true
+ * will cause a line containing only whitespace to be considered
+ * a blank line terminating the header.
+ *
+ * @see	javax.mail.internet.MimeUtility
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class InternetHeaders {
+    private static final boolean ignoreWhitespaceLines =
+	PropUtil.getBooleanSystemProperty("mail.mime.ignorewhitespacelines",
+					    false);
+
+    /**
+     * An individual internet header.  This class is only used by
+     * subclasses of InternetHeaders. <p>
+     *
+     * An InternetHeader object with a null value is used as a placeholder
+     * for headers of that name, to preserve the order of headers.
+     * A placeholder InternetHeader object with a name of ":" marks
+     * the location in the list of headers where new headers are
+     * added by default.
+     *
+     * @since	JavaMail 1.4
+     */
+    protected static final class InternetHeader extends Header {
+	/*
+	 * Note that the value field from the superclass
+	 * isn't used in this class.  We extract the value
+	 * from the line field as needed.  We store the line
+	 * rather than just the value to ensure that we can
+	 * get back the exact original line, with the original
+	 * whitespace, etc.
+	 */
+	String line;    // the entire RFC822 header "line",
+			// or null if placeholder
+
+	/**
+	 * Constructor that takes a line and splits out
+	 * the header name.
+	 *
+	 * @param	l	the header line
+	 */
+	public InternetHeader(String l) {
+	    super("", "");	// XXX - we'll change it later
+	    int i = l.indexOf(':');
+	    if (i < 0) {
+		// should never happen
+		name = l.trim();
+	    } else {
+		name = l.substring(0, i).trim();
+	    }
+	    line = l;
+	}
+
+	/**
+	 * Constructor that takes a header name and value.
+	 *
+	 * @param	n	the name of the header
+	 * @param	v	the value of the header
+	 */
+	public InternetHeader(String n, String v) {
+	    super(n, "");
+	    if (v != null)
+		line = n + ": " + v;
+	    else
+		line = null;
+	}
+
+	/**
+	 * Return the "value" part of the header line.
+	 */
+	@Override
+	public String getValue() {
+	    int i = line.indexOf(':');
+	    if (i < 0)
+		return line;
+	    // skip whitespace after ':'
+	    int j;
+	    for (j = i + 1; j < line.length(); j++) {
+		char c = line.charAt(j);
+		if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n'))
+		    break;
+	    }
+	    return line.substring(j);
+	}
+    }
+
+    /*
+     * The enumeration object used to enumerate an
+     * InternetHeaders object.  Can return
+     * either a String or a Header object.
+     */
+    static class MatchEnum {
+	private Iterator<InternetHeader> e;	// enum object of headers List
+	// XXX - is this overkill?  should we step through in index
+	// order instead?
+	private String names[];	// names to match, or not
+	private boolean match;	// return matching headers?
+	private boolean want_line;	// return header lines?
+	private InternetHeader next_header; // the next header to be returned
+
+	/*
+	 * Constructor.  Initialize the enumeration for the entire
+	 * List of headers, the set of headers, whether to return
+	 * matching or non-matching headers, and whether to return
+	 * header lines or Header objects.
+	 */
+	MatchEnum(List<InternetHeader> v, String n[], boolean m, boolean l) {
+	    e = v.iterator();
+	    names = n;
+	    match = m;
+	    want_line = l;
+	    next_header = null;
+	}
+
+	/*
+	 * Any more elements in this enumeration?
+	 */
+	public boolean hasMoreElements() {
+	    // if necessary, prefetch the next matching header,
+	    // and remember it.
+	    if (next_header == null)
+		next_header = nextMatch();
+	    return next_header != null;
+	}
+
+	/*
+	 * Return the next element.
+	 */
+	public Object nextElement() {
+	    if (next_header == null)
+		next_header = nextMatch();
+
+	    if (next_header == null)
+		throw new NoSuchElementException("No more headers");
+
+	    InternetHeader h = next_header;
+	    next_header = null;
+	    if (want_line)
+		return h.line;
+	    else
+		return new Header(h.getName(), h.getValue());
+	}
+
+	/*
+	 * Return the next Header object according to the match
+	 * criteria, or null if none left.
+	 */
+	private InternetHeader nextMatch() {
+	    next:
+	    while (e.hasNext()) {
+		InternetHeader h = e.next();
+
+		// skip "place holder" headers
+		if (h.line == null)
+		    continue;
+
+		// if no names to match against, return appropriately
+		if (names == null)
+		    return match ? null : h;
+
+		// check whether this header matches any of the names
+		for (int i = 0; i < names.length; i++) {
+		    if (names[i].equalsIgnoreCase(h.getName())) {
+			if (match)
+			    return h;
+			else
+			    // found a match, but we're
+			    // looking for non-matches.
+			    // try next header.
+			    continue next;
+		    }
+		}
+		// found no matches.  if that's what we wanted, return it.
+		if (!match)
+		    return h;
+	    }
+	    return null;
+	}
+    }
+
+    static class MatchStringEnum extends MatchEnum
+	    implements Enumeration<String> {
+
+	MatchStringEnum(List<InternetHeader> v, String[] n, boolean m) {
+	    super(v, n, m, true);
+	}
+
+	@Override
+	public String nextElement() {
+	    return (String) super.nextElement();
+	}
+
+    }
+
+    static class MatchHeaderEnum extends MatchEnum
+	    implements Enumeration<Header> {
+
+	MatchHeaderEnum(List<InternetHeader> v, String[] n, boolean m) {
+	    super(v, n, m, false);
+	}
+
+	@Override
+	public Header nextElement() {
+	    return (Header) super.nextElement();
+	}
+
+    }
+
+    /**
+     * The actual list of Headers, including placeholder entries.
+     * Placeholder entries are Headers with a null value and
+     * are never seen by clients of the InternetHeaders class.
+     * Placeholder entries are used to keep track of the preferred
+     * order of headers.  Headers are never actually removed from
+     * the list, they're converted into placeholder entries.
+     * New headers are added after existing headers of the same name
+     * (or before in the case of <code>Received</code> and
+     * <code>Return-Path</code> headers).  If no existing header
+     * or placeholder for the header is found, new headers are
+     * added after the special placeholder with the name ":".
+     *
+     * @since	JavaMail 1.4
+     */
+    protected List<InternetHeader> headers;
+
+    /**
+     * Create an empty InternetHeaders object.  Placeholder entries
+     * are inserted to indicate the preferred order of headers.
+     */
+    public InternetHeaders() { 
+   	headers = new ArrayList<>(40); 
+	headers.add(new InternetHeader("Return-Path", null));
+	headers.add(new InternetHeader("Received", null));
+	headers.add(new InternetHeader("Resent-Date", null));
+	headers.add(new InternetHeader("Resent-From", null));
+	headers.add(new InternetHeader("Resent-Sender", null));
+	headers.add(new InternetHeader("Resent-To", null));
+	headers.add(new InternetHeader("Resent-Cc", null));
+	headers.add(new InternetHeader("Resent-Bcc", null));
+	headers.add(new InternetHeader("Resent-Message-Id", null));
+	headers.add(new InternetHeader("Date", null));
+	headers.add(new InternetHeader("From", null));
+	headers.add(new InternetHeader("Sender", null));
+	headers.add(new InternetHeader("Reply-To", null));
+	headers.add(new InternetHeader("To", null));
+	headers.add(new InternetHeader("Cc", null));
+	headers.add(new InternetHeader("Bcc", null));
+	headers.add(new InternetHeader("Message-Id", null));
+	headers.add(new InternetHeader("In-Reply-To", null));
+	headers.add(new InternetHeader("References", null));
+	headers.add(new InternetHeader("Subject", null));
+	headers.add(new InternetHeader("Comments", null));
+	headers.add(new InternetHeader("Keywords", null));
+	headers.add(new InternetHeader("Errors-To", null));
+	headers.add(new InternetHeader("MIME-Version", null));
+	headers.add(new InternetHeader("Content-Type", null));
+	headers.add(new InternetHeader("Content-Transfer-Encoding", null));
+	headers.add(new InternetHeader("Content-MD5", null));
+	headers.add(new InternetHeader(":", null));
+	headers.add(new InternetHeader("Content-Length", null));
+	headers.add(new InternetHeader("Status", null));
+    }
+
+    /**
+     * Read and parse the given RFC822 message stream till the 
+     * blank line separating the header from the body. The input 
+     * stream is left positioned at the start of the body. The 
+     * header lines are stored internally. <p>
+     *
+     * For efficiency, wrap a BufferedInputStream around the actual
+     * input stream and pass it as the parameter. <p>
+     *
+     * No placeholder entries are inserted; the original order of
+     * the headers is preserved.
+     *
+     * @param	is 	RFC822 input stream
+     * @exception	MessagingException for any I/O error reading the stream
+     */
+    public InternetHeaders(InputStream is) throws MessagingException {
+	this(is, false);
+    }
+
+    /**
+     * Read and parse the given RFC822 message stream till the 
+     * blank line separating the header from the body. The input 
+     * stream is left positioned at the start of the body. The 
+     * header lines are stored internally. <p>
+     *
+     * For efficiency, wrap a BufferedInputStream around the actual
+     * input stream and pass it as the parameter. <p>
+     *
+     * No placeholder entries are inserted; the original order of
+     * the headers is preserved.
+     *
+     * @param	is 	RFC822 input stream
+     * @param	allowutf8 	if UTF-8 encoded headers are allowed
+     * @exception	MessagingException for any I/O error reading the stream
+     * @since		JavaMail 1.6
+     */
+    public InternetHeaders(InputStream is, boolean allowutf8)
+				throws MessagingException {
+   	headers = new ArrayList<>(40); 
+	load(is, allowutf8);
+    }
+
+    /**
+     * Read and parse the given RFC822 message stream till the
+     * blank line separating the header from the body. Store the
+     * header lines inside this InternetHeaders object. The order
+     * of header lines is preserved. <p>
+     *
+     * Note that the header lines are added into this InternetHeaders
+     * object, so any existing headers in this object will not be
+     * affected.  Headers are added to the end of the existing list
+     * of headers, in order.
+     *
+     * @param	is 	RFC822 input stream
+     * @exception	MessagingException for any I/O error reading the stream
+     */
+    public void load(InputStream is) throws MessagingException {
+	load(is, false);
+    }
+
+    /**
+     * Read and parse the given RFC822 message stream till the
+     * blank line separating the header from the body. Store the
+     * header lines inside this InternetHeaders object. The order
+     * of header lines is preserved. <p>
+     *
+     * Note that the header lines are added into this InternetHeaders
+     * object, so any existing headers in this object will not be
+     * affected.  Headers are added to the end of the existing list
+     * of headers, in order.
+     *
+     * @param	is 	RFC822 input stream
+     * @param	allowutf8 	if UTF-8 encoded headers are allowed
+     * @exception	MessagingException for any I/O error reading the stream
+     * @since		JavaMail 1.6
+     */
+    public void load(InputStream is, boolean allowutf8)
+				throws MessagingException {
+	// Read header lines until a blank line. It is valid
+	// to have BodyParts with no header lines.
+	String line;
+	LineInputStream lis = new LineInputStream(is, allowutf8);
+	String prevline = null;	// the previous header line, as a string
+	// a buffer to accumulate the header in, when we know it's needed
+	StringBuilder lineBuffer = new StringBuilder();
+
+	try {
+	    // if the first line being read is a continuation line,
+	    // we ignore it if it's otherwise empty or we treat it as
+	    // a non-continuation line if it has non-whitespace content
+	    boolean first = true;
+	    do {
+		line = lis.readLine();
+		if (line != null &&
+			(line.startsWith(" ") || line.startsWith("\t"))) {
+		    // continuation of header
+		    if (prevline != null) {
+			lineBuffer.append(prevline);
+			prevline = null;
+		    }
+		    if (first) {
+			String lt = line.trim();
+			if (lt.length() > 0)
+			    lineBuffer.append(lt);
+		    } else {
+			if (lineBuffer.length() > 0)
+			    lineBuffer.append("\r\n");
+			lineBuffer.append(line);
+		    }
+		} else {
+		    // new header
+		    if (prevline != null)
+			addHeaderLine(prevline);
+		    else if (lineBuffer.length() > 0) {
+			// store previous header first
+			addHeaderLine(lineBuffer.toString());
+			lineBuffer.setLength(0);
+		    }
+		    prevline = line;
+		}
+		first = false;
+	    } while (line != null && !isEmpty(line));
+	} catch (IOException ioex) {
+	    throw new MessagingException("Error in input stream", ioex);
+	}
+    }
+
+    /**
+     * Is this line an empty (blank) line?
+     */
+    private static final boolean isEmpty(String line) {
+	return line.length() == 0 ||
+	    (ignoreWhitespaceLines && line.trim().length() == 0);
+    }
+
+    /**
+     * Return all the values for the specified header. The
+     * values are String objects.  Returns <code>null</code>
+     * if no headers with the specified name exist.
+     *
+     * @param	name 	header name
+     * @return		array of header values, or null if none
+     */
+    public String[] getHeader(String name) {
+	Iterator<InternetHeader> e = headers.iterator();
+	// XXX - should we just step through in index order?
+	List<String> v = new ArrayList<>(); // accumulate return values
+
+	while (e.hasNext()) {
+	    InternetHeader h = e.next();
+	    if (name.equalsIgnoreCase(h.getName()) && h.line != null) {
+		v.add(h.getValue());
+	    }
+	}
+	if (v.size() == 0)
+	    return (null);
+	// convert List to an array for return
+	String r[] = new String[v.size()];
+	r = v.toArray(r);
+	return (r);
+    }
+
+    /**
+     * Get all the headers for this header name, returned as a single
+     * String, with headers separated by the delimiter. If the
+     * delimiter is <code>null</code>, only the first header is 
+     * returned.  Returns <code>null</code>
+     * if no headers with the specified name exist.
+     *
+     * @param	name 		header name
+     * @param   delimiter	delimiter
+     * @return                  the value fields for all headers with
+     *				this name, or null if none
+     */
+    public String getHeader(String name, String delimiter) {
+	String s[] = getHeader(name);
+
+	if (s == null)
+	    return null;
+	
+	if ((s.length == 1) || delimiter == null)
+	    return s[0];
+	
+	StringBuilder r = new StringBuilder(s[0]);
+	for (int i = 1; i < s.length; i++) {
+	    r.append(delimiter);
+	    r.append(s[i]);
+	}
+	return r.toString();
+    }
+
+    /**
+     * Change the first header line that matches name
+     * to have value, adding a new header if no existing header
+     * matches. Remove all matching headers but the first. <p>
+     *
+     * Note that RFC822 headers can only contain US-ASCII characters
+     *
+     * @param	name	header name
+     * @param	value	header value
+     */
+    public void setHeader(String name, String value) {
+	boolean found = false;
+
+	for (int i = 0; i < headers.size(); i++) {
+	    InternetHeader h = headers.get(i);
+	    if (name.equalsIgnoreCase(h.getName())) {
+		if (!found) {
+		    int j;
+		    if (h.line != null && (j = h.line.indexOf(':')) >= 0) {
+			h.line = h.line.substring(0, j + 1) + " " + value;
+			// preserves capitalization, spacing
+		    } else {
+			h.line = name + ": " + value;
+		    }
+		    found = true;
+		} else {
+		    headers.remove(i);
+		    i--;    // have to look at i again
+		}
+	    }
+	}
+    
+	if (!found) {
+	    addHeader(name, value);
+	}
+    }
+
+    /**
+     * Add a header with the specified name and value to the header list. <p>
+     *
+     * The current implementation knows about the preferred order of most
+     * well-known headers and will insert headers in that order.  In
+     * addition, it knows that <code>Received</code> headers should be
+     * inserted in reverse order (newest before oldest), and that they
+     * should appear at the beginning of the headers, preceeded only by
+     * a possible <code>Return-Path</code> header.  <p>
+     *
+     * Note that RFC822 headers can only contain US-ASCII characters.
+     *
+     * @param	name	header name
+     * @param	value	header value
+     */ 
+    public void addHeader(String name, String value) {
+	int pos = headers.size();
+	boolean addReverse =
+	    name.equalsIgnoreCase("Received") ||
+	    name.equalsIgnoreCase("Return-Path");
+	if (addReverse)
+	    pos = 0;
+	for (int i = headers.size() - 1; i >= 0; i--) {
+	    InternetHeader h = headers.get(i);
+	    if (name.equalsIgnoreCase(h.getName())) {
+		if (addReverse) {
+		    pos = i;
+		} else {
+		    headers.add(i + 1, new InternetHeader(name, value));
+		    return;
+		}
+	    }
+	    // marker for default place to add new headers
+	    if (!addReverse && h.getName().equals(":"))
+		pos = i;
+	}
+	headers.add(pos, new InternetHeader(name, value));
+    }
+
+    /**
+     * Remove all header entries that match the given name
+     * @param	name 	header name
+     */
+    public void removeHeader(String name) { 
+	for (int i = 0; i < headers.size(); i++) {
+	    InternetHeader h = headers.get(i);
+	    if (name.equalsIgnoreCase(h.getName())) {
+		h.line = null;
+		//headers.remove(i);
+		//i--;    // have to look at i again
+	    }
+	}
+    }
+
+    /**
+     * Return all the headers as an Enumeration of
+     * {@link javax.mail.Header} objects.
+     *
+     * @return	Enumeration of Header objects	
+     */
+    public Enumeration<Header> getAllHeaders() {
+	return (new MatchHeaderEnum(headers, null, false));
+    }
+
+    /**
+     * Return all matching {@link javax.mail.Header} objects.
+     *
+     * @param	names	the headers to return
+     * @return	Enumeration of matching Header objects	
+     */
+    public Enumeration<Header> getMatchingHeaders(String[] names) {
+	return (new MatchHeaderEnum(headers, names, true));
+    }
+
+    /**
+     * Return all non-matching {@link javax.mail.Header} objects.
+     *
+     * @param	names	the headers to not return
+     * @return	Enumeration of non-matching Header objects	
+     */
+    public Enumeration<Header> getNonMatchingHeaders(String[] names) {
+	return (new MatchHeaderEnum(headers, names, false));
+    }
+
+    /**
+     * Add an RFC822 header line to the header store.
+     * If the line starts with a space or tab (a continuation line),
+     * add it to the last header line in the list.  Otherwise,
+     * append the new header line to the list.  <p>
+     *
+     * Note that RFC822 headers can only contain US-ASCII characters
+     *
+     * @param	line	raw RFC822 header line
+     */
+    public void addHeaderLine(String line) {
+	try {
+	    char c = line.charAt(0);
+	    if (c == ' ' || c == '\t') {
+		InternetHeader h = headers.get(headers.size() - 1);
+		h.line += "\r\n" + line;
+	    } else
+		headers.add(new InternetHeader(line));
+	} catch (StringIndexOutOfBoundsException e) {
+	    // line is empty, ignore it
+	    return;
+	} catch (NoSuchElementException e) {
+	    // XXX - list is empty?
+	}
+    }
+
+    /**
+     * Return all the header lines as an Enumeration of Strings.
+     *
+     * @return	Enumeration of Strings of all header lines
+     */
+    public Enumeration<String> getAllHeaderLines() { 
+	return (getNonMatchingHeaderLines(null));
+    }
+
+    /**
+     * Return all matching header lines as an Enumeration of Strings.
+     *
+     * @param	names	the headers to return
+     * @return	Enumeration of Strings of all matching header lines
+     */
+    public Enumeration<String> getMatchingHeaderLines(String[] names) {
+	return (new MatchStringEnum(headers, names, true));	
+    }
+
+    /**
+     * Return all non-matching header lines
+     *
+     * @param	names	the headers to not return
+     * @return	Enumeration of Strings of all non-matching header lines
+     */
+    public Enumeration<String> getNonMatchingHeaderLines(String[] names) {
+	return (new MatchStringEnum(headers, names, false));
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/MailDateFormat.java b/mail/src/main/java/javax/mail/internet/MailDateFormat.java
new file mode 100644
index 0000000..0bd47b9
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/MailDateFormat.java
@@ -0,0 +1,1042 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamException;
+import java.util.Date;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.logging.Level;
+import java.text.DateFormatSymbols;
+import java.text.SimpleDateFormat;
+import java.text.NumberFormat;
+import java.text.FieldPosition;
+import java.text.ParsePosition;
+import java.text.ParseException;
+
+import com.sun.mail.util.MailLogger;
+
+/**
+ * Formats and parses date specification based on
+ * <a href="http://www.ietf.org/rfc/rfc2822.txt" target="_top">RFC 2822</a>. <p>
+ *
+ * This class does not support methods that influence the format. It always
+ * formats the date based on the specification below.<p>
+ *
+ * 3.3. Date and Time Specification
+ * <p>
+ * Date and time occur in several header fields.  This section specifies
+ * the syntax for a full date and time specification.  Though folding
+ * white space is permitted throughout the date-time specification, it is
+ * RECOMMENDED that a single space be used in each place that FWS appears
+ * (whether it is required or optional); some older implementations may
+ * not interpret other occurrences of folding white space correctly.
+ * <pre>
+ * date-time       =       [ day-of-week "," ] date FWS time [CFWS]
+ *
+ * day-of-week     =       ([FWS] day-name) / obs-day-of-week
+ *
+ * day-name        =       "Mon" / "Tue" / "Wed" / "Thu" /
+ *                         "Fri" / "Sat" / "Sun"
+ *
+ * date            =       day month year
+ *
+ * year            =       4*DIGIT / obs-year
+ *
+ * month           =       (FWS month-name FWS) / obs-month
+ *
+ * month-name      =       "Jan" / "Feb" / "Mar" / "Apr" /
+ *                         "May" / "Jun" / "Jul" / "Aug" /
+ *                         "Sep" / "Oct" / "Nov" / "Dec"
+ *
+ * day             =       ([FWS] 1*2DIGIT) / obs-day
+ *
+ * time            =       time-of-day FWS zone
+ *
+ * time-of-day     =       hour ":" minute [ ":" second ]
+ *
+ * hour            =       2DIGIT / obs-hour
+ *
+ * minute          =       2DIGIT / obs-minute
+ *
+ * second          =       2DIGIT / obs-second
+ *
+ * zone            =       (( "+" / "-" ) 4DIGIT) / obs-zone
+ * </pre>
+ * The day is the numeric day of the month.  The year is any numeric year
+ * 1900 or later.
+ * <p>
+ * The time-of-day specifies the number of hours, minutes, and optionally
+ * seconds since midnight of the date indicated.
+ * <p>
+ * The date and time-of-day SHOULD express local time.
+ * <p>
+ * The zone specifies the offset from Coordinated Universal Time (UTC,
+ * formerly referred to as "Greenwich Mean Time") that the date and
+ * time-of-day represent.  The "+" or "-" indicates whether the
+ * time-of-day is ahead of (i.e., east of) or behind (i.e., west of)
+ * Universal Time.  The first two digits indicate the number of hours
+ * difference from Universal Time, and the last two digits indicate the
+ * number of minutes difference from Universal Time.  (Hence, +hhmm means
+ * +(hh * 60 + mm) minutes, and -hhmm means -(hh * 60 + mm) minutes).  The
+ * form "+0000" SHOULD be used to indicate a time zone at Universal Time.
+ * Though "-0000" also indicates Universal Time, it is used to indicate
+ * that the time was generated on a system that may be in a local time
+ * zone other than Universal Time and therefore indicates that the
+ * date-time contains no information about the local time zone.
+ * <p>
+ * A date-time specification MUST be semantically valid.  That is, the
+ * day-of-the-week (if included) MUST be the day implied by the date, the
+ * numeric day-of-month MUST be between 1 and the number of days allowed
+ * for the specified month (in the specified year), the time-of-day MUST
+ * be in the range 00:00:00 through 23:59:60 (the number of seconds
+ * allowing for a leap second; see [STD12]), and the zone MUST be within
+ * the range -9959 through +9959.
+ *
+ * <h3><a name="synchronization">Synchronization</a></h3>
+ * 
+ * <p>
+ * Date formats are not synchronized.
+ * It is recommended to create separate format instances for each thread.
+ * If multiple threads access a format concurrently, it must be synchronized
+ * externally.
+ *
+ * @author	Anthony Vanelverdinghe
+ * @author	Max Spivak
+ * @since	JavaMail 1.2
+ */
+public class MailDateFormat extends SimpleDateFormat {
+
+    private static final long serialVersionUID = -8148227605210628779L;
+    private static final String PATTERN = "EEE, d MMM yyyy HH:mm:ss Z (z)";
+
+    private static final MailLogger LOGGER = new MailLogger(
+            MailDateFormat.class, "DEBUG", false, System.out);
+
+    private static final int UNKNOWN_DAY_NAME = -1;
+    private static final TimeZone UTC = TimeZone.getTimeZone("UTC");
+    private static final int LEAP_SECOND = 60;
+
+    /**
+     * Create a new date format for the RFC2822 specification with lenient
+     * parsing.
+     */
+    public MailDateFormat() {
+        super(PATTERN, Locale.US);
+    }
+
+    /**
+     * Allows to serialize instances such that they are deserializable with the
+     * previous implementation.
+     *
+     * @return the object to be serialized
+     * @throws ObjectStreamException	never
+     */
+    private Object writeReplace() throws ObjectStreamException {
+        MailDateFormat fmt = new MailDateFormat();
+        fmt.superApplyPattern("EEE, d MMM yyyy HH:mm:ss 'XXXXX' (z)");
+        fmt.setTimeZone(getTimeZone());
+        return fmt;
+    }
+
+    /**
+     * Allows to deserialize instances that were serialized with the previous
+     * implementation.
+     *
+     * @param in the stream containing the serialized object
+     * @throws IOException	on read failures
+     * @throws ClassNotFoundException	never
+     */
+    private void readObject(ObjectInputStream in)
+            throws IOException, ClassNotFoundException {
+        in.defaultReadObject();
+        super.applyPattern(PATTERN);
+    }
+
+    /**
+     * Overrides Cloneable.
+     *
+     * @return a clone of this instance
+     * @since JavaMail 1.6
+     */
+    @Override
+    public MailDateFormat clone() {
+        return (MailDateFormat) super.clone();
+    }
+
+    /**
+     * Formats the given date in the format specified by 
+     * RFC 2822 in the current TimeZone.
+     *
+     * @param   date            the Date object
+     * @param   dateStrBuf      the formatted string
+     * @param   fieldPosition   the current field position
+     * @return	StringBuffer    the formatted String
+     * @since			JavaMail 1.2
+     */
+    @Override
+    public StringBuffer format(Date date, StringBuffer dateStrBuf,
+            FieldPosition fieldPosition) {
+        return super.format(date, dateStrBuf, fieldPosition);
+    }
+
+    /**
+     * Parses the given date in the format specified by
+     * RFC 2822.
+     * <ul>
+     * <li>With strict parsing, obs-* tokens are unsupported. Lenient parsing
+     * supports obs-year and obs-zone, with the exception of the 1-character
+     * military time zones.
+     * <li>The optional CFWS token at the end is not parsed.
+     * <li>RFC 2822 specifies that a zone of "-0000" indicates that the
+     * date-time contains no information about the local time zone. This class
+     * uses the UTC time zone in this case.
+     * </ul>
+     *
+     * @param   text    the formatted date to be parsed
+     * @param   pos     the current parse position
+     * @return	Date    the parsed date. In case of error, returns null.
+     * @since		JavaMail 1.2
+     */
+    @Override
+    public Date parse(String text, ParsePosition pos) {
+        if (text == null || pos == null) {
+            throw new NullPointerException();
+        } else if (0 > pos.getIndex() || pos.getIndex() >= text.length()) {
+            return null;
+        }
+
+        return isLenient()
+                ? new Rfc2822LenientParser(text, pos).parse()
+                : new Rfc2822StrictParser(text, pos).parse();
+    }
+
+    /**
+     * This method always throws an UnsupportedOperationException and should not
+     * be used because RFC 2822 mandates a specific calendar.
+     *
+     * @throws UnsupportedOperationException if this method is invoked
+     */
+    @Override
+    public void setCalendar(Calendar newCalendar) {
+        throw new UnsupportedOperationException("Method "
+                + "setCalendar() shouldn't be called");
+    }
+
+    /**
+     * This method always throws an UnsupportedOperationException and should not
+     * be used because RFC 2822 mandates a specific number format.
+     *
+     * @throws UnsupportedOperationException if this method is invoked
+     */
+    @Override
+    public void setNumberFormat(NumberFormat newNumberFormat) {
+        throw new UnsupportedOperationException("Method "
+                + "setNumberFormat() shouldn't be called");
+    }
+
+    /**
+     * This method always throws an UnsupportedOperationException and should not
+     * be used because RFC 2822 mandates a specific pattern.
+     *
+     * @throws UnsupportedOperationException if this method is invoked
+     * @since JavaMail 1.6
+     */
+    @Override
+    public void applyLocalizedPattern(String pattern) {
+        throw new UnsupportedOperationException("Method "
+                + "applyLocalizedPattern() shouldn't be called");
+    }
+
+    /**
+     * This method always throws an UnsupportedOperationException and should not
+     * be used because RFC 2822 mandates a specific pattern.
+     *
+     * @throws UnsupportedOperationException if this method is invoked
+     * @since JavaMail 1.6
+     */
+    @Override
+    public void applyPattern(String pattern) {
+        throw new UnsupportedOperationException("Method "
+                + "applyPattern() shouldn't be called");
+    }
+
+    /**
+     * This method allows serialization to change the pattern.
+     */
+    private void superApplyPattern(String pattern) {
+        super.applyPattern(pattern);
+    }
+
+    /**
+     * This method always throws an UnsupportedOperationException and should not
+     * be used because RFC 2822 mandates another strategy for interpreting
+     * 2-digits years.
+     *
+     * @return the start of the 100-year period into which two digit years are
+     * parsed
+     * @throws UnsupportedOperationException if this method is invoked
+     * @since JavaMail 1.6
+     */
+    @Override
+    public Date get2DigitYearStart() {
+        throw new UnsupportedOperationException("Method "
+                + "get2DigitYearStart() shouldn't be called");
+    }
+
+    /**
+     * This method always throws an UnsupportedOperationException and should not
+     * be used because RFC 2822 mandates another strategy for interpreting
+     * 2-digits years.
+     *
+     * @throws UnsupportedOperationException if this method is invoked
+     * @since JavaMail 1.6
+     */
+    @Override
+    public void set2DigitYearStart(Date startDate) {
+        throw new UnsupportedOperationException("Method "
+                + "set2DigitYearStart() shouldn't be called");
+    }
+
+    /**
+     * This method always throws an UnsupportedOperationException and should not
+     * be used because RFC 2822 mandates specific date format symbols.
+     *
+     * @throws UnsupportedOperationException if this method is invoked
+     * @since JavaMail 1.6
+     */
+    @Override
+    public void setDateFormatSymbols(DateFormatSymbols newFormatSymbols) {
+        throw new UnsupportedOperationException("Method "
+                + "setDateFormatSymbols() shouldn't be called");
+    }
+
+    /**
+     * Returns the date, as specified by the parameters.
+     *
+     * @param dayName
+     * @param day
+     * @param month
+     * @param year
+     * @param hour
+     * @param minute
+     * @param second
+     * @param zone
+     * @return the date, as specified by the parameters
+     * @throws IllegalArgumentException if this instance's Calendar is
+     * non-lenient and any of the parameters have invalid values, or if dayName
+     * is not consistent with day-month-year
+     */
+    private Date toDate(int dayName, int day, int month, int year,
+            int hour, int minute, int second, int zone) {
+        if (second == LEAP_SECOND) {
+            second = 59;
+        }
+
+        TimeZone tz = calendar.getTimeZone();
+        try {
+            calendar.setTimeZone(UTC);
+            calendar.clear();
+            calendar.set(year, month, day, hour, minute, second);
+
+            if (dayName == UNKNOWN_DAY_NAME
+                    || dayName == calendar.get(Calendar.DAY_OF_WEEK)) {
+                calendar.add(Calendar.MINUTE, zone);
+                return calendar.getTime();
+            } else {
+                throw new IllegalArgumentException("Inconsistent day-name");
+            }
+        } finally {
+            calendar.setTimeZone(tz);
+        }
+    }
+
+    /**
+     * This class provides the building blocks for date parsing.
+     * <p>
+     * It has the following invariants:
+     * <ul>
+     * <li>no exceptions are thrown, except for java.text.ParseException from
+     * parse* methods
+     * <li>when parse* throws ParseException OR get* returns INVALID_CHAR OR
+     * skip* returns false OR peek* is invoked, then pos.getIndex() on method
+     * exit is the same as it was on method entry
+     * </ul>
+     */
+    private static abstract class AbstractDateParser {
+
+        static final int INVALID_CHAR = -1;
+        static final int MAX_YEAR_DIGITS = 8; // guarantees that:
+        // year < new GregorianCalendar().getMaximum(Calendar.YEAR)
+
+        final String text;
+        final ParsePosition pos;
+
+        AbstractDateParser(String text, ParsePosition pos) {
+            this.text = text;
+            this.pos = pos;
+        }
+
+        final Date parse() {
+            int startPosition = pos.getIndex();
+            try {
+                return tryParse();
+            } catch (Exception e) { // == ParseException | RuntimeException e
+                if (LOGGER.isLoggable(Level.FINE)) {
+                    LOGGER.log(Level.FINE, "Bad date: '" + text + "'", e);
+                }
+                pos.setErrorIndex(pos.getIndex());
+                pos.setIndex(startPosition);
+                return null;
+            }
+        }
+
+        abstract Date tryParse() throws ParseException;
+
+        /**
+         * @return the java.util.Calendar constant for the parsed day name
+         */
+        final int parseDayName() throws ParseException {
+            switch (getChar()) {
+                case 'S':
+                    if (skipPair('u', 'n')) {
+                        return Calendar.SUNDAY;
+                    } else if (skipPair('a', 't')) {
+                        return Calendar.SATURDAY;
+                    }
+                    break;
+                case 'T':
+                    if (skipPair('u', 'e')) {
+                        return Calendar.TUESDAY;
+                    } else if (skipPair('h', 'u')) {
+                        return Calendar.THURSDAY;
+                    }
+                    break;
+                case 'M':
+                    if (skipPair('o', 'n')) {
+                        return Calendar.MONDAY;
+                    }
+                    break;
+                case 'W':
+                    if (skipPair('e', 'd')) {
+                        return Calendar.WEDNESDAY;
+                    }
+                    break;
+                case 'F':
+                    if (skipPair('r', 'i')) {
+                        return Calendar.FRIDAY;
+                    }
+                    break;
+                case INVALID_CHAR:
+                    throw new ParseException("Invalid day-name",
+                            pos.getIndex());
+            }
+            pos.setIndex(pos.getIndex() - 1);
+            throw new ParseException("Invalid day-name", pos.getIndex());
+        }
+
+        /**
+         * @return the java.util.Calendar constant for the parsed month name
+         */
+        @SuppressWarnings("fallthrough")
+        final int parseMonthName(boolean caseSensitive) throws ParseException {
+            switch (getChar()) {
+                case 'j':
+                    if (caseSensitive) {
+                        break;
+                    }
+                case 'J':
+                    if (skipChar('u') || (!caseSensitive && skipChar('U'))) {
+                        if (skipChar('l') || (!caseSensitive
+                                && skipChar('L'))) {
+                            return Calendar.JULY;
+                        } else if (skipChar('n') || (!caseSensitive
+                                && skipChar('N'))) {
+                            return Calendar.JUNE;
+                        } else {
+                            pos.setIndex(pos.getIndex() - 1);
+                        }
+                    } else if (skipPair('a', 'n') || (!caseSensitive
+                            && skipAlternativePair('a', 'A', 'n', 'N'))) {
+                        return Calendar.JANUARY;
+                    }
+                    break;
+                case 'm':
+                    if (caseSensitive) {
+                        break;
+                    }
+                case 'M':
+                    if (skipChar('a') || (!caseSensitive && skipChar('A'))) {
+                        if (skipChar('r') || (!caseSensitive
+                                && skipChar('R'))) {
+                            return Calendar.MARCH;
+                        } else if (skipChar('y') || (!caseSensitive
+                                && skipChar('Y'))) {
+                            return Calendar.MAY;
+                        } else {
+                            pos.setIndex(pos.getIndex() - 1);
+                        }
+                    }
+                    break;
+                case 'a':
+                    if (caseSensitive) {
+                        break;
+                    }
+                case 'A':
+                    if (skipPair('u', 'g') || (!caseSensitive
+                            && skipAlternativePair('u', 'U', 'g', 'G'))) {
+                        return Calendar.AUGUST;
+                    } else if (skipPair('p', 'r') || (!caseSensitive
+                            && skipAlternativePair('p', 'P', 'r', 'R'))) {
+                        return Calendar.APRIL;
+                    }
+                    break;
+                case 'd':
+                    if (caseSensitive) {
+                        break;
+                    }
+                case 'D':
+                    if (skipPair('e', 'c') || (!caseSensitive
+                            && skipAlternativePair('e', 'E', 'c', 'C'))) {
+                        return Calendar.DECEMBER;
+                    }
+                    break;
+                case 'o':
+                    if (caseSensitive) {
+                        break;
+                    }
+                case 'O':
+                    if (skipPair('c', 't') || (!caseSensitive
+                            && skipAlternativePair('c', 'C', 't', 'T'))) {
+                        return Calendar.OCTOBER;
+                    }
+                    break;
+                case 's':
+                    if (caseSensitive) {
+                        break;
+                    }
+                case 'S':
+                    if (skipPair('e', 'p') || (!caseSensitive
+                            && skipAlternativePair('e', 'E', 'p', 'P'))) {
+                        return Calendar.SEPTEMBER;
+                    }
+                    break;
+                case 'n':
+                    if (caseSensitive) {
+                        break;
+                    }
+                case 'N':
+                    if (skipPair('o', 'v') || (!caseSensitive
+                            && skipAlternativePair('o', 'O', 'v', 'V'))) {
+                        return Calendar.NOVEMBER;
+                    }
+                    break;
+                case 'f':
+                    if (caseSensitive) {
+                        break;
+                    }
+                case 'F':
+                    if (skipPair('e', 'b') || (!caseSensitive
+                            && skipAlternativePair('e', 'E', 'b', 'B'))) {
+                        return Calendar.FEBRUARY;
+                    }
+                    break;
+                case INVALID_CHAR:
+                    throw new ParseException("Invalid month", pos.getIndex());
+            }
+            pos.setIndex(pos.getIndex() - 1);
+            throw new ParseException("Invalid month", pos.getIndex());
+        }
+
+        /**
+         * @return the number of minutes to be added to the time in the local
+         * time zone, in order to obtain the equivalent time in the UTC time
+         * zone. Returns 0 if the date-time contains no information about the
+         * local time zone.
+         */
+        final int parseZoneOffset() throws ParseException {
+            int sign = getChar();
+            if (sign == '+' || sign == '-') {
+                int offset = parseAsciiDigits(4, 4, true);
+                if (!isValidZoneOffset(offset)) {
+                    pos.setIndex(pos.getIndex() - 5);
+                    throw new ParseException("Invalid zone", pos.getIndex());
+                }
+
+                return ((sign == '+') ? -1 : 1)
+                        * (offset / 100 * 60 + offset % 100);
+            } else if (sign != INVALID_CHAR) {
+                pos.setIndex(pos.getIndex() - 1);
+            }
+            throw new ParseException("Invalid zone", pos.getIndex());
+        }
+
+        boolean isValidZoneOffset(int offset) {
+            return (offset % 100) < 60;
+        }
+
+        final int parseAsciiDigits(int count) throws ParseException {
+            return parseAsciiDigits(count, count);
+        }
+
+        final int parseAsciiDigits(int min, int max) throws ParseException {
+            return parseAsciiDigits(min, max, false);
+        }
+
+        final int parseAsciiDigits(int min, int max, boolean isEOF)
+                throws ParseException {
+            int result = 0;
+            int nbDigitsParsed = 0;
+            while (nbDigitsParsed < max && peekAsciiDigit()) {
+                result = result * 10 + getAsciiDigit();
+                nbDigitsParsed++;
+            }
+
+            if ((nbDigitsParsed < min)
+                    || (nbDigitsParsed == max && !isEOF && peekAsciiDigit())) {
+                pos.setIndex(pos.getIndex() - nbDigitsParsed);
+            } else {
+                return result;
+            }
+
+            String range = (min == max)
+                    ? Integer.toString(min)
+                    : "between " + min + " and " + max;
+            throw new ParseException("Invalid input: expected "
+                    + range + " ASCII digits", pos.getIndex());
+        }
+
+        final void parseFoldingWhiteSpace() throws ParseException {
+            if (!skipFoldingWhiteSpace()) {
+                throw new ParseException("Invalid input: expected FWS",
+                        pos.getIndex());
+            }
+        }
+
+        final void parseChar(char ch) throws ParseException {
+            if (!skipChar(ch)) {
+                throw new ParseException("Invalid input: expected '" + ch + "'",
+                        pos.getIndex());
+            }
+        }
+
+        final int getAsciiDigit() {
+            int ch = getChar();
+            if ('0' <= ch && ch <= '9') {
+                return Character.digit((char) ch, 10);
+            } else {
+                if (ch != INVALID_CHAR) {
+                    pos.setIndex(pos.getIndex() - 1);
+                }
+                return INVALID_CHAR;
+            }
+        }
+
+        final int getChar() {
+            if (pos.getIndex() < text.length()) {
+                char ch = text.charAt(pos.getIndex());
+                pos.setIndex(pos.getIndex() + 1);
+                return ch;
+            } else {
+                return INVALID_CHAR;
+            }
+        }
+
+        boolean skipFoldingWhiteSpace() {
+            // fast paths: a single ASCII space or no FWS
+            if (skipChar(' ')) {
+                if (!peekFoldingWhiteSpace()) {
+                    return true;
+                } else {
+                    pos.setIndex(pos.getIndex() - 1);
+                }
+            } else if (!peekFoldingWhiteSpace()) {
+                return false;
+            }
+
+            // normal path
+            int startIndex = pos.getIndex();
+            if (skipWhiteSpace()) {
+                while (skipNewline()) {
+                    if (!skipWhiteSpace()) {
+                        pos.setIndex(startIndex);
+                        return false;
+                    }
+                }
+                return true;
+            } else if (skipNewline() && skipWhiteSpace()) {
+                return true;
+            } else {
+                pos.setIndex(startIndex);
+                return false;
+            }
+        }
+
+        final boolean skipWhiteSpace() {
+            int startIndex = pos.getIndex();
+            while (skipAlternative(' ', '\t')) { /* empty */ }
+            return pos.getIndex() > startIndex;
+        }
+
+        final boolean skipNewline() {
+            return skipPair('\r', '\n');
+        }
+
+        final boolean skipAlternativeTriple(
+                char firstStandard, char firstAlternative,
+                char secondStandard, char secondAlternative,
+                char thirdStandard, char thirdAlternative
+        ) {
+            if (skipAlternativePair(firstStandard, firstAlternative,
+                    secondStandard, secondAlternative)) {
+                if (skipAlternative(thirdStandard, thirdAlternative)) {
+                    return true;
+                } else {
+                    pos.setIndex(pos.getIndex() - 2);
+                }
+            }
+            return false;
+        }
+
+        final boolean skipAlternativePair(
+                char firstStandard, char firstAlternative,
+                char secondStandard, char secondAlternative
+        ) {
+            if (skipAlternative(firstStandard, firstAlternative)) {
+                if (skipAlternative(secondStandard, secondAlternative)) {
+                    return true;
+                } else {
+                    pos.setIndex(pos.getIndex() - 1);
+                }
+            }
+            return false;
+        }
+
+        final boolean skipAlternative(char standard, char alternative) {
+            return skipChar(standard) || skipChar(alternative);
+        }
+
+        final boolean skipPair(char first, char second) {
+            if (skipChar(first)) {
+                if (skipChar(second)) {
+                    return true;
+                } else {
+                    pos.setIndex(pos.getIndex() - 1);
+                }
+            }
+            return false;
+        }
+
+        final boolean skipChar(char ch) {
+            if (pos.getIndex() < text.length()
+                    && text.charAt(pos.getIndex()) == ch) {
+                pos.setIndex(pos.getIndex() + 1);
+                return true;
+            } else {
+                return false;
+            }
+        }
+
+        final boolean peekAsciiDigit() {
+            return (pos.getIndex() < text.length()
+                    && '0' <= text.charAt(pos.getIndex())
+                    && text.charAt(pos.getIndex()) <= '9');
+        }
+
+        boolean peekFoldingWhiteSpace() {
+            return (pos.getIndex() < text.length()
+                    && (text.charAt(pos.getIndex()) == ' '
+                    || text.charAt(pos.getIndex()) == '\t'
+                    || text.charAt(pos.getIndex()) == '\r'));
+        }
+
+        final boolean peekChar(char ch) {
+            return (pos.getIndex() < text.length()
+                    && text.charAt(pos.getIndex()) == ch);
+        }
+
+    }
+
+    private class Rfc2822StrictParser extends AbstractDateParser {
+
+        Rfc2822StrictParser(String text, ParsePosition pos) {
+            super(text, pos);
+        }
+
+        @Override
+        Date tryParse() throws ParseException {
+            int dayName = parseOptionalBegin();
+
+            int day = parseDay();
+            int month = parseMonth();
+            int year = parseYear();
+
+            parseFoldingWhiteSpace();
+
+            int hour = parseHour();
+            parseChar(':');
+            int minute = parseMinute();
+            int second = (skipChar(':')) ? parseSecond() : 0;
+
+            parseFwsBetweenTimeOfDayAndZone();
+
+            int zone = parseZone();
+
+            try {
+                return MailDateFormat.this.toDate(dayName, day, month, year,
+                        hour, minute, second, zone);
+            } catch (IllegalArgumentException e) {
+                throw new ParseException("Invalid input: some of the calendar "
+                        + "fields have invalid values, or day-name is "
+                        + "inconsistent with date", pos.getIndex());
+            }
+        }
+
+        /**
+         * @return the java.util.Calendar constant for the parsed day name, or
+         * UNKNOWN_DAY_NAME iff the begin is missing
+         */
+        int parseOptionalBegin() throws ParseException {
+            int dayName;
+            if (!peekAsciiDigit()) {
+                skipFoldingWhiteSpace();
+                dayName = parseDayName();
+                parseChar(',');
+            } else {
+                dayName = UNKNOWN_DAY_NAME;
+            }
+            return dayName;
+        }
+
+        int parseDay() throws ParseException {
+            skipFoldingWhiteSpace();
+            return parseAsciiDigits(1, 2);
+        }
+
+        /**
+         * @return the java.util.Calendar constant for the parsed month name
+         */
+        int parseMonth() throws ParseException {
+            parseFwsInMonth();
+            int month = parseMonthName(isMonthNameCaseSensitive());
+            parseFwsInMonth();
+            return month;
+        }
+
+        void parseFwsInMonth() throws ParseException {
+            parseFoldingWhiteSpace();
+        }
+
+        boolean isMonthNameCaseSensitive() {
+            return true;
+        }
+
+        int parseYear() throws ParseException {
+            int year = parseAsciiDigits(4, MAX_YEAR_DIGITS);
+            if (year >= 1900) {
+                return year;
+            } else {
+                pos.setIndex(pos.getIndex() - 4);
+                while (text.charAt(pos.getIndex() - 1) == '0') {
+                    pos.setIndex(pos.getIndex() - 1);
+                }
+                throw new ParseException("Invalid year", pos.getIndex());
+            }
+        }
+
+        int parseHour() throws ParseException {
+            return parseAsciiDigits(2);
+        }
+
+        int parseMinute() throws ParseException {
+            return parseAsciiDigits(2);
+        }
+
+        int parseSecond() throws ParseException {
+            return parseAsciiDigits(2);
+        }
+
+        void parseFwsBetweenTimeOfDayAndZone() throws ParseException {
+            parseFoldingWhiteSpace();
+        }
+
+        int parseZone() throws ParseException {
+            return parseZoneOffset();
+        }
+
+    }
+
+    private class Rfc2822LenientParser extends Rfc2822StrictParser {
+
+        private Boolean hasDefaultFws;
+
+        Rfc2822LenientParser(String text, ParsePosition pos) {
+            super(text, pos);
+        }
+
+        @Override
+        int parseOptionalBegin() {
+            while (pos.getIndex() < text.length() && !peekAsciiDigit()) {
+                pos.setIndex(pos.getIndex() + 1);
+            }
+
+            return UNKNOWN_DAY_NAME;
+        }
+
+        @Override
+        int parseDay() throws ParseException {
+            skipFoldingWhiteSpace();
+            return parseAsciiDigits(1, 3);
+        }
+
+        @Override
+        void parseFwsInMonth() throws ParseException {
+            // '-' is allowed to accomodate for the date format as specified in
+            // <a href="http://www.ietf.org/rfc/rfc3501.txt">RFC 3501</a>
+            if (hasDefaultFws == null) {
+                hasDefaultFws = !skipChar('-');
+                skipFoldingWhiteSpace();
+            } else if (hasDefaultFws) {
+                skipFoldingWhiteSpace();
+            } else {
+                parseChar('-');
+            }
+        }
+
+        @Override
+        boolean isMonthNameCaseSensitive() {
+            return false;
+        }
+
+        @Override
+        int parseYear() throws ParseException {
+            int year = parseAsciiDigits(1, MAX_YEAR_DIGITS);
+            if (year >= 1000) {
+                return year;
+            } else if (year >= 50) {
+                return year + 1900;
+            } else {
+                return year + 2000;
+            }
+        }
+
+        @Override
+        int parseHour() throws ParseException {
+            return parseAsciiDigits(1, 2);
+        }
+
+        @Override
+        int parseMinute() throws ParseException {
+            return parseAsciiDigits(1, 2);
+        }
+
+        @Override
+        int parseSecond() throws ParseException {
+            return parseAsciiDigits(1, 2);
+        }
+
+        @Override
+        void parseFwsBetweenTimeOfDayAndZone() throws ParseException {
+            skipFoldingWhiteSpace();
+        }
+
+        @Override
+        int parseZone() throws ParseException {
+            try {
+                if (pos.getIndex() >= text.length()) {
+                    throw new ParseException("Missing zone", pos.getIndex());
+                }
+
+                if (peekChar('+') || peekChar('-')) {
+                    return parseZoneOffset();
+                } else if (skipAlternativePair('U', 'u', 'T', 't')) {
+                    return 0;
+                } else if (skipAlternativeTriple('G', 'g', 'M', 'm',
+                        'T', 't')) {
+                    return 0;
+                } else {
+                    int hoursOffset;
+                    if (skipAlternative('E', 'e')) {
+                        hoursOffset = 4;
+                    } else if (skipAlternative('C', 'c')) {
+                        hoursOffset = 5;
+                    } else if (skipAlternative('M', 'm')) {
+                        hoursOffset = 6;
+                    } else if (skipAlternative('P', 'p')) {
+                        hoursOffset = 7;
+                    } else {
+                        throw new ParseException("Invalid zone",
+                                pos.getIndex());
+                    }
+                    if (skipAlternativePair('S', 's', 'T', 't')) {
+                        hoursOffset += 1;
+                    } else if (skipAlternativePair('D', 'd', 'T', 't')) {
+                    } else {
+                        pos.setIndex(pos.getIndex() - 1);
+                        throw new ParseException("Invalid zone",
+                                pos.getIndex());
+                    }
+                    return hoursOffset * 60;
+                }
+            } catch (ParseException e) {
+                if (LOGGER.isLoggable(Level.FINE)) {
+                    LOGGER.log(Level.FINE, "No timezone? : '" + text + "'", e);
+                }
+
+                return 0;
+            }
+        }
+
+        @Override
+        boolean isValidZoneOffset(int offset) {
+            return true;
+        }
+
+        @Override
+        boolean skipFoldingWhiteSpace() {
+            boolean result = peekFoldingWhiteSpace();
+
+            skipLoop:
+            while (pos.getIndex() < text.length()) {
+                switch (text.charAt(pos.getIndex())) {
+                    case ' ':
+                    case '\t':
+                    case '\r':
+                    case '\n':
+                        pos.setIndex(pos.getIndex() + 1);
+                        break;
+                    default:
+                        break skipLoop;
+                }
+            }
+
+            return result;
+        }
+
+        @Override
+        boolean peekFoldingWhiteSpace() {
+            return super.peekFoldingWhiteSpace()
+                    || (pos.getIndex() < text.length()
+                    && text.charAt(pos.getIndex()) == '\n');
+        }
+
+    }
+
+}
diff --git a/mail/src/main/java/javax/mail/internet/MimeBodyPart.java b/mail/src/main/java/javax/mail/internet/MimeBodyPart.java
new file mode 100644
index 0000000..1dd3876
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/MimeBodyPart.java
@@ -0,0 +1,1712 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.*;
+import javax.activation.*;
+import java.io.*;
+import java.util.*;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.MimeUtil;
+import com.sun.mail.util.MessageRemovedIOException;
+import com.sun.mail.util.FolderClosedIOException;
+import com.sun.mail.util.LineOutputStream;
+
+/**
+ * This class represents a MIME body part. It implements the 
+ * <code>BodyPart</code> abstract class and the <code>MimePart</code>
+ * interface. MimeBodyParts are contained in <code>MimeMultipart</code>
+ * objects. <p>
+ *
+ * MimeBodyPart uses the <code>InternetHeaders</code> class to parse
+ * and store the headers of that body part.
+ *
+ * <hr><strong>A note on RFC 822 and MIME headers</strong><p>
+ *
+ * RFC 822 header fields <strong>must</strong> 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. <p>
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ * @author Kanwar Oberoi
+ * @see javax.mail.Part
+ * @see javax.mail.internet.MimePart
+ * @see javax.mail.internet.MimeUtility
+ */
+
+public class MimeBodyPart extends BodyPart implements MimePart {
+
+    // Paranoia:
+    // allow this last minute change to be disabled if it causes problems
+    private static final boolean setDefaultTextCharset =
+	PropUtil.getBooleanSystemProperty(
+	    "mail.mime.setdefaulttextcharset", true);
+
+    private static final boolean setContentTypeFileName =
+	PropUtil.getBooleanSystemProperty(
+	    "mail.mime.setcontenttypefilename", true);
+
+    private static final boolean encodeFileName =
+	PropUtil.getBooleanSystemProperty("mail.mime.encodefilename", false);
+    private static final boolean decodeFileName =
+	PropUtil.getBooleanSystemProperty("mail.mime.decodefilename", false);
+    private static final boolean ignoreMultipartEncoding =
+	PropUtil.getBooleanSystemProperty(
+	    "mail.mime.ignoremultipartencoding", true);
+    private static final boolean allowutf8 =
+	PropUtil.getBooleanSystemProperty("mail.mime.allowutf8", true);
+
+    // Paranoia:
+    // allow this last minute change to be disabled if it causes problems
+    static final boolean cacheMultipart = 	// accessed by MimeMessage
+	PropUtil.getBooleanSystemProperty("mail.mime.cachemultipart", true);
+
+
+    /**
+     * The DataHandler object representing this Part's content.
+     */
+    protected DataHandler dh;
+
+    /**
+     * Byte array that holds the bytes of the content of this Part.
+     */
+    protected byte[] content;
+
+    /**
+     * If the data for this body part was supplied by an
+     * InputStream that implements the SharedInputStream interface,
+     * <code>contentStream</code> is another such stream representing
+     * the content of this body part.  In this case, <code>content</code>
+     * will be null.
+     *
+     * @since	JavaMail 1.2
+     */
+    protected InputStream contentStream;
+
+    /**
+     * The InternetHeaders object that stores all the headers
+     * of this body part.
+     */
+    protected InternetHeaders headers;
+
+    /**
+     * If our content is a Multipart of Message object, we save it
+     * the first time it's created by parsing a stream so that changes
+     * to the contained objects will not be lost.
+     *
+     * If this field is not null, it's return by the {@link #getContent}
+     * method.  The {@link #getContent} method sets this field if it
+     * would return a Multipart or MimeMessage object.  This field is
+     * is cleared by the {@link #setDataHandler} method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected Object cachedContent;
+
+    /**
+     * An empty MimeBodyPart object is created.
+     * This body part maybe filled in by a client constructing a multipart
+     * message.
+     */
+    public MimeBodyPart() {
+	super();
+	headers = new InternetHeaders();
+    }
+
+    /**
+     * Constructs a MimeBodyPart by reading and parsing the data from
+     * the specified input stream. The parser consumes data till the end
+     * of the given input stream.  The input stream must start at the
+     * beginning of a valid MIME body part and must terminate at the end
+     * of that body part. <p>
+     *
+     * Note that the "boundary" string that delimits body parts must 
+     * <strong>not</strong> be included in the input stream. The intention 
+     * is that the MimeMultipart parser will extract each body part's bytes
+     * from a multipart stream and feed them into this constructor, without 
+     * the delimiter strings.
+     *
+     * @param	is	the body part Input Stream
+     * @exception	MessagingException for failures
+     */
+    public MimeBodyPart(InputStream is) throws MessagingException {
+	if (!(is instanceof ByteArrayInputStream) &&
+	    !(is instanceof BufferedInputStream) &&
+	    !(is instanceof SharedInputStream))
+	    is = new BufferedInputStream(is);
+	
+	headers = new InternetHeaders(is);
+
+	if (is instanceof SharedInputStream) {
+	    SharedInputStream sis = (SharedInputStream)is;
+	    contentStream = sis.newStream(sis.getPosition(), -1);
+	} else {
+	    try {
+		content = ASCIIUtility.getBytes(is);
+	    } catch (IOException ioex) {
+		throw new MessagingException("Error reading input stream", ioex);
+	    }
+	}
+
+    }
+
+    /**
+     * Constructs a MimeBodyPart using the given header and
+     * content bytes. <p>
+     *
+     * Used by providers.
+     *
+     * @param	headers	The header of this part
+     * @param	content	bytes representing the body of this part.
+     * @exception	MessagingException for failures
+     */
+    public MimeBodyPart(InternetHeaders headers, byte[] content) 
+			throws MessagingException {
+	super();
+	this.headers = headers;
+	this.content = content;
+    }
+
+    /**
+     * Return the size of the content of this body part 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.
+     *
+     * @return size in bytes, or -1 if not known
+     */
+    @Override
+    public int getSize() throws MessagingException {
+	if (content != null)
+	    return content.length;
+	if (contentStream != null) {
+	    try {
+		int size = contentStream.available();
+		// only believe the size if it's greate than zero, since zero
+		// is the default returned by the InputStream class itself
+		if (size > 0)
+		    return size;
+	    } catch (IOException ex) {
+		// ignore it
+	    }
+	}
+	return -1;
+    }
+
+    /**
+     * Return the number of lines for the content of this Part.
+     * 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.
+     *
+     * @return number of lines, or -1 if not known
+     */  
+    @Override
+     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
+     * body part. This value must not be null. If this field is
+     * unavailable, "text/plain" should be returned. <p>
+     *
+     * This implementation uses <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     *
+     * @return	Content-Type of this body part
+     */
+    @Override
+    public String getContentType() throws MessagingException {
+	String s = getHeader("Content-Type", null);
+	s = MimeUtil.cleanContentType(this, s);
+	if (s == null)
+	    s = "text/plain";
+	return s;
+    }
+
+    /**
+     * Is this Part of the specified MIME type?  This method
+     * compares <strong>only the <code>primaryType</code> and 
+     * <code>subType</code></strong>.
+     * The parameters of the content types are ignored. <p>
+     *
+     * For example, this method will return <code>true</code> when
+     * comparing a Part of content type <strong>"text/plain"</strong>
+     * with <strong>"text/plain; charset=foobar"</strong>. <p>
+     *
+     * If the <code>subType</code> of <code>mimeType</code> is the
+     * special character '*', then the subtype is ignored during the
+     * comparison.
+     *
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public boolean isMimeType(String mimeType) throws MessagingException {
+	return isMimeType(this, mimeType);
+    }
+
+    /**
+     * Returns the disposition from 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 <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     *
+     * @exception	MessagingException for failures
+     * @see #headers
+     */
+    @Override
+    public String getDisposition() throws MessagingException {
+	return getDisposition(this);
+    }
+
+    /**
+     * Set the disposition in the "Content-Disposition" header field
+     * of this body part.  If the disposition is null, any existing
+     * "Content-Disposition" header field is removed.
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this body part is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void setDisposition(String disposition) throws MessagingException {
+	setDisposition(this, disposition);
+    }
+
+    /**
+     * Returns the content transfer encoding from the
+     * "Content-Transfer-Encoding" header
+     * field. Returns <code>null</code> if the header is unavailable
+     * or its value is absent. <p>
+     *
+     * This implementation uses <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     *
+     * @see #headers
+     */
+    @Override
+    public String getEncoding() throws MessagingException {
+	return getEncoding(this);
+    }
+
+    /**
+     * Returns the value of the "Content-ID" header field. Returns
+     * <code>null</code> if the field is unavailable or its value is 
+     * absent. <p>
+     *
+     * This implementation uses <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     */
+    @Override
+    public String getContentID() throws MessagingException {
+	return getHeader("Content-Id", null);
+    }
+
+    /**
+     * Set the "Content-ID" header field of this body part.
+     * If the <code>cid</code> parameter is null, any existing 
+     * "Content-ID" is removed.
+     *
+     * @param	cid	the Content-ID
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this body part is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException for other failures
+     * @since		JavaMail 1.3
+     */
+    public void setContentID(String cid) throws MessagingException {
+	if (cid == null)
+	    removeHeader("Content-ID");
+	else
+	    setHeader("Content-ID", cid);
+    }
+
+    /**
+     * Return the value of the "Content-MD5" header field. Returns 
+     * <code>null</code> if this field is unavailable or its value
+     * is absent. <p>
+     *
+     * This implementation uses <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     */
+    @Override
+    public String getContentMD5() throws MessagingException {
+	return getHeader("Content-MD5", null);
+    }
+
+    /**
+     * Set the "Content-MD5" header field of this body part.
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this body part is
+     *			obtained from a READ_ONLY folder.
+     */
+    @Override
+    public void setContentMD5(String md5) throws MessagingException {
+	setHeader("Content-MD5", md5);
+    }
+
+    /**
+     * Get the languages specified in the Content-Language header
+     * of this MimePart. The Content-Language header is defined by
+     * RFC 1766. Returns <code>null</code> if this header is not
+     * available or its value is absent. <p>
+     *
+     * This implementation uses <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     */
+    @Override
+    public String[] getContentLanguage() throws MessagingException {
+	return getContentLanguage(this);
+    }
+
+    /**
+     * Set the Content-Language header of this MimePart. The
+     * Content-Language header is defined by RFC 1766.
+     *
+     * @param languages 	array of language tags
+     */
+    @Override
+    public void setContentLanguage(String[] languages)
+			throws MessagingException {
+	setContentLanguage(this, languages);
+    }
+
+    /**
+     * Returns the "Content-Description" header field of this body part.
+     * 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 <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     * 
+     * @return	content description
+     */
+    @Override
+    public String getDescription() throws MessagingException {
+	return getDescription(this);
+    }
+
+    /**
+     * Set the "Content-Description" header field for this body part.
+     * If the description parameter is <code>null</code>, 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
+     * @exception	IllegalStateException if this body part is
+     *			obtained from a READ_ONLY folder.
+     * @exception       MessagingException otherwise; an
+     *                  UnsupportedEncodingException may be included
+     *                  in the exception chain if the charset
+     *                  conversion fails.
+     */
+    @Override
+    public void setDescription(String description) throws MessagingException {
+	setDescription(description, null);
+    }
+
+    /**
+     * Set the "Content-Description" header field for this body part.
+     * If the description parameter is <code>null</code>, 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	Description
+     * @param	charset		Charset for encoding
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this body part is
+     *			obtained from a READ_ONLY folder.
+     * @exception       MessagingException otherwise; an
+     *                  UnsupportedEncodingException may be included
+     *                  in the exception chain if the charset
+     *                  conversion fails.
+     */
+    public void setDescription(String description, String charset) 
+		throws MessagingException {
+	setDescription(this, description, charset);
+    }
+
+    /**
+     * Get the filename associated with this body part. <p>
+     *
+     * Returns the value of the "filename" parameter from the
+     * "Content-Disposition" header field of this body part. If its
+     * not available, returns the value of the "name" parameter from
+     * the "Content-Type" header field of this body part.
+     * Returns <code>null</code> if both are absent. <p>
+     *
+     * If the <code>mail.mime.decodefilename</code> System property
+     * is set to true, the {@link MimeUtility#decodeText
+     * MimeUtility.decodeText} method will be used to decode the
+     * filename.  While such encoding is not supported by the MIME
+     * spec, many mailers use this technique to support non-ASCII
+     * characters in filenames.  The default value of this property
+     * is false.
+     *
+     * @return	filename
+     */
+    @Override
+    public String getFileName() throws MessagingException {
+	return getFileName(this);
+    }
+
+    /**
+     * Set the filename associated with this body part, if possible. <p>
+     *
+     * Sets the "filename" parameter of the "Content-Disposition"
+     * header field of this body part.  For compatibility with older
+     * mailers, the "name" parameter of the "Content-Type" header is
+     * also set. <p>
+     *
+     * If the <code>mail.mime.encodefilename</code> System property
+     * is set to true, the {@link MimeUtility#encodeText
+     * MimeUtility.encodeText} method will be used to encode the
+     * filename.  While such encoding is not supported by the MIME
+     * spec, many mailers use this technique to support non-ASCII
+     * characters in filenames.  The default value of this property
+     * is false.
+     *
+     * @param	filename	the file name
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this body part is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void setFileName(String filename) throws MessagingException {
+	setFileName(this, filename);
+    }
+
+    /**
+     * Return a decoded input stream for this body part's "content". <p>
+     *
+     * This implementation obtains the input stream from the DataHandler.
+     * That is, it invokes getDataHandler().getInputStream();
+     *
+     * @return 		an InputStream
+     * @exception       IOException this is typically thrown by the
+     *			DataHandler. Refer to the documentation for
+     *			javax.activation.DataHandler for more details.
+     * @exception	MessagingException for other failures
+     *
+     * @see	#getContentStream
+     * @see 	javax.activation.DataHandler#getInputStream
+     */
+    @Override
+    public InputStream getInputStream() 
+		throws IOException, MessagingException {
+	return getDataHandler().getInputStream();
+    }
+
+   /**
+     * Produce the raw bytes of the content. This method is used
+     * when creating a DataHandler object for the content. Subclasses
+     * that can provide a separate input stream for just the Part
+     * content might want to override this method. <p>
+     * 
+     * @return	an InputStream containing the raw bytes
+     * @exception	MessagingException for failures
+     * @see #content
+     * @see MimeMessage#getContentStream
+     */
+    protected InputStream getContentStream() throws MessagingException {
+	if (contentStream != null)
+	    return ((SharedInputStream)contentStream).newStream(0, -1);
+	if (content != null)
+	    return new ByteArrayInputStream(content);
+	
+	throw new MessagingException("No MimeBodyPart 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.
+     *
+     * @return	an InputStream containing the raw bytes
+     * @exception	MessagingException for failures
+     * @see	#getInputStream
+     * @see	#getContentStream
+     * @since	JavaMail 1.2
+     */
+    public InputStream getRawInputStream() throws MessagingException {
+	return getContentStream();
+    }
+
+    /**
+     * Return a DataHandler for this body part's content. <p>
+     *
+     * The implementation provided here works just like the
+     * the implementation in MimeMessage.
+     * @see	MimeMessage#getDataHandler
+     */  
+    @Override
+    public DataHandler getDataHandler() throws MessagingException {
+	if (dh == null)
+	    dh = new MimePartDataHandler(this);
+	return dh;
+    }
+
+    /**
+     * Return the content as a Java object. The type of the object
+     * returned is of course 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"
+     * content 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 getDataHandler().getContent();
+     * If the content is a Multipart or Message object and was created by
+     * parsing a stream, the object is cached and returned in subsequent
+     * calls so that modifications to the content will not be lost.
+     *
+     * @return          Object
+     * @exception       IOException this is typically thrown by the
+     *			DataHandler. Refer to the documentation for
+     *			javax.activation.DataHandler for more details.
+     * @exception       MessagingException for other failures
+     */  
+    @Override
+    public Object getContent() throws IOException, MessagingException {
+	if (cachedContent != null)
+	    return cachedContent;
+	Object c;
+	try {
+	    c = getDataHandler().getContent();
+	} catch (FolderClosedIOException fex) {
+	    throw new FolderClosedException(fex.getFolder(), fex.getMessage());
+	} catch (MessageRemovedIOException mex) {
+	    throw new MessageRemovedException(mex.getMessage());
+	}
+	if (cacheMultipart &&
+		(c instanceof Multipart || c instanceof Message) &&
+		(content != null || contentStream != null)) {
+	    cachedContent = c;
+	    /*
+	     * We may abandon the input stream so make sure
+	     * the MimeMultipart has consumed the stream.
+	     */
+	    if (c instanceof MimeMultipart)
+		((MimeMultipart)c).parse();
+	}
+	return c;
+    }
+
+    /**
+     * This method provides the mechanism to set this body 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
+     * @exception	IllegalStateException if this body part is
+     *			obtained from a READ_ONLY folder.
+     */                 
+    @Override
+    public void setDataHandler(DataHandler dh) 
+		throws MessagingException {
+	this.dh = dh;
+	cachedContent = null;
+	MimeBodyPart.invalidateContentHeaders(this);
+    }
+
+    /**
+     * A convenience method for setting this body part'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.
+     * That is, 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 body part is
+     *			obtained from a READ_ONLY folder.
+     */
+    @Override
+    public void setContent(Object o, String type) 
+		throws MessagingException {
+	if (o instanceof Multipart) {
+	    setContent((Multipart)o);
+	} else {
+	    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
+     * <code>text</code> 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 charset parameter.
+     *
+     * @param	text	the text content to set
+     * @exception	MessagingException	if an error occurs
+     * @see	#setText(String text, String charset)
+     */
+    @Override
+    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.
+     *
+     * @param	text	the text content to set
+     * @param	charset	the charset to use for the text
+     * @exception	MessagingException	if an error occurs
+     */
+    @Override
+    public void setText(String text, String charset)
+			throws MessagingException {
+	setText(this, text, charset, "plain");
+    }
+
+    /**
+     * Convenience method that sets the given String as this part's
+     * content, with a primary MIME type of "text" and the specified
+     * MIME subtype.  The given Unicode string will be charset-encoded
+     * using the specified charset. The charset is also used to set
+     * the "charset" parameter.
+     *
+     * @param	text	the text content to set
+     * @param	charset	the charset to use for the text
+     * @param	subtype	the MIME subtype to use (e.g., "html")
+     * @exception	MessagingException	if an error occurs
+     * @since	JavaMail 1.4
+     */
+    @Override
+    public void setText(String text, String charset, String subtype)
+                        throws MessagingException {
+	setText(this, text, charset, subtype);
+    }
+ 
+    /**
+     * This method sets the body part'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 body part is
+     *			obtained from a READ_ONLY folder.
+     */
+    @Override
+    public void setContent(Multipart mp) throws MessagingException {
+	setDataHandler(new DataHandler(mp, mp.getContentType()));
+	mp.setParent(this);
+    }
+
+    /**
+     * Use the specified file to provide the data for this part.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The encoding will be chosen appropriately for the
+     * file data.  The disposition of this part is set to
+     * {@link Part#ATTACHMENT Part.ATTACHMENT}.
+     *
+     * @param		file		the File object to attach
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void attachFile(File file) throws IOException, MessagingException {
+	FileDataSource fds = new FileDataSource(file);   	
+	this.setDataHandler(new DataHandler(fds));
+	this.setFileName(fds.getName());
+	this.setDisposition(ATTACHMENT);
+    }
+
+    /**
+     * Use the specified file to provide the data for this part.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The encoding will be chosen appropriately for the
+     * file data.
+     *
+     * @param		file		the name of the file to attach
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void attachFile(String file) throws IOException, MessagingException {
+    	File f = new File(file);
+    	attachFile(f);
+    }
+
+    /**
+     * Use the specified file with the specified Content-Type and
+     * Content-Transfer-Encoding to provide the data for this part.
+     * If contentType or encoding are null, appropriate values will
+     * be chosen.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The disposition of this part is set to
+     * {@link Part#ATTACHMENT Part.ATTACHMENT}.
+     *
+     * @param		file		the File object to attach
+     * @param		contentType	the Content-Type, or null
+     * @param		encoding	the Content-Transfer-Encoding, or null
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.5
+     */
+    public void attachFile(File file, String contentType, String encoding)
+				throws IOException, MessagingException {
+	DataSource fds = new EncodedFileDataSource(file, contentType, encoding);
+	this.setDataHandler(new DataHandler(fds));
+	this.setFileName(fds.getName());
+	this.setDisposition(ATTACHMENT);
+    }
+
+    /**
+     * Use the specified file with the specified Content-Type and
+     * Content-Transfer-Encoding to provide the data for this part.
+     * If contentType or encoding are null, appropriate values will
+     * be chosen.
+     * The simple file name is used as the file name for this
+     * part and the data in the file is used as the data for this
+     * part.  The disposition of this part is set to
+     * {@link Part#ATTACHMENT Part.ATTACHMENT}.
+     *
+     * @param		file		the name of the file
+     * @param		contentType	the Content-Type, or null
+     * @param		encoding	the Content-Transfer-Encoding, or null
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.5
+     */
+    public void attachFile(String file, String contentType, String encoding)
+				throws IOException, MessagingException {
+	attachFile(new File(file), contentType, encoding);
+    }
+
+    /**
+     * A FileDataSource class that allows us to specify the
+     * Content-Type and Content-Transfer-Encoding.
+     */
+    private static class EncodedFileDataSource extends FileDataSource
+					implements EncodingAware {
+	private String contentType;
+	private String encoding;
+
+	public EncodedFileDataSource(File file, String contentType,
+						String encoding) {
+	    super(file);
+	    this.contentType = contentType;
+	    this.encoding = encoding;
+	}
+
+	// overrides DataSource.getContentType()
+	@Override
+	public String getContentType() {
+	    return contentType != null ? contentType : super.getContentType();
+	}
+
+	// implements EncodingAware.getEncoding()
+	@Override
+	public String getEncoding() {
+	    return encoding;
+	}
+    }
+
+    /**
+     * Save the contents of this part in the specified file.  The content
+     * is decoded and saved, without any of the MIME headers.
+     *
+     * @param		file		the File object to write to
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void saveFile(File file) throws IOException, MessagingException {
+    	OutputStream out = null;
+        InputStream in = null;
+        try {
+	    out = new BufferedOutputStream(new FileOutputStream(file));
+	    in = this.getInputStream();
+	    byte[] buf = new byte[8192];
+	    int len;
+	    while ((len = in.read(buf)) > 0)
+		out.write(buf, 0, len); 
+        } finally {
+	    // close streams, but don't mask original exception, if any
+	    try {
+		if (in != null)
+		    in.close();
+	    } catch (IOException ex) { }
+	    try {
+		if (out != null)
+		    out.close();
+	    } catch (IOException ex) { }
+        }
+    }
+
+    /**
+     * Save the contents of this part in the specified file.  The content
+     * is decoded and saved, without any of the MIME headers.
+     *
+     * @param		file		the name of the file to write to
+     * @exception	IOException	errors related to accessing the file
+     * @exception	MessagingException	message related errors
+     * @since		JavaMail 1.4
+     */
+    public void saveFile(String file) throws IOException, MessagingException {
+    	File f = new File(file);
+    	saveFile(f);
+    }
+
+    /**
+     * Output the body part as an RFC 822 format stream.
+     *
+     * @exception IOException	if an error occurs writing to the
+     *				stream or if an error is generated
+     *				by the javax.activation layer.
+     * @exception MessagingException for other failures
+     * @see javax.activation.DataHandler#writeTo
+     */
+    @Override
+    public void writeTo(OutputStream os)
+				throws IOException, MessagingException {
+	writeTo(this, os, null);
+    }
+
+    /**
+     * 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.
+     *
+     * @param   name    name of header
+     * @return  array of headers
+     * @see     javax.mail.internet.MimeUtility
+     */  
+    @Override
+    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 <code>null</code>, only the first header is 
+     * returned.
+     *
+     * @param name		the name of this header
+     * @param delimiter		delimiter between fields in returned string
+     * @return			the value fields for all headers with 
+     *				this name
+     * @exception       	MessagingException for failures
+     */
+    @Override
+    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 be encoded as per the
+     * rules of RFC 2047.
+     *
+     * @param   name    header name
+     * @param   value   header value
+     * @see     javax.mail.internet.MimeUtility
+     */
+    @Override
+    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 be encoded as per the rules of RFC 2047.
+     *
+     * @param   name    header name
+     * @param   value   header value
+     * @see     javax.mail.internet.MimeUtility
+     */
+    @Override
+    public void addHeader(String name, String value)
+                                throws MessagingException {
+	headers.addHeader(name, value);    
+    }
+
+    /**
+     * Remove all headers with this name.
+     */
+    @Override
+    public void removeHeader(String name) throws MessagingException {
+	headers.removeHeader(name);
+    }
+ 
+    /**
+     * Return all the headers from this Message as an Enumeration of
+     * Header objects.
+     */
+    @Override
+    public Enumeration<Header> getAllHeaders() throws MessagingException {
+	return headers.getAllHeaders();
+    }
+   
+    /**
+     * Return matching headers from this Message as an Enumeration of
+     * Header objects.
+     */
+    @Override
+    public Enumeration<Header> getMatchingHeaders(String[] names)
+                        throws MessagingException {
+	return headers.getMatchingHeaders(names);
+    }
+ 
+    /**
+     * Return non-matching headers from this Message as an
+     * Enumeration of Header objects.
+     */
+    @Override
+    public Enumeration<Header> getNonMatchingHeaders(String[] names)
+                        throws MessagingException {
+	return headers.getNonMatchingHeaders(names);
+    }
+      
+    /**
+     * Add a header line to this body part
+     */
+    @Override
+    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.
+     */
+    @Override
+    public Enumeration<String> 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.
+     */
+    @Override
+    public Enumeration<String> 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.
+     */
+    @Override
+    public Enumeration<String> getNonMatchingHeaderLines(String[] names)  
+                                        throws MessagingException {
+	return headers.getNonMatchingHeaderLines(names);
+    }
+
+    /**
+     * Examine the content of this body part and update the appropriate
+     * MIME headers.  Typical headers that get set here are
+     * <code>Content-Type</code> and <code>Content-Transfer-Encoding</code>.
+     * Headers might need to be updated in two cases:
+     *
+     * <br>
+     * - A message being crafted by a mail application will certainly
+     * need to activate this method at some point to fill up its internal
+     * headers.
+     *
+     * <br>
+     * - A message read in from a Store will have obtained
+     * all its headers from the store, and so doesn't need this.
+     * However, if this message is editable and if any edits have
+     * been made to either the content or message structure, we might
+     * need to resync our headers.
+     *
+     * <br>
+     * In both cases this method is typically called by the
+     * <code>Message.saveChanges</code> method. <p>
+     *
+     * If the {@link #cachedContent} field is not null (that is,
+     * it references a Multipart or Message object), then
+     * that object is used to set a new DataHandler, any
+     * stream data used to create this object is discarded,
+     * and the {@link #cachedContent} field is cleared.
+     *
+     * @exception	MessagingException for failures
+     */
+    protected void updateHeaders() throws MessagingException {
+	updateHeaders(this);
+	/*
+	 * If we've cached a Multipart or Message object then
+	 * we're now committed to using this instance of the
+	 * object and we discard any stream data used to create
+	 * this object.
+	 */
+	if (cachedContent != null) {
+	    dh = new DataHandler(cachedContent, getContentType());
+	    cachedContent = null;
+	    content = null;
+	    if (contentStream != null) {
+		try {
+		    contentStream.close();
+		} catch (IOException ioex) { }	// nothing to do
+	    }
+	    contentStream = null;
+	}
+    }
+
+    /////////////////////////////////////////////////////////////
+    // Package private convenience methods to share code among //
+    // MimeMessage and MimeBodyPart                            //
+    /////////////////////////////////////////////////////////////
+
+    static boolean isMimeType(MimePart part, String mimeType)
+				throws MessagingException {
+	// XXX - lots of room for optimization here!
+	String type = part.getContentType();
+	try {
+	    return new ContentType(type).match(mimeType);
+	} catch (ParseException ex) {
+	    // we only need the type and subtype so throw away the rest
+	    try {
+		int i = type.indexOf(';');
+		if (i > 0)
+		    return new ContentType(type.substring(0, i)).match(mimeType);
+	    } catch (ParseException pex2) {
+	    }
+	    return type.equalsIgnoreCase(mimeType);
+	}
+    }
+
+    static void setText(MimePart part, String text, String charset,
+			String subtype) throws MessagingException {
+	if (charset == null) {
+	    if (MimeUtility.checkAscii(text) != MimeUtility.ALL_ASCII)
+		charset = MimeUtility.getDefaultMIMECharset();
+	    else
+		charset = "us-ascii";
+	}
+	// XXX - should at least ensure that subtype is an atom
+	part.setContent(text, "text/" + subtype + "; charset=" +
+			MimeUtility.quote(charset, HeaderTokenizer.MIME));
+    }
+
+    static String getDisposition(MimePart part) throws MessagingException {
+	String s = part.getHeader("Content-Disposition", null);
+
+	if (s == null)
+	    return null;
+
+	ContentDisposition cd = new ContentDisposition(s);
+	return cd.getDisposition();
+    }
+
+    static void setDisposition(MimePart part, String disposition)
+			throws MessagingException {
+	if (disposition == null)
+	    part.removeHeader("Content-Disposition");
+	else {
+	    String s = part.getHeader("Content-Disposition", null);
+	    if (s != null) { 
+		/* A Content-Disposition header already exists ..
+		 *
+		 * Override disposition, but attempt to retain 
+		 * existing disposition parameters
+		 */
+		ContentDisposition cd = new ContentDisposition(s);
+		cd.setDisposition(disposition);
+		disposition = cd.toString();
+	    }
+	    part.setHeader("Content-Disposition", disposition);
+	}
+    }
+
+    static String getDescription(MimePart part) 
+			throws MessagingException {
+	String rawvalue = part.getHeader("Content-Description", null);
+
+	if (rawvalue == null)
+	    return null;
+
+	try {
+	    return MimeUtility.decodeText(MimeUtility.unfold(rawvalue));
+	} catch (UnsupportedEncodingException ex) {
+	    return rawvalue;
+	}
+    }
+
+    static void 
+    setDescription(MimePart part, String description, String charset) 
+			throws MessagingException {
+	if (description == null) {
+	    part.removeHeader("Content-Description");
+	    return;
+	}
+	
+	try {
+	    part.setHeader("Content-Description", MimeUtility.fold(21,
+		MimeUtility.encodeText(description, charset, null)));
+	} catch (UnsupportedEncodingException uex) {
+	    throw new MessagingException("Encoding error", uex);
+	}
+    }
+
+    static String getFileName(MimePart part) throws MessagingException {
+	String filename = null;
+	String s = part.getHeader("Content-Disposition", null);
+
+	if (s != null) {
+	    // Parse the header ..
+	    ContentDisposition cd = new ContentDisposition(s);
+	    filename = cd.getParameter("filename");
+	}
+	if (filename == null) {
+	    // Still no filename ? Try the "name" ContentType parameter
+	    s = part.getHeader("Content-Type", null);
+	    s = MimeUtil.cleanContentType(part, s);
+	    if (s != null) {
+		try {
+		    ContentType ct = new ContentType(s);
+		    filename = ct.getParameter("name");
+		} catch (ParseException pex) { }	// ignore it
+	    }
+	}
+	if (decodeFileName && filename != null) {
+	    try {
+		filename = MimeUtility.decodeText(filename);
+	    } catch (UnsupportedEncodingException ex) {
+		throw new MessagingException("Can't decode filename", ex);
+	    }
+	}
+	return filename;
+    }
+
+    static void setFileName(MimePart part, String name) 
+		throws MessagingException {
+	if (encodeFileName && name != null) {
+	    try {
+		name = MimeUtility.encodeText(name);
+	    } catch (UnsupportedEncodingException ex) {
+		throw new MessagingException("Can't encode filename", ex);
+	    }
+	}
+
+	// Set the Content-Disposition "filename" parameter
+	String s = part.getHeader("Content-Disposition", null);
+	ContentDisposition cd = 
+		new ContentDisposition(s == null ? Part.ATTACHMENT : s);
+	// ensure that the filename is encoded if necessary
+	String charset = MimeUtility.getDefaultMIMECharset();
+	ParameterList p = cd.getParameterList();
+	if (p == null) {
+	    p = new ParameterList();
+	    cd.setParameterList(p);
+	}
+	if (encodeFileName)
+	    p.setLiteral("filename", name);
+	else
+	    p.set("filename", name, charset);
+	part.setHeader("Content-Disposition", cd.toString());
+
+	/*
+	 * Also attempt to set the Content-Type "name" parameter,
+	 * to satisfy ancient MUAs.  XXX - This is not RFC compliant.
+	 */
+	if (setContentTypeFileName) {
+	    s = part.getHeader("Content-Type", null);
+	    s = MimeUtil.cleanContentType(part, s);
+	    if (s != null) {
+		try {
+		    ContentType cType = new ContentType(s);
+		    // ensure that the filename is encoded if necessary
+		    p = cType.getParameterList();
+		    if (p == null) {
+			p = new ParameterList();
+			cType.setParameterList(p);
+		    }
+		    if (encodeFileName)
+			p.setLiteral("name", name);
+		    else
+			p.set("name", name, charset);
+		    part.setHeader("Content-Type", cType.toString());
+		} catch (ParseException pex) { }	// ignore it
+	    }
+	}
+    }
+
+    static String[] getContentLanguage(MimePart part) 
+		throws MessagingException {
+	String s = part.getHeader("Content-Language", null);
+
+	if (s == null)
+	    return null;
+
+	// Tokenize the header to obtain the Language-tags (skip comments)
+	HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
+	List<String> v = new ArrayList<>();
+
+	HeaderTokenizer.Token tk;
+	int tkType;
+
+	while (true) {
+	    tk = h.next(); // get a language-tag
+	    tkType = tk.getType();
+	    if (tkType == HeaderTokenizer.Token.EOF)
+		break; // done
+	    else if (tkType == HeaderTokenizer.Token.ATOM)
+		v.add(tk.getValue());
+	    else // invalid token, skip it.
+		continue;
+	}
+
+	if (v.isEmpty())
+	    return null;
+
+	String[] language = new String[v.size()];
+	v.toArray(language);
+	return language;	
+    }
+
+    static void setContentLanguage(MimePart part, String[] languages)
+			throws MessagingException {
+	StringBuilder sb = new StringBuilder(languages[0]);
+	int len = "Content-Language".length() + 2 + languages[0].length();
+	for (int i = 1; i < languages.length; i++) {
+	    sb.append(',');
+	    len++;
+	    if (len > 76) {
+		sb.append("\r\n\t");
+		len = 8;
+	    }
+	    sb.append(languages[i]);
+	    len += languages[i].length();
+	}
+	part.setHeader("Content-Language", sb.toString());
+    }
+
+    static String getEncoding(MimePart part) throws MessagingException {
+	String s = part.getHeader("Content-Transfer-Encoding", null);
+
+	if (s == null)
+	    return null;
+
+	s = s.trim();	// get rid of trailing spaces
+	if (s.length() == 0)
+	    return null;
+	// quick check for known values to avoid unnecessary use
+	// of tokenizer.
+	if (s.equalsIgnoreCase("7bit") || s.equalsIgnoreCase("8bit") ||
+		s.equalsIgnoreCase("quoted-printable") ||
+		s.equalsIgnoreCase("binary") ||
+		s.equalsIgnoreCase("base64"))
+	    return s;
+
+	// Tokenize the header to obtain the encoding (skip comments)
+	HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
+
+	HeaderTokenizer.Token tk;
+	int tkType;
+
+	for (;;) {
+	    tk = h.next(); // get a token
+	    tkType = tk.getType();
+	    if (tkType == HeaderTokenizer.Token.EOF)
+		break; // done
+	    else if (tkType == HeaderTokenizer.Token.ATOM)
+		return tk.getValue();
+	    else // invalid token, skip it.
+		continue;
+	}
+	return s;
+    }
+
+    static void setEncoding(MimePart part, String encoding)
+				throws MessagingException {
+	part.setHeader("Content-Transfer-Encoding", encoding);
+    }
+
+    /**
+     * Restrict the encoding to values allowed for the
+     * Content-Type of the specified MimePart.  Returns
+     * either the original encoding or null.
+     */
+    static String restrictEncoding(MimePart part, String encoding)
+				throws MessagingException {
+	if (!ignoreMultipartEncoding || encoding == null)
+	    return encoding;
+
+	if (encoding.equalsIgnoreCase("7bit") ||
+		encoding.equalsIgnoreCase("8bit") ||
+		encoding.equalsIgnoreCase("binary"))
+	    return encoding;	// these encodings are always valid
+
+	String type = part.getContentType();
+	if (type == null)
+	    return encoding;
+
+	try {
+	    /*
+	     * multipart and message types aren't allowed to have
+	     * encodings except for the three mentioned above.
+	     * If it's one of these types, ignore the encoding.
+	     */
+	    ContentType cType = new ContentType(type);
+	    if (cType.match("multipart/*"))
+		return null;
+	    if (cType.match("message/*") &&
+		    !PropUtil.getBooleanSystemProperty(
+			"mail.mime.allowencodedmessages", false))
+		return null;
+	} catch (ParseException pex) {
+	    // ignore it
+	}
+	return encoding;
+    }
+
+    static void updateHeaders(MimePart part) throws MessagingException {
+	DataHandler dh = part.getDataHandler();
+	if (dh == null) // Huh ?
+	    return;
+
+	try {
+	    String type = dh.getContentType();
+	    boolean composite = false;
+	    boolean needCTHeader = part.getHeader("Content-Type") == null;
+
+	    ContentType cType = new ContentType(type);
+
+	    /*
+	     * If this is a multipart, give sub-parts a chance to
+	     * update their headers.  Even though the data for this
+	     * multipart may have come from a stream, one of the
+	     * sub-parts may have been updated.
+	     */
+	    if (cType.match("multipart/*")) {
+		// If multipart, recurse
+		composite = true;
+		Object o;
+		if (part instanceof MimeBodyPart) {
+		    MimeBodyPart mbp = (MimeBodyPart)part;
+		    o = mbp.cachedContent != null ?
+				mbp.cachedContent : dh.getContent();
+		} else if (part instanceof MimeMessage) {
+		    MimeMessage msg = (MimeMessage)part;
+		    o = msg.cachedContent != null ?
+				msg.cachedContent : dh.getContent();
+		} else
+		    o = dh.getContent();
+		if (o instanceof MimeMultipart)
+		    ((MimeMultipart)o).updateHeaders();
+		else
+		    throw new MessagingException("MIME part of type \"" +
+			type + "\" contains object of type " +
+			o.getClass().getName() + " instead of MimeMultipart");
+	    } else if (cType.match("message/rfc822")) {
+		composite = true;
+		// XXX - call MimeMessage.updateHeaders()?
+	    }
+
+	    /*
+	     * If this is our own MimePartDataHandler, we can't update any
+	     * of the headers.
+	     *
+	     * If this is a MimePartDataHandler coming from another part,
+	     * we need to copy over the content headers from the other part.
+	     * Note that the MimePartDataHandler still refers to the original
+	     * data and the original MimePart.
+	     */
+	    if (dh instanceof MimePartDataHandler) {
+		MimePartDataHandler mdh = (MimePartDataHandler)dh;
+		MimePart mpart = mdh.getPart();
+		if (mpart != part) {
+		    if (needCTHeader)
+			part.setHeader("Content-Type", mpart.getContentType());
+		    // XXX - can't change the encoding of the data from the
+		    // other part without decoding and reencoding it, so
+		    // we just force it to match the original, but if the
+		    // original has no encoding we'll consider reencoding it
+		    String enc = mpart.getEncoding();
+		    if (enc != null) {
+			setEncoding(part, enc);
+			return;
+		    }
+		} else
+		    return;
+	    }
+
+	    // Content-Transfer-Encoding, but only if we don't
+	    // already have one
+	    if (!composite) {	// not allowed on composite parts
+		if (part.getHeader("Content-Transfer-Encoding") == null)
+		    setEncoding(part, MimeUtility.getEncoding(dh));
+
+		if (needCTHeader && setDefaultTextCharset &&
+			cType.match("text/*") &&
+			cType.getParameter("charset") == null) {
+		    /*
+		     * Set a default charset for text parts.
+		     * We really should examine the data to determine
+		     * whether or not it's all ASCII, but that's too
+		     * expensive so we make an assumption:  If we
+		     * chose 7bit encoding for this data, it's probably
+		     * ASCII.  (MimeUtility.getEncoding will choose
+		     * 7bit only in this case, but someone might've
+		     * set the Content-Transfer-Encoding header manually.)
+		     */
+		    String charset;
+		    String enc = part.getEncoding();
+		    if (enc != null && enc.equalsIgnoreCase("7bit"))
+			charset = "us-ascii";
+		    else
+			charset = MimeUtility.getDefaultMIMECharset();
+		    cType.setParameter("charset", charset);
+		    type = cType.toString();
+		}
+	    }
+
+	    // Now, let's update our own headers ...
+
+	    // Content-type, but only if we don't already have one
+	    if (needCTHeader) {
+		/*
+		 * Pull out "filename" from Content-Disposition, and
+		 * use that to set the "name" parameter. This is to
+		 * satisfy older MUAs (DtMail, Roam and probably
+		 * a bunch of others).
+		 */
+		if (setContentTypeFileName) {
+		    String s = part.getHeader("Content-Disposition", null);
+		    if (s != null) {
+			// Parse the header ..
+			ContentDisposition cd = new ContentDisposition(s);
+			String filename = cd.getParameter("filename");
+			if (filename != null) {
+			    ParameterList p = cType.getParameterList();
+			    if (p == null) {
+				p = new ParameterList();
+				cType.setParameterList(p);
+			    }
+			    if (encodeFileName)
+				p.setLiteral("name",
+					MimeUtility.encodeText(filename));
+			    else
+				p.set("name", filename,
+					MimeUtility.getDefaultMIMECharset());
+			    type = cType.toString();
+			}
+		    }
+		}
+		
+		part.setHeader("Content-Type", type);
+	    }
+	} catch (IOException ex) {
+	    throw new MessagingException("IOException updating headers", ex);
+	}
+    }
+
+    static void invalidateContentHeaders(MimePart part)
+					throws MessagingException {
+	part.removeHeader("Content-Type");
+	part.removeHeader("Content-Transfer-Encoding");
+    }
+    
+    static void writeTo(MimePart part, OutputStream os, String[] ignoreList)
+			throws IOException, MessagingException {
+
+	// see if we already have a LOS
+	LineOutputStream los = null;
+	if (os instanceof LineOutputStream) {
+	    los = (LineOutputStream) os;
+	} else {
+	    los = new LineOutputStream(os, allowutf8);
+	}
+
+	// First, write out the header
+	Enumeration<String> hdrLines
+		= part.getNonMatchingHeaderLines(ignoreList);
+	while (hdrLines.hasMoreElements())
+	    los.writeln(hdrLines.nextElement());
+
+	// The CRLF separator between header and content
+	los.writeln();
+
+	// Finally, the content. Encode if required.
+	// XXX: May need to account for ESMTP ?
+	InputStream is = null;
+	byte[] buf = null;
+	try {
+	    /*
+	     * If the data for this part comes from a stream,
+	     * and is already encoded,
+	     * just copy it to the output stream without decoding
+	     * and reencoding it.
+	     */
+	    DataHandler dh = part.getDataHandler();
+	    if (dh instanceof MimePartDataHandler) {
+		MimePartDataHandler mpdh = (MimePartDataHandler)dh;
+		MimePart mpart = mpdh.getPart();
+		if (mpart.getEncoding() != null)
+		    is = mpdh.getContentStream();
+	    }
+	    if (is != null) {
+		// now copy the data to the output stream
+		buf = new byte[8192];
+		int len;
+		while ((len = is.read(buf)) > 0)
+		    os.write(buf, 0, len);
+	    } else {
+		os = MimeUtility.encode(os,
+			restrictEncoding(part, part.getEncoding()));
+		part.getDataHandler().writeTo(os);
+	    }
+	} finally {
+	    if (is != null)
+		is.close();
+	    buf = null;
+	}
+	os.flush(); // Needed to complete encoding
+    }
+
+    /**
+     * A special DataHandler used only as a marker to indicate that
+     * the source of the data is a MimePart (that is, a byte array
+     * or a stream).  This prevents updateHeaders from trying to
+     * change the headers for such data.  In particular, the original
+     * Content-Transfer-Encoding for the data must be preserved.
+     * Otherwise the data would need to be decoded and reencoded.
+     */
+    static class MimePartDataHandler extends DataHandler {
+	MimePart part;
+	public MimePartDataHandler(MimePart part) {
+	    super(new MimePartDataSource(part));
+	    this.part = part;
+	}
+
+	InputStream getContentStream() throws MessagingException {
+	    InputStream is = null;
+
+	    if (part instanceof MimeBodyPart) {
+		MimeBodyPart mbp = (MimeBodyPart)part;
+		is = mbp.getContentStream();
+	    } else if (part instanceof MimeMessage) {
+		MimeMessage msg = (MimeMessage)part;
+		is = msg.getContentStream();
+	    }
+	    return is;
+	}
+
+	MimePart getPart() {
+	    return part;
+	}
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/MimeMessage.java b/mail/src/main/java/javax/mail/internet/MimeMessage.java
new file mode 100644
index 0000000..bc5a8f3
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/MimeMessage.java
@@ -0,0 +1,2299 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.*;
+import javax.activation.*;
+import java.lang.*;
+import java.io.*;
+import java.util.*;
+import java.text.ParseException;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.MimeUtil;
+import com.sun.mail.util.MessageRemovedIOException;
+import com.sun.mail.util.FolderClosedIOException;
+import com.sun.mail.util.LineOutputStream;
+import javax.mail.util.SharedByteArrayInputStream;
+
+/**
+ * This class represents a MIME style email message. It implements
+ * the <code>Message</code> abstract class and the <code>MimePart</code>
+ * 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 <code>InternetHeaders</code> class to parse and
+ * store the top level RFC 822 headers of a message. <p>
+ *
+ * The <code>mail.mime.address.strict</code> session property controls
+ * the parsing of address headers.  By default, strict parsing of address
+ * headers is done.  If this property is set to <code>"false"</code>,
+ * strict parsing is not done and many illegal addresses that sometimes
+ * occur in real messages are allowed.  See the <code>InternetAddress</code>
+ * class for details.
+ *
+ * <hr><strong>A note on RFC 822 and MIME headers</strong><p>
+ *
+ * RFC 822 header fields <strong>must</strong> 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. <p>
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ * @author Max Spivak
+ * @author Kanwar Oberoi
+ * @see	javax.mail.internet.MimeUtility
+ * @see	javax.mail.Part
+ * @see	javax.mail.Message
+ * @see	javax.mail.internet.MimePart
+ * @see	javax.mail.internet.InternetAddress
+ */
+
+public class MimeMessage extends Message implements MimePart {
+
+    /**
+     * 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,
+     * <code>contentStream</code> is another such stream representing
+     * the content of this message.  In this case, <code>content</code>
+     * will be null.
+     *
+     * @since	JavaMail 1.2
+     */
+    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
+     * <code>content</code> 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.
+     *
+     * @since	JavaMail 1.2
+     */
+    protected boolean modified = false;
+
+    /**
+     * Does the <code>saveChanges</code> 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.
+     *
+     * @since	JavaMail 1.2
+     */
+    protected boolean saved = false;
+
+    /**
+     * If our content is a Multipart or Message object, we save it
+     * the first time it's created by parsing a stream so that changes
+     * to the contained objects will not be lost. <p>
+     *
+     * If this field is not null, it's return by the {@link #getContent}
+     * method.  The {@link #getContent} method sets this field if it
+     * would return a Multipart or MimeMessage object.  This field is
+     * is cleared by the {@link #setDataHandler} method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected Object cachedContent;
+
+    // Used to parse dates
+    private static final MailDateFormat mailDateFormat = new MailDateFormat();
+
+    // Should addresses in headers be parsed in "strict" mode?
+    private boolean strict = true;
+    // Is UTF-8 allowed in headers?
+    private boolean allowutf8 = false;
+
+    /**
+     * Default constructor. An empty message object is created.
+     * The <code>headers</code> field is set to an empty InternetHeaders
+     * object. The <code>flags</code> field is set to an empty Flags
+     * object. The <code>modified</code> flag is set to true.
+     *
+     * @param	session	the Sesssion
+     */
+    public MimeMessage(Session session) {
+	super(session);
+	modified = true;
+	headers = new InternetHeaders();
+	flags = new Flags();	// empty flags object
+	initStrict();
+    }
+
+    /**
+     * 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>
+     *
+     * The input stream contains an entire MIME formatted message with
+     * headers and data.
+     *
+     * @param session	Session object for this message
+     * @param is	the message input stream
+     * @exception	MessagingException for failures
+     */
+    public MimeMessage(Session session, InputStream is) 
+			throws MessagingException {
+	super(session);
+	flags = new Flags(); // empty Flags object
+	initStrict();
+	parse(is);
+	saved = true;
+    }
+
+    /**
+     * Constructs a new MimeMessage with content initialized from the
+     * <code>source</code> 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
+     * @exception	MessagingException for failures
+     * @since		JavaMail 1.2
+     */
+    public MimeMessage(MimeMessage source) throws MessagingException {
+	super(source.session);
+	flags = source.getFlags();
+	if (flags == null)	// make sure flags is always set
+	    flags = new Flags();
+	ByteArrayOutputStream bos;
+	int size = source.getSize();
+	if (size > 0)
+	    bos = new ByteArrayOutputStream(size);
+	else
+	    bos = new ByteArrayOutputStream();
+	try {
+	    strict = source.strict;
+	    source.writeTo(bos);
+	    bos.close();
+	    SharedByteArrayInputStream bis =
+			    new SharedByteArrayInputStream(bos.toByteArray());
+	    parse(bis);
+	    bis.close();
+	    saved = true;
+	} catch (IOException ex) {
+	    // should never happen, but just in case...
+	    throw new MessagingException("IOException while copying message",
+					    ex);
+	}
+    }
+
+    /**
+     * Constructs an empty MimeMessage object with the given Folder
+     * and message number. <p>
+     *
+     * This method is for providers subclassing <code>MimeMessage</code>.
+     *
+     * @param	folder	the Folder this message is from
+     * @param	msgnum	the number of this message
+     */
+    protected MimeMessage(Folder folder, int msgnum) {
+	super(folder, msgnum);
+	flags = new Flags();  // empty Flags object
+	saved = true;
+	initStrict();
+    }
+
+    /**
+     * 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 <code>MimeMessage</code>.
+     *
+     * @param folder	The containing folder.
+     * @param is	the message input stream
+     * @param msgnum	Message number of this message within its folder
+     * @exception	MessagingException for failures
+     */
+    protected MimeMessage(Folder folder, InputStream is, int msgnum)
+		throws MessagingException {
+	this(folder, msgnum);
+	initStrict();
+	parse(is);
+    }
+
+    /**
+     * Constructs a MimeMessage from the given InternetHeaders object
+     * and content.
+     *
+     * This method is for providers subclassing <code>MimeMessage</code>.
+     *
+     * @param folder	The containing folder.
+     * @param headers	The headers
+     * @param content	The message content
+     * @param msgnum	Message number of this message within its folder
+     * @exception	MessagingException for failures
+     */
+    protected MimeMessage(Folder folder, InternetHeaders headers,
+		byte[] content, int msgnum) throws MessagingException {
+	this(folder, msgnum);
+	this.headers = headers;
+	this.content = content;
+	initStrict();
+    }
+
+    /**
+     * Set the strict flag based on property.
+     */
+    private void initStrict() {
+	if (session != null) {
+	    Properties props = session.getProperties();
+	    strict = PropUtil.getBooleanProperty(props,
+				    "mail.mime.address.strict", true);
+	    allowutf8 = PropUtil.getBooleanProperty(props,
+				    "mail.mime.allowutf8", false);
+	}
+    }
+
+    /**
+     * Parse the InputStream setting the <code>headers</code> and
+     * <code>content</code> fields appropriately.  Also resets the
+     * <code>modified</code> 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
+     * @exception	MessagingException for failures
+     */
+    protected void parse(InputStream is) throws MessagingException {
+
+	if (!(is instanceof ByteArrayInputStream) &&
+	    !(is instanceof BufferedInputStream) &&
+	    !(is instanceof SharedInputStream))
+	    is = new BufferedInputStream(is);
+	
+	headers = createInternetHeaders(is);
+
+	if (is instanceof SharedInputStream) {
+	    SharedInputStream sis = (SharedInputStream)is;
+	    contentStream = sis.newStream(sis.getPosition(), -1);
+	} else {
+	    try {
+		content = ASCIIUtility.getBytes(is);
+	    } catch (IOException ioex) {
+		throw new MessagingException("IOException", ioex);
+	    }
+	}
+
+	modified = false;
+    }
+
+    /** 
+     * 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, <code>null</code>
+     * is returned.<p>
+     *
+     * This implementation uses the <code>getHeader</code> method
+     * to obtain the requisite header field.
+     *
+     * @return		Address object
+     * @exception	MessagingException for failures
+     * @see	#headers
+     */
+    @Override
+    public Address[] getFrom() throws MessagingException {
+	Address[] a = getAddressHeader("From");
+	if (a == null)
+	    a = getAddressHeader("Sender");
+	
+	return a;
+    }
+
+    /**
+     * Set the RFC 822 "From" header field. Any existing values are 
+     * replaced with the given address. If address is <code>null</code>,
+     * 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.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void setFrom(Address address) throws MessagingException {
+	if (address == null)
+	    removeHeader("From");
+	else
+	    setHeader("From", MimeUtility.fold(6, address.toString()));
+    }
+
+    /**
+     * Set the RFC 822 "From" header field. Any existing values are 
+     * replaced with the given addresses. If address is <code>null</code>,
+     * this header is removed.
+     *
+     * @param address	the sender(s) 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.
+     * @exception	MessagingException for other failures
+     * @since		JvaMail 1.5
+     */
+    public void setFrom(String address) throws MessagingException {
+	if (address == null)
+	    removeHeader("From");
+	else
+	    setAddressHeader("From", InternetAddress.parse(address));
+    }
+
+    /**
+     * 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.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void setFrom() throws MessagingException {
+	InternetAddress me = null;
+	try {
+	    me = InternetAddress._getLocalAddress(session);
+	} catch (Exception ex) {
+	    // if anything goes wrong (SecurityException, UnknownHostException),
+	    // chain the exception
+	    throw new MessagingException("No From address", ex);
+	}
+	if (me != null)
+	    setFrom(me);
+	else
+	    throw new MessagingException("No From 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.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void addFrom(Address[] addresses) throws MessagingException {
+	addAddressHeader("From", 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 requisite header field.
+     *
+     * @return		Address object
+     * @exception	MessagingException for failures
+     * @see		#headers
+     * @since		JavaMail 1.3
+     */
+    public Address getSender() throws MessagingException {
+	Address[] a = getAddressHeader("Sender");
+	if (a == null || a.length == 0)
+	    return null;
+	return a[0];	// there can be only one
+    }
+
+    /**
+     * 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 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.
+     * @exception	MessagingException for other failures
+     * @since		JavaMail 1.3
+     */
+    public void setSender(Address address) throws MessagingException {
+	if (address == null)
+	    removeHeader("Sender");
+	else
+	    setHeader("Sender", MimeUtility.fold(8, address.toString()));
+    }
+
+    /**
+     * This inner class extends the javax.mail.Message.RecipientType
+     * class to add additional RecipientTypes. The one additional
+     * RecipientType currently defined here is NEWSGROUPS.
+     *
+     * @see javax.mail.Message.RecipientType
+     */
+    public static class RecipientType extends Message.RecipientType {
+
+	private static final long serialVersionUID = -5468290701714395543L;
+
+	/**
+	 * The "Newsgroup" (Usenet news) recipients.
+	 */
+	public static final RecipientType NEWSGROUPS =
+					new RecipientType("Newsgroups");
+	protected RecipientType(String type) {
+	    super(type);
+	}
+
+	@Override
+	protected Object readResolve() throws ObjectStreamException {
+	    if (type.equals("Newsgroups"))
+		return NEWSGROUPS;
+	    else
+		return super.readResolve();
+	}
+    }
+
+    /**
+     * Returns the recepients specified by the type. The mapping
+     * between the type and the corresponding RFC 822 header is
+     * as follows:
+     * <pre>
+     *		Message.RecipientType.TO		"To"
+     *		Message.RecipientType.CC		"Cc"
+     *		Message.RecipientType.BCC		"Bcc"
+     *		MimeMessage.RecipientType.NEWSGROUPS	"Newsgroups"
+     * </pre><br>
+     *
+     * 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	Type of recepient
+     * @return          array of Address objects
+     * @exception       MessagingException if header could not
+     *                  be retrieved
+     * @exception       AddressException if the header is misformatted
+     * @see		#headers
+     * @see		javax.mail.Message.RecipientType#TO
+     * @see		javax.mail.Message.RecipientType#CC
+     * @see		javax.mail.Message.RecipientType#BCC
+     * @see		javax.mail.internet.MimeMessage.RecipientType#NEWSGROUPS
+     */
+    @Override
+    public Address[] getRecipients(Message.RecipientType type)
+				throws MessagingException {
+	if (type == RecipientType.NEWSGROUPS) {
+	    String s = getHeader("Newsgroups", ",");
+	    return (s == null) ? null : NewsAddress.parse(s);
+	} else
+	    return getAddressHeader(getHeaderName(type));
+    }
+
+    /**
+     * Get all the recipient addresses for the message.
+     * Extracts the TO, CC, BCC, and NEWSGROUPS recipients.
+     *
+     * @return          array of Address objects
+     * @exception       MessagingException for failures
+     * @see		javax.mail.Message.RecipientType#TO
+     * @see		javax.mail.Message.RecipientType#CC
+     * @see		javax.mail.Message.RecipientType#BCC
+     * @see		javax.mail.internet.MimeMessage.RecipientType#NEWSGROUPS
+     */
+    @Override
+    public Address[] getAllRecipients() throws MessagingException {
+	Address[] all = super.getAllRecipients();
+	Address[] ng = getRecipients(RecipientType.NEWSGROUPS);
+
+	if (ng == null)
+	    return all;		// the common case
+	if (all == null)
+	    return ng;		// a rare case
+
+	Address[] addresses = new Address[all.length + ng.length];
+	System.arraycopy(all, 0, addresses, 0, all.length);
+	System.arraycopy(ng, 0, addresses, all.length, ng.length);
+	return addresses;
+    }
+	
+    /**
+     * Set the specified recipient type to the given addresses.
+     * If the address parameter is <code>null</code>, 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.
+     * @exception	MessagingException for other failures
+     * @see		#getRecipients
+     */
+    @Override
+    public void setRecipients(Message.RecipientType type, Address[] addresses)
+                                throws MessagingException {
+	if (type == RecipientType.NEWSGROUPS) {
+	    if (addresses == null || addresses.length == 0)
+		removeHeader("Newsgroups");
+	    else
+		setHeader("Newsgroups", NewsAddress.toString(addresses));
+	} else
+	    setAddressHeader(getHeaderName(type), addresses);
+    }
+
+    /**
+     * Set the specified recipient type to the given addresses.
+     * If the address parameter is <code>null</code>, the corresponding
+     * recipient field is removed.
+     *   
+     * @param type      Recipient type
+     * @param addresses Addresses
+     * @exception       AddressException if the attempt to parse the
+     *                  addresses String fails
+     * @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.
+     * @exception       MessagingException for other failures
+     * @see             #getRecipients
+     * @since           JavaMail 1.2
+     */
+    public void setRecipients(Message.RecipientType type, String addresses)
+                                throws MessagingException {
+        if (type == RecipientType.NEWSGROUPS) {
+            if (addresses == null || addresses.length() == 0)
+                removeHeader("Newsgroups");
+            else
+                setHeader("Newsgroups", addresses);
+        } else
+            setAddressHeader(getHeaderName(type),
+		addresses == null ? null : 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.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void addRecipients(Message.RecipientType type, Address[] addresses)
+                                throws MessagingException {
+	if (type == RecipientType.NEWSGROUPS) {
+	    String s = NewsAddress.toString(addresses);
+	    if (s != null)
+		addHeader("Newsgroups", s);
+	} else
+	    addAddressHeader(getHeaderName(type), addresses);
+    }
+
+    /**
+     * Add the given addresses to the specified recipient type.
+     * 
+     * @param type      Recipient type
+     * @param addresses Addresses
+     * @exception       AddressException if the attempt to parse the
+     *                  addresses String fails
+     * @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.
+     * @exception       MessagingException for other failures
+     * @since           JavaMail 1.2
+     */
+    public void addRecipients(Message.RecipientType type, String addresses)
+                                throws MessagingException {
+        if (type == RecipientType.NEWSGROUPS) {
+            if (addresses != null && addresses.length() != 0)
+                addHeader("Newsgroups", addresses);
+        } else
+            addAddressHeader(getHeaderName(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.
+     *
+     * @exception	MessagingException for failures
+     * @see		#headers
+     */
+    @Override
+    public Address[] getReplyTo() throws MessagingException {
+	Address[] a = getAddressHeader("Reply-To");
+	if (a == null || a.length == 0)
+	    a = getFrom();
+	return a;
+    }
+
+    /**
+     * Set the RFC 822 "Reply-To" header field. If the address 
+     * parameter is <code>null</code>, this header is removed.
+     *
+     * @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.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void setReplyTo(Address[] addresses) throws MessagingException {
+	setAddressHeader("Reply-To", addresses);
+    }
+
+    // Convenience method to get addresses
+    private Address[] getAddressHeader(String name) 
+			throws MessagingException {
+	String s = getHeader(name, ",");
+	return (s == null) ? null : InternetAddress.parseHeader(s, strict);
+    }
+
+    // Convenience method to set addresses
+    private void setAddressHeader(String name, Address[] addresses)
+			throws MessagingException {
+	String s;
+	if (allowutf8)
+	    s = InternetAddress.toUnicodeString(addresses, name.length() + 2);
+	else
+	    s = InternetAddress.toString(addresses, name.length() + 2);
+	if (s == null)
+	    removeHeader(name);
+	else
+	    setHeader(name, s);
+    }
+
+    private void addAddressHeader(String name, Address[] addresses)
+			throws MessagingException {
+	if (addresses == null || addresses.length == 0)
+	    return;
+	Address[] a = getAddressHeader(name);
+	Address[] anew;
+	if (a == null || a.length == 0)
+	    anew = addresses;
+	else {
+	    anew = new Address[a.length + addresses.length];
+	    System.arraycopy(a, 0, anew, 0, a.length);
+	    System.arraycopy(addresses, 0, anew, a.length, addresses.length);
+	}
+	String s;
+	if (allowutf8)
+	    s = InternetAddress.toUnicodeString(anew, name.length() + 2);
+	else
+	    s = InternetAddress.toString(anew, name.length() + 2);
+	if (s == null)
+	    return;
+	setHeader(name, s);
+    }
+
+    /**
+     * 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.
+     *
+     * @return          Subject
+     * @exception	MessagingException for failures
+     * @see		#headers
+     */
+    @Override
+    public String getSubject() throws MessagingException {
+	String rawvalue = getHeader("Subject", null);
+
+	if (rawvalue == null)
+	    return null;
+
+	try {
+	    return MimeUtility.decodeText(MimeUtility.unfold(rawvalue));
+	} catch (UnsupportedEncodingException ex) {
+	    return rawvalue;
+	}
+    }
+
+    /**
+     * 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>
+     *
+     * The application must ensure that the subject does not contain
+     * any line breaks. <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.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    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" header field is removed. <p>
+     *
+     * The application must ensure that the subject does not contain
+     * any line breaks. <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.
+     * @exception		MessagingException for other failures
+     */
+    public void setSubject(String subject, String charset)
+			throws MessagingException {
+	if (subject == null) {
+	    removeHeader("Subject");
+	} else {
+	    try {
+		setHeader("Subject", MimeUtility.fold(9,
+		    MimeUtility.encodeText(subject, charset, null)));
+	    } catch (UnsupportedEncodingException uex) {
+		throw new MessagingException("Encoding error", uex);
+	    }
+	}
+    }
+
+    /**
+     * 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.
+     *
+     * @return          The sent Date
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public Date getSentDate() throws MessagingException {
+	String s = getHeader("Date", null);
+	if (s != null) {
+	    try {
+		synchronized (mailDateFormat) {
+		    return mailDateFormat.parse(s);
+		}
+	    } catch (ParseException pex) {
+		return null;
+	    }
+	}
+	
+	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 date parameter is 
+     * <code>null</code>, the existing "Date" field is removed.
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void setSentDate(Date d) throws MessagingException {
+	if (d == null)
+	    removeHeader("Date");
+	else {
+	    synchronized (mailDateFormat) {
+		setHeader("Date", mailDateFormat.format(d));
+	    }
+	}
+    }
+
+    /**
+     * Returns the Date on this message was received. Returns 
+     * <code>null</code> 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. <p>
+     *
+     * This implementation returns <code>null</code>.
+     *
+     * @return          the date this message was received
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public Date getReceivedDate() throws MessagingException {
+	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.
+     *
+     * @return          size of content in bytes
+     * @exception	MessagingException for failures
+     */  
+    @Override
+    public int getSize() throws MessagingException {
+	if (content != null)
+	    return content.length;
+	if (contentStream != null) {
+	    try {
+		int size = contentStream.available();
+		// only believe the size if it's greater than zero, since zero
+		// is the default returned by the InputStream class itself
+		if (size > 0)
+		    return size;
+	    } catch (IOException ex) {
+		// ignore it
+	    }
+	}
+	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.
+     *
+     * @return          number of lines in the content.
+     * @exception	MessagingException for failures
+     */  
+    @Override
+    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.
+     *
+     * @return          The ContentType of this part
+     * @exception	MessagingException for failures
+     * @see             javax.activation.DataHandler
+     */
+    @Override
+    public String getContentType() throws MessagingException {
+	String s = getHeader("Content-Type", null);
+	s = MimeUtil.cleanContentType(this, s);
+	if (s == null)
+	    return "text/plain";
+	return s;
+    }
+
+    /**
+     * Is this Part of the specified MIME type?  This method
+     * compares <strong>only the <code>primaryType</code> and 
+     * <code>subType</code></strong>.
+     * The parameters of the content types are ignored. <p>
+     *
+     * For example, this method will return <code>true</code> when
+     * comparing a Part of content type <strong>"text/plain"</strong>
+     * with <strong>"text/plain; charset=foobar"</strong>. <p>
+     *
+     * If the <code>subType</code> of <code>mimeType</code> is the
+     * special character '*', then the subtype is ignored during the
+     * comparison.
+     *
+     * @param	mimeType	the MIME type to check
+     * @return			true if it matches the MIME type
+     * @exception		MessagingException for failures
+     */
+    @Override
+    public boolean isMimeType(String mimeType) throws MessagingException {
+	return MimeBodyPart.isMimeType(this, mimeType);
+    }
+
+    /**
+     * Returns the disposition from 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, 
+     * <code>null</code> is returned. <p>
+     *
+     * This implementation uses the <code>getHeader</code> method
+     * to obtain the requisite header field.
+     *
+     * @return          disposition of this part, or null if unknown
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public String getDisposition() throws MessagingException {
+	return MimeBodyPart.getDisposition(this);
+    }
+
+    /**
+     * Set the disposition in the "Content-Disposition" header field
+     * of this body part.  If the disposition is null, any existing
+     * "Content-Disposition" header field is removed.
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void setDisposition(String disposition) throws MessagingException {
+	MimeBodyPart.setDisposition(this, disposition);
+    }
+
+    /**
+     * Returns the content transfer encoding from the
+     * "Content-Transfer-Encoding" header
+     * field. Returns <code>null</code> 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.
+     *
+     * @return          content-transfer-encoding
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public String getEncoding() throws MessagingException {
+	return MimeBodyPart.getEncoding(this);
+    }
+
+    /**
+     * Returns the value of the "Content-ID" header field. Returns
+     * <code>null</code> 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.
+     *
+     * @return          content-ID
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public String getContentID() throws MessagingException {
+	return getHeader("Content-Id", null);
+    }
+
+    /**
+     * Set the "Content-ID" header field of this Message.
+     * If the <code>cid</code> parameter is null, any existing 
+     * "Content-ID" is removed.
+     *
+     * @param	cid	the content ID
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException for other failures
+     */
+    public void setContentID(String cid) throws MessagingException {
+	if (cid == null)
+	    removeHeader("Content-ID");
+	else
+	    setHeader("Content-ID", cid);
+    }
+
+    /**
+     * Return the value of the "Content-MD5" header field. Returns 
+     * <code>null</code> 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.
+     *
+     * @return          content-MD5
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public String getContentMD5() throws MessagingException {
+	return getHeader("Content-MD5", null);
+    }
+
+    /**
+     * Set the "Content-MD5" header field of this Message.
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void setContentMD5(String md5) throws MessagingException {
+	setHeader("Content-MD5", 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.
+     * 
+     * @return	content-description
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public String getDescription() throws MessagingException {
+	return MimeBodyPart.getDescription(this);
+    }
+
+    /**
+     * Set the "Content-Description" header field for this Message.
+     * If the description parameter is <code>null</code>, 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
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception	MessagingException An
+     * 			UnsupportedEncodingException may be included
+     *			in the exception chain if the charset
+     *			conversion fails.
+     */
+    @Override
+    public void setDescription(String description) throws MessagingException {
+	setDescription(description, null);
+    }
+
+    /**
+     * Set the "Content-Description" header field for this Message.
+     * If the description parameter is <code>null</code>, 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	Description
+     * @param	charset		Charset for encoding
+     * @exception		IllegalWriteException if the underlying
+     *				implementation does not support modification
+     * @exception		IllegalStateException if this message is
+     *				obtained from a READ_ONLY folder.
+     * @exception		MessagingException An
+     * 				UnsupportedEncodingException may be included
+     *				in the exception chain if the charset
+     *				conversion fails.
+     */
+    public void setDescription(String description, String charset) 
+		throws MessagingException {
+	MimeBodyPart.setDescription(this, description, charset);
+    }
+
+    /**
+     * Get the languages specified in the "Content-Language" header
+     * field of this message. The Content-Language header is defined by
+     * RFC 1766. Returns <code>null</code> 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.
+     *
+     * @return			value of content-language header.
+     * @exception		MessagingException for failures
+     */
+    @Override
+    public String[] getContentLanguage() throws MessagingException {
+	return MimeBodyPart.getContentLanguage(this);
+    }
+
+    /**
+     * 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
+     * @exception		IllegalStateException if this message is
+     *				obtained from a READ_ONLY folder.
+     * @exception		MessagingException for other failures
+     */
+    @Override
+    public void setContentLanguage(String[] languages)
+			throws MessagingException {
+	MimeBodyPart.setContentLanguage(this, languages);
+    }
+
+    /**
+     * Returns the value of the "Message-ID" header field. Returns
+     * null if this field is unavailable or its value is absent. <p>
+     *
+     * The default implementation provided here uses the
+     * <code>getHeader</code> method to return the value of the
+     * "Message-ID" field.
+     *
+     * @return     Message-ID
+     * @exception  MessagingException if the retrieval of this field
+     *			causes any exception.
+     * @see        javax.mail.search.MessageIDTerm
+     * @since 	   JavaMail 1.1
+     */
+    public String getMessageID() throws MessagingException {
+	return getHeader("Message-ID", 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 <code>null</code> if both are absent. <p>
+     *
+     * If the <code>mail.mime.encodefilename</code> System property
+     * is set to true, the {@link MimeUtility#decodeText
+     * MimeUtility.decodeText} method will be used to decode the
+     * filename.  While such encoding is not supported by the MIME
+     * spec, many mailers use this technique to support non-ASCII
+     * characters in filenames.  The default value of this property
+     * is false.
+     *
+     * @return	filename
+     * @exception		MessagingException for failures
+     */
+    @Override
+    public String getFileName() throws MessagingException {
+	return MimeBodyPart.getFileName(this);
+    }
+
+    /**
+     * Set the filename associated with this part, if possible. <p>
+     *
+     * Sets the "filename" parameter of the "Content-Disposition"
+     * header field of this message. <p>
+     *
+     * If the <code>mail.mime.encodefilename</code> System property
+     * is set to true, the {@link MimeUtility#encodeText
+     * MimeUtility.encodeText} method will be used to encode the
+     * filename.  While such encoding is not supported by the MIME
+     * spec, many mailers use this technique to support non-ASCII
+     * characters in filenames.  The default value of this property
+     * is false.
+     *
+     * @exception		IllegalWriteException if the underlying
+     *				implementation does not support modification
+     * @exception		IllegalStateException if this message is
+     *				obtained from a READ_ONLY folder.
+     * @exception		MessagingException for other failures
+     */
+    @Override
+    public void setFileName(String filename) throws MessagingException {
+	MimeBodyPart.setFileName(this, filename);	
+    }
+
+    private String getHeaderName(Message.RecipientType type)
+				throws MessagingException {
+	String headerName;
+
+	if (type == Message.RecipientType.TO)
+	    headerName = "To";
+	else if (type == Message.RecipientType.CC)
+	    headerName = "Cc";
+	else if (type == Message.RecipientType.BCC)
+	    headerName = "Bcc";
+	else if (type == MimeMessage.RecipientType.NEWSGROUPS)
+	    headerName = "Newsgroups";
+	else
+	    throw new MessagingException("Invalid Recipient Type");
+	return headerName;
+    }
+
+
+    /**
+     * 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>.
+     *
+     * @return 		an InputStream
+     * @exception       IOException this is typically thrown by the
+     *			DataHandler. Refer to the documentation for
+     *			javax.activation.DataHandler for more details.
+     * @exception	MessagingException for other failures
+     *
+     * @see	#getContentStream
+     * @see 	javax.activation.DataHandler#getInputStream
+     */
+    @Override
+    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 returns a SharedInputStream, if
+     * <code>contentStream</code> is not null.  Otherwise, it
+     * returns a ByteArrayInputStream constructed
+     * out of the <code>content</code> byte array.
+     *
+     * @return	an InputStream containing the raw bytes
+     * @exception	MessagingException for failures
+     * @see #content
+     */
+    protected InputStream getContentStream() throws MessagingException {
+	if (contentStream != null)
+	    return ((SharedInputStream)contentStream).newStream(0, -1);
+	if (content != null)
+	    return new SharedByteArrayInputStream(content);
+
+	throw new MessagingException("No MimeMessage 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.
+     *
+     * @return	an InputStream containing the raw bytes
+     * @exception	MessagingException for failures
+     * @see	#getInputStream
+     * @see	#getContentStream
+     * @since	JavaMail 1.2
+     */
+    public InputStream getRawInputStream() throws MessagingException {
+	return getContentStream();
+    }
+
+    /**                                                            
+     * Return a DataHandler for this Message's content. <p>
+     *
+     * The implementation provided here works approximately 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.
+     *
+     * <blockquote><pre>
+     *  getDataHandler() {
+     *      if (dh == null) {
+     *          dh = new DataHandler(new MimePartDataSource(this));
+     *      }
+     *      return dh;
+     *  }
+     *
+     *  class MimePartDataSource implements DataSource {
+     *      public getInputStream() {
+     *          return MimeUtility.decode(
+     *		     getContentStream(), getEncoding());
+     *      }
+     *	
+     *		.... &lt;other DataSource methods&gt;
+     *  }
+     * </pre></blockquote><p>
+     *
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public synchronized DataHandler getDataHandler() 
+		throws MessagingException {
+	if (dh == null)
+	    dh = new MimeBodyPart.MimePartDataHandler(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>.
+     * If the content is a Multipart or Message object and was created by
+     * parsing a stream, the object is cached and returned in subsequent
+     * calls so that modifications to the content will not be lost.
+     *
+     * @return          Object
+     * @see		javax.mail.Part
+     * @see 		javax.activation.DataHandler#getContent
+     * @exception       IOException this is typically thrown by the
+     *			DataHandler. Refer to the documentation for
+     *			javax.activation.DataHandler for more details.
+     * @exception       MessagingException for other failures
+     */
+    @Override
+    public Object getContent() throws IOException, MessagingException {
+	if (cachedContent != null)
+	    return cachedContent;
+	Object c;
+	try {
+	    c = getDataHandler().getContent();
+	} catch (FolderClosedIOException fex) {
+	    throw new FolderClosedException(fex.getFolder(), fex.getMessage());
+	} catch (MessageRemovedIOException mex) {
+	    throw new MessageRemovedException(mex.getMessage());
+	}
+	if (MimeBodyPart.cacheMultipart &&
+		(c instanceof Multipart || c instanceof Message) &&
+		(content != null || contentStream != null)) {
+	    cachedContent = c;
+	    /*
+	     * We may abandon the input stream so make sure
+	     * the MimeMultipart has consumed the stream.
+	     */
+	    if (c instanceof MimeMultipart)
+		((MimeMultipart)c).parse();
+	}
+	return c;
+    }
+
+    /**
+     * 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
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception       MessagingException for other failures
+     */
+    @Override
+    public synchronized void setDataHandler(DataHandler dh) 
+		throws MessagingException {
+	this.dh = dh;
+	cachedContent = null;
+	MimeBodyPart.invalidateContentHeaders(this);
+    }
+
+    /**
+     * 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.
+     * @exception       MessagingException for other failures
+     */
+    @Override
+    public void setContent(Object o, String type) 
+			throws MessagingException {
+	if (o instanceof Multipart)
+	    setContent((Multipart)o);
+	else
+	    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
+     * <code>text</code> 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 charset parameter.
+     *
+     * @param	text	the text content to set
+     * @exception	MessagingException	if an error occurs
+     * @see	#setText(String text, String charset)
+     */
+    @Override
+    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.
+     *
+     * @param	text	the text content to set
+     * @param	charset	the charset to use for the text
+     * @exception	MessagingException	if an error occurs
+     */
+    @Override
+    public void setText(String text, String charset)
+			throws MessagingException {
+	MimeBodyPart.setText(this, text, charset, "plain");
+    }
+
+    /**
+     * Convenience method that sets the given String as this part's
+     * content, with a primary MIME type of "text" and the specified
+     * MIME subtype.  The given Unicode string will be charset-encoded
+     * using the specified charset. The charset is also used to set
+     * the "charset" parameter.
+     *
+     * @param	text	the text content to set
+     * @param	charset	the charset to use for the text
+     * @param	subtype	the MIME subtype to use (e.g., "html")
+     * @exception	MessagingException	if an error occurs
+     * @since	JavaMail 1.4
+     */
+    @Override
+    public void setText(String text, String charset, String subtype)
+                        throws MessagingException {
+	MimeBodyPart.setText(this, text, charset, subtype);
+    }
+
+    /**
+     * 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.
+     * @exception       MessagingException for other failures
+     */
+    @Override
+    public void setContent(Multipart mp) throws MessagingException {
+	setDataHandler(new DataHandler(mp, mp.getContentType()));
+	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 <strong>not</strong> 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 <code>ANSWERED</code>
+     * flag is set in this message.
+     *
+     * The current implementation also sets the "References" header
+     * in the new message to include the contents of the "References"
+     * header (or, if missing, the "In-Reply-To" header) in this message,
+     * plus the contents of the "Message-Id" header of this message,
+     * as described in RFC 2822.
+     *
+     * @param	replyToAll	reply should be sent to all recipients
+     *				of this message
+     * @return		the reply Message
+     * @exception	MessagingException for failures
+     */
+    @Override
+    public Message reply(boolean replyToAll) throws MessagingException {
+	return reply(replyToAll, true);
+    }
+
+    /**
+     * 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 <strong>not</strong> 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>
+     *
+     * If <code>setAnswered</code> is set, the
+     * {@link javax.mail.Flags.Flag#ANSWERED ANSWERED} flag is set
+     * in this message. <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 current implementation also sets the "References" header
+     * in the new message to include the contents of the "References"
+     * header (or, if missing, the "In-Reply-To" header) in this message,
+     * plus the contents of the "Message-Id" header of this message,
+     * as described in RFC 2822.
+     *
+     * @param	replyToAll	reply should be sent to all recipients
+     *				of this message
+     * @param	setAnswered	set the ANSWERED flag in this message?
+     * @return		the reply Message
+     * @exception	MessagingException for failures
+     * @since		JavaMail 1.5
+     */
+    public Message reply(boolean replyToAll, boolean setAnswered)
+				throws MessagingException {
+	MimeMessage reply = createMimeMessage(session);
+	/*
+	 * Have to manipulate the raw Subject header so that we don't lose
+	 * any encoding information.  This is safe because "Re:" isn't
+	 * internationalized and (generally) isn't encoded.  If the entire
+	 * Subject header is encoded, prefixing it with "Re: " still leaves
+	 * a valid and correct encoded header.
+	 */
+	String subject = getHeader("Subject", null);
+	if (subject != null) {
+	    if (!subject.regionMatches(true, 0, "Re: ", 0, 4))
+		subject = "Re: " + subject;
+	    reply.setHeader("Subject", subject);
+	}
+	Address a[] = getReplyTo();
+	reply.setRecipients(Message.RecipientType.TO, a);
+	if (replyToAll) {
+	    List<Address> v = new ArrayList<>();
+	    // add my own address to list
+	    InternetAddress me = InternetAddress.getLocalAddress(session);
+	    if (me != null)
+		v.add(me);
+	    // add any alternate names I'm known by
+	    String alternates = null;
+	    if (session != null)
+		alternates = session.getProperty("mail.alternates");
+	    if (alternates != null)
+		eliminateDuplicates(v,
+				InternetAddress.parse(alternates, false));
+	    // should we Cc all other original recipients?
+	    String replyallccStr = null;
+	    boolean replyallcc = false;
+	    if (session != null)
+		replyallcc = PropUtil.getBooleanProperty(
+						session.getProperties(),
+						"mail.replyallcc", false);
+	    // add the recipients from the To field so far
+	    eliminateDuplicates(v, a);
+	    a = getRecipients(Message.RecipientType.TO);
+	    a = eliminateDuplicates(v, a);
+	    if (a != null && a.length > 0) {
+		if (replyallcc)
+		    reply.addRecipients(Message.RecipientType.CC, a);
+		else
+		    reply.addRecipients(Message.RecipientType.TO, a);
+	    }
+	    a = getRecipients(Message.RecipientType.CC);
+	    a = eliminateDuplicates(v, a);
+	    if (a != null && a.length > 0)
+		reply.addRecipients(Message.RecipientType.CC, a);
+	    // don't eliminate duplicate newsgroups
+	    a = getRecipients(RecipientType.NEWSGROUPS);
+	    if (a != null && a.length > 0)
+		reply.setRecipients(RecipientType.NEWSGROUPS, a);
+	}
+
+	String msgId = getHeader("Message-Id", null);
+	if (msgId != null)
+	    reply.setHeader("In-Reply-To", msgId);
+
+	/*
+	 * Set the References header as described in RFC 2822:
+	 *
+	 * The "References:" field will contain the contents of the parent's
+	 * "References:" field (if any) followed by the contents of the parent's
+	 * "Message-ID:" field (if any).  If the parent message does not contain
+	 * a "References:" field but does have an "In-Reply-To:" field
+	 * containing a single message identifier, then the "References:" field
+	 * will contain the contents of the parent's "In-Reply-To:" field
+	 * followed by the contents of the parent's "Message-ID:" field (if
+	 * any).  If the parent has none of the "References:", "In-Reply-To:",
+	 * or "Message-ID:" fields, then the new message will have no
+	 * "References:" field.
+	 */
+	String refs = getHeader("References", " ");
+	if (refs == null) {
+	    // XXX - should only use if it contains a single message identifier
+	    refs = getHeader("In-Reply-To", " ");
+	}
+	if (msgId != null) {
+	    if (refs != null)
+		refs = MimeUtility.unfold(refs) + " " + msgId;
+	    else
+		refs = msgId;
+	}
+	if (refs != null)
+	    reply.setHeader("References", MimeUtility.fold(12, refs));
+
+	if (setAnswered) {
+	    try {
+		setFlags(answeredFlag, true);
+	    } catch (MessagingException mex) {
+		// ignore it
+	    }
+	}
+	return reply;
+    }
+
+    // used above in reply()
+    private static final Flags answeredFlag = new Flags(Flags.Flag.ANSWERED);
+
+    /**
+     * Check addrs for any duplicates that may already be in v.
+     * Return a new array without the duplicates.  Add any new
+     * addresses to v.  Note that the input array may be modified.
+     */
+    private Address[] eliminateDuplicates(List<Address> v, Address[] addrs) {
+	if (addrs == null)
+	    return null;
+	int gone = 0;
+	for (int i = 0; i < addrs.length; i++) {
+	    boolean found = false;
+	    // search the list for this address
+	    for (int j = 0; j < v.size(); j++) {
+		if (((InternetAddress)v.get(j)).equals(addrs[i])) {
+		    // found it; count it and remove it from the input array
+		    found = true;
+		    gone++;
+		    addrs[i] = null;
+		    break;
+		}
+	    }
+	    if (!found)
+		v.add(addrs[i]);	// add new address to list
+	}
+	// if we found any duplicates, squish the array
+	if (gone != 0) {
+	    Address[] a;
+	    // new array should be same type as original array
+	    // XXX - there must be a better way, perhaps reflection?
+	    if (addrs instanceof InternetAddress[])
+		a = new InternetAddress[addrs.length - gone];
+	    else
+		a = new Address[addrs.length - gone];
+	    for (int i = 0, j = 0; i < addrs.length; i++)
+		if (addrs[i] != null)
+		    a[j++] = addrs[i];
+	    addrs = a;
+	}
+	return addrs;
+    }
+
+    /**
+     * Output the message as an RFC 822 format stream. <p>
+     *
+     * Note that, depending on how the messag 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.
+     * @exception MessagingException for other failures
+     * @see javax.activation.DataHandler#writeTo
+     */
+    @Override
+    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 <code>saved</code> 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.
+     *
+     * @param	os		the stream to write to
+     * @param	ignoreList	the headers to not include in the output
+     * @exception IOException	if an error occurs writing to the stream
+     *				or if an error is generated by the
+     *				javax.activation layer.
+     * @exception javax.mail.MessagingException for other failures
+     * @see javax.activation.DataHandler#writeTo
+     */
+    public void writeTo(OutputStream os, String[] ignoreList)
+				throws IOException, MessagingException {
+	if (!saved)
+	    saveChanges();
+
+	if (modified) {
+	    MimeBodyPart.writeTo(this, os, ignoreList);
+	    return;
+	}
+
+	// Else, the content is untouched, so we can just output it
+	// First, write out the header
+	Enumeration<String> hdrLines = getNonMatchingHeaderLines(ignoreList);
+	LineOutputStream los = new LineOutputStream(os, allowutf8);
+	while (hdrLines.hasMoreElements())
+	    los.writeln(hdrLines.nextElement());
+
+	// The CRLF separator between header and content
+	los.writeln();
+
+	// Finally, the content. 
+	if (content == null) {
+	    // call getContentStream to give subclass a chance to
+	    // provide the data on demand
+	    InputStream is = null;
+	    byte[] buf = new byte[8192];
+	    try {
+		is = getContentStream();
+		// now copy the data to the output stream
+		int len;
+		while ((len = is.read(buf)) > 0)
+		    os.write(buf, 0, len);
+	    } finally {
+		if (is != null)
+		    is.close();
+		buf = null;
+	    }
+	} else {
+	    os.write(content);
+	}
+	os.flush();
+    }
+
+    /**
+     * 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
+     * @exception       MessagingException for failures
+     * @see 	javax.mail.internet.MimeUtility
+     */
+    @Override
+    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 <code>null</code>, only the first header is 
+     * returned.
+     *
+     * @param name		the name of this header
+     * @param delimiter		separator between values
+     * @return                  the value fields for all headers with 
+     *				this name
+     * @exception       	MessagingException for failures
+     */
+    @Override
+    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
+     * @see 	javax.mail.internet.MimeUtility
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception       MessagingException for other failures
+     */
+    @Override
+    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
+     * @see 	javax.mail.internet.MimeUtility
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception       MessagingException for other failures
+     */
+    @Override
+    public void addHeader(String name, String value)
+                                throws MessagingException {
+	headers.addHeader(name, value);
+    }
+
+    /**
+     * Remove all headers with this name.
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception       MessagingException for other failures
+     */
+    @Override
+    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
+     * @exception  MessagingException for failures
+     * @see 	javax.mail.internet.MimeUtility
+     */
+    @Override
+    public Enumeration<Header> getAllHeaders() throws MessagingException {
+	return headers.getAllHeaders();	
+    }
+
+    /**
+     * Return matching headers from this Message as an Enumeration of
+     * Header objects. This implementation obtains the headers from
+     * the <code>headers</code> InternetHeaders object.
+     *
+     * @exception  MessagingException for failures
+     */
+    @Override
+    public Enumeration<Header> getMatchingHeaders(String[] names)
+			throws MessagingException {
+	return headers.getMatchingHeaders(names);
+    }
+
+    /**
+     * Return non-matching headers from this Message as an
+     * Enumeration of Header objects. This implementation 
+     * obtains the header from the <code>headers</code> InternetHeaders object.
+     *
+     * @exception  MessagingException for failures
+     */
+    @Override
+    public Enumeration<Header> getNonMatchingHeaders(String[] names)
+			throws MessagingException {
+	return headers.getNonMatchingHeaders(names);
+    }
+
+    /**
+     * Add a raw RFC 822 header-line. 
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception  	MessagingException for other failures
+     */
+    @Override
+    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. 
+     *
+     * @exception  	MessagingException for failures
+     */
+    @Override
+    public Enumeration<String> 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.
+     *
+     * @exception  	MessagingException for failures
+     */
+    @Override
+    public Enumeration<String> 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.
+     *
+     * @exception  	MessagingException for failures
+     */
+    @Override
+    public Enumeration<String> getNonMatchingHeaderLines(String[] names)
+                                        throws MessagingException {
+	return headers.getNonMatchingHeaderLines(names);
+    }
+
+    /**
+     * Return a <code>Flags</code> 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
+     * @exception  	MessagingException for failures
+     * @see 		javax.mail.Flags
+     */
+    @Override
+    public synchronized Flags getFlags() throws MessagingException {
+	return (Flags)flags.clone();
+    }
+
+    /**
+     * Check whether the flag specified in the <code>flag</code>
+     * argument is set in this message. <p>
+     *
+     * This implementation checks this message's internal 
+     * <code>flags</code> object.
+     *
+     * @param flag	the flag
+     * @return		value of the specified flag for this message
+     * @exception       MessagingException for failures
+     * @see 		javax.mail.Flags.Flag
+     * @see		javax.mail.Flags.Flag#ANSWERED
+     * @see		javax.mail.Flags.Flag#DELETED
+     * @see		javax.mail.Flags.Flag#DRAFT
+     * @see		javax.mail.Flags.Flag#FLAGGED
+     * @see		javax.mail.Flags.Flag#RECENT
+     * @see		javax.mail.Flags.Flag#SEEN
+     */
+    @Override
+    public synchronized boolean isSet(Flags.Flag flag)
+				throws MessagingException {
+	return (flags.contains(flag));
+    }
+
+    /**
+     * Set the flags for this message. <p>
+     *
+     * This implementation modifies the <code>flags</code> field.
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception  	MessagingException for other failures
+     */
+    @Override
+    public synchronized 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 saveChanges 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
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception  	MessagingException for other failures
+     */
+    @Override
+    public void saveChanges() throws MessagingException {
+	modified = true;
+	saved = true;
+	updateHeaders();
+    }
+
+    /**
+     * Update the Message-ID header.  This method is called
+     * by the <code>updateHeaders</code> and allows a subclass
+     * to override only the algorithm for choosing a Message-ID.
+     *
+     * @exception  	MessagingException for failures
+     * @since		JavaMail 1.4
+     */
+    protected void updateMessageID() throws MessagingException {
+	setHeader("Message-ID", 
+		  "<" + UniqueValue.getUniqueMessageIDValue(session) + ">");
+          
+    }	
+
+    /**
+     * Called by the <code>saveChanges</code> method to actually
+     * update the MIME headers.  The implementation here sets the
+     * <code>Content-Transfer-Encoding</code> header (if needed
+     * and not already set), the <code>Date</code> header (if
+     * not already set), the <code>MIME-Version</code> header
+     * and the <code>Message-ID</code> header. Also, if the content
+     * of this message is a <code>MimeMultipart</code>, its
+     * <code>updateHeaders</code> method is called. <p>
+     *
+     * If the {@link #cachedContent} field is not null (that is,
+     * it references a Multipart or Message object), then
+     * that object is used to set a new DataHandler, any
+     * stream data used to create this object is discarded,
+     * and the {@link #cachedContent} field is cleared.
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this message is
+     *			obtained from a READ_ONLY folder.
+     * @exception  	MessagingException for other failures
+     */
+    protected synchronized void updateHeaders() throws MessagingException {
+	MimeBodyPart.updateHeaders(this);	
+	setHeader("MIME-Version", "1.0");
+	if (getHeader("Date") == null)
+	    setSentDate(new Date());
+        updateMessageID();
+
+	if (cachedContent != null) {
+	    dh = new DataHandler(cachedContent, getContentType());
+	    cachedContent = null;
+	    content = null;
+	    if (contentStream != null) {
+		try {
+		    contentStream.close();
+		} catch (IOException ioex) { }	// nothing to do
+	    }
+	    contentStream = null;
+	}
+    }
+
+    /**
+     * 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.
+     *
+     * @return	an InternetHeaders object
+     * @param	is	the InputStream to read the headers from
+     * @exception  	MessagingException for failures
+     * @since		JavaMail 1.2
+     */
+    protected InternetHeaders createInternetHeaders(InputStream is)
+				throws MessagingException {
+	return new InternetHeaders(is, allowutf8);
+    }
+
+    /**
+     * Create and return a MimeMessage object.  The reply method
+     * uses this method to create the MimeMessage object that it
+     * will return.  Subclasses can override this method to return
+     * a subclass of MimeMessage.  This implementation simply constructs
+     * and returns a MimeMessage object using the supplied Session.
+     *
+     * @param	session	the Session to use for the new message
+     * @return		the new MimeMessage object
+     * @exception  	MessagingException for failures
+     * @since		JavaMail 1.4
+     */
+    protected MimeMessage createMimeMessage(Session session)
+				throws MessagingException {
+	return new MimeMessage(session);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/MimeMultipart.java b/mail/src/main/java/javax/mail/internet/MimeMultipart.java
new file mode 100644
index 0000000..04090ee
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/MimeMultipart.java
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.*;
+import javax.activation.*;
+import java.util.*;
+import java.io.*;
+import com.sun.mail.util.LineOutputStream;
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.PropUtil;
+
+/**
+ * The MimeMultipart class is an implementation of the abstract Multipart
+ * class that uses MIME conventions for the multipart data. <p>
+ *
+ * A MimeMultipart is obtained from a MimePart whose primary type
+ * is "multipart" (by invoking the part's <code>getContent()</code> method)
+ * or it can be created by a client as part of creating a new MimeMessage. <p>
+ *
+ * The default multipart subtype is "mixed".  The other multipart
+ * subtypes, such as "alternative", "related", and so on, can be
+ * implemented as subclasses of MimeMultipart with additional methods
+ * to implement the additional semantics of that type of multipart
+ * content. The intent is that service providers, mail JavaBean writers
+ * and mail clients will write many such subclasses and their Command
+ * Beans, and will install them into the JavaBeans Activation
+ * Framework, so that any JavaMail implementation and its clients can
+ * transparently find and use these classes. Thus, a MIME multipart
+ * handler is treated just like any other type handler, thereby
+ * decoupling the process of providing multipart handlers from the
+ * JavaMail API. Lacking these additional MimeMultipart subclasses,
+ * all subtypes of MIME multipart data appear as MimeMultipart objects. <p>
+ *
+ * An application can directly construct a MIME multipart object of any
+ * subtype by using the <code>MimeMultipart(String subtype)</code>
+ * constructor.  For example, to create a "multipart/alternative" object,
+ * use <code>new MimeMultipart("alternative")</code>. <p>
+ *
+ * The <code>mail.mime.multipart.ignoremissingendboundary</code>
+ * property may be set to <code>false</code> to cause a
+ * <code>MessagingException</code> to be thrown if the multipart
+ * data does not end with the required end boundary line.  If this
+ * property is set to <code>true</code> or not set, missing end
+ * boundaries are not considered an error and the final body part
+ * ends at the end of the data. <p>
+ *
+ * The <code>mail.mime.multipart.ignoremissingboundaryparameter</code>
+ * System property may be set to <code>false</code> to cause a
+ * <code>MessagingException</code> to be thrown if the Content-Type
+ * of the MimeMultipart does not include a <code>boundary</code> parameter.
+ * If this property is set to <code>true</code> or not set, the multipart
+ * parsing code will look for a line that looks like a bounary line and
+ * use that as the boundary separating the parts. <p>
+ *
+ * The <code>mail.mime.multipart.ignoreexistingboundaryparameter</code>
+ * System property may be set to <code>true</code> to cause any boundary
+ * to be ignored and instead search for a boundary line in the message
+ * as with <code>mail.mime.multipart.ignoremissingboundaryparameter</code>. <p>
+ *
+ * Normally, when writing out a MimeMultipart that contains no body
+ * parts, or when trying to parse a multipart message with no body parts,
+ * a <code>MessagingException</code> is thrown.  The MIME spec does not allow
+ * multipart content with no body parts.  The
+ * <code>mail.mime.multipart.allowempty</code> System property may be set to
+ * <code>true</code> to override this behavior.
+ * When writing out such a MimeMultipart, a single empty part will be
+ * included.  When reading such a multipart, a MimeMultipart will be created
+ * with no body parts.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ * @author  Max Spivak
+ */
+
+public class MimeMultipart extends Multipart {
+
+    /**
+     * The DataSource supplying our InputStream.
+     */
+    protected DataSource ds = null;
+
+    /**
+     * Have we parsed the data from our InputStream yet?
+     * Defaults to true; set to false when our constructor is
+     * given a DataSource with an InputStream that we need to
+     * parse.
+     */
+    protected boolean parsed = true;
+
+    /**
+     * Have we seen the final bounary line?
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean complete = true;
+
+    /**
+     * The MIME multipart preamble text, the text that
+     * occurs before the first boundary line.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected String preamble = null;
+
+    /**
+     * Flag corresponding to the "mail.mime.multipart.ignoremissingendboundary"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean ignoreMissingEndBoundary = true;
+
+    /**
+     * Flag corresponding to the
+     * "mail.mime.multipart.ignoremissingboundaryparameter"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean ignoreMissingBoundaryParameter = true;
+
+    /**
+     * Flag corresponding to the
+     * "mail.mime.multipart.ignoreexistingboundaryparameter"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean ignoreExistingBoundaryParameter = false;
+
+    /**
+     * Flag corresponding to the "mail.mime.multipart.allowempty"
+     * property, set in the {@link #initializeProperties} method called from
+     * constructors and the parse method.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected boolean allowEmpty = false;
+
+    /**
+     * Default constructor. An empty MimeMultipart object
+     * is created. Its content type is set to "multipart/mixed".
+     * A unique boundary string is generated and this string is
+     * setup as the "boundary" parameter for the 
+     * <code>contentType</code> field. <p>
+     *
+     * MimeBodyParts may be added later.
+     */
+    public MimeMultipart() {
+	this("mixed");
+    }
+
+    /**
+     * Construct a MimeMultipart object of the given subtype.
+     * A unique boundary string is generated and this string is
+     * setup as the "boundary" parameter for the 
+     * <code>contentType</code> field.
+     * Calls the {@link #initializeProperties} method.<p>
+     *
+     * MimeBodyParts may be added later.
+     *
+     * @param	subtype	the MIME content subtype
+     */
+    public MimeMultipart(String subtype) {
+	super();
+	/*
+	 * Compute a boundary string.
+	 */
+	String boundary = UniqueValue.getUniqueBoundaryValue();
+	ContentType cType = new ContentType("multipart", subtype, null);
+	cType.setParameter("boundary", boundary);
+	contentType = cType.toString();
+	initializeProperties();
+    }
+
+    /**
+     * Construct a MimeMultipart object of the default "mixed" subtype,
+     * and with the given body parts.  More body parts may be added later.
+     *
+     * @param	parts	the body parts
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.5
+     */
+    public MimeMultipart(BodyPart... parts) throws MessagingException {
+	this();
+	for (BodyPart bp : parts)
+	    super.addBodyPart(bp);
+    }
+
+    /**
+     * Construct a MimeMultipart object of the given subtype
+     * and with the given body parts.  More body parts may be added later.
+     *
+     * @param	subtype	the MIME content subtype
+     * @param	parts	the body parts
+     * @exception	MessagingException for failures
+     * @since	JavaMail 1.5
+     */
+    public MimeMultipart(String subtype, BodyPart... parts)
+				throws MessagingException {
+	this(subtype);
+	for (BodyPart bp : parts)
+	    super.addBodyPart(bp);
+    }
+
+    /**
+     * Constructs a MimeMultipart object and its bodyparts from the 
+     * given DataSource. <p>
+     *
+     * This constructor handles as a special case the situation where the
+     * given DataSource is a MultipartDataSource object.  In this case, this
+     * method just invokes the superclass (i.e., Multipart) constructor
+     * that takes a MultipartDataSource object. <p>
+     *
+     * Otherwise, the DataSource is assumed to provide a MIME multipart 
+     * byte stream.  The <code>parsed</code> flag is set to false.  When
+     * the data for the body parts are needed, the parser extracts the
+     * "boundary" parameter from the content type of this DataSource,
+     * skips the 'preamble' and reads bytes till the terminating
+     * boundary and creates MimeBodyParts for each part of the stream.
+     *
+     * @param	ds	DataSource, can be a MultipartDataSource
+     * @exception	ParseException for failures parsing the message
+     * @exception	MessagingException for other failures
+     */
+    public MimeMultipart(DataSource ds) throws MessagingException {
+	super();
+
+	if (ds instanceof MessageAware) {
+	    MessageContext mc = ((MessageAware)ds).getMessageContext();
+	    setParent(mc.getPart());
+	}
+
+	if (ds instanceof MultipartDataSource) {
+	    // ask super to do this for us.
+	    setMultipartDataSource((MultipartDataSource)ds);
+	    return;
+	}
+
+	// 'ds' was not a MultipartDataSource, we have
+	// to parse this ourself.
+	parsed = false;
+	this.ds = ds;
+	contentType = ds.getContentType();
+    }
+
+    /**
+     * Initialize flags that control parsing behavior,
+     * based on System properties described above in
+     * the class documentation.
+     *
+     * @since	JavaMail 1.5
+     */
+    protected void initializeProperties() {
+	// read properties that control parsing
+
+	// default to true
+	ignoreMissingEndBoundary = PropUtil.getBooleanSystemProperty(
+	    "mail.mime.multipart.ignoremissingendboundary", true);
+	// default to true
+	ignoreMissingBoundaryParameter = PropUtil.getBooleanSystemProperty(
+	    "mail.mime.multipart.ignoremissingboundaryparameter", true);
+	// default to false
+	ignoreExistingBoundaryParameter = PropUtil.getBooleanSystemProperty(
+	    "mail.mime.multipart.ignoreexistingboundaryparameter", false);
+	// default to false
+	allowEmpty = PropUtil.getBooleanSystemProperty(
+	    "mail.mime.multipart.allowempty", false);
+    }
+
+    /**
+     * Set the subtype. This method should be invoked only on a new
+     * MimeMultipart object created by the client. The default subtype
+     * of such a multipart object is "mixed". <p>
+     *
+     * @param	subtype		Subtype
+     * @exception	MessagingException for failures
+     */
+    public synchronized void setSubType(String subtype) 
+			throws MessagingException {
+	ContentType cType = new ContentType(contentType);	
+	cType.setSubType(subtype);
+	contentType = cType.toString();
+    }
+
+    /**
+     * Return the number of enclosed BodyPart objects.
+     *
+     * @return		number of parts
+     */
+    @Override
+    public synchronized int getCount() throws MessagingException {
+	parse();
+	return super.getCount();
+    }
+
+    /**
+     * Get the specified BodyPart.  BodyParts are numbered starting at 0.
+     *
+     * @param index	the index of the desired BodyPart
+     * @return		the Part
+     * @exception       MessagingException if no such BodyPart exists
+     */
+    @Override
+    public synchronized BodyPart getBodyPart(int index) 
+			throws MessagingException {
+	parse();
+	return super.getBodyPart(index);
+    }
+
+    /**
+     * Get the MimeBodyPart referred to by the given ContentID (CID). 
+     * Returns null if the part is not found.
+     *
+     * @param  CID 	the ContentID of the desired part
+     * @return          the Part
+     * @exception	MessagingException for failures
+     */
+    public synchronized BodyPart getBodyPart(String CID) 
+			throws MessagingException {
+	parse();
+
+	int count = getCount();
+	for (int i = 0; i < count; i++) {
+	   MimeBodyPart part = (MimeBodyPart)getBodyPart(i);
+	   String s = part.getContentID();
+	   if (s != null && s.equals(CID))
+		return part;    
+	}
+	return null;
+    }
+
+    /**
+     * Remove the specified part from the multipart message.
+     * Shifts all the parts after the removed part down one.
+     *
+     * @param   part	The part to remove
+     * @return		true if part removed, false otherwise
+     * @exception	MessagingException if no such Part exists
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     *			of existing values
+     */
+    @Override
+    public boolean removeBodyPart(BodyPart part) throws MessagingException {
+	parse();
+	return super.removeBodyPart(part);
+    }
+
+    /**
+     * Remove the part at specified location (starting from 0).
+     * Shifts all the parts after the removed part down one.
+     *
+     * @param   index	Index of the part to remove
+     * @exception       IndexOutOfBoundsException if the given index
+     *			is out of range.
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     *			of existing values
+     * @exception	MessagingException for other failures
+     */
+    @Override
+    public void removeBodyPart(int index) throws MessagingException {
+	parse();
+	super.removeBodyPart(index);
+    }
+
+    /**
+     * Adds a Part to the multipart.  The BodyPart is appended to 
+     * the list of existing Parts.
+     *
+     * @param  part  The Part to be appended
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     *			of existing values
+     * @exception       MessagingException for other failures
+     */
+    @Override
+    public synchronized void addBodyPart(BodyPart part) 
+		throws MessagingException {
+	parse();
+	super.addBodyPart(part);
+    }
+
+    /**
+     * Adds a BodyPart at position <code>index</code>.
+     * If <code>index</code> is not the last one in the list,
+     * the subsequent parts are shifted up. If <code>index</code>
+     * is larger than the number of parts present, the
+     * BodyPart is appended to the end.
+     *
+     * @param  part  The BodyPart to be inserted
+     * @param  index Location where to insert the part
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     *			of existing values
+     * @exception       MessagingException for other failures
+     */
+    @Override
+    public synchronized void addBodyPart(BodyPart part, int index) 
+				throws MessagingException {
+	parse();
+	super.addBodyPart(part, index);
+    }
+
+    /**
+     * Return true if the final boundary line for this
+     * multipart was seen.  When parsing multipart content,
+     * this class will (by default) terminate parsing with
+     * no error if the end of input is reached before seeing
+     * the final multipart boundary line.  In such a case,
+     * this method will return false.  (If the System property
+     * "mail.mime.multipart.ignoremissingendboundary" is set to
+     * false, parsing such a message will instead throw a
+     * MessagingException.)
+     *
+     * @return	true if the final boundary line was seen
+     * @exception	MessagingException for failures
+     * @since		JavaMail 1.4
+     */
+    public synchronized boolean isComplete() throws MessagingException {
+	parse();
+	return complete;
+    }
+
+    /**
+     * Get the preamble text, if any, that appears before the
+     * first body part of this multipart.  Some protocols,
+     * such as IMAP, will not allow access to the preamble text.
+     *
+     * @return		the preamble text, or null if no preamble
+     * @exception	MessagingException for failures
+     * @since		JavaMail 1.4
+     */
+    public synchronized String getPreamble() throws MessagingException {
+	parse();
+	return preamble;
+    }
+
+    /**
+     * Set the preamble text to be included before the first
+     * body part.  Applications should generally not include
+     * any preamble text.  In some cases it may be helpful to
+     * include preamble text with instructions for users of
+     * pre-MIME software.  The preamble text should be complete
+     * lines, including newlines.
+     *
+     * @param	preamble	the preamble text
+     * @exception	MessagingException for failures
+     * @since		JavaMail 1.4
+     */
+    public synchronized void setPreamble(String preamble)
+				throws MessagingException {
+	this.preamble = preamble;
+    }
+
+    /**
+     * Update headers. The default implementation here just
+     * calls the <code>updateHeaders</code> method on each of its
+     * children BodyParts. <p>
+     *
+     * Note that the boundary parameter is already set up when
+     * a new and empty MimeMultipart object is created. <p>
+     *
+     * This method is called when the <code>saveChanges</code>
+     * method is invoked on the Message object containing this
+     * Multipart. This is typically done as part of the Message
+     * send process, however note that a client is free to call
+     * it any number of times. So if the header updating process is 
+     * expensive for a specific MimeMultipart subclass, then it
+     * might itself want to track whether its internal state actually
+     * did change, and do the header updating only if necessary.
+     *
+     * @exception	MessagingException for failures
+     */
+    protected synchronized void updateHeaders() throws MessagingException {
+	parse();
+	for (int i = 0; i < parts.size(); i++)
+	    ((MimeBodyPart)parts.elementAt(i)).updateHeaders();
+    }
+
+    /**
+     * Iterates through all the parts and outputs each MIME part
+     * separated by a boundary.
+     */
+    @Override
+    public synchronized void writeTo(OutputStream os)
+				throws IOException, MessagingException {
+	parse();
+	
+	String boundary = "--" + 
+		(new ContentType(contentType)).getParameter("boundary");
+	LineOutputStream los = new LineOutputStream(os);
+
+	// if there's a preamble, write it out
+	if (preamble != null) {
+	    byte[] pb = ASCIIUtility.getBytes(preamble);
+	    los.write(pb);
+	    // make sure it ends with a newline
+	    if (pb.length > 0 &&
+		    !(pb[pb.length-1] == '\r' || pb[pb.length-1] == '\n')) {
+		los.writeln();
+	    }
+	    // XXX - could force a blank line before start boundary
+	}
+
+	if (parts.size() == 0) {
+	    if (allowEmpty) {
+		// write out a single empty body part
+		los.writeln(boundary); // put out boundary
+		los.writeln(); // put out empty line
+	    } else {
+		throw new MessagingException("Empty multipart: " + contentType);
+	    }
+	} else {
+	    for (int i = 0; i < parts.size(); i++) {
+		los.writeln(boundary); // put out boundary
+		((MimeBodyPart)parts.elementAt(i)).writeTo(os);
+		los.writeln(); // put out empty line
+	    }
+	}
+
+	// put out last boundary
+	los.writeln(boundary + "--");
+    }
+
+    /**
+     * Parse the InputStream from our DataSource, constructing the
+     * appropriate MimeBodyParts.  The <code>parsed</code> flag is
+     * set to true, and if true on entry nothing is done.  This
+     * method is called by all other methods that need data for
+     * the body parts, to make sure the data has been parsed.
+     * The {@link #initializeProperties} method is called before
+     * parsing the data.
+     *
+     * @exception	ParseException for failures parsing the message
+     * @exception	MessagingException for other failures
+     * @since	JavaMail 1.2
+     */
+    protected synchronized void parse() throws MessagingException {
+	if (parsed)
+	    return;
+
+	initializeProperties();
+
+	InputStream in = null;
+	SharedInputStream sin = null;
+	long start = 0, end = 0;
+
+	try {
+	    in = ds.getInputStream();
+	    if (!(in instanceof ByteArrayInputStream) &&
+		!(in instanceof BufferedInputStream) &&
+		!(in instanceof SharedInputStream))
+		in = new BufferedInputStream(in);
+	} catch (Exception ex) {
+	    throw new MessagingException("No inputstream from datasource", ex);
+	}
+	if (in instanceof SharedInputStream)
+	    sin = (SharedInputStream)in;
+
+	ContentType cType = new ContentType(contentType);
+	String boundary = null;
+	if (!ignoreExistingBoundaryParameter) {
+	    String bp = cType.getParameter("boundary");
+	    if (bp != null)
+		boundary = "--" + bp;
+	}
+	if (boundary == null && !ignoreMissingBoundaryParameter &&
+		!ignoreExistingBoundaryParameter)
+	    throw new ParseException("Missing boundary parameter");
+
+	try {
+	    // Skip and save the preamble
+	    LineInputStream lin = new LineInputStream(in);
+	    StringBuilder preamblesb = null;
+	    String line;
+	    while ((line = lin.readLine()) != null) {
+		/*
+		 * Strip trailing whitespace.  Can't use trim method
+		 * because it's too aggressive.  Some bogus MIME
+		 * messages will include control characters in the
+		 * boundary string.
+		 */
+		int i;
+		for (i = line.length() - 1; i >= 0; i--) {
+		    char c = line.charAt(i);
+		    if (!(c == ' ' || c == '\t'))
+			break;
+		}
+		line = line.substring(0, i + 1);
+		if (boundary != null) {
+		    if (line.equals(boundary))
+			break;
+		    if (line.length() == boundary.length() + 2 &&
+			    line.startsWith(boundary) && line.endsWith("--")) {
+			line = null;	// signal end of multipart
+			break;
+		    }
+		} else {
+		    /*
+		     * Boundary hasn't been defined, does this line
+		     * look like a boundary?  If so, assume it is
+		     * the boundary and save it.
+		     */
+		    if (line.length() > 2 && line.startsWith("--")) {
+			if (line.length() > 4 && allDashes(line)) {
+			    /*
+			     * The first boundary-like line we find is
+			     * probably *not* the end-of-multipart boundary
+			     * line.  More likely it's a line full of dashes
+			     * in the preamble text.  Just keep reading.
+			     */
+			} else {
+			    boundary = line;
+			    break;
+			}
+		    }
+		}
+
+		// save the preamble after skipping blank lines
+		if (line.length() > 0) {
+		    // accumulate the preamble
+		    if (preamblesb == null)
+			preamblesb = new StringBuilder(line.length() + 2);
+		    preamblesb.append(line).append(System.lineSeparator());
+		}
+	    }
+
+	    if (preamblesb != null)
+		preamble = preamblesb.toString();
+
+	    if (line == null) {
+		if (allowEmpty)
+		    return;
+		else
+		    throw new ParseException("Missing start boundary");
+	    }
+
+	    // save individual boundary bytes for comparison later
+	    byte[] bndbytes = ASCIIUtility.getBytes(boundary);
+	    int bl = bndbytes.length;
+
+	    /*
+	     * Compile Boyer-Moore parsing tables.
+	     */
+
+	    // initialize Bad Character Shift table
+	    int[] bcs = new int[256];
+	    for (int i = 0; i < bl; i++)
+		bcs[bndbytes[i] & 0xff] = i + 1;
+
+	    // initialize Good Suffix Shift table
+	    int[] gss = new int[bl];
+	NEXT:
+	    for (int i = bl; i > 0; i--) {
+		int j;	// the beginning index of the suffix being considered
+		for (j = bl - 1; j >= i; j--) {
+		    // Testing for good suffix
+		    if (bndbytes[j] == bndbytes[j - i]) {
+			// bndbytes[j..len] is a good suffix
+			gss[j - 1] = i;
+		    } else {
+		       // No match. The array has already been
+		       // filled up with correct values before.
+		       continue NEXT;
+		    }
+		}
+		while (j > 0)
+		    gss[--j] = i;
+	    }
+	    gss[bl - 1] = 1;
+ 
+	    /*
+	     * Read and process body parts until we see the
+	     * terminating boundary line (or EOF).
+	     */
+	    boolean done = false;
+	getparts:
+	    while (!done) {
+		InternetHeaders headers = null;
+		if (sin != null) {
+		    start = sin.getPosition();
+		    // skip headers
+		    while ((line = lin.readLine()) != null && line.length() > 0)
+			;
+		    if (line == null) {
+			if (!ignoreMissingEndBoundary)
+			    throw new ParseException(
+					"missing multipart end boundary");
+			// assume there's just a missing end boundary
+			complete = false;
+			break getparts;
+		    }
+		} else {
+		    // collect the headers for this body part
+		    headers = createInternetHeaders(in);
+		}
+
+		if (!in.markSupported())
+		    throw new MessagingException("Stream doesn't support mark");
+
+		ByteArrayOutputStream buf = null;
+		// if we don't have a shared input stream, we copy the data
+		if (sin == null)
+		    buf = new ByteArrayOutputStream();
+		else
+		    end = sin.getPosition();
+		int b;
+
+		/*
+		 * These buffers contain the bytes we're checking
+		 * for a match.  inbuf is the current buffer and
+		 * previnbuf is the previous buffer.  We need the
+		 * previous buffer to check that we're preceeded
+		 * by an EOL.
+		 */
+		// XXX - a smarter algorithm would use a sliding window
+		//	 over a larger buffer
+		byte[] inbuf = new byte[bl];
+		byte[] previnbuf = new byte[bl];
+		int inSize = 0;		// number of valid bytes in inbuf
+		int prevSize = 0;	// number of valid bytes in previnbuf
+		int eolLen;
+		boolean first = true;
+
+		/*
+		 * Read and save the content bytes in buf.
+		 */
+		for (;;) {
+		    in.mark(bl + 4 + 1000); // bnd + "--\r\n" + lots of LWSP
+		    eolLen = 0;
+		    inSize = readFully(in, inbuf, 0, bl);
+		    if (inSize < bl) {
+			// hit EOF
+			if (!ignoreMissingEndBoundary)
+			    throw new ParseException(
+					"missing multipart end boundary");
+			if (sin != null)
+			    end = sin.getPosition();
+			complete = false;
+			done = true;
+			break;
+		    }
+		    // check whether inbuf contains a boundary string
+		    int i;
+		    for (i = bl - 1; i >= 0; i--) {
+			if (inbuf[i] != bndbytes[i])
+			    break;
+		    }
+		    if (i < 0) {	// matched all bytes
+			eolLen = 0;
+			if (!first) {
+			    // working backwards, find out if we were preceeded
+			    // by an EOL, and if so find its length
+			    b = previnbuf[prevSize - 1];
+			    if (b == '\r' || b == '\n') {
+				eolLen = 1;
+				if (b == '\n' && prevSize >= 2) {
+				    b = previnbuf[prevSize - 2];
+				    if (b == '\r')
+					eolLen = 2;
+				}
+			    }
+			}
+			if (first || eolLen > 0) {	// yes, preceed by EOL
+			    if (sin != null) {
+				// update "end", in case this really is
+				// a valid boundary
+				end = sin.getPosition() - bl - eolLen;
+			    }
+			    // matched the boundary, check for last boundary
+			    int b2 = in.read();
+			    if (b2 == '-') {
+				if (in.read() == '-') {
+				    complete = true;
+				    done = true;
+				    break;	// ignore trailing text
+				}
+			    }
+			    // skip linear whitespace
+			    while (b2 == ' ' || b2 == '\t')
+				b2 = in.read();
+			    // check for end of line
+			    if (b2 == '\n')
+				break;	// got it!  break out of the loop
+			    if (b2 == '\r') {
+				in.mark(1);
+				if (in.read() != '\n')
+				    in.reset();
+				break;	// got it!  break out of the loop
+			    }
+			}
+			i = 0;
+		    }
+
+		    /*
+		     * Get here if boundary didn't match,
+		     * wasn't preceeded by EOL, or wasn't
+		     * followed by whitespace or EOL.
+		     */
+
+		    // compute how many bytes we can skip
+		    int skip = Math.max(i + 1 - bcs[inbuf[i] & 0x7f], gss[i]);
+		    // want to keep at least two characters
+		    if (skip < 2) {
+			// only skipping one byte, save one byte
+			// from previous buffer as well
+			// first, write out bytes we're done with
+			if (sin == null && prevSize > 1)
+			    buf.write(previnbuf, 0, prevSize - 1);
+			in.reset();
+			skipFully(in, 1);
+			if (prevSize >= 1) {	// is there a byte to save?
+			    // yes, save one from previous and one from current
+			    previnbuf[0] = previnbuf[prevSize - 1];
+			    previnbuf[1] = inbuf[0];
+			    prevSize = 2;
+			} else {
+			    // no previous bytes to save, can only save current
+			    previnbuf[0] = inbuf[0];
+			    prevSize = 1;
+			}
+		    } else {
+			// first, write out data from previous buffer before
+			// we dump it
+			if (prevSize > 0 && sin == null)
+			    buf.write(previnbuf, 0, prevSize);
+			// all the bytes we're skipping are saved in previnbuf
+			prevSize = skip;
+			in.reset();
+			skipFully(in, prevSize);
+			// swap buffers
+			byte[] tmp = inbuf;
+			inbuf = previnbuf;
+			previnbuf = tmp;
+		    }
+		    first = false;
+		}
+
+		/*
+		 * Create a MimeBody element to represent this body part.
+		 */
+		MimeBodyPart part;
+		if (sin != null) {
+		    part = createMimeBodyPartIs(sin.newStream(start, end));
+		} else {
+		    // write out data from previous buffer, not including EOL
+		    if (prevSize - eolLen > 0)
+			buf.write(previnbuf, 0, prevSize - eolLen);
+		    // if we didn't find a trailing boundary,
+		    // the current buffer has data we need too
+		    if (!complete && inSize > 0)
+			buf.write(inbuf, 0, inSize);
+		    part = createMimeBodyPart(headers, buf.toByteArray());
+		}
+		super.addBodyPart(part);
+	    }
+	} catch (IOException ioex) {
+	    throw new MessagingException("IO Error", ioex);
+	} finally {
+	    try {
+		in.close();
+	    } catch (IOException cex) {
+		// ignore
+	    }
+	}
+
+	parsed = true;
+    }
+
+    /**
+     * Is the string all dashes ('-')?
+     */
+    private static boolean allDashes(String s) {
+	for (int i = 0; i < s.length(); i++) {
+	    if (s.charAt(i) != '-')
+		return false;
+	}
+	return true;
+    }
+
+    /**
+     * Read data from the input stream to fill the buffer starting
+     * at the specified offset with the specified number of bytes.
+     * If len is zero, return zero.  If at EOF, return -1.  Otherwise,
+     * return the number of bytes read.  Call the read method on the
+     * input stream as many times as necessary to read len bytes.
+     *
+     * @param	in	InputStream to read from
+     * @param	buf	buffer to read into
+     * @param	off	offset in the buffer for first byte
+     * @param	len	number of bytes to read
+     * @return		-1 on EOF, otherwise number of bytes read
+     * @exception	IOException	on I/O errors
+     */
+    private static int readFully(InputStream in, byte[] buf, int off, int len)
+				throws IOException {
+	if (len == 0)
+	    return 0;
+	int total = 0;
+	while (len > 0) {
+	    int bsize = in.read(buf, off, len);
+	    if (bsize <= 0)	// should never be zero
+		break;
+	    off += bsize;
+	    total += bsize;
+	    len -= bsize;
+	}
+	return total > 0 ? total : -1;
+    }
+
+    /**
+     * Skip the specified number of bytes, repeatedly calling
+     * the skip method as necessary.
+     */
+    private void skipFully(InputStream in, long offset) throws IOException {
+	while (offset > 0) {
+	    long cur = in.skip(offset);
+	    if (cur <= 0)
+		throw new EOFException("can't skip");
+	    offset -= cur;
+	}
+    }
+
+    /**
+     * 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
+     * @return	an InternetHeaders object
+     * @exception  	MessagingException for failures
+     * @since		JavaMail 1.2
+     */
+    protected InternetHeaders createInternetHeaders(InputStream is)
+				throws MessagingException {
+	return new InternetHeaders(is);
+    }
+
+    /**
+     * Create and return a MimeBodyPart object to represent a
+     * body part parsed from the InputStream.  Subclasses can override
+     * this method to return a subclass of MimeBodyPart, if
+     * necessary.  This implementation simply constructs and returns
+     * a MimeBodyPart object.
+     *
+     * @param	headers		the headers for the body part
+     * @param	content		the content of the body part
+     * @return	a MimeBodyPart
+     * @exception  		MessagingException for failures
+     * @since			JavaMail 1.2
+     */
+    protected MimeBodyPart createMimeBodyPart(InternetHeaders headers,
+				byte[] content) throws MessagingException {
+	return new MimeBodyPart(headers, content);
+    }
+
+    /**
+     * Create and return a MimeBodyPart object to represent a
+     * body part parsed from the InputStream.  Subclasses can override
+     * this method to return a subclass of MimeBodyPart, if
+     * necessary.  This implementation simply constructs and returns
+     * a MimeBodyPart object.
+     *
+     * @param	is		InputStream containing the body part
+     * @return	a MimeBodyPart
+     * @exception  		MessagingException for failures
+     * @since			JavaMail 1.2
+     */
+    protected MimeBodyPart createMimeBodyPart(InputStream is)
+				throws MessagingException {
+	return new MimeBodyPart(is);
+    }
+
+    private MimeBodyPart createMimeBodyPartIs(InputStream is)
+				throws MessagingException {
+	try {
+	    return createMimeBodyPart(is);
+	} finally {
+	    try {
+		is.close();
+	    } catch (IOException ex) {
+		// ignore it
+	    }
+	}
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/MimePart.java b/mail/src/main/java/javax/mail/internet/MimePart.java
new file mode 100644
index 0000000..b43fc4a
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/MimePart.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.*;
+import java.io.*;
+import java.util.Enumeration;
+
+/**
+ * The MimePart interface models an <strong>Entity</strong> as defined
+ * by MIME (RFC2045, Section 2.4). <p>
+ *
+ * MimePart extends the Part interface to add additional RFC822 and MIME
+ * specific semantics and attributes. It provides the base interface for
+ * the MimeMessage and  MimeBodyPart classes 
+ * 
+ * <hr> <strong>A note on RFC822 and MIME headers</strong><p>
+ *
+ * RFC822 and MIME header fields <strong>must</strong> contain only 
+ * US-ASCII characters. If a header contains non US-ASCII characters,
+ * it must be encoded as per the rules in RFC 2047. 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. <p>
+ *
+ * @see		MimeUtility
+ * @see		javax.mail.Part
+ * @author 	John Mani
+ */
+
+public interface MimePart extends Part {
+
+    /**
+     * Get the values of all header fields available for this header,
+     * returned as a single String, with the values separated by the 
+     * delimiter. If the delimiter is <code>null</code>, only the 
+     * first value is returned.
+     *
+     * @param name		the name of this header
+     * @param delimiter		delimiter between fields in returned string
+     * @return                  the value fields for all headers with 
+     *				this name
+     * @exception       	MessagingException for failures
+     */
+    public String getHeader(String name, String delimiter)
+				throws MessagingException;
+
+    /**
+     * Add a raw RFC822 header-line. 
+     *
+     * @param	line	the line to add
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this Part is
+     *			obtained from a READ_ONLY folder
+     * @exception       MessagingException for other failures
+     */
+    public void addHeaderLine(String line) throws MessagingException;
+
+    /**
+     * Get all header lines as an Enumeration of Strings. A Header
+     * line is a raw RFC822 header-line, containing both the "name" 
+     * and "value" field. 
+     *
+     * @return	an Enumeration of Strings
+     * @exception	MessagingException for failures
+     */
+    public Enumeration<String> getAllHeaderLines() throws MessagingException;
+
+    /**
+     * Get matching header lines as an Enumeration of Strings. 
+     * A Header line is a raw RFC822 header-line, containing both 
+     * the "name" and "value" field.
+     *
+     * @param	names	the headers to return
+     * @return	an Enumeration of Strings
+     * @exception	MessagingException for failures
+     */
+    public Enumeration<String> getMatchingHeaderLines(String[] names)
+			throws MessagingException;
+
+    /**
+     * Get non-matching header lines as an Enumeration of Strings. 
+     * A Header line is a raw RFC822 header-line, containing both 
+     * the "name"  and "value" field.
+     *
+     * @param	names	the headers to not return
+     * @return	an Enumeration of Strings
+     * @exception	MessagingException for failures
+     */
+    public Enumeration<String> getNonMatchingHeaderLines(String[] names)
+			throws MessagingException;
+
+    /**
+     * Get the transfer encoding of this part.
+     *
+     * @return		content-transfer-encoding
+     * @exception	MessagingException for failures
+     */
+    public String getEncoding() throws MessagingException;
+
+    /**
+     * Get the Content-ID of this part. Returns null if none present.
+     *
+     * @return		content-ID
+     * @exception	MessagingException for failures
+     */
+    public String getContentID() throws MessagingException;
+
+    /**
+     * Get the Content-MD5 digest of this part. Returns null if
+     * none present.
+     *
+     * @return		content-MD5
+     * @exception	MessagingException for failures
+     */
+    public String getContentMD5() throws MessagingException;
+
+    /**
+     * Set the Content-MD5 of this part.
+     *
+     * @param  md5	the MD5 value
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this Part is
+     *			obtained from a READ_ONLY folder
+     */
+    public void setContentMD5(String md5) throws MessagingException;
+
+    /**
+     * Get the language tags specified in the Content-Language header
+     * of this MimePart. The Content-Language header is defined by
+     * RFC 1766. Returns <code>null</code> if this header is not
+     * available.
+     *
+     * @return	array of content language strings
+     * @exception	MessagingException for failures
+     */
+    public String[] getContentLanguage() throws MessagingException;
+
+    /**
+     * Set the Content-Language header of this MimePart. The
+     * Content-Language header is defined by RFC1766.
+     *
+     * @param languages	array of language tags
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this Part is
+     *			obtained from a READ_ONLY folder
+     */
+    public void setContentLanguage(String[] languages)
+			throws MessagingException;
+    
+    /**
+     * 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
+     * <code>text</code> 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 charset parameter.
+     *
+     * @param	text	the text content to set
+     * @exception	MessagingException	if an error occurs
+     * @see	#setText(String text, String charset)
+     */
+    @Override
+    public void setText(String text) throws MessagingException;
+
+    /**
+     * 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
+     * "charset" parameter.
+     *
+     * @param	text	the text content to set
+     * @param	charset	the charset to use for the text
+     * @exception	MessagingException	if an error occurs
+     */
+    public void setText(String text, String charset)
+			throws MessagingException;
+
+    /**
+     * Convenience method that sets the given String as this part's
+     * content, with a primary MIME type of "text" and the specified
+     * MIME subtype.  The given Unicode string will be charset-encoded
+     * using the specified charset. The charset is also used to set
+     * the "charset" parameter.
+     *
+     * @param	text	the text content to set
+     * @param	charset	the charset to use for the text
+     * @param	subtype	the MIME subtype to use (e.g., "html")
+     * @exception	MessagingException	if an error occurs
+     * @since	JavaMail 1.4
+     */
+    public void setText(String text, String charset, String subtype)
+                        throws MessagingException;
+}
diff --git a/mail/src/main/java/javax/mail/internet/MimePartDataSource.java b/mail/src/main/java/javax/mail/internet/MimePartDataSource.java
new file mode 100644
index 0000000..0e481ae
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/MimePartDataSource.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.*;
+import javax.activation.*;
+import java.io.*;
+import java.net.UnknownServiceException;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.FolderClosedIOException;
+
+/**
+ * A utility class that implements a DataSource out of
+ * a MimePart. This class is primarily meant for service providers.
+ *
+ * @see		javax.mail.internet.MimePart
+ * @see		javax.activation.DataSource
+ * @author 	John Mani
+ */
+
+public class MimePartDataSource implements DataSource, MessageAware {
+    /**
+     * The MimePart that provides the data for this DataSource.
+     *
+     * @since	JavaMail 1.4
+     */
+    protected MimePart part;
+
+    private MessageContext context;
+
+    /**
+     * Constructor, that constructs a DataSource from a MimePart.
+     *
+     * @param	part	the MimePart
+     */
+    public MimePartDataSource(MimePart part) {
+	this.part = part;
+    }
+
+    /**
+     * Returns an input stream from this  MimePart. <p>
+     *
+     * This method applies the appropriate transfer-decoding, based 
+     * on the Content-Transfer-Encoding attribute of this MimePart.
+     * Thus the returned input stream is a decoded stream of bytes.<p>
+     *
+     * This implementation obtains the raw content from the Part
+     * using the <code>getContentStream()</code> method and decodes
+     * it using the <code>MimeUtility.decode()</code> method.
+     *
+     * @see	javax.mail.internet.MimeMessage#getContentStream
+     * @see	javax.mail.internet.MimeBodyPart#getContentStream
+     * @see	javax.mail.internet.MimeUtility#decode
+     * @return 	decoded input stream
+     */
+    @Override
+    public InputStream getInputStream() throws IOException {
+	InputStream is;
+
+	try {
+	    if (part instanceof MimeBodyPart)
+		is = ((MimeBodyPart)part).getContentStream();
+	    else if (part instanceof MimeMessage)
+		is = ((MimeMessage)part).getContentStream();
+	    else
+		throw new MessagingException("Unknown part");
+	    
+	    String encoding =
+		MimeBodyPart.restrictEncoding(part, part.getEncoding());
+	    if (encoding != null)
+		return MimeUtility.decode(is, encoding);
+	    else
+		return is;
+	} catch (FolderClosedException fex) {
+	    throw new FolderClosedIOException(fex.getFolder(),
+						fex.getMessage());
+	} catch (MessagingException mex) {
+	    IOException ioex = new IOException(mex.getMessage());
+	    ioex.initCause(mex);
+	    throw ioex;
+	}
+    }
+
+    /**
+     * DataSource method to return an output stream. <p>
+     *
+     * This implementation throws the UnknownServiceException.
+     */
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+	throw new UnknownServiceException("Writing not supported");
+    }
+
+    /**
+     * Returns the content-type of this DataSource. <p>
+     *
+     * This implementation just invokes the <code>getContentType</code>
+     * method on the MimePart.
+     */
+    @Override
+    public String getContentType() {
+	try {
+	    return part.getContentType();
+	} catch (MessagingException mex) {
+	    // would like to be able to reflect the exception to the
+	    // application, but since we can't do that we return a
+	    // generic "unknown" value here and hope for another
+	    // exception later.
+	    return "application/octet-stream";
+	}
+    }
+
+    /**
+     * DataSource method to return a name.  <p>
+     *
+     * This implementation just returns an empty string.
+     */
+    @Override
+    public String getName() {
+	try {
+	    if (part instanceof MimeBodyPart)
+		return ((MimeBodyPart)part).getFileName();
+	} catch (MessagingException mex) {
+	    // ignore it
+	}
+	return "";
+    }
+
+    /**
+     * Return the <code>MessageContext</code> for the current part.
+     * @since JavaMail 1.1
+     */
+    @Override
+    public synchronized MessageContext getMessageContext() {
+	if (context == null)
+	    context = new MessageContext(part);
+	return context;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/MimeUtility.java b/mail/src/main/java/javax/mail/internet/MimeUtility.java
new file mode 100644
index 0000000..2bb043b
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/MimeUtility.java
@@ -0,0 +1,1708 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.MessagingException;
+import javax.mail.EncodingAware;
+import javax.activation.*;
+import java.util.*;
+import java.io.*;
+import java.nio.charset.Charset;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.ASCIIUtility;
+import com.sun.mail.util.BASE64DecoderStream;
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.BEncoderStream;
+import com.sun.mail.util.LineInputStream;
+import com.sun.mail.util.LineOutputStream;
+import com.sun.mail.util.LogOutputStream;
+import com.sun.mail.util.QDecoderStream;
+import com.sun.mail.util.QEncoderStream;
+import com.sun.mail.util.QPDecoderStream;
+import com.sun.mail.util.QPEncoderStream;
+import com.sun.mail.util.UUDecoderStream;
+import com.sun.mail.util.UUEncoderStream;
+
+/**
+ * This is a utility class that provides various MIME related
+ * functionality. <p>
+ *
+ * There are a set of methods to encode and decode MIME headers as 
+ * per RFC 2047.  Note that, in general, these methods are
+ * <strong>not</strong> needed when using methods such as
+ * <code>setSubject</code> and <code>setRecipients</code>; JavaMail
+ * will automatically encode and decode data when using these "higher
+ * level" methods.  The methods below are only needed when maniuplating
+ * raw MIME headers using <code>setHeader</code> and <code>getHeader</code>
+ * methods.  A brief description on handling such headers is given below: <p>
+ *
+ * RFC 822 mail headers <strong>must</strong> contain only US-ASCII
+ * characters. Headers that contain non US-ASCII characters must be
+ * encoded so that they contain only US-ASCII characters. Basically,
+ * this process involves using either BASE64 or QP to encode certain
+ * characters. RFC 2047 describes this in detail. <p>
+ *
+ * In Java, Strings contain (16 bit) Unicode characters. ASCII is a
+ * subset of Unicode (and occupies the range 0 - 127). A String
+ * that contains only ASCII characters is already mail-safe. If the
+ * String contains non US-ASCII characters, it must be encoded. An
+ * additional complexity in this step is that since Unicode is not
+ * yet a widely used charset, one might want to first charset-encode
+ * the String into another charset and then do the transfer-encoding.
+ * <p>
+ * Note that to get the actual bytes of a mail-safe String (say,
+ * for sending over SMTP), one must do 
+ * <blockquote><pre>
+ *
+ *	byte[] bytes = string.getBytes("iso-8859-1");	
+ *
+ * </pre></blockquote><p>
+ * 
+ * The <code>setHeader</code> and <code>addHeader</code> methods
+ * on MimeMessage and MimeBodyPart assume that the given header values
+ * are Unicode strings that contain only US-ASCII characters. Hence
+ * the callers of those methods must insure that the values they pass
+ * do not contain non US-ASCII characters. The methods in this class 
+ * help do this. <p>
+ *
+ * The <code>getHeader</code> family of methods on MimeMessage and
+ * MimeBodyPart return the raw header value. These might be encoded
+ * as per RFC 2047, and if so, must be decoded into Unicode Strings.
+ * The methods in this class help to do this. <p>
+ *
+ * Several System properties control strict conformance to the MIME
+ * spec.  Note that these are not session properties but must be set
+ * globally as System properties. <p>
+ *
+ * The <code>mail.mime.decodetext.strict</code> property controls
+ * decoding of MIME encoded words.  The MIME spec requires that encoded
+ * words start at the beginning of a whitespace separated word.  Some
+ * mailers incorrectly include encoded words in the middle of a word.
+ * If the <code>mail.mime.decodetext.strict</code> System property is
+ * set to <code>"false"</code>, an attempt will be made to decode these
+ * illegal encoded words. The default is true. <p>
+ *
+ * The <code>mail.mime.encodeeol.strict</code> property controls the
+ * choice of Content-Transfer-Encoding for MIME parts that are not of
+ * type "text".  Often such parts will contain textual data for which
+ * an encoding that allows normal end of line conventions is appropriate.
+ * In rare cases, such a part will appear to contain entirely textual
+ * data, but will require an encoding that preserves CR and LF characters
+ * without change.  If the <code>mail.mime.encodeeol.strict</code>
+ * System property is set to <code>"true"</code>, such an encoding will
+ * be used when necessary.  The default is false. <p>
+ *
+ * In addition, the <code>mail.mime.charset</code> System property can
+ * be used to specify the default MIME charset to use for encoded words
+ * and text parts that don't otherwise specify a charset.  Normally, the
+ * default MIME charset is derived from the default Java charset, as
+ * specified in the <code>file.encoding</code> System property.  Most
+ * applications will have no need to explicitly set the default MIME
+ * charset.  In cases where the default MIME charset to be used for
+ * mail messages is different than the charset used for files stored on
+ * the system, this property should be set. <p>
+ *
+ * The current implementation also supports the following System property.
+ * <p>
+ * The <code>mail.mime.ignoreunknownencoding</code> property controls
+ * whether unknown values in the <code>Content-Transfer-Encoding</code>
+ * header, as passed to the <code>decode</code> method, cause an exception.
+ * If set to <code>"true"</code>, unknown values are ignored and 8bit
+ * encoding is assumed.  Otherwise, unknown values cause a MessagingException
+ * to be thrown.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class MimeUtility {
+
+    // This class cannot be instantiated
+    private MimeUtility() { }
+
+    public static final int ALL = -1;
+
+    // cached map of whether a charset is compatible with ASCII
+    // Map<String,Boolean>
+    private static final Map<String, Boolean> nonAsciiCharsetMap
+	    = new HashMap<>();
+
+    private static final boolean decodeStrict =
+	PropUtil.getBooleanSystemProperty("mail.mime.decodetext.strict", true);
+    private static final boolean encodeEolStrict =
+	PropUtil.getBooleanSystemProperty("mail.mime.encodeeol.strict", false);
+    private static final boolean ignoreUnknownEncoding =
+	PropUtil.getBooleanSystemProperty(
+	    "mail.mime.ignoreunknownencoding", false);
+    private static final boolean allowUtf8 =
+	PropUtil.getBooleanSystemProperty("mail.mime.allowutf8", false);
+    /*
+     * The following two properties allow disabling the fold()
+     * and unfold() methods and reverting to the previous behavior.
+     * They should never need to be changed and are here only because
+     * of my paranoid concern with compatibility.
+     */
+    private static final boolean foldEncodedWords =
+	PropUtil.getBooleanSystemProperty("mail.mime.foldencodedwords", false);
+    private static final boolean foldText =
+	PropUtil.getBooleanSystemProperty("mail.mime.foldtext", true);
+
+
+    /**
+     * Get the Content-Transfer-Encoding that should be applied
+     * to the input stream of this DataSource, to make it mail-safe. <p>
+     *
+     * The algorithm used here is: <br>
+     * <ul>
+     * <li>
+     * If the DataSource implements {@link EncodingAware}, ask it
+     * what encoding to use.  If it returns non-null, return that value.
+     * <li>
+     * If the primary type of this datasource is "text" and if all
+     * the bytes in its input stream are US-ASCII, then the encoding
+     * is "7bit". If more than half of the bytes are non-US-ASCII, then
+     * the encoding is "base64". If less than half of the bytes are
+     * non-US-ASCII, then the encoding is "quoted-printable".
+     * <li>
+     * If the primary type of this datasource is not "text", then if
+     * all the bytes of its input stream are US-ASCII, the encoding
+     * is "7bit". If there is even one non-US-ASCII character, the
+     * encoding is "base64".
+     * </ul>
+     *
+     * @param	ds	the DataSource
+     * @return		the encoding. This is either "7bit",
+     *			"quoted-printable" or "base64"
+     */ 
+    public static String getEncoding(DataSource ds) {
+	ContentType cType = null;
+	InputStream is = null;
+	String encoding = null;
+
+	if (ds instanceof EncodingAware) {
+	    encoding = ((EncodingAware)ds).getEncoding();
+	    if (encoding != null)
+		return encoding;
+	}
+	try {
+	    cType = new ContentType(ds.getContentType());
+	    is = ds.getInputStream();
+
+	    boolean isText = cType.match("text/*");
+	    // if not text, stop processing when we see non-ASCII
+	    int i = checkAscii(is, ALL, !isText);
+	    switch (i) {
+	    case ALL_ASCII:
+		encoding = "7bit"; // all ASCII
+		break;
+	    case MOSTLY_ASCII:
+		if (isText && nonAsciiCharset(cType))
+		    encoding = "base64"; // charset isn't compatible with ASCII
+		else
+		    encoding = "quoted-printable";	// mostly ASCII
+		break;
+	    default:
+		encoding = "base64"; // mostly binary
+		break;
+	    }
+
+	} catch (Exception ex) {
+	    return "base64"; // what else ?!
+	} finally {
+	    // Close the input stream
+	    try {
+		if (is != null)
+		    is.close();
+	    } catch (IOException ioex) { }
+	}
+
+	return encoding;
+    }
+
+    /**
+     * Determine whether the charset in the Content-Type is compatible
+     * with ASCII or not.  A charset is compatible with ASCII if the
+     * encoded byte stream representing the Unicode string "\r\n" is
+     * the ASCII characters CR and LF.  For example, the utf-16be
+     * charset is not compatible with ASCII.
+     *
+     * For performance, we keep a static map that caches the results.
+     */
+    private static boolean nonAsciiCharset(ContentType ct) {
+	String charset = ct.getParameter("charset");
+	if (charset == null)
+	    return false;
+	charset = charset.toLowerCase(Locale.ENGLISH);
+	Boolean bool;
+	synchronized (nonAsciiCharsetMap) {
+	    bool = nonAsciiCharsetMap.get(charset);
+	}
+	if (bool == null) {
+	    try {
+		byte[] b = "\r\n".getBytes(charset);
+		bool = Boolean.valueOf(
+		    b.length != 2 || b[0] != 015 || b[1] != 012);
+	    } catch (UnsupportedEncodingException uex) {
+		bool = Boolean.FALSE;	// a guess
+	    } catch (RuntimeException ex) {
+		bool = Boolean.TRUE;	// one of the weird ones?
+	    }
+	    synchronized (nonAsciiCharsetMap) {
+		nonAsciiCharsetMap.put(charset, bool);
+	    }
+	}
+	return bool.booleanValue();
+    }
+
+    /**
+     * Same as <code>getEncoding(DataSource)</code> except that instead
+     * of reading the data from an <code>InputStream</code> it uses the
+     * <code>writeTo</code> method to examine the data.  This is more
+     * efficient in the common case of a <code>DataHandler</code>
+     * created with an object and a MIME type (for example, a
+     * "text/plain" String) because all the I/O is done in this
+     * thread.  In the case requiring an <code>InputStream</code> the
+     * <code>DataHandler</code> uses a thread, a pair of pipe streams,
+     * and the <code>writeTo</code> method to produce the data. <p>
+     *
+     * @param	dh	the DataHandler
+     * @return	the Content-Transfer-Encoding
+     * @since	JavaMail 1.2
+     */
+    public static String getEncoding(DataHandler dh) {
+	ContentType cType = null;
+	String encoding = null;
+
+	/*
+	 * Try to pick the most efficient means of determining the
+	 * encoding.  If this DataHandler was created using a DataSource,
+	 * the getEncoding(DataSource) method is typically faster.  If
+	 * the DataHandler was created with an object, this method is
+	 * much faster.  To distinguish the two cases, we use a heuristic.
+	 * A DataHandler created with an object will always have a null name.
+	 * A DataHandler created with a DataSource will usually have a
+	 * non-null name.
+	 *
+	 * XXX - This is actually quite a disgusting hack, but it makes
+	 *	 a common case run over twice as fast.
+	 */
+	if (dh.getName() != null)
+	    return getEncoding(dh.getDataSource());
+
+	try {
+	    cType = new ContentType(dh.getContentType());
+	} catch (Exception ex) {
+	    return "base64"; // what else ?!
+	}
+
+	if (cType.match("text/*")) {
+	    // Check all of the available bytes
+	    AsciiOutputStream aos = new AsciiOutputStream(false, false);
+	    try {
+		dh.writeTo(aos);
+	    } catch (IOException ex) {
+	    	// ignore it, can't happen
+	    }
+	    switch (aos.getAscii()) {
+	    case ALL_ASCII:
+		encoding = "7bit"; // all ascii
+		break;
+	    case MOSTLY_ASCII:
+		encoding = "quoted-printable"; // mostly ascii
+		break;
+	    default:
+		encoding = "base64"; // mostly binary
+		break;
+	    }
+	} else { // not "text"
+	    // Check all of available bytes, break out if we find
+	    // at least one non-US-ASCII character
+	    AsciiOutputStream aos =
+			new AsciiOutputStream(true, encodeEolStrict);
+	    try {
+		dh.writeTo(aos);
+	    } catch (IOException ex) { }	// ignore it
+	    if (aos.getAscii() == ALL_ASCII) // all ascii
+		encoding = "7bit";
+	    else // found atleast one non-ascii character, use b64 
+		encoding = "base64";
+	}
+
+	return encoding;
+    }
+
+    /**
+     * Decode the given input stream. The Input stream returned is
+     * the decoded input stream. All the encodings defined in RFC 2045
+     * are supported here. They include "base64", "quoted-printable",
+     * "7bit", "8bit", and "binary". In addition, "uuencode" is also
+     * supported. <p>
+     *
+     * In the current implementation, if the
+     * <code>mail.mime.ignoreunknownencoding</code> system property is set to
+     * <code>"true"</code>, unknown encoding values are ignored and the
+     * original InputStream is returned.
+     *
+     * @param	is		input stream
+     * @param	encoding	the encoding of the stream.
+     * @return			decoded input stream.
+     * @exception MessagingException	if the encoding is unknown
+     */
+    public static InputStream decode(InputStream is, String encoding)
+		throws MessagingException {
+	if (encoding.equalsIgnoreCase("base64"))
+	    return new BASE64DecoderStream(is);
+	else if (encoding.equalsIgnoreCase("quoted-printable"))
+	    return new QPDecoderStream(is);
+	else if (encoding.equalsIgnoreCase("uuencode") ||
+		 encoding.equalsIgnoreCase("x-uuencode") ||
+		 encoding.equalsIgnoreCase("x-uue"))
+	    return new UUDecoderStream(is);
+	else if (encoding.equalsIgnoreCase("binary") ||
+		 encoding.equalsIgnoreCase("7bit") ||
+		 encoding.equalsIgnoreCase("8bit"))
+	    return is;
+	else {
+	    if (!ignoreUnknownEncoding)
+		throw new MessagingException("Unknown encoding: " + encoding);
+	    return is;
+	}
+    }
+
+    /**
+     * Wrap an encoder around the given output stream. 
+     * All the encodings defined in RFC 2045 are supported here. 
+     * They include "base64", "quoted-printable", "7bit", "8bit" and
+     * "binary". In addition, "uuencode" is also supported.
+     *
+     * @param	os		output stream
+     * @param	encoding	the encoding of the stream. 
+     * @return			output stream that applies the
+     *				specified encoding.
+     * @exception MessagingException	if the encoding is unknown
+     */
+    public static OutputStream encode(OutputStream os, String encoding)
+		throws MessagingException {
+        if (encoding == null)
+	    return os;
+	else if (encoding.equalsIgnoreCase("base64"))
+	    return new BASE64EncoderStream(os);
+	else if (encoding.equalsIgnoreCase("quoted-printable"))
+	    return new QPEncoderStream(os);
+	else if (encoding.equalsIgnoreCase("uuencode") ||
+		 encoding.equalsIgnoreCase("x-uuencode") ||
+		 encoding.equalsIgnoreCase("x-uue"))
+	    return new UUEncoderStream(os);
+	else if (encoding.equalsIgnoreCase("binary") ||
+		 encoding.equalsIgnoreCase("7bit") ||
+		 encoding.equalsIgnoreCase("8bit"))
+	    return os;
+	else
+	    throw new MessagingException("Unknown encoding: " +encoding);
+    }
+
+    /**
+     * Wrap an encoder around the given output stream.
+     * All the encodings defined in RFC 2045 are supported here.
+     * They include "base64", "quoted-printable", "7bit", "8bit" and
+     * "binary". In addition, "uuencode" is also supported.
+     * The <code>filename</code> parameter is used with the "uuencode"
+     * encoding and is included in the encoded output.
+     *
+     * @param   os              output stream
+     * @param   encoding        the encoding of the stream.
+     * @param   filename        name for the file being encoded (only used
+     *                          with uuencode)
+     * @return                  output stream that applies the
+     *                          specified encoding.
+     * @exception		MessagingException for unknown encodings
+     * @since                   JavaMail 1.2
+     */
+    public static OutputStream encode(OutputStream os, String encoding,
+                                      String filename)
+                throws MessagingException {
+        if (encoding == null)
+            return os;
+        else if (encoding.equalsIgnoreCase("base64"))
+            return new BASE64EncoderStream(os);
+        else if (encoding.equalsIgnoreCase("quoted-printable"))
+            return new QPEncoderStream(os);
+        else if (encoding.equalsIgnoreCase("uuencode") ||
+                 encoding.equalsIgnoreCase("x-uuencode") ||
+                 encoding.equalsIgnoreCase("x-uue"))
+            return new UUEncoderStream(os, filename);
+        else if (encoding.equalsIgnoreCase("binary") ||
+                 encoding.equalsIgnoreCase("7bit") ||
+                 encoding.equalsIgnoreCase("8bit"))
+            return os;
+        else
+            throw new MessagingException("Unknown encoding: " +encoding);
+    }
+
+    /**
+     * Encode a RFC 822 "text" token into mail-safe form as per
+     * RFC 2047. <p>
+     *
+     * The given Unicode string is examined for non US-ASCII
+     * characters. If the string contains only US-ASCII characters,
+     * it is returned as-is.  If the string contains non US-ASCII
+     * characters, it is first character-encoded using the platform's
+     * default charset, then transfer-encoded using either the B or 
+     * Q encoding. The resulting bytes are then returned as a Unicode 
+     * string containing only ASCII  characters. <p>
+     *
+     * Note that this method should be used to encode only 
+     * "unstructured" RFC 822 headers. <p>
+     *
+     * Example of usage:
+     * <blockquote><pre>
+     *
+     *  MimePart part = ...
+     *  String rawvalue = "FooBar Mailer, Japanese version 1.1"
+     *  try {
+     *    // If we know for sure that rawvalue contains only US-ASCII 
+     *    // characters, we can skip the encoding part
+     *    part.setHeader("X-mailer", MimeUtility.encodeText(rawvalue));
+     *  } catch (UnsupportedEncodingException e) {
+     *    // encoding failure
+     *  } catch (MessagingException me) {
+     *   // setHeader() failure
+     *  }
+     *
+     * </pre></blockquote><p>
+     * 
+     * @param	text	Unicode string
+     * @return	Unicode string containing only US-ASCII characters
+     * @exception UnsupportedEncodingException if the encoding fails
+     */
+    public static String encodeText(String text)
+			throws UnsupportedEncodingException {
+	return encodeText(text, null, null);
+    }
+
+    /**
+     * Encode a RFC 822 "text" token into mail-safe form as per
+     * RFC 2047. <p>
+     *
+     * The given Unicode string is examined for non US-ASCII
+     * characters. If the string contains only US-ASCII characters,
+     * it is returned as-is.  If the string contains non US-ASCII
+     * characters, it is first character-encoded using the specified
+     * charset, then transfer-encoded using either the B or Q encoding.
+     * The resulting bytes are then returned as a Unicode string 
+     * containing only ASCII characters. <p>
+     *
+     * Note that this method should be used to encode only 
+     * "unstructured" RFC 822 headers. 
+     * 
+     * @param	text	the header value
+     * @param	charset	the charset. If this parameter is null, the
+     *		platform's default chatset is used.
+     * @param	encoding the encoding to be used. Currently supported
+     *		values are "B" and "Q". If this parameter is null, then
+     *		the "Q" encoding is used if most of characters to be
+     *		encoded are in the ASCII charset, otherwise "B" encoding
+     *		is used.
+     * @return	Unicode string containing only US-ASCII characters
+     * @exception       UnsupportedEncodingException if the charset
+     *			conversion failed.
+     */
+    public static String encodeText(String text, String charset,
+				    String encoding)
+			throws UnsupportedEncodingException {
+	return encodeWord(text, charset, encoding, false);
+    }
+
+    /**
+     * Decode "unstructured" headers, that is, headers that are defined
+     * as '*text' as per RFC 822. <p>
+     *
+     * The string is decoded using the algorithm specified in
+     * RFC 2047, Section 6.1. If the charset-conversion fails
+     * for any sequence, an UnsupportedEncodingException is thrown.
+     * If the String is not an RFC 2047 style encoded header, it is
+     * returned as-is <p>
+     *
+     * Example of usage:
+     * <blockquote><pre>
+     *
+     *  MimePart part = ...
+     *  String rawvalue = null;
+     *  String  value = null;
+     *  try {
+     *    if ((rawvalue = part.getHeader("X-mailer")[0]) != null)
+     *      value = MimeUtility.decodeText(rawvalue);
+     *  } catch (UnsupportedEncodingException e) {
+     *      // Don't care
+     *      value = rawvalue;
+     *  } catch (MessagingException me) { }
+     *
+     *  return value;
+     *
+     * </pre></blockquote><p>
+     *
+     * @param	etext	the possibly encoded value
+     * @return	the decoded text
+     * @exception       UnsupportedEncodingException if the charset
+     *			conversion failed.
+     */
+    public static String decodeText(String etext)
+		throws UnsupportedEncodingException {
+	/*
+	 * We look for sequences separated by "linear-white-space".
+	 * (as per RFC 2047, Section 6.1)
+	 * RFC 822 defines "linear-white-space" as SPACE | HT | CR | NL.
+	 */
+	String lwsp = " \t\n\r";
+	StringTokenizer st;
+
+	/*
+	 * First, lets do a quick run thru the string and check
+	 * whether the sequence "=?"  exists at all. If none exists,
+	 * we know there are no encoded-words in here and we can just
+	 * return the string as-is, without suffering thru the later 
+	 * decoding logic. 
+	 * This handles the most common case of unencoded headers 
+	 * efficiently.
+	 */
+	if (etext.indexOf("=?") == -1)
+	    return etext;
+
+	// Encoded words found. Start decoding ...
+
+	st = new StringTokenizer(etext, lwsp, true);
+	StringBuilder sb = new StringBuilder();  // decode buffer
+	StringBuilder wsb = new StringBuilder(); // white space buffer
+	boolean prevWasEncoded = false;
+
+	while (st.hasMoreTokens()) {
+	    char c;
+	    String s = st.nextToken();
+	    // If whitespace, append it to the whitespace buffer
+	    if (((c = s.charAt(0)) == ' ') || (c == '\t') ||
+		(c == '\r') || (c == '\n'))
+		wsb.append(c);
+	    else {
+		// Check if token is an 'encoded-word' ..
+		String word;
+		try {
+		    word = decodeWord(s);
+		    // Yes, this IS an 'encoded-word'.
+		    if (!prevWasEncoded && wsb.length() > 0) {
+			// if the previous word was also encoded, we
+			// should ignore the collected whitespace. Else
+			// we include the whitespace as well.
+			sb.append(wsb);
+		    }
+		    prevWasEncoded = true;
+		} catch (ParseException pex) {
+		    // This is NOT an 'encoded-word'.
+		    word = s;
+		    // possibly decode inner encoded words
+		    if (!decodeStrict) {
+			String dword = decodeInnerWords(word);
+			if (dword != word) {
+			    // if a different String object was returned,
+			    // decoding was done.
+			    if (prevWasEncoded && word.startsWith("=?")) {
+				// encoded followed by encoded,
+				// throw away whitespace between
+			    } else {
+				// include collected whitespace ..
+				if (wsb.length() > 0)
+				    sb.append(wsb);
+			    }
+			    // did original end with encoded?
+			    prevWasEncoded = word.endsWith("?=");
+			    word = dword;
+			} else {
+			    // include collected whitespace ..
+			    if (wsb.length() > 0)
+				sb.append(wsb);
+			    prevWasEncoded = false;
+			}
+		    } else {
+			// include collected whitespace ..
+			if (wsb.length() > 0)
+			    sb.append(wsb);
+			prevWasEncoded = false;
+		    }
+		}
+		sb.append(word); // append the actual word
+		wsb.setLength(0); // reset wsb for reuse
+	    }
+	}
+	sb.append(wsb);		// append trailing whitespace
+	return sb.toString();
+    }
+
+    /**
+     * Encode a RFC 822 "word" token into mail-safe form as per
+     * RFC 2047. <p>
+     *
+     * The given Unicode string is examined for non US-ASCII
+     * characters. If the string contains only US-ASCII characters,
+     * it is returned as-is.  If the string contains non US-ASCII
+     * characters, it is first character-encoded using the platform's
+     * default charset, then transfer-encoded using either the B or 
+     * Q encoding. The resulting bytes are then returned as a Unicode 
+     * string containing only ASCII  characters. <p>
+     * 
+     * This method is meant to be used when creating RFC 822 "phrases".
+     * The InternetAddress class, for example, uses this to encode
+     * it's 'phrase' component.
+     *
+     * @param	word	Unicode string
+     * @return	Array of Unicode strings containing only US-ASCII 
+     *		characters.
+     * @exception UnsupportedEncodingException if the encoding fails
+     */
+    public static String encodeWord(String word) 
+			throws UnsupportedEncodingException {
+	return encodeWord(word, null, null);
+    }
+
+    /**
+     * Encode a RFC 822 "word" token into mail-safe form as per
+     * RFC 2047. <p>
+     *
+     * The given Unicode string is examined for non US-ASCII
+     * characters. If the string contains only US-ASCII characters,
+     * it is returned as-is.  If the string contains non US-ASCII
+     * characters, it is first character-encoded using the specified
+     * charset, then transfer-encoded using either the B or Q encoding.
+     * The resulting bytes are then returned as a Unicode string 
+     * containing only ASCII characters. <p>
+     * 
+     * @param	word	Unicode string
+     * @param	charset	the MIME charset
+     * @param	encoding the encoding to be used. Currently supported
+     *		values are "B" and "Q". If this parameter is null, then
+     *		the "Q" encoding is used if most of characters to be
+     *		encoded are in the ASCII charset, otherwise "B" encoding
+     *		is used.
+     * @return	Unicode string containing only US-ASCII characters
+     * @exception UnsupportedEncodingException if the encoding fails
+     */
+    public static String encodeWord(String word, String charset, 
+				    String encoding)
+    			throws UnsupportedEncodingException {
+	return encodeWord(word, charset, encoding, true);
+    }
+
+    /*
+     * Encode the given string. The parameter 'encodingWord' should
+     * be true if a RFC 822 "word" token is being encoded and false if a
+     * RFC 822 "text" token is being encoded. This is because the 
+     * "Q" encoding defined in RFC 2047 has more restrictions when
+     * encoding "word" tokens. (Sigh)
+     */ 
+    private static String encodeWord(String string, String charset,
+				     String encoding, boolean encodingWord)
+			throws UnsupportedEncodingException {
+
+	// If 'string' contains only US-ASCII characters, just
+	// return it.
+	int ascii = checkAscii(string);
+	if (ascii == ALL_ASCII)
+	    return string;
+
+	// Else, apply the specified charset conversion.
+	String jcharset;
+	if (charset == null) { // use default charset
+	    jcharset = getDefaultJavaCharset(); // the java charset
+	    charset = getDefaultMIMECharset(); // the MIME equivalent
+	} else // MIME charset -> java charset
+	    jcharset = javaCharset(charset);
+
+	// If no transfer-encoding is specified, figure one out.
+	if (encoding == null) {
+	    if (ascii != MOSTLY_NONASCII)
+		encoding = "Q";
+	    else
+		encoding = "B";
+	}
+
+	boolean b64;
+	if (encoding.equalsIgnoreCase("B")) 
+	    b64 = true;
+	else if (encoding.equalsIgnoreCase("Q"))
+	    b64 = false;
+	else
+	    throw new UnsupportedEncodingException(
+			"Unknown transfer encoding: " + encoding);
+
+	StringBuilder outb = new StringBuilder(); // the output buffer
+	doEncode(string, b64, jcharset, 
+		 // As per RFC 2047, size of an encoded string should not
+		 // exceed 75 bytes.
+		 // 7 = size of "=?", '?', 'B'/'Q', '?', "?="
+		 75 - 7 - charset.length(), // the available space
+		 "=?" + charset + "?" + encoding + "?", // prefix
+		 true, encodingWord, outb);
+
+	return outb.toString();
+    }
+
+    private static void doEncode(String string, boolean b64, 
+		String jcharset, int avail, String prefix, 
+		boolean first, boolean encodingWord, StringBuilder buf)
+			throws UnsupportedEncodingException {
+
+	// First find out what the length of the encoded version of
+	// 'string' would be.
+	byte[] bytes = string.getBytes(jcharset);
+	int len;
+	if (b64) // "B" encoding
+	    len = BEncoderStream.encodedLength(bytes);
+	else // "Q"
+	    len = QEncoderStream.encodedLength(bytes, encodingWord);
+	
+	int size;
+	if ((len > avail) && ((size = string.length()) > 1)) { 
+	    // If the length is greater than 'avail', split 'string'
+	    // into two and recurse.
+	    // Have to make sure not to split a Unicode surrogate pair.
+	    int split = size / 2;
+	    if (Character.isHighSurrogate(string.charAt(split-1)))
+		split--;
+	    if (split > 0)
+		doEncode(string.substring(0, split), b64, jcharset, 
+			 avail, prefix, first, encodingWord, buf);
+	    doEncode(string.substring(split, size), b64, jcharset,
+		     avail, prefix, false, encodingWord, buf);
+	} else {
+	    // length <= than 'avail'. Encode the given string
+	    ByteArrayOutputStream os = new ByteArrayOutputStream();
+	    OutputStream eos; // the encoder
+	    if (b64) // "B" encoding
+		eos = new BEncoderStream(os);
+	    else // "Q" encoding
+		eos = new QEncoderStream(os, encodingWord);
+	    
+	    try { // do the encoding
+		eos.write(bytes);
+		eos.close();
+	    } catch (IOException ioex) { }
+
+	    byte[] encodedBytes = os.toByteArray(); // the encoded stuff
+	    // Now write out the encoded (all ASCII) bytes into our
+	    // StringBuilder
+	    if (!first) // not the first line of this sequence
+		if (foldEncodedWords)
+		    buf.append("\r\n "); // start a continuation line
+		else
+		    buf.append(" "); // line will be folded later
+
+	    buf.append(prefix);
+	    for (int i = 0; i < encodedBytes.length; i++)
+		buf.append((char)encodedBytes[i]);
+	    buf.append("?="); // terminate the current sequence
+	}
+    }
+
+    /**
+     * The string is parsed using the rules in RFC 2047 and RFC 2231 for
+     * parsing an "encoded-word".  If the parse fails, a ParseException is 
+     * thrown. Otherwise, it is transfer-decoded, and then 
+     * charset-converted into Unicode. If the charset-conversion
+     * fails, an UnsupportedEncodingException is thrown.<p>
+     *
+     * @param	eword	the encoded value
+     * @return	the decoded word
+     * @exception       ParseException if the string is not an
+     *			encoded-word as per RFC 2047 and RFC 2231.
+     * @exception       UnsupportedEncodingException if the charset
+     *			conversion failed.
+     */
+    public static String decodeWord(String eword)
+		throws ParseException, UnsupportedEncodingException {
+
+	if (!eword.startsWith("=?")) // not an encoded word
+	    throw new ParseException(
+		"encoded word does not start with \"=?\": " + eword);
+	
+	// get charset
+	int start = 2; int pos; 
+	if ((pos = eword.indexOf('?', start)) == -1)
+	    throw new ParseException(
+		"encoded word does not include charset: " + eword);
+	String charset = eword.substring(start, pos);
+	int lpos = charset.indexOf('*');	// RFC 2231 language specified?
+	if (lpos >= 0)				// yes, throw it away
+	    charset = charset.substring(0, lpos);
+	charset = javaCharset(charset);
+
+	// get encoding
+	start = pos+1;
+	if ((pos = eword.indexOf('?', start)) == -1)
+	    throw new ParseException(
+		"encoded word does not include encoding: " + eword);
+	String encoding = eword.substring(start, pos);
+
+	// get encoded-sequence
+	start = pos+1;
+	if ((pos = eword.indexOf("?=", start)) == -1)
+	    throw new ParseException(
+		"encoded word does not end with \"?=\": " + eword);
+	/*
+	 * XXX - should include this, but leaving it out for compatibility...
+	 *
+	if (decodeStrict && pos != eword.length() - 2)
+	    throw new ParseException(
+		"encoded word does not end with \"?=\": " + eword););
+	 */
+	String word = eword.substring(start, pos);
+
+	try {
+	    String decodedWord;
+	    if (word.length() > 0) {
+		// Extract the bytes from word
+		ByteArrayInputStream bis = 
+		    new ByteArrayInputStream(ASCIIUtility.getBytes(word));
+
+		// Get the appropriate decoder
+		InputStream is;
+		if (encoding.equalsIgnoreCase("B")) 
+		    is = new BASE64DecoderStream(bis);
+		else if (encoding.equalsIgnoreCase("Q"))
+		    is = new QDecoderStream(bis);
+		else
+		    throw new UnsupportedEncodingException(
+				    "unknown encoding: " + encoding);
+
+		// For b64 & q, size of decoded word <= size of word. So
+		// the decoded bytes must fit into the 'bytes' array. This
+		// is certainly more efficient than writing bytes into a
+		// ByteArrayOutputStream and then pulling out the byte[]
+		// from it.
+		int count = bis.available();
+		byte[] bytes = new byte[count];
+		// count is set to the actual number of decoded bytes 
+		count = is.read(bytes, 0, count);
+
+		// Finally, convert the decoded bytes into a String using
+		// the specified charset
+		decodedWord = count <= 0 ? "" :
+				new String(bytes, 0, count, charset);
+	    } else {
+		// no characters to decode, return empty string
+		decodedWord = "";
+	    }
+	    if (pos + 2 < eword.length()) {
+		// there's still more text in the string
+		String rest = eword.substring(pos + 2);
+		if (!decodeStrict)
+		    rest = decodeInnerWords(rest);
+		decodedWord += rest;
+	    }
+	    return decodedWord;
+	} catch (UnsupportedEncodingException uex) {
+	    // explicitly catch and rethrow this exception, otherwise
+	    // the below IOException catch will swallow this up!
+	    throw uex;
+	} catch (IOException ioex) {
+	    // Shouldn't happen.
+	    throw new ParseException(ioex.toString());
+	} catch (IllegalArgumentException iex) {
+	    /* An unknown charset of the form ISO-XXX-XXX, will cause
+	     * the JDK to throw an IllegalArgumentException ... Since the
+	     * JDK will attempt to create a classname using this string,
+	     * but valid classnames must not contain the character '-',
+	     * and this results in an IllegalArgumentException, rather than
+	     * the expected UnsupportedEncodingException. Yikes
+	     */
+	    throw new UnsupportedEncodingException(charset);
+	}
+    }
+
+    /**
+     * Look for encoded words within a word.  The MIME spec doesn't
+     * allow this, but many broken mailers, especially Japanese mailers,
+     * produce such incorrect encodings.
+     */
+    private static String decodeInnerWords(String word)
+				throws UnsupportedEncodingException {
+	int start = 0, i;
+	StringBuilder buf = new StringBuilder();
+	while ((i = word.indexOf("=?", start)) >= 0) {
+	    buf.append(word.substring(start, i));
+	    // find first '?' after opening '=?' - end of charset
+	    int end = word.indexOf('?', i + 2);
+	    if (end < 0)
+		break;
+	    // find next '?' after that - end of encoding
+	    end = word.indexOf('?', end + 1);
+	    if (end < 0)
+		break;
+	    // find terminating '?='
+	    end = word.indexOf("?=", end + 1);
+	    if (end < 0)
+		break;
+	    String s = word.substring(i, end + 2);
+	    try {
+		s = decodeWord(s);
+	    } catch (ParseException pex) {
+		// ignore it, just use the original string
+	    }
+	    buf.append(s);
+	    start = end + 2;
+	}
+	if (start == 0)
+	    return word;
+	if (start < word.length())
+	    buf.append(word.substring(start));
+	return buf.toString();
+    }
+
+    /**
+     * A utility method to quote a word, if the word contains any
+     * characters from the specified 'specials' list.<p>
+     *
+     * The <code>HeaderTokenizer</code> class defines two special
+     * sets of delimiters - MIME and RFC 822. <p>
+     *
+     * This method is typically used during the generation of 
+     * RFC 822 and MIME header fields.
+     *
+     * @param	word	word to be quoted
+     * @param	specials the set of special characters
+     * @return		the possibly quoted word
+     * @see	javax.mail.internet.HeaderTokenizer#MIME
+     * @see	javax.mail.internet.HeaderTokenizer#RFC822
+     */
+    public static String quote(String word, String specials) {
+	int len = word == null ? 0 : word.length();
+	if (len == 0)
+	    return "\"\"";	// an empty string is handled specially
+
+	/*
+	 * Look for any "bad" characters, Escape and
+	 *  quote the entire string if necessary.
+	 */
+	boolean needQuoting = false;
+	for (int i = 0; i < len; i++) {
+	    char c = word.charAt(i);
+	    if (c == '"' || c == '\\' || c == '\r' || c == '\n') {
+		// need to escape them and then quote the whole string
+		StringBuilder sb = new StringBuilder(len + 3);
+		sb.append('"');
+		sb.append(word.substring(0, i));
+		int lastc = 0;
+		for (int j = i; j < len; j++) {
+		    char cc = word.charAt(j);
+		    if ((cc == '"') || (cc == '\\') || 
+			(cc == '\r') || (cc == '\n'))
+			if (cc == '\n' && lastc == '\r')
+			    ;	// do nothing, CR was already escaped
+			else
+			    sb.append('\\');	// Escape the character
+		    sb.append(cc);
+		    lastc = cc;
+		}
+		sb.append('"');
+		return sb.toString();
+	    } else if (c < 040 || (c >= 0177 && !allowUtf8) ||
+		    specials.indexOf(c) >= 0)
+		// These characters cause the string to be quoted
+		needQuoting = true;
+	}
+
+	if (needQuoting) {
+	    StringBuilder sb = new StringBuilder(len + 2);
+	    sb.append('"').append(word).append('"');
+	    return sb.toString();
+	} else 
+	    return word;
+    }
+
+    /**
+     * Fold a string at linear whitespace so that each line is no longer
+     * than 76 characters, if possible.  If there are more than 76
+     * non-whitespace characters consecutively, the string is folded at
+     * the first whitespace after that sequence.  The parameter
+     * <code>used</code> indicates how many characters have been used in
+     * the current line; it is usually the length of the header name. <p>
+     *
+     * Note that line breaks in the string aren't escaped; they probably
+     * should be.
+     *
+     * @param	used	characters used in line so far
+     * @param	s	the string to fold
+     * @return		the folded string
+     * @since		JavaMail 1.4
+     */
+    public static String fold(int used, String s) {
+	if (!foldText)
+	    return s;
+
+	int end;
+	char c;
+	// Strip trailing spaces and newlines
+	for (end = s.length() - 1; end >= 0; end--) {
+	    c = s.charAt(end);
+	    if (c != ' ' && c != '\t' && c != '\r' && c != '\n')
+		break;
+	}
+	if (end != s.length() - 1)
+	    s = s.substring(0, end + 1);
+
+	// if the string fits now, just return it
+	if (used + s.length() <= 76)
+	    return makesafe(s);
+
+	// have to actually fold the string
+	StringBuilder sb = new StringBuilder(s.length() + 4);
+	char lastc = 0;
+	while (used + s.length() > 76) {
+	    int lastspace = -1;
+	    for (int i = 0; i < s.length(); i++) {
+		if (lastspace != -1 && used + i > 76)
+		    break;
+		c = s.charAt(i);
+		if (c == ' ' || c == '\t')
+		    if (!(lastc == ' ' || lastc == '\t'))
+			lastspace = i;
+		lastc = c;
+	    }
+	    if (lastspace == -1) {
+		// no space, use the whole thing
+		sb.append(s);
+		s = "";
+		used = 0;
+		break;
+	    }
+	    sb.append(s.substring(0, lastspace));
+	    sb.append("\r\n");
+	    lastc = s.charAt(lastspace);
+	    sb.append(lastc);
+	    s = s.substring(lastspace + 1);
+	    used = 1;
+	}
+	sb.append(s);
+	return makesafe(sb);
+    }
+
+    /**
+     * If the String or StringBuilder has any embedded newlines,
+     * make sure they're followed by whitespace, to prevent header
+     * injection errors.
+     */
+    private static String makesafe(CharSequence s) {
+	int i;
+	for (i = 0; i < s.length(); i++) {
+	    char c = s.charAt(i);
+	    if (c == '\r' || c == '\n')
+		break;
+	}
+	if (i == s.length())	// went through whole string with no CR or LF
+	    return s.toString();
+
+	// read the lines in the string and reassemble them,
+	// eliminating blank lines and inserting whitespace as necessary
+	StringBuilder sb = new StringBuilder(s.length() + 1);
+	BufferedReader r = new BufferedReader(new StringReader(s.toString()));
+	String line;
+	try {
+	    while ((line = r.readLine()) != null) {
+		if (line.trim().length() == 0)
+		    continue;	// ignore empty lines
+		if (sb.length() > 0) {
+		    sb.append("\r\n");
+		    assert line.length() > 0; // proven above
+		    char c = line.charAt(0);
+		    if (c != ' ' && c != '\t')
+			sb.append(' ');
+		}
+		sb.append(line);
+	    }
+	} catch (IOException ex) {
+	    // XXX - should never happen when reading from a string
+	    return s.toString();
+	}
+	return sb.toString();
+    }
+
+    /**
+     * Unfold a folded header.  Any line breaks that aren't escaped and
+     * are followed by whitespace are removed.
+     *
+     * @param	s	the string to unfold
+     * @return		the unfolded string
+     * @since		JavaMail 1.4
+     */
+    public static String unfold(String s) {
+	if (!foldText)
+	    return s;
+
+	StringBuilder sb = null;
+	int i;
+	while ((i = indexOfAny(s, "\r\n")) >= 0) {
+	    int start = i;
+	    int slen = s.length();
+	    i++;		// skip CR or NL
+	    if (i < slen && s.charAt(i - 1) == '\r' && s.charAt(i) == '\n')
+		i++;	// skip LF
+	    if (start > 0 && s.charAt(start - 1) == '\\') {
+		// there's a backslash before the line break
+		// strip it out, but leave in the line break
+		if (sb == null)
+		    sb = new StringBuilder(s.length());
+		sb.append(s.substring(0, start - 1));
+		sb.append(s.substring(start, i));
+		s = s.substring(i);
+	    } else {
+		char c;
+		// if next line starts with whitespace,
+		// or at the end of the string, remove the line break
+		// XXX - next line should always start with whitespace
+		if (i >= slen || (c = s.charAt(i)) == ' ' || c == '\t') {
+		    if (sb == null)
+			sb = new StringBuilder(s.length());
+		    sb.append(s.substring(0, start));
+		    s = s.substring(i);
+		} else {
+		    // it's not a continuation line, just leave in the newline
+		    if (sb == null)
+			sb = new StringBuilder(s.length());
+		    sb.append(s.substring(0, i));
+		    s = s.substring(i);
+		}
+	    }
+	}
+	if (sb != null) {
+	    sb.append(s);
+	    return sb.toString();
+	} else
+	    return s;
+    }
+
+    /**
+     * Return the first index of any of the characters in "any" in "s",
+     * or -1 if none are found.
+     *
+     * This should be a method on String.
+     */
+    private static int indexOfAny(String s, String any) {
+	return indexOfAny(s, any, 0);
+    }
+
+    private static int indexOfAny(String s, String any, int start) {
+	try {
+	    int len = s.length();
+	    for (int i = start; i < len; i++) {
+		if (any.indexOf(s.charAt(i)) >= 0)
+		    return i;
+	    }
+	    return -1;
+	} catch (StringIndexOutOfBoundsException e) {
+	    return -1;
+	}
+    }
+
+    /**
+     * Convert a MIME charset name into a valid Java charset name. <p>
+     *
+     * @param charset	the MIME charset name
+     * @return  the Java charset equivalent. If a suitable mapping is
+     *		not available, the passed in charset is itself returned.
+     */
+    public static String javaCharset(String charset) {
+	if (mime2java == null || charset == null)
+	    // no mapping table, or charset parameter is null
+	    return charset;
+
+	String alias = mime2java.get(charset.toLowerCase(Locale.ENGLISH));
+	if (alias != null) {
+	    // verify that the mapped name is valid before trying to use it
+	    try {
+		Charset.forName(alias);
+	    } catch (Exception ex) {
+		alias = null;	// charset alias not valid, use original name
+	    }
+	}
+	return alias == null ? charset : alias;
+    }
+
+    /**
+     * Convert a java charset into its MIME charset name. <p>
+     *
+     * Note that a future version of JDK (post 1.2) might provide
+     * this functionality, in which case, we may deprecate this
+     * method then.
+     *
+     * @param   charset    the JDK charset
+     * @return      	the MIME/IANA equivalent. If a mapping
+     *			is not possible, the passed in charset itself
+     *			is returned.
+     * @since		JavaMail 1.1
+     */
+    public static String mimeCharset(String charset) {
+	if (java2mime == null || charset == null) 
+	    // no mapping table or charset param is null
+	    return charset;
+
+	String alias = java2mime.get(charset.toLowerCase(Locale.ENGLISH));
+	return alias == null ? charset : alias;
+    }
+
+    private static String defaultJavaCharset;
+    private static String defaultMIMECharset;
+
+    /**
+     * Get the default charset corresponding to the system's current 
+     * default locale.  If the System property <code>mail.mime.charset</code>
+     * is set, a system charset corresponding to this MIME charset will be
+     * returned. <p>
+     * 
+     * @return	the default charset of the system's default locale, 
+     * 		as a Java charset. (NOT a MIME charset)
+     * @since	JavaMail 1.1
+     */
+    public static String getDefaultJavaCharset() {
+	if (defaultJavaCharset == null) {
+	    /*
+	     * If mail.mime.charset is set, it controls the default
+	     * Java charset as well.
+	     */
+	    String mimecs = null;
+	    try {
+		mimecs = System.getProperty("mail.mime.charset");
+	    } catch (SecurityException ex) { }	// ignore it
+	    if (mimecs != null && mimecs.length() > 0) {
+		defaultJavaCharset = javaCharset(mimecs);
+		return defaultJavaCharset;
+	    }
+
+	    try {
+		defaultJavaCharset = System.getProperty("file.encoding", 
+							"8859_1");
+	    } catch (SecurityException sex) {
+		
+		class NullInputStream extends InputStream {
+		    @Override
+		    public int read() {
+			return 0;
+		    }
+		}
+		InputStreamReader reader = 
+			new InputStreamReader(new NullInputStream());
+		defaultJavaCharset = reader.getEncoding();
+		if (defaultJavaCharset == null)
+		    defaultJavaCharset = "8859_1";
+	    }
+	}
+
+	return defaultJavaCharset;
+    }
+
+    /*
+     * Get the default MIME charset for this locale.
+     */
+    static String getDefaultMIMECharset() {
+	if (defaultMIMECharset == null) {
+	    try {
+		defaultMIMECharset = System.getProperty("mail.mime.charset");
+	    } catch (SecurityException ex) { }	// ignore it
+	}
+	if (defaultMIMECharset == null)
+	    defaultMIMECharset = mimeCharset(getDefaultJavaCharset());
+	return defaultMIMECharset;
+    }
+
+    // Tables to map MIME charset names to Java names and vice versa.
+    // XXX - Should eventually use J2SE 1.4 java.nio.charset.Charset
+    private static Map<String, String> mime2java;
+    private static Map<String, String> java2mime;
+
+    static {
+	java2mime = new HashMap<>(40);
+	mime2java = new HashMap<>(14);
+
+	try {
+	    // Use this class's classloader to load the mapping file
+	    // XXX - we should use SecuritySupport, but it's in another package
+	    InputStream is = 
+		    javax.mail.internet.MimeUtility.class.getResourceAsStream(
+		    "/META-INF/javamail.charset.map");
+
+	    if (is != null) {
+		try {
+		    is = new LineInputStream(is);
+
+		    // Load the JDK-to-MIME charset mapping table
+		    loadMappings((LineInputStream)is, java2mime);
+
+		    // Load the MIME-to-JDK charset mapping table
+		    loadMappings((LineInputStream)is, mime2java);
+		} finally {
+		    try {
+			is.close();
+		    } catch (Exception cex) {
+			// ignore
+		    }
+		}
+	    }
+	} catch (Exception ex) { }
+
+	// If we didn't load the tables, e.g., because we didn't have
+	// permission, load them manually.  The entries here should be
+	// the same as the default javamail.charset.map.
+	if (java2mime.isEmpty()) {
+	    java2mime.put("8859_1", "ISO-8859-1");
+	    java2mime.put("iso8859_1", "ISO-8859-1");
+	    java2mime.put("iso8859-1", "ISO-8859-1");
+
+	    java2mime.put("8859_2", "ISO-8859-2");
+	    java2mime.put("iso8859_2", "ISO-8859-2");
+	    java2mime.put("iso8859-2", "ISO-8859-2");
+
+	    java2mime.put("8859_3", "ISO-8859-3");
+	    java2mime.put("iso8859_3", "ISO-8859-3");
+	    java2mime.put("iso8859-3", "ISO-8859-3");
+
+	    java2mime.put("8859_4", "ISO-8859-4");
+	    java2mime.put("iso8859_4", "ISO-8859-4");
+	    java2mime.put("iso8859-4", "ISO-8859-4");
+
+	    java2mime.put("8859_5", "ISO-8859-5");
+	    java2mime.put("iso8859_5", "ISO-8859-5");
+	    java2mime.put("iso8859-5", "ISO-8859-5");
+
+	    java2mime.put("8859_6", "ISO-8859-6");
+	    java2mime.put("iso8859_6", "ISO-8859-6");
+	    java2mime.put("iso8859-6", "ISO-8859-6");
+
+	    java2mime.put("8859_7", "ISO-8859-7");
+	    java2mime.put("iso8859_7", "ISO-8859-7");
+	    java2mime.put("iso8859-7", "ISO-8859-7");
+
+	    java2mime.put("8859_8", "ISO-8859-8");
+	    java2mime.put("iso8859_8", "ISO-8859-8");
+	    java2mime.put("iso8859-8", "ISO-8859-8");
+
+	    java2mime.put("8859_9", "ISO-8859-9");
+	    java2mime.put("iso8859_9", "ISO-8859-9");
+	    java2mime.put("iso8859-9", "ISO-8859-9");
+
+	    java2mime.put("sjis", "Shift_JIS");
+	    java2mime.put("jis", "ISO-2022-JP");
+	    java2mime.put("iso2022jp", "ISO-2022-JP");
+	    java2mime.put("euc_jp", "euc-jp");
+	    java2mime.put("koi8_r", "koi8-r");
+	    java2mime.put("euc_cn", "euc-cn");
+	    java2mime.put("euc_tw", "euc-tw");
+	    java2mime.put("euc_kr", "euc-kr");
+	}
+	if (mime2java.isEmpty()) {
+	    mime2java.put("iso-2022-cn", "ISO2022CN");
+	    mime2java.put("iso-2022-kr", "ISO2022KR");
+	    mime2java.put("utf-8", "UTF8");
+	    mime2java.put("utf8", "UTF8");
+	    mime2java.put("ja_jp.iso2022-7", "ISO2022JP");
+	    mime2java.put("ja_jp.eucjp", "EUCJIS");
+	    mime2java.put("euc-kr", "KSC5601");
+	    mime2java.put("euckr", "KSC5601");
+	    mime2java.put("us-ascii", "ISO-8859-1");
+	    mime2java.put("x-us-ascii", "ISO-8859-1");
+	    mime2java.put("gb2312", "GB18030");
+	    mime2java.put("cp936", "GB18030");
+	    mime2java.put("ms936", "GB18030");
+	    mime2java.put("gbk", "GB18030");
+	}
+    }
+
+    private static void loadMappings(LineInputStream is,
+	    Map<String, String> table) {
+	String currLine;
+
+	while (true) {
+	    try {
+		currLine = is.readLine();
+	    } catch (IOException ioex) {
+		break; // error in reading, stop
+	    }
+
+	    if (currLine == null) // end of file, stop
+		break;
+	    if (currLine.startsWith("--") && currLine.endsWith("--"))
+		// end of this table
+		break;	
+
+	    // ignore empty lines and comments
+	    if (currLine.trim().length() == 0 || currLine.startsWith("#"))
+		continue;
+	    
+	    // A valid entry is of the form <key><separator><value>
+	    // where, <separator> := SPACE | HT. Parse this
+	    StringTokenizer tk = new StringTokenizer(currLine, " \t");
+	    try {
+		String key = tk.nextToken();
+		String value = tk.nextToken();
+		table.put(key.toLowerCase(Locale.ENGLISH), value);
+	    } catch (NoSuchElementException nex) { }
+	}
+    }
+
+    static final int ALL_ASCII 		= 1;
+    static final int MOSTLY_ASCII 	= 2;
+    static final int MOSTLY_NONASCII 	= 3;
+
+    /** 
+     * Check if the given string contains non US-ASCII characters.
+     * @param	s	string
+     * @return		ALL_ASCII if all characters in the string 
+     *			belong to the US-ASCII charset. MOSTLY_ASCII
+     *			if more than half of the available characters
+     *			are US-ASCII characters. Else MOSTLY_NONASCII.
+     */
+    static int checkAscii(String s) {
+	int ascii = 0, non_ascii = 0;
+	int l = s.length();
+
+	for (int i = 0; i < l; i++) {
+	    if (nonascii((int)s.charAt(i))) // non-ascii
+		non_ascii++;
+	    else
+		ascii++;
+	}
+
+	if (non_ascii == 0)
+	    return ALL_ASCII;
+	if (ascii > non_ascii)
+	    return MOSTLY_ASCII;
+
+	return MOSTLY_NONASCII;
+    }
+
+    /** 
+     * Check if the given byte array contains non US-ASCII characters.
+     * @param	b	byte array
+     * @return		ALL_ASCII if all characters in the string 
+     *			belong to the US-ASCII charset. MOSTLY_ASCII
+     *			if more than half of the available characters
+     *			are US-ASCII characters. Else MOSTLY_NONASCII.
+     *
+     * XXX - this method is no longer used
+     */
+    static int checkAscii(byte[] b) {
+	int ascii = 0, non_ascii = 0;
+
+	for (int i=0; i < b.length; i++) {
+	    // The '&' operator automatically causes b[i] to be promoted
+	    // to an int, and we mask out the higher bytes in the int 
+	    // so that the resulting value is not a negative integer.
+	    if (nonascii(b[i] & 0xff)) // non-ascii
+		non_ascii++;
+	    else
+		ascii++;
+	}
+	
+	if (non_ascii == 0)
+	    return ALL_ASCII;
+	if (ascii > non_ascii)
+	    return MOSTLY_ASCII;
+	
+	return MOSTLY_NONASCII;
+    }
+
+    /** 
+     * Check if the given input stream contains non US-ASCII characters.
+     * Upto <code>max</code> bytes are checked. If <code>max</code> is
+     * set to <code>ALL</code>, then all the bytes available in this
+     * input stream are checked. If <code>breakOnNonAscii</code> is true
+     * the check terminates when the first non-US-ASCII character is
+     * found and MOSTLY_NONASCII is returned. Else, the check continues
+     * till <code>max</code> bytes or till the end of stream.
+     *
+     * @param	is	the input stream
+     * @param	max	maximum bytes to check for. The special value
+     *			ALL indicates that all the bytes in this input
+     *			stream must be checked.
+     * @param	breakOnNonAscii if <code>true</code>, then terminate the
+     *			the check when the first non-US-ASCII character
+     *			is found.
+     * @return		ALL_ASCII if all characters in the string 
+     *			belong to the US-ASCII charset. MOSTLY_ASCII
+     *			if more than half of the available characters
+     *			are US-ASCII characters. Else MOSTLY_NONASCII.
+     */
+    static int checkAscii(InputStream is, int max, boolean breakOnNonAscii) {
+	int ascii = 0, non_ascii = 0;
+	int len;
+	int block = 4096;
+	int linelen = 0;
+	boolean longLine = false, badEOL = false;
+	boolean checkEOL = encodeEolStrict && breakOnNonAscii;
+	byte buf[] = null;
+	if (max != 0) {
+	    block = (max == ALL) ? 4096 : Math.min(max, 4096);
+	    buf = new byte[block]; 
+	}
+	while (max != 0) {
+	    try {
+		if ((len = is.read(buf, 0, block)) == -1)
+		    break;
+		int lastb = 0;
+		for (int i = 0; i < len; i++) {
+	    	    // The '&' operator automatically causes b[i] to 
+		    // be promoted to an int, and we mask out the higher
+		    // bytes in the int so that the resulting value is 
+		    // not a negative integer.
+		    int b = buf[i] & 0xff;
+		    if (checkEOL &&
+			    ((lastb == '\r' && b != '\n') ||
+			    (lastb != '\r' && b == '\n')))
+			badEOL = true;
+		    if (b == '\r' || b == '\n')
+			linelen = 0;
+		    else {
+			linelen++;
+			if (linelen > 998)	// 1000 - CRLF
+			    longLine = true;
+		    }
+		    if (nonascii(b)) {	// non-ascii
+		        if (breakOnNonAscii) // we are done
+			    return MOSTLY_NONASCII;
+		        else
+			    non_ascii++;
+		    } else
+		        ascii++;
+		    lastb = b;
+		}
+	    } catch (IOException ioex) {
+		break;
+	    }
+	    if (max != ALL)
+		max -= len;
+	}
+
+	if (max == 0 && breakOnNonAscii)
+	    // We have been told to break on the first non-ascii character.
+	    // We haven't got any non-ascii character yet, but then we
+	    // have not checked all of the available bytes either. So we
+	    // cannot say for sure that this input stream is ALL_ASCII,
+	    // and hence we must play safe and return MOSTLY_NONASCII
+
+	    return MOSTLY_NONASCII;
+
+	if (non_ascii == 0) { // no non-us-ascii characters so far
+	    // If we're looking at non-text data, and we saw CR without LF
+	    // or vice versa, consider this mostly non-ASCII so that it
+	    // will be base64 encoded (since the quoted-printable encoder
+	    // doesn't encode this case properly).
+	    if (badEOL)
+		return MOSTLY_NONASCII;
+	    // if we've seen a long line, we degrade to mostly ascii
+	    else if (longLine)
+		return MOSTLY_ASCII;
+	    else
+		return ALL_ASCII;
+	}
+	if (ascii > non_ascii) // mostly ascii
+	    return MOSTLY_ASCII;
+	return MOSTLY_NONASCII;
+    }
+
+    static final boolean nonascii(int b) {
+	return b >= 0177 || (b < 040 && b != '\r' && b != '\n' && b != '\t');
+    }
+}
+
+/**
+ * An OutputStream that determines whether the data written to
+ * it is all ASCII, mostly ASCII, or mostly non-ASCII.
+ */
+class AsciiOutputStream extends OutputStream {
+    private boolean breakOnNonAscii;
+    private int ascii = 0, non_ascii = 0;
+    private int linelen = 0;
+    private boolean longLine = false;
+    private boolean badEOL = false;
+    private boolean checkEOL = false;
+    private int lastb = 0;
+    private int ret = 0;
+
+    public AsciiOutputStream(boolean breakOnNonAscii, boolean encodeEolStrict) {
+	this.breakOnNonAscii = breakOnNonAscii;
+	checkEOL = encodeEolStrict && breakOnNonAscii;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+	check(b);
+    }
+
+    @Override
+    public void write(byte b[]) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    @Override
+    public void write(byte b[], int off, int len) throws IOException {
+	len += off;
+	for (int i = off; i < len ; i++)
+	    check(b[i]);
+    }
+
+    private final void check(int b) throws IOException {
+	b &= 0xff;
+	if (checkEOL &&
+		((lastb == '\r' && b != '\n') || (lastb != '\r' && b == '\n')))
+	    badEOL = true;
+	if (b == '\r' || b == '\n')
+	    linelen = 0;
+	else {
+	    linelen++;
+	    if (linelen > 998)	// 1000 - CRLF
+		longLine = true;
+	}
+	if (MimeUtility.nonascii(b)) { // non-ascii
+	    non_ascii++;
+	    if (breakOnNonAscii) {	// we are done
+		ret = MimeUtility.MOSTLY_NONASCII;
+		throw new EOFException();
+	    }
+	} else
+	    ascii++;
+	lastb = b;
+    }
+
+    /**
+     * Return ASCII-ness of data stream.
+     */
+    public int getAscii() {
+	if (ret != 0)
+	    return ret;
+	// If we're looking at non-text data, and we saw CR without LF
+	// or vice versa, consider this mostly non-ASCII so that it
+	// will be base64 encoded (since the quoted-printable encoder
+	// doesn't encode this case properly).
+	if (badEOL)
+	    return MimeUtility.MOSTLY_NONASCII;
+	else if (non_ascii == 0) { // no non-us-ascii characters so far
+	    // if we've seen a long line, we degrade to mostly ascii
+	    if (longLine)
+		return MimeUtility.MOSTLY_ASCII;
+	    else
+		return MimeUtility.ALL_ASCII;
+	}
+	if (ascii > non_ascii) // mostly ascii
+	    return MimeUtility.MOSTLY_ASCII;
+	return MimeUtility.MOSTLY_NONASCII;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/NewsAddress.java b/mail/src/main/java/javax/mail/internet/NewsAddress.java
new file mode 100644
index 0000000..3ca148b
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/NewsAddress.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Locale;
+import javax.mail.*;
+
+/**
+ * This class models an RFC1036 newsgroup address.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+
+public class NewsAddress extends Address {
+
+    protected String newsgroup;
+    protected String host;	// may be null
+
+    private static final long serialVersionUID = -4203797299824684143L;
+
+    /**
+     * Default constructor.
+     */
+    public NewsAddress() { }
+
+    /**
+     * Construct a NewsAddress with the given newsgroup.
+     *
+     * @param newsgroup	the newsgroup
+     */
+    public NewsAddress(String newsgroup) {
+	this(newsgroup, null);
+    }
+
+    /**
+     * Construct a NewsAddress with the given newsgroup and host.
+     *
+     * @param newsgroup	the newsgroup
+     * @param host	the host
+     */
+    public NewsAddress(String newsgroup, String host) {
+	// XXX - this method should throw an exception so we can report
+	// illegal addresses, but for now just remove whitespace
+	this.newsgroup = newsgroup.replaceAll("\\s+", "");
+	this.host = host;
+    }
+
+    /**
+     * Return the type of this address.  The type of a NewsAddress
+     * is "news".
+     */
+    @Override
+    public String getType() {
+	return "news";
+    }
+
+    /**
+     * Set the newsgroup.
+     *
+     * @param	newsgroup	the newsgroup
+     */
+    public void setNewsgroup(String newsgroup) {
+	this.newsgroup = newsgroup;
+    }
+
+    /**
+     * Get the newsgroup.
+     *
+     * @return	newsgroup
+     */
+    public String getNewsgroup() {
+	return newsgroup;
+    }
+
+    /**
+     * Set the host.
+     *
+     * @param	host	the host
+     */
+    public void setHost(String host) {
+	this.host = host;
+    }
+
+    /**
+     * Get the host.
+     *
+     * @return	host
+     */
+    public String getHost() {
+	return host;
+    }
+
+    /**
+     * Convert this address into a RFC 1036 address.
+     *
+     * @return		newsgroup
+     */
+    @Override
+    public String toString() {
+	return newsgroup;
+    }
+
+    /**
+     * The equality operator.
+     */
+    @Override
+    public boolean equals(Object a) {
+	if (!(a instanceof NewsAddress))
+	    return false;
+
+	NewsAddress s = (NewsAddress)a;
+	return ((newsgroup == null && s.newsgroup == null) ||
+	     (newsgroup != null && newsgroup.equals(s.newsgroup))) &&
+	    ((host == null && s.host == null) ||
+	     (host != null && s.host != null && host.equalsIgnoreCase(s.host)));
+    }
+
+    /**
+     * Compute a hash code for the address.
+     */
+    @Override
+    public int hashCode() {
+	int hash = 0;
+	if (newsgroup != null)
+	    hash += newsgroup.hashCode();
+	if (host != null)
+	    hash += host.toLowerCase(Locale.ENGLISH).hashCode();
+	return hash;
+    }
+
+    /**
+     * Convert the given array of NewsAddress objects into
+     * a comma separated sequence of address strings. The
+     * resulting string contains only US-ASCII characters, and
+     * hence is mail-safe.
+     *
+     * @param addresses	array of NewsAddress objects
+     * @exception   	ClassCastException if any address object in the
+     *              	given array is not a NewsAddress objects. Note
+     *              	that this is a RuntimeException.
+     * @return	    	comma separated address strings
+     */
+    public static String toString(Address[] addresses) {
+	if (addresses == null || addresses.length == 0)
+	    return null;
+
+	StringBuilder s =
+		new StringBuilder(((NewsAddress)addresses[0]).toString());
+	int used = s.length();
+	for (int i = 1; i < addresses.length; i++) {
+	    s.append(",");
+	    used++;
+	    String ng = ((NewsAddress)addresses[i]).toString();
+	    if (used + ng.length() > 76) {
+		s.append("\r\n\t");
+		used = 8;
+	    }
+	    s.append(ng);
+	    used += ng.length();
+	}
+	
+	return s.toString();
+    }
+
+    /**
+     * Parse the given comma separated sequence of newsgroups into
+     * NewsAddress objects.
+     *
+     * @param newsgroups	comma separated newsgroup string
+     * @return			array of NewsAddress objects
+     * @exception		AddressException if the parse failed
+     */
+    public static NewsAddress[] parse(String newsgroups) 
+				throws AddressException {
+	// XXX - verify format of newsgroup name?
+	StringTokenizer st = new StringTokenizer(newsgroups, ",");
+	List<NewsAddress> nglist = new ArrayList<>();
+	while (st.hasMoreTokens()) {
+	    String ng = st.nextToken();
+	    nglist.add(new NewsAddress(ng));
+	}
+	return nglist.toArray(new NewsAddress[nglist.size()]);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/ParameterList.java b/mail/src/main/java/javax/mail/internet/ParameterList.java
new file mode 100644
index 0000000..5509b39
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/ParameterList.java
@@ -0,0 +1,893 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.util.*;
+import java.io.*;
+import com.sun.mail.util.PropUtil;
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * This class holds MIME parameters (attribute-value pairs).
+ * The <code>mail.mime.encodeparameters</code> and
+ * <code>mail.mime.decodeparameters</code> System properties
+ * control whether encoded parameters, as specified by 
+ * <a href="http://www.ietf.org/rfc/rfc2231.txt" target="_top">RFC 2231</a>,
+ * are supported.  By default, such encoded parameters <b>are</b>
+ * supported. <p>
+ *
+ * Also, in the current implementation, setting the System property
+ * <code>mail.mime.decodeparameters.strict</code> to <code>"true"</code>
+ * will cause a <code>ParseException</code> to be thrown for errors
+ * detected while decoding encoded parameters.  By default, if any
+ * decoding errors occur, the original (undecoded) string is used. <p>
+ *
+ * The current implementation supports the System property
+ * <code>mail.mime.parameters.strict</code>, which if set to false
+ * when parsing a parameter list allows parameter values
+ * to contain whitespace and other special characters without
+ * being quoted; the parameter value ends at the next semicolon.
+ * If set to true (the default), parameter values are required to conform
+ * to the MIME specification and must be quoted if they contain whitespace
+ * or special characters.
+ *
+ * @author  John Mani
+ * @author  Bill Shannon
+ */
+
+public class ParameterList {
+
+    /**
+     * The map of name, value pairs.
+     * The value object is either a String, for unencoded
+     * values, or a Value object, for encoded values,
+     * or a MultiValue object, for multi-segment parameters,
+     * or a LiteralValue object for strings that should not be encoded.
+     *
+     * We use a LinkedHashMap so that parameters are (as much as
+     * possible) kept in the original order.  Note however that
+     * multi-segment parameters (see below) will appear in the
+     * position of the first seen segment and orphan segments
+     * will all move to the end.
+     */
+    // keep parameters in order
+    private Map<String, Object> list = new LinkedHashMap<>();
+
+    /**
+     * A set of names for multi-segment parameters that we
+     * haven't processed yet.  Normally such names are accumulated
+     * during the inital parse and processed at the end of the parse,
+     * but such names can also be set via the set method when the
+     * IMAP provider accumulates pre-parsed pieces of a parameter list.
+     * (A special call to the set method tells us when the IMAP provider
+     * is done setting parameters.)
+     *
+     * A multi-segment parameter is defined by RFC 2231.  For example,
+     * "title*0=part1; title*1=part2", which represents a parameter
+     * named "title" with value "part1part2".
+     *
+     * Note also that each segment of the value might or might not be
+     * encoded, indicated by a trailing "*" on the parameter name.
+     * If any segment is encoded, the first segment must be encoded.
+     * Only the first segment contains the charset and language
+     * information needed to decode any encoded segments.
+     *
+     * RFC 2231 introduces many possible failure modes, which we try
+     * to handle as gracefully as possible.  Generally, a failure to
+     * decode a parameter value causes the non-decoded parameter value
+     * to be used instead.  Missing segments cause all later segments
+     * to be appear as independent parameters with names that include
+     * the segment number.  For example, "title*0=part1; title*1=part2;
+     * title*3=part4" appears as two parameters named "title" and "title*3".
+     */
+    private Set<String> multisegmentNames;
+
+    /**
+     * A map containing the segments for all not-yet-processed
+     * multi-segment parameters.  The map is indexed by "name*seg".
+     * The value object is either a String or a Value object.
+     * The Value object is not decoded during the initial parse
+     * because the segments may appear in any order and until the
+     * first segment appears we don't know what charset to use to
+     * decode the encoded segments.  The segments are hex decoded
+     * in order, combined into a single byte array, and converted
+     * to a String using the specified charset in the
+     * combineMultisegmentNames method.
+     */
+    private Map<String, Object> slist;
+
+    /**
+     * MWB 3BView: The name of the last parameter added to the map.
+     * Used for the AppleMail hack.
+     */
+    private String lastName = null;
+
+    private static final boolean encodeParameters =
+	PropUtil.getBooleanSystemProperty("mail.mime.encodeparameters", true);
+    private static final boolean decodeParameters =
+	PropUtil.getBooleanSystemProperty("mail.mime.decodeparameters", true);
+    private static final boolean decodeParametersStrict =
+	PropUtil.getBooleanSystemProperty(
+	    "mail.mime.decodeparameters.strict", false);
+    private static final boolean applehack =
+	PropUtil.getBooleanSystemProperty("mail.mime.applefilenames", false);
+    private static final boolean windowshack =
+	PropUtil.getBooleanSystemProperty("mail.mime.windowsfilenames", false);
+    private static final boolean parametersStrict = 
+	PropUtil.getBooleanSystemProperty("mail.mime.parameters.strict", true);
+    private static final boolean splitLongParameters = 
+	PropUtil.getBooleanSystemProperty(
+	    "mail.mime.splitlongparameters", true);
+
+
+    /**
+     * A struct to hold an encoded value.
+     * A parsed encoded value is stored as both the
+     * decoded value and the original encoded value
+     * (so that toString will produce the same result).
+     * An encoded value that is set explicitly is stored
+     * as the original value and the encoded value, to
+     * ensure that get will return the same value that
+     * was set.
+     */
+    private static class Value {
+	String value;
+	String charset;
+	String encodedValue;
+    }
+
+    /**
+     * A struct to hold a literal value that shouldn't be further encoded.
+     */
+    private static class LiteralValue {
+	String value;
+    }
+
+    /**
+     * A struct for a multi-segment parameter.  Each entry in the
+     * List is either a String or a Value object.  When all the
+     * segments are present and combined in the combineMultisegmentNames
+     * method, the value field contains the combined and decoded value.
+     * Until then the value field contains an empty string as a placeholder.
+     */
+    private static class MultiValue extends ArrayList<Object> {
+	// keep lint happy
+	private static final long serialVersionUID = 699561094618751023L;
+
+	String value;
+    }
+
+    /**
+     * Map the LinkedHashMap's keySet iterator to an Enumeration.
+     */
+    private static class ParamEnum implements Enumeration<String> {
+	private Iterator<String> it;
+
+	ParamEnum(Iterator<String> it) {
+	    this.it = it;
+	}
+
+	@Override
+	public boolean hasMoreElements() {
+	    return it.hasNext();
+	}
+
+	@Override
+	public String nextElement() {
+	    return it.next();
+	}
+    }
+
+    /**
+     * No-arg Constructor.
+     */
+    public ParameterList() { 
+	// initialize other collections only if they'll be needed
+	if (decodeParameters) {
+	    multisegmentNames = new HashSet<>();
+	    slist = new HashMap<>();
+	}
+    }
+
+    /**
+     * Constructor that takes a parameter-list string. The String
+     * is parsed and the parameters are collected and stored internally.
+     * A ParseException is thrown if the parse fails. 
+     * Note that an empty parameter-list string is valid and will be 
+     * parsed into an empty ParameterList.
+     *
+     * @param	s	the parameter-list string.
+     * @exception	ParseException if the parse fails.
+     */
+    public ParameterList(String s) throws ParseException {
+	this();
+
+	HeaderTokenizer h = new HeaderTokenizer(s, HeaderTokenizer.MIME);
+	for (;;) {
+	    HeaderTokenizer.Token tk = h.next();
+	    int type = tk.getType();
+	    String name, value;
+
+	    if (type == HeaderTokenizer.Token.EOF) // done
+		break;
+
+	    if ((char)type == ';') {
+		// expect parameter name
+		tk = h.next();
+		// tolerate trailing semicolon, even though it violates the spec
+		if (tk.getType() == HeaderTokenizer.Token.EOF)
+		    break;
+		// parameter name must be a MIME Atom
+		if (tk.getType() != HeaderTokenizer.Token.ATOM)
+		    throw new ParseException("In parameter list <" + s + ">" +
+					    ", expected parameter name, " +
+					    "got \"" + tk.getValue() + "\"");
+		name = tk.getValue().toLowerCase(Locale.ENGLISH);
+
+		// expect '='
+		tk = h.next();
+		if ((char)tk.getType() != '=')
+		    throw new ParseException("In parameter list <" + s + ">" +
+					    ", expected '=', " +
+					    "got \"" + tk.getValue() + "\"");
+
+		// expect parameter value
+		if (windowshack &&
+			(name.equals("name") || name.equals("filename")))
+		    tk = h.next(';', true);
+		else if (parametersStrict)
+		    tk = h.next();
+		else
+		    tk = h.next(';');
+		type = tk.getType();
+		// parameter value must be a MIME Atom or Quoted String
+		if (type != HeaderTokenizer.Token.ATOM &&
+		    type != HeaderTokenizer.Token.QUOTEDSTRING)
+		    throw new ParseException("In parameter list <" + s + ">" +
+					    ", expected parameter value, " +
+					    "got \"" + tk.getValue() + "\"");
+
+		value = tk.getValue();
+		lastName = name;
+		if (decodeParameters)
+		    putEncodedName(name, value);
+		else
+		    list.put(name, value);
+            } else {
+		// MWB 3BView new code to add in filenames generated by 
+		// AppleMail.
+		// Note - one space is assumed between name elements.
+		// This may not be correct but it shouldn't matter too much.
+		// Note: AppleMail encodes filenames with non-ascii characters 
+		// correctly, so we don't need to worry about the name* subkeys.
+		if (type == HeaderTokenizer.Token.ATOM && lastName != null &&
+			    ((applehack &&
+				(lastName.equals("name") ||
+				 lastName.equals("filename"))) ||
+			    !parametersStrict)
+			 ) {
+		    // Add value to previous value
+		    String lastValue = (String)list.get(lastName);
+		    value = lastValue + " " + tk.getValue();
+		    list.put(lastName, value);
+                } else {
+		    throw new ParseException("In parameter list <" + s + ">" +
+					    ", expected ';', got \"" +
+					    tk.getValue() + "\"");
+		}
+	    }
+        }
+
+	if (decodeParameters) {
+	    /*
+	     * After parsing all the parameters, combine all the
+	     * multi-segment parameter values together.
+	     */
+	    combineMultisegmentNames(false);
+	}
+    }
+
+    /**
+     * Normal users of this class will use simple parameter names.
+     * In some cases, for example, when processing IMAP protocol
+     * messages, individual segments of a multi-segment name
+     * (specified by RFC 2231) will be encountered and passed to
+     * the {@link #set} method.  After all these segments are added
+     * to this ParameterList, they need to be combined to represent
+     * the logical parameter name and value.  This method will combine
+     * all segments of multi-segment names. <p>
+     *
+     * Normal users should never need to call this method.
+     *
+     * @since	JavaMail 1.5
+     */ 
+    public void combineSegments() {
+	/*
+	 * If we've accumulated any multi-segment names from calls to
+	 * the set method from (e.g.) the IMAP provider, combine the pieces.
+	 * Ignore any parse errors (e.g., from decoding the values)
+	 * because it's too late to report them.
+	 */
+	if (decodeParameters && multisegmentNames.size() > 0) {
+	    try {
+		combineMultisegmentNames(true);
+	    } catch (ParseException pex) {
+		// too late to do anything about it
+	    }
+	}
+    }
+
+    /**
+     * If the name is an encoded or multi-segment name (or both)
+     * handle it appropriately, storing the appropriate String
+     * or Value object.  Multi-segment names are stored in the
+     * main parameter list as an emtpy string as a placeholder,
+     * replaced later in combineMultisegmentNames with a MultiValue
+     * object.  This causes all pieces of the multi-segment parameter
+     * to appear in the position of the first seen segment of the
+     * parameter.
+     */
+    private void putEncodedName(String name, String value)
+				throws ParseException {
+	int star = name.indexOf('*');
+	if (star < 0) {
+	    // single parameter, unencoded value
+	    list.put(name, value);
+	} else if (star == name.length() - 1) {
+	    // single parameter, encoded value
+	    name = name.substring(0, star);
+	    Value v = extractCharset(value);
+	    try {
+		v.value = decodeBytes(v.value, v.charset);
+	    } catch (UnsupportedEncodingException ex) {
+		if (decodeParametersStrict)
+		    throw new ParseException(ex.toString());
+	    }
+	    list.put(name, v);
+	} else {
+	    // multiple segments
+	    String rname = name.substring(0, star);
+	    multisegmentNames.add(rname);
+	    list.put(rname, "");
+
+	    Object v;
+	    if (name.endsWith("*")) {
+		// encoded value
+		if (name.endsWith("*0*")) {	// first segment
+		    v = extractCharset(value);
+		} else {
+		    v = new Value();
+		    ((Value)v).encodedValue = value;
+		    ((Value)v).value = value;	// default; decoded later
+		}
+		name = name.substring(0, name.length() - 1);
+	    } else {
+		// unencoded value
+		v = value;
+	    }
+	    slist.put(name, v);
+	}
+    }
+
+    /**
+     * Iterate through the saved set of names of multi-segment parameters,
+     * for each parameter find all segments stored in the slist map,
+     * decode each segment as needed, combine the segments together into
+     * a single decoded value, and save all segments in a MultiValue object
+     * in the main list indexed by the parameter name.
+     */
+    private void combineMultisegmentNames(boolean keepConsistentOnFailure)
+				throws ParseException {
+	boolean success = false;
+	try {
+	    Iterator<String> it = multisegmentNames.iterator();
+	    while (it.hasNext()) {
+		String name = it.next();
+		MultiValue mv = new MultiValue();
+		/*
+		 * Now find all the segments for this name and
+		 * decode each segment as needed.
+		 */
+		String charset = null;
+		ByteArrayOutputStream bos = new ByteArrayOutputStream();
+		int segment;
+		for (segment = 0; ; segment++) {
+		    String sname = name + "*" + segment;
+		    Object v = slist.get(sname);
+		    if (v == null)	// out of segments
+			break;
+		    mv.add(v);
+		    try {
+			if (v instanceof Value) {
+			    Value vv = (Value)v;
+			    if (segment == 0) {
+				// the first segment specifies the charset
+				// for all other encoded segments
+				charset = vv.charset;
+			    } else {
+				if (charset == null) {
+				    // should never happen
+				    multisegmentNames.remove(name);
+				    break;
+				}
+			    }
+			    decodeBytes(vv.value, bos);
+			} else {
+			    bos.write(ASCIIUtility.getBytes((String)v));
+			}
+		    } catch (IOException ex) {
+			// XXX - should never happen
+		    }
+		    slist.remove(sname);
+		}
+		if (segment == 0) {
+		    // didn't find any segments at all
+		    list.remove(name);
+		} else {
+		    try {
+			if (charset != null)
+			    charset = MimeUtility.javaCharset(charset);
+			if (charset == null || charset.length() == 0)
+			    charset = MimeUtility.getDefaultJavaCharset();
+			if (charset != null)
+			    mv.value = bos.toString(charset);
+			else
+			    mv.value = bos.toString();
+		    } catch (UnsupportedEncodingException uex) {
+			if (decodeParametersStrict)
+			    throw new ParseException(uex.toString());
+			// convert as if iso-8859-1
+			try {
+			    mv.value = bos.toString("iso-8859-1");
+			} catch (UnsupportedEncodingException ex) {
+			    // should never happen
+			}
+		    }
+		    list.put(name, mv);
+		}
+	    }
+	    success = true;
+	} finally {
+	    /*
+	     * If we get here because of an exception that's going to
+	     * be thrown (success == false) from the constructor
+	     * (keepConsistentOnFailure == false), this is all wasted effort.
+	     */
+	    if (keepConsistentOnFailure || success)  {
+		// we should never end up with anything in slist,
+		// but if we do, add it all to list
+		if (slist.size() > 0) {
+		    // first, decode any values that we'll add to the list
+		    Iterator<Object> sit = slist.values().iterator();
+		    while (sit.hasNext()) {
+			Object v = sit.next();
+			if (v instanceof Value) {
+			    Value vv = (Value)v;
+			    try {
+				vv.value =
+				    decodeBytes(vv.value, vv.charset);
+			    } catch (UnsupportedEncodingException ex) {
+				if (decodeParametersStrict)
+				    throw new ParseException(ex.toString());
+			    }
+			}
+		    }
+		    list.putAll(slist);
+		}
+
+		// clear out the set of names and segments
+		multisegmentNames.clear();
+		slist.clear();
+	    }
+	}
+    }
+
+    /**
+     * Return the number of parameters in this list.
+     * 
+     * @return  number of parameters.
+     */
+    public int size() {
+	return list.size();
+    }
+
+    /**
+     * Returns the value of the specified parameter. Note that 
+     * parameter names are case-insensitive.
+     *
+     * @param name	parameter name.
+     * @return		Value of the parameter. Returns 
+     *			<code>null</code> if the parameter is not 
+     *			present.
+     */
+    public String get(String name) {
+	String value;
+	Object v = list.get(name.trim().toLowerCase(Locale.ENGLISH));
+	if (v instanceof MultiValue)
+	    value = ((MultiValue)v).value;
+	else if (v instanceof LiteralValue)
+	    value = ((LiteralValue)v).value;
+	else if (v instanceof Value)
+	    value = ((Value)v).value;
+	else
+	    value = (String)v;
+	return value;
+    }
+
+    /**
+     * Set a parameter. If this parameter already exists, it is
+     * replaced by this new value.
+     *
+     * @param	name 	name of the parameter.
+     * @param	value	value of the parameter.
+     */
+    public void set(String name, String value) {
+	name = name.trim().toLowerCase(Locale.ENGLISH);
+	if (decodeParameters) {
+	    try {
+		putEncodedName(name, value);
+	    } catch (ParseException pex) {
+		// ignore it
+		list.put(name, value);
+	    }
+	} else
+	    list.put(name, value);
+    }
+
+    /**
+     * Set a parameter. If this parameter already exists, it is
+     * replaced by this new value.  If the
+     * <code>mail.mime.encodeparameters</code> System property
+     * is true, and the parameter value is non-ASCII, it will be
+     * encoded with the specified charset, as specified by RFC 2231.
+     *
+     * @param	name 	name of the parameter.
+     * @param	value	value of the parameter.
+     * @param	charset	charset of the parameter value.
+     * @since	JavaMail 1.4
+     */
+    public void set(String name, String value, String charset) {
+	if (encodeParameters) {
+	    Value ev = encodeValue(value, charset);
+	    // was it actually encoded?
+	    if (ev != null)
+		list.put(name.trim().toLowerCase(Locale.ENGLISH), ev);
+	    else
+		set(name, value);
+	} else
+	    set(name, value);
+    }
+
+    /**
+     * Package-private method to set a literal value that won't be
+     * further encoded.  Used to set the filename parameter when
+     * "mail.mime.encodefilename" is true.
+     *
+     * @param	name 	name of the parameter.
+     * @param	value	value of the parameter.
+     */
+    void setLiteral(String name, String value) {
+	LiteralValue lv = new LiteralValue();
+	lv.value = value;
+	list.put(name, lv);
+    }
+
+    /**
+     * Removes the specified parameter from this ParameterList.
+     * This method does nothing if the parameter is not present.
+     *
+     * @param	name	name of the parameter.
+     */
+    public void remove(String name) {
+	list.remove(name.trim().toLowerCase(Locale.ENGLISH));
+    }
+
+    /**
+     * Return an enumeration of the names of all parameters in this
+     * list.
+     *
+     * @return Enumeration of all parameter names in this list.
+     */
+    public Enumeration<String> getNames() {
+	return new ParamEnum(list.keySet().iterator());
+    }
+
+    /**
+     * Convert this ParameterList into a MIME String. If this is
+     * an empty list, an empty string is returned.
+     *
+     * @return		String
+     */
+    @Override
+    public String toString() {
+	return toString(0);
+    }
+
+    /**
+     * Convert this ParameterList into a MIME String. If this is
+     * an empty list, an empty string is returned.
+     *   
+     * The 'used' parameter specifies the number of character positions
+     * already taken up in the field into which the resulting parameter
+     * list is to be inserted. It's used to determine where to fold the
+     * resulting parameter list.
+     *
+     * @param used      number of character positions already used, in
+     *                  the field into which the parameter list is to
+     *                  be inserted.
+     * @return          String
+     */  
+    public String toString(int used) {
+        ToStringBuffer sb = new ToStringBuffer(used);
+        Iterator<Map.Entry<String, Object>> e = list.entrySet().iterator();
+ 
+        while (e.hasNext()) {
+	    Map.Entry<String, Object> ent = e.next();
+	    String name = ent.getKey();
+	    String value;
+	    Object v = ent.getValue();
+	    if (v instanceof MultiValue) {
+		MultiValue vv = (MultiValue)v;
+		name += "*";
+		for (int i = 0; i < vv.size(); i++) {
+		    Object va = vv.get(i);
+		    String ns;
+		    if (va instanceof Value) {
+			ns = name + i + "*";
+			value = ((Value)va).encodedValue;
+		    } else {
+			ns = name + i;
+			value = (String)va;
+		    }
+		    sb.addNV(ns, quote(value));
+		}
+	    } else if (v instanceof LiteralValue) {
+		value = ((LiteralValue)v).value;
+		sb.addNV(name, quote(value));
+	    } else if (v instanceof Value) {
+		/*
+		 * XXX - We could split the encoded value into multiple
+		 * segments if it's too long, but that's more difficult.
+		 */
+		name += "*";
+		value = ((Value)v).encodedValue;
+		sb.addNV(name, quote(value));
+	    } else {
+		value = (String)v;
+		/*
+		 * If this value is "long", split it into a multi-segment
+		 * parameter.  Only do this if we've enabled RFC2231 style
+		 * encoded parameters.
+		 *
+		 * Note that we check the length before quoting the value.
+		 * Quoting might make the string longer, although typically
+		 * not much, so we allow a little slop in the calculation.
+		 * In the worst case, a 60 character string will turn into
+		 * 122 characters when quoted, which is long but not
+		 * outrageous.
+		 */
+		if (value.length() > 60 &&
+				splitLongParameters && encodeParameters) {
+		    int seg = 0;
+		    name += "*";
+		    while (value.length() > 60) {
+			sb.addNV(name + seg, quote(value.substring(0, 60)));
+			value = value.substring(60);
+			seg++;
+		    }
+		    if (value.length() > 0)
+			sb.addNV(name + seg, quote(value));
+		} else {
+		    sb.addNV(name, quote(value));
+		}
+	    }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * A special wrapper for a StringBuffer that keeps track of the
+     * number of characters used in a line, wrapping to a new line
+     * as necessary; for use by the toString method.
+     */
+    private static class ToStringBuffer {
+	private int used;	// keep track of how much used on current line
+	private StringBuilder sb = new StringBuilder();
+
+	public ToStringBuffer(int used) {
+	    this.used = used;
+	}
+
+	public void addNV(String name, String value) {
+	    sb.append("; ");
+	    used += 2;
+	    int len = name.length() + value.length() + 1;
+	    if (used + len > 76) { // overflows ...
+		sb.append("\r\n\t"); // .. start new continuation line
+		used = 8; // account for the starting <tab> char
+	    }
+	    sb.append(name).append('=');
+	    used += name.length() + 1;
+	    if (used + value.length() > 76) { // still overflows ...
+		// have to fold value
+		String s = MimeUtility.fold(used, value);
+		sb.append(s);
+		int lastlf = s.lastIndexOf('\n');
+		if (lastlf >= 0)	// always true
+		    used += s.length() - lastlf - 1;
+		else
+		    used += s.length();
+	    } else {
+		sb.append(value);
+		used += value.length();
+	    }
+	}
+
+	@Override
+	public String toString() {
+	    return sb.toString();
+	}
+    }
+ 
+    // Quote a parameter value token if required.
+    private static String quote(String value) {
+	return MimeUtility.quote(value, HeaderTokenizer.MIME);
+    }
+
+    private static final char hex[] = {
+	'0','1', '2', '3', '4', '5', '6', '7',
+	'8','9', 'A', 'B', 'C', 'D', 'E', 'F'
+    };
+
+    /**
+     * Encode a parameter value, if necessary.
+     * If the value is encoded, a Value object is returned.
+     * Otherwise, null is returned.
+     * XXX - Could return a MultiValue object if parameter value is too long.
+     */
+    private static Value encodeValue(String value, String charset) {
+	if (MimeUtility.checkAscii(value) == MimeUtility.ALL_ASCII)
+	    return null;	// no need to encode it
+
+	byte[] b;	// charset encoded bytes from the string
+	try {
+	    b = value.getBytes(MimeUtility.javaCharset(charset));
+	} catch (UnsupportedEncodingException ex) {
+	    return null;
+	}
+	StringBuffer sb = new StringBuffer(b.length + charset.length() + 2);
+	sb.append(charset).append("''");
+	for (int i = 0; i < b.length; i++) {
+	    char c = (char)(b[i] & 0xff);
+	    // do we need to encode this character?
+	    if (c <= ' ' || c >= 0x7f || c == '*' || c == '\'' || c == '%' ||
+		    HeaderTokenizer.MIME.indexOf(c) >= 0) {
+		sb.append('%').append(hex[c>>4]).append(hex[c&0xf]);
+	    } else
+		sb.append(c);
+	}
+	Value v = new Value();
+	v.charset = charset;
+	v.value = value;
+	v.encodedValue = sb.toString();
+	return v;
+    }
+
+    /**
+     * Extract charset and encoded value.
+     * Value will be decoded later.
+     */
+    private static Value extractCharset(String value) throws ParseException {
+	Value v = new Value();
+	v.value = v.encodedValue = value;
+	try {
+	    int i = value.indexOf('\'');
+	    if (i < 0) {
+		if (decodeParametersStrict)
+		    throw new ParseException(
+			"Missing charset in encoded value: " + value);
+		return v;	// not encoded correctly?  return as is.
+	    }
+	    String charset = value.substring(0, i);
+	    int li = value.indexOf('\'', i + 1);
+	    if (li < 0) {
+		if (decodeParametersStrict)
+		    throw new ParseException(
+			"Missing language in encoded value: " + value);
+		return v;	// not encoded correctly?  return as is.
+	    }
+	    // String lang = value.substring(i + 1, li);
+	    v.value = value.substring(li + 1);
+	    v.charset = charset;
+	} catch (NumberFormatException nex) {
+	    if (decodeParametersStrict)
+		throw new ParseException(nex.toString());
+	} catch (StringIndexOutOfBoundsException ex) {
+	    if (decodeParametersStrict)
+		throw new ParseException(ex.toString());
+	}
+	return v;
+    }
+
+    /**
+     * Decode the encoded bytes in value using the specified charset.
+     */
+    private static String decodeBytes(String value, String charset)
+			throws ParseException, UnsupportedEncodingException {
+	/*
+	 * Decode the ASCII characters in value
+	 * into an array of bytes, and then convert
+	 * the bytes to a String using the specified
+	 * charset.  We'll never need more bytes than
+	 * encoded characters, so use that to size the
+	 * array.
+	 */
+	byte[] b = new byte[value.length()];
+	int i, bi;
+	for (i = 0, bi = 0; i < value.length(); i++) {
+	    char c = value.charAt(i);
+	    if (c == '%') {
+		try {
+		    String hex = value.substring(i + 1, i + 3);
+		    c = (char)Integer.parseInt(hex, 16);
+		    i += 2;
+		} catch (NumberFormatException ex) {
+		    if (decodeParametersStrict)
+			throw new ParseException(ex.toString());
+		} catch (StringIndexOutOfBoundsException ex) {
+		    if (decodeParametersStrict)
+			throw new ParseException(ex.toString());
+		}
+	    }
+	    b[bi++] = (byte)c;
+	}
+	if (charset != null)
+	    charset = MimeUtility.javaCharset(charset);
+	if (charset == null || charset.length() == 0)
+	    charset = MimeUtility.getDefaultJavaCharset();
+	return new String(b, 0, bi, charset);
+    }
+
+    /**
+     * Decode the encoded bytes in value and write them to the OutputStream.
+     */
+    private static void decodeBytes(String value, OutputStream os)
+				throws ParseException, IOException {
+	/*
+	 * Decode the ASCII characters in value
+	 * and write them to the stream.
+	 */
+	int i;
+	for (i = 0; i < value.length(); i++) {
+	    char c = value.charAt(i);
+	    if (c == '%') {
+		try {
+		    String hex = value.substring(i + 1, i + 3);
+		    c = (char)Integer.parseInt(hex, 16);
+		    i += 2;
+		} catch (NumberFormatException ex) {
+		    if (decodeParametersStrict)
+			throw new ParseException(ex.toString());
+		} catch (StringIndexOutOfBoundsException ex) {
+		    if (decodeParametersStrict)
+			throw new ParseException(ex.toString());
+		}
+	    }
+	    os.write((byte)c);
+	}
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/ParseException.java b/mail/src/main/java/javax/mail/internet/ParseException.java
new file mode 100644
index 0000000..8497be8
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/ParseException.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.MessagingException;
+
+/**
+ * The exception thrown due to an error in parsing RFC822 
+ * or MIME headers, including multipart bodies.
+ *
+ * @author John Mani
+ */
+
+public class ParseException extends MessagingException {
+
+    private static final long serialVersionUID = 7649991205183658089L;
+
+    /**
+     * Constructs a ParseException with no detail message.
+     */
+    public ParseException() {
+	super();
+    }
+
+    /**
+     * Constructs a ParseException with the specified detail message.
+     * @param s		the detail message
+     */
+    public ParseException(String s) {
+	super(s);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/PreencodedMimeBodyPart.java b/mail/src/main/java/javax/mail/internet/PreencodedMimeBodyPart.java
new file mode 100644
index 0000000..3188eca
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/PreencodedMimeBodyPart.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.*;
+import java.util.Enumeration;
+import javax.mail.*;
+
+import com.sun.mail.util.LineOutputStream;
+
+/**
+ * A MimeBodyPart that handles data that has already been encoded.
+ * This class is useful when constructing a message and attaching
+ * data that has already been encoded (for example, using base64
+ * encoding).  The data may have been encoded by the application,
+ * or may have been stored in a file or database in encoded form.
+ * The encoding is supplied when this object is created.  The data
+ * is attached to this object in the usual fashion, by using the
+ * <code>setText</code>, <code>setContent</code>, or
+ * <code>setDataHandler</code> methods.
+ *
+ * @since	JavaMail 1.4
+ */
+
+public class PreencodedMimeBodyPart extends MimeBodyPart {
+    private String encoding;
+
+    /**
+     * Create a PreencodedMimeBodyPart that assumes the data is
+     * encoded using the specified encoding.  The encoding must
+     * be a MIME supported Content-Transfer-Encoding.
+     *
+     * @param	encoding	the Content-Transfer-Encoding
+     */
+    public PreencodedMimeBodyPart(String encoding) {
+	this.encoding = encoding;
+    }
+
+    /**
+     * Returns the content transfer encoding specified when
+     * this object was created.
+     */
+    @Override
+    public String getEncoding() throws MessagingException {
+	return encoding;
+    }
+
+    /**
+     * Output the body part as an RFC 822 format stream.
+     *
+     * @exception IOException	if an error occurs writing to the
+     *				stream or if an error is generated
+     *				by the javax.activation layer.
+     * @exception MessagingException for other failures
+     * @see javax.activation.DataHandler#writeTo
+     */
+    @Override
+    public void writeTo(OutputStream os)
+			throws IOException, MessagingException {
+
+	// see if we already have a LOS
+	LineOutputStream los = null;
+	if (os instanceof LineOutputStream) {
+	    los = (LineOutputStream) os;
+	} else {
+	    los = new LineOutputStream(os);
+	}
+
+	// First, write out the header
+	Enumeration<String> hdrLines = getAllHeaderLines();
+	while (hdrLines.hasMoreElements())
+	    los.writeln(hdrLines.nextElement());
+
+	// The CRLF separator between header and content
+	los.writeln();
+
+	// Finally, the content, already encoded.
+	getDataHandler().writeTo(os);
+	os.flush();
+    }
+
+    /**
+     * Force the <code>Content-Transfer-Encoding</code> header to use
+     * the encoding that was specified when this object was created.
+     */
+    @Override
+    protected void updateHeaders() throws MessagingException {
+	super.updateHeaders();
+	MimeBodyPart.setEncoding(this, encoding);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/SharedInputStream.java b/mail/src/main/java/javax/mail/internet/SharedInputStream.java
new file mode 100644
index 0000000..b5cd419
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/SharedInputStream.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.*;
+
+/**
+ * An InputStream that is backed by data that can be shared by multiple
+ * readers may implement this interface.  This allows users of such an
+ * InputStream to determine the current position in the InputStream, and
+ * to create new InputStreams representing a subset of the data in the
+ * original InputStream.  The new InputStream will access the same
+ * underlying data as the original, without copying the data. <p>
+ *
+ * Note that implementations of this interface must ensure that the
+ * <code>close</code> method does not close any underlying stream
+ * that might be shared by multiple instances of <code>SharedInputStream</code>
+ * until all shared instances have been closed.
+ *
+ * @author  Bill Shannon
+ * @since JavaMail 1.2
+ */
+
+public interface SharedInputStream {
+    /**
+     * Return the current position in the InputStream, as an
+     * offset from the beginning of the InputStream.
+     *
+     * @return	the current position
+     */
+    public long getPosition();
+
+    /**
+     * Return a new InputStream representing a subset of the data
+     * from this InputStream, starting at <code>start</code> (inclusive)
+     * up to <code>end</code> (exclusive).  <code>start</code> must be
+     * non-negative.  If <code>end</code> is -1, the new stream ends
+     * at the same place as this stream.  The returned InputStream
+     * will also implement the SharedInputStream interface.
+     *
+     * @param	start	the starting position
+     * @param	end	the ending position + 1
+     * @return		the new stream
+     */
+    public InputStream newStream(long start, long end);
+}
diff --git a/mail/src/main/java/javax/mail/internet/UniqueValue.java b/mail/src/main/java/javax/mail/internet/UniqueValue.java
new file mode 100644
index 0000000..a3e9f8c
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/UniqueValue.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.net.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.mail.Session;
+
+/**
+ * This is a utility class that generates unique values. The generated
+ * String contains only US-ASCII characters and hence is safe for use
+ * in RFC822 headers. <p>
+ *
+ * This is a package private class.
+ *
+ * @author John Mani
+ * @author Max Spivak
+ * @author Bill Shannon
+ */
+
+class UniqueValue {
+    /**
+     * A global unique number, to ensure uniqueness of generated strings.
+     */
+    private static AtomicInteger id = new AtomicInteger();
+
+    /**
+     * Get a unique value for use in a multipart boundary string.
+     *
+     * This implementation generates it by concatenating a global
+     * part number, a newly created object's <code>hashCode()</code>,
+     * and the current time (in milliseconds).
+     */
+    public static String getUniqueBoundaryValue() {
+	StringBuilder s = new StringBuilder();
+	long hash = s.hashCode();
+
+	// Unique string is ----=_Part_<part>_<hashcode>.<currentTime>
+	s.append("----=_Part_").append(id.getAndIncrement()).append("_").
+	  append(hash).append('.').
+	  append(System.currentTimeMillis());
+	return s.toString();
+    }
+
+    /**
+     * Get a unique value for use in a Message-ID.
+     *
+     * This implementation generates it by concatenating a newly
+     * created object's <code>hashCode()</code>, a global ID
+     * (incremented on every use), the current time (in milliseconds),
+     * and the host name from this user's local address generated by 
+     * <code>InternetAddress.getLocalAddress()</code>.
+     * (The host name defaults to "localhost" if
+     * <code>getLocalAddress()</code> returns null.)
+     *
+     * @param ssn Session object used to get the local address
+     * @see javax.mail.internet.InternetAddress
+     */
+    public static String getUniqueMessageIDValue(Session ssn) {
+	String suffix = null;
+
+	InternetAddress addr = InternetAddress.getLocalAddress(ssn);
+	if (addr != null)
+	    suffix = addr.getAddress();
+	else {
+	    suffix = "javamailuser@localhost"; // worst-case default
+	}
+	int at = suffix.lastIndexOf('@');
+	if (at >= 0)
+	    suffix = suffix.substring(at);
+
+	StringBuilder s = new StringBuilder();
+
+	// Unique string is <hashcode>.<id>.<currentTime><suffix>
+	s.append(s.hashCode()).append('.').
+	  append(id.getAndIncrement()).append('.').
+	  append(System.currentTimeMillis()).
+	  append(suffix);
+	return s.toString();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/internet/package.html b/mail/src/main/java/javax/mail/internet/package.html
new file mode 100644
index 0000000..4ecf4af
--- /dev/null
+++ b/mail/src/main/java/javax/mail/internet/package.html
@@ -0,0 +1,548 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>javax.mail.internet package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+Classes specific to Internet mail systems.
+This package supports features that are specific to Internet mail systems
+based on the MIME standard
+(<A HREF="http://www.ietf.org/rfc/rfc2045.txt" TARGET="_top">RFC 2045</A>,
+<A HREF="http://www.ietf.org/rfc/rfc2045.txt" TARGET="_top">RFC 2046</A>, and
+<A HREF="http://www.ietf.org/rfc/rfc2045.txt" TARGET="_top">RFC 2047</A>).
+The IMAP, SMTP, and POP3 protocols use
+{@link javax.mail.internet.MimeMessage MimeMessages}.
+</P>
+<A NAME="properties"><STRONG>Properties</STRONG></A>
+<P>
+The JavaMail API supports the following standard properties,
+which may be set in the <code>Session</code> object, or in the
+<code>Properties</code> object used to create the <code>Session</code> object.
+The properties are always set as strings; the Type column describes
+how the string is interpreted.  For example, use
+</P>
+<PRE>
+	session.setProperty("mail.mime.address.strict", "false");
+</PRE>
+<P>
+to set the <CODE>mail.mime.address.strict</CODE> property,
+which is of type boolean.
+</P>
+<TABLE BORDER SUMMARY="JavaMail properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.address.strict">mail.mime.address.strict</A></TD>
+<TD>boolean</TD>
+<TD>
+The <code>mail.mime.address.strict</code> session property controls
+the parsing of address headers.  By default, strict parsing of address
+headers is done.  If this property is set to <code>"false"</code>,
+strict parsing is not done and many illegal addresses that sometimes
+occur in real messages are allowed.  See the <code>InternetAddress</code>
+class for details.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.allowutf8">mail.mime.allowutf8</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, UTF-8 strings are allowed in message headers,
+e.g., in addresses.  This should <b>only</b> be set if the mail server also
+supports UTF-8.
+</TD>
+</TR>
+</TABLE>
+<P>
+The JavaMail API specification requires support for the following properties,
+which must be set in the <code>System</code> properties.
+The properties are always set as strings; the Type column describes
+how the string is interpreted.  For example, use
+</P>
+<PRE>
+	System.setProperty("mail.mime.decodetext.strict", "false");
+</PRE>
+<P>
+to set the <CODE>mail.mime.decodetext.strict</CODE> property,
+which is of type boolean.
+</P>
+<TABLE BORDER SUMMARY="JavaMail System properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.charset">mail.mime.charset</A></TD>
+<TD>String</TD>
+<TD>
+The <code>mail.mime.charset</code> System property can
+be used to specify the default MIME charset to use for encoded words
+and text parts that don't otherwise specify a charset.  Normally, the
+default MIME charset is derived from the default Java charset, as
+specified in the <code>file.encoding</code> System property.  Most
+applications will have no need to explicitly set the default MIME
+charset.  In cases where the default MIME charset to be used for
+mail messages is different than the charset used for files stored on
+the system, this property should be set.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.decodetext.strict">mail.mime.decodetext.strict</A></TD>
+<TD>boolean</TD>
+<TD>
+The <code>mail.mime.decodetext.strict</code> property controls
+decoding of MIME encoded words.  The MIME spec requires that encoded
+words start at the beginning of a whitespace separated word.  Some
+mailers incorrectly include encoded words in the middle of a word.
+If the <code>mail.mime.decodetext.strict</code> System property is
+set to <code>"false"</code>, an attempt will be made to decode these
+illegal encoded words. The default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.encodeeol.strict">mail.mime.encodeeol.strict</A></TD>
+<TD>boolean</TD>
+<TD>
+The <code>mail.mime.encodeeol.strict</code> property controls the
+choice of Content-Transfer-Encoding for MIME parts that are not of
+type "text".  Often such parts will contain textual data for which
+an encoding that allows normal end of line conventions is appropriate.
+In rare cases, such a part will appear to contain entirely textual
+data, but will require an encoding that preserves CR and LF characters
+without change.  If the <code>mail.mime.encodeeol.strict</code>
+System property is set to <code>"true"</code>, such an encoding will
+be used when necessary.  The default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.decodefilename">mail.mime.decodefilename</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, the <code>getFileName</code> method
+uses the <code>MimeUtility</code>
+method <code>decodeText</code> to decode any
+non-ASCII characters in the filename.  Note that this decoding
+violates the MIME specification, but is useful for interoperating
+with some mail clients that use this convention.
+The default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.encodefilename">mail.mime.encodefilename</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, the <code>setFileName</code> method
+uses the <code>MimeUtility</code>
+method <code>encodeText</code> to encode any
+non-ASCII characters in the filename.  Note that this encoding
+violates the MIME specification, but is useful for interoperating
+with some mail clients that use this convention.
+The default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.decodeparameters">mail.mime.decodeparameters</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"false"</code>, non-ASCII parameters in a
+<code>ParameterList</code>, e.g., in a Content-Type header,
+will <b>not</b> be decoded as specified by
+<A HREF="http://www.ietf.org/rfc/rfc2231.txt" TARGET="_top">RFC 2231</A>.
+The default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.encodeparameters">mail.mime.encodeparameters</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"false"</code>, non-ASCII parameters in a
+<code>ParameterList</code>, e.g., in a Content-Type header,
+will <b>not</b> be encoded as specified by
+<A HREF="http://www.ietf.org/rfc/rfc2231.txt" TARGET="_top">RFC 2231</A>.
+The default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.multipart.ignoremissingendboundary">mail.mime.multipart. ignoremissingendboundary</A></TD>
+<TD>boolean</TD>
+<TD>
+Normally, when parsing a multipart MIME message, a message that is
+missing the final end boundary line is not considered an error.
+The data simply ends at the end of the input.  Note that messages
+of this form violate the MIME specification.  If the property
+<code>mail.mime.multipart.ignoremissingendboundary</code> is set
+to <code>false</code>, such messages are considered an error and a
+<code>MesagingException</code> will be thrown when parsing such a
+message.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.multipart.ignoremissingboundaryparameter">mail.mime.multipart. ignoremissingboundaryparameter</A></TD>
+<TD>boolean</TD>
+<TD>
+If the Content-Type header for a multipart content does not have
+a <code>boundary</code> parameter, the multipart parsing code
+will look for the first line in the content that looks like a
+boundary line and extract the boundary parameter from the line.
+If this property is set to <code>"false"</code>, a
+<code>MessagingException</code> will be thrown if the Content-Type
+header doesn't specify a boundary parameter.
+The default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.multipart.ignoreexistingboundaryparameter">mail.mime.multipart. ignoreexistingboundaryparameter</A></TD>
+<TD>boolean</TD>
+<TD>
+Normally the boundary parameter in the Content-Type header of a multipart
+body part is used to specify the separator between parts of the multipart
+body.  This System property may be set to <code>"true"</code> to cause
+the parser to look for a line in the multipart body that looks like a
+boundary line and use that value as the separator between subsequent parts.
+This may be useful in cases where a broken anti-virus product has rewritten
+the message incorrectly such that the boundary parameter and the actual
+boundary value no longer match.
+The default value of this property is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.multipart.allowempty">mail.mime.multipart. allowempty</A></TD>
+<TD>boolean</TD>
+<TD>
+Normally, when writing out a MimeMultipart that contains no body
+parts, or when trying to parse a multipart message with no body parts,
+a <code>MessagingException</code> is thrown.  The MIME spec does not allow
+multipart content with no body parts.  This
+System property may be set to <code>"true"</code> to override this behavior.
+When writing out such a MimeMultipart, a single empty part will be
+included.  When reading such a multipart, a MimeMultipart will be created
+with no body parts.
+The default value of this property is false.
+</TD>
+</TR>
+
+</TABLE>
+
+
+<P>
+The following properties are supported by the reference implementation (RI) of
+JavaMail, but are not currently a required part of the specification.
+These must be set as <CODE>Session</CODE> properties.
+The names, types, defaults, and semantics of these properties may
+change in future releases.
+</P>
+<TABLE BORDER SUMMARY="JavaMail RI properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.alternates">mail.alternates</A></TD>
+<TD>String</TD>
+<TD>
+A string containing other email addresses that the current user is known by.
+The <code>MimeMessage</code> <code>reply</code> method will eliminate any
+of these addresses from the recipient list in the message it constructs,
+to avoid sending the reply back to the sender.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.replyallcc">mail.replyallcc</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, the <code>MimeMessage</code>
+<code>reply</code> method will put all recipients except the original
+sender in the <code>Cc</code> list of the newly constructed message.
+Normally, recipients in the <code>To</code> header of the original
+message will also appear in the <code>To</code> list of the newly
+constructed message.
+</TD>
+</TR>
+</TABLE>
+
+<P>
+The following properties are supported by the reference implementation (RI) of
+JavaMail, but are not currently a required part of the specification.
+These must be set as <CODE>System</CODE> properties.
+The names, types, defaults, and semantics of these properties may
+change in future releases.
+</P>
+<TABLE BORDER SUMMARY="JavaMail RI System properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.base64.ignoreerrors">mail.mime.base64.ignoreerrors</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, the BASE64 decoder will ignore errors
+in the encoded data, returning EOF.  This may be useful when dealing
+with improperly encoded messages that contain extraneous data at the
+end of the encoded stream.  Note however that errors anywhere in the
+stream will cause the decoder to stop decoding so this should be used
+with extreme caution.  The default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.foldtext">mail.mime.foldtext</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, header fields containing just text
+such as the <code>Subject</code> and <code>Content-Description</code>
+header fields, and long parameter values in structured headers such
+as <code>Content-Type</code> will be folded (broken into 76 character lines)
+when set and unfolded when read.  The default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.setcontenttypefilename">mail.mime.setcontenttypefilename</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, the <code>setFileName</code> method
+will also set the <code>name</code> parameter on the <code>Content-Type</code>
+header to the specified filename.  This supports interoperability with
+some old mail clients.  The default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.setdefaulttextcharset">mail.mime.setdefaulttextcharset</A></TD>
+<TD>boolean</TD>
+<TD>
+When updating the headers of a message, a body
+part with a <code>text</code> content type but no <code>charset</code>
+parameter will have a <code>charset</code> parameter added to it
+if this property is set to <code>"true"</code>.
+The default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.parameters.strict">mail.mime.parameters.strict</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to false, when reading a message, parameter values in header fields
+such as <code>Content-Type</code> and <code>Content-Disposition</code>
+are allowed to contain whitespace and other special characters without
+being quoted; the parameter value ends at the next semicolon.
+If set to true (the default), parameter values are required to conform
+to the MIME specification and must be quoted if they contain whitespace
+or special characters.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.applefilenames">mail.mime.applefilenames</A></TD>
+<TD>boolean</TD>
+<TD>
+Apple Mail incorrectly encodes filenames that contain spaces,
+forgetting to quote the parameter value.  If this property is
+set to <code>"true"</code>, JavaMail will try to detect this
+situation when parsing parameters and work around it.
+The default is false.
+Note that this property handles a subset of the cases handled
+by setting the mail.mime.parameters.strict property to false.
+This property will likely be removed in a future release.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.windowsfilenames">mail.mime.windowsfilenames</A></TD>
+<TD>boolean</TD>
+<TD>
+Internet Explorer 6 incorrectly includes a complete pathname
+in the filename parameter of the Content-Disposition header
+for uploaded files, and fails to properly escape the backslashes
+in the pathname.  If this property is
+set to <code>"true"</code>, JavaMail will preserve all backslashes
+in the "filename" and "name" parameters of any MIME header.
+The default is false.
+Note that this is a violation of the MIME specification but may
+be useful when using JavaMail to parse HTTP messages for uploaded
+files sent by IE6.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.ignoreunknownencoding">mail.mime. ignoreunknownencoding</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, an unknown value in the
+<code>Content-Transfer-Encoding</code> header will be ignored
+when reading a message and an encoding of "8bit" will be assumed.
+If set to <code>"false"</code>, an exception is thrown for an
+unknown encoding value.  The default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.uudecode.ignoreerrors">mail.mime.uudecode. ignoreerrors</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, errors in the encoded format of a
+uuencoded document will be ignored when reading a message part.
+If set to <code>"false"</code>, an exception is thrown for an
+incorrectly encoded message part.  The default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.uudecode.ignoremissingbeginend">mail.mime.uudecode. ignoremissingbeginend</A></TD>
+<TD>boolean</TD>
+<TD>
+If set to <code>"true"</code>, a missing "being" or "end" line in a
+uuencoded document will be ignored when reading a message part.
+If set to <code>"false"</code>, an exception is thrown for a
+uuencoded message part without the required "begin" and "end" lines.
+The default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.ignorewhitespacelines">mail.mime. ignorewhitespacelines</A></TD>
+<TD>boolean</TD>
+<TD>
+Normally the header of a MIME part is separated from the body by an empty
+line.  This System property may be set to <code>"true"</code> to cause
+the parser to consider a line containing only whitespace to be an empty
+line.  The default value of this property is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.ignoremultipartencoding">mail.mime. ignoremultipartencoding</A></TD>
+<TD>boolean</TD>
+<TD>
+The MIME spec does not allow body parts of type multipart/* to be encoded.
+The Content-Transfer-Encoding header is ignored in this case.
+Setting this System property to <code>"false"</code> will
+cause the Content-Transfer-Encoding header to be honored for multipart
+content.
+The default value of this property is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.allowencodedmessages">mail.mime.allowencodedmessages</A></TD>
+<TD>boolean</TD>
+<TD>
+The MIME spec does not allow body parts of type message/* to be encoded.
+The Content-Transfer-Encoding header is ignored in this case.
+Some versions of Microsoft Outlook will incorrectly encode message
+attachments.  Setting this System property to <code>"true"</code> will
+cause the Content-Transfer-Encoding header to be honored for message
+attachments.
+The default value of this property is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.contenttypehandler">mail.mime.contenttypehandler</A></TD>
+<TD>String</TD>
+<TD>
+In some cases JavaMail is unable to process messages with an invalid
+Content-Type header.  The header may have incorrect syntax or other
+problems.  This property specifies the name of a class that will be
+used to clean up the Content-Type header value before JavaMail uses it.
+The class must have a method with this signature:
+<CODE>public static String cleanContentType(MimePart mp, String contentType)</CODE>
+Whenever JavaMail accesses the Content-Type header of a message, it
+will pass the value to this method and use the returned value instead.
+The value may be null if the Content-Type header isn't present.
+Returning null will cause the default Content-Type to be used.
+The MimePart may be used to access other headers of the message part
+to determine how to correct the Content-Type.
+Note that the Content-Type handler doesn't affect the
+<CODE>getHeader</CODE> method, which still returns the raw header value.
+Note also that the handler doesn't affect the IMAP provider; the IMAP
+server is responsible for returning pre-parsed, syntactically correct
+Content-Type information.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.address.usecanonicalhostname">mail.mime.address.usecanonicalhostname</A></TD>
+<TD>boolean</TD>
+<TD>
+Use the
+{@link java.net.InetAddress#getCanonicalHostName InetAddress.getCanonicalHostName}
+method to determine the host name in the
+{@link javax.mail.internet.InternetAddress#getLocalAddress InternetAddress.getLocalAddress}
+method.
+With some network configurations, InetAddress.getCanonicalHostName may be
+slow or may return an address instead of a host name.
+In that case, setting this System property to false will cause the
+{@link java.net.InetAddress#getHostName InetAddress.getHostName}
+method to be used instead.
+The default is true.
+</TD>
+</TR>
+
+</TABLE>
+<P>
+The current
+implementation of classes in this package log debugging information using
+{@link java.util.logging.Logger} as described in the following table:
+</P>
+<TABLE BORDER SUMMARY="JavaMail Loggers">
+<TR>
+<TH>Logger Name</TH>
+<TH>Logging Level</TH>
+<TH>Purpose</TH>
+</TR>
+
+<TR>
+<TD>javax.mail.internet</TD>
+<TD>FINE</TD>
+<TD>General debugging output</TD>
+</TR>
+</TABLE>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/javax/mail/package.html b/mail/src/main/java/javax/mail/package.html
new file mode 100644
index 0000000..411d023
--- /dev/null
+++ b/mail/src/main/java/javax/mail/package.html
@@ -0,0 +1,347 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>javax.mail package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+The JavaMail&trade; API
+provides classes that model a mail system.
+The <code>javax.mail</code> package defines classes that are common to
+all mail systems.
+The <code>javax.mail.internet</code> package defines classes that are specific
+to mail systems based on internet standards such as MIME, SMTP, POP3, and IMAP.
+The JavaMail API includes the <code>javax.mail</code> package and subpackages.
+</P>
+<P>
+For an overview of the JavaMail API, read the
+<A HREF="https://javaee.github.io/javamail/docs/JavaMail-1.6.pdf" TARGET="_top">
+JavaMail specification</A>.
+</P>
+<P>
+The code to send a plain text message can be as simple as the following:
+</P>
+<PRE>
+    Properties props = new Properties();
+    props.put("mail.smtp.host", "my-mail-server");
+    Session session = Session.getInstance(props, null);
+
+    try {
+	MimeMessage msg = new MimeMessage(session);
+	msg.setFrom("me@example.com");
+	msg.setRecipients(Message.RecipientType.TO,
+			  "you@example.com");
+	msg.setSubject("JavaMail hello world example");
+	msg.setSentDate(new Date());
+	msg.setText("Hello, world!\n");
+	Transport.send(msg, "me@example.com", "my-password");
+    } catch (MessagingException mex) {
+	System.out.println("send failed, exception: " + mex);
+    }
+</PRE>
+<P>
+The JavaMail download bundle contains many more complete examples
+in the "demo" directory.
+</P>
+<P>
+Don't forget to see the
+<A HREF="https://javaee.github.io/javamail/FAQ.html" TARGET="_top">
+JavaMail API FAQ</A>
+for answers to the most common questions.
+The <A HREF="https://javaee.github.io/javamail/" TARGET="_top">
+JavaMail web site</A>
+contains many additional resources.
+</P>
+<A NAME="properties"><STRONG>Properties</STRONG></A>
+<P>
+The JavaMail API supports the following standard properties,
+which may be set in the <code>Session</code> object, or in the
+<code>Properties</code> object used to create the <code>Session</code> object.
+The properties are always set as strings; the Type column describes
+how the string is interpreted.  For example, use
+</P>
+<PRE>
+	props.put("mail.debug", "true");
+</PRE>
+<P>
+to set the <code>mail.debug</code> property, which is of type boolean.
+</P>
+<TABLE BORDER SUMMARY="JavaMail properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.debug">mail.debug</A></TD>
+<TD>boolean</TD>
+<TD>
+The initial debug mode.
+Default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.from">mail.from</A></TD>
+<TD>String</TD>
+<TD>
+The return email address of the current user, used by the
+<code>InternetAddress</code> method <code>getLocalAddress</code>.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.address.strict">mail.mime.address.strict</A></TD>
+<TD>boolean</TD>
+<TD>
+The MimeMessage class uses the <code>InternetAddress</code> method
+<code>parseHeader</code> to parse headers in messages.  This property
+controls the strict flag passed to the <code>parseHeader</code>
+method.  The default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.host">mail.host</A></TD>
+<TD>String</TD>
+<TD>
+The default host name of the mail server for both Stores and Transports.
+Used if the <code>mail.<i>protocol</i>.host</code> property isn't set.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.store.protocol">mail.store.protocol</A></TD>
+<TD>String</TD>
+<TD>
+Specifies the default message access protocol.  The
+<code>Session</code> method <code>getStore()</code> returns a Store
+object that implements this protocol.  By default the first Store
+provider in the configuration files is returned.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.transport.protocol">mail.transport.protocol</A></TD>
+<TD>String</TD>
+<TD>
+Specifies the default message transport protocol.  The
+<code>Session</code> method <code>getTransport()</code> returns a Transport
+object that implements this protocol.  By default the first Transport
+provider in the configuration files is returned.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.user">mail.user</A></TD>
+<TD>String</TD>
+<TD>
+The default user name to use when connecting to the mail server.
+Used if the <code>mail.<i>protocol</i>.user</code> property isn't set.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.protocol.class">mail.<i>protocol</i>.class</A></TD>
+<TD>String</TD>
+<TD>
+Specifies the fully qualified class name of the provider for the
+specified protocol.  Used in cases where more than one provider
+for a given protocol exists; this property can be used to specify
+which provider to use by default.  The provider must still be listed
+in a configuration file.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.protocol.host">mail.<i>protocol</i>.host</A></TD>
+<TD>String</TD>
+<TD>
+The host name of the mail server for the specified protocol.
+Overrides the <code>mail.host</code> property.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.protocol.port">mail.<i>protocol</i>.port</A></TD>
+<TD>int</TD>
+<TD>
+The port number of the mail server for the specified protocol.
+If not specified the protocol's default port number is used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.protocol.user">mail.<i>protocol</i>.user</A></TD>
+<TD>String</TD>
+<TD>
+The user name to use when connecting to mail servers
+using the specified protocol.
+Overrides the <code>mail.user</code> property.
+</TD>
+</TR>
+
+</TABLE>
+
+<P>
+The following properties are supported by the reference implementation (RI) of
+JavaMail, but are not currently a required part of the specification.
+The names, types, defaults, and semantics of these properties may
+change in future releases.
+</P>
+<TABLE BORDER SUMMARY="JavaMail RI properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.debug.auth">mail.debug.auth</A></TD>
+<TD>boolean</TD>
+<TD>
+Include protocol authentication commands (including usernames and passwords)
+in the debug output.
+Default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.debug.auth.username">mail.debug.auth.username</A></TD>
+<TD>boolean</TD>
+<TD>
+Include the user name in non-protocol debug output.
+Default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.debug.auth.password">mail.debug.auth.password</A></TD>
+<TD>boolean</TD>
+<TD>
+Include the password in non-protocol debug output.
+Default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.transport.protocol.address-type">mail.transport.protocol.<i>address-type</i></A></TD>
+<TD>String</TD>
+<TD>
+Specifies the default message transport protocol for the specified address type.
+The <code>Session</code> method <code>getTransport(Address)</code> returns a
+Transport object that implements this protocol when the address is of the
+specified type (e.g., "rfc822" for standard internet addresses).
+By default the first Transport configured for that address type is used.
+This property can be used to override the behavior of the
+{@link javax.mail.Transport#send send} method of the
+{@link javax.mail.Transport Transport} class so that (for example) the "smtps"
+protocol is used instead of the "smtp" protocol by setting the property
+<code>mail.transport.protocol.rfc822</code> to <code>"smtps"</code>.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.event.scope">mail.event.scope</A></TD>
+<TD>String</TD>
+<TD>
+Controls the scope of events.  (See the javax.mail.event package.)
+By default, a separate event queue and thread is used for events for each
+Store, Transport, or Folder.
+If this property is set to "session", all such events are put in a single
+event queue processed by a single thread for the current session.
+If this property is set to "application", all such events are put in a single
+event queue processed by a single thread for the current application.
+(Applications are distinguished by their context class loader.)
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.event.executor">mail.event.executor</A></TD>
+<TD>java.util.concurrent.Executor</TD>
+<TD>
+By default, a new Thread is created for each event queue.
+This thread is used to call the listeners for these events.
+If this property is set to an instance of an Executor, the
+Executor.execute method is used to run the event dispatcher
+for an event queue.  The event dispatcher runs until the
+event queue is no longer in use.
+</TD>
+</TR>
+
+</TABLE>
+
+<P>
+The JavaMail API also supports several System properties;
+see the {@link javax.mail.internet} package documentation
+for details.
+</P>
+<P>
+The JavaMail reference
+implementation includes protocol providers in subpackages of
+<code>com.sun.mail</code>.  Note that the APIs to these protocol
+providers are not part of the standard JavaMail API.  Portable
+programs will not use these APIs.
+</P>
+<P>
+Nonportable programs may use the APIs of the protocol providers
+by (for example) casting a returned <code>Folder</code> object to a
+<code>com.sun.mail.imap.IMAPFolder</code> object.  Similarly for
+<code>Store</code> and <code>Message</code> objects returned from the
+standard JavaMail APIs.
+</P>
+<P>
+The protocol providers also support properties that are specific to
+those providers.  The package documentation for the
+{@link com.sun.mail.imap IMAP}, {@link com.sun.mail.pop3 POP3},
+and {@link com.sun.mail.smtp SMTP} packages provide details.
+</P>
+<P>
+In addition to printing debugging output as controlled by the
+{@link javax.mail.Session Session} configuration, the current
+implementation of classes in this package log the same information using
+{@link java.util.logging.Logger} as described in the following table:
+</P>
+<TABLE BORDER SUMMARY="JavaMail Loggers">
+<TR>
+<TH>Logger Name</TH>
+<TH>Logging Level</TH>
+<TH>Purpose</TH>
+</TR>
+
+<TR>
+<TD>javax.mail</TD>
+<TD>CONFIG</TD>
+<TD>Configuration of the Session</TD>
+</TR>
+
+<TR>
+<TD>javax.mail</TD>
+<TD>FINE</TD>
+<TD>General debugging output</TD>
+</TR>
+</TABLE>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/javax/mail/search/AddressStringTerm.java b/mail/src/main/java/javax/mail/search/AddressStringTerm.java
new file mode 100644
index 0000000..1e48e72
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/AddressStringTerm.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+import javax.mail.Address;
+import javax.mail.internet.InternetAddress;
+
+/**
+ * This abstract class implements string comparisons for Message 
+ * addresses. <p>
+ *
+ * Note that this class differs from the <code>AddressTerm</code> class
+ * in that this class does comparisons on address strings rather than
+ * Address objects.
+ *
+ * @since       JavaMail 1.1
+ */
+
+public abstract class AddressStringTerm extends StringTerm {
+
+    private static final long serialVersionUID = 3086821234204980368L;
+
+    /**
+     * Constructor.
+     *
+     * @param pattern   the address pattern to be compared.
+     */
+    protected AddressStringTerm(String pattern) {
+	super(pattern, true); // we need case-insensitive comparison.
+    }
+
+    /**
+     * Check whether the address pattern specified in the constructor is
+     * a substring of the string representation of the given Address
+     * object. <p>
+     *
+     * Note that if the string representation of the given Address object
+     * contains charset or transfer encodings, the encodings must be 
+     * accounted for, during the match process. <p>
+     *
+     * @param   a 	The comparison is applied to this Address object.
+     * @return          true if the match succeeds, otherwise false.
+     */
+    protected boolean match(Address a) {
+	if (a instanceof InternetAddress) {
+	    InternetAddress ia = (InternetAddress)a;
+	    // We dont use toString() to get "a"'s String representation,
+	    // because InternetAddress.toString() returns a RFC 2047 
+	    // encoded string, which isn't what we need here.
+
+	    return super.match(ia.toUnicodeString());
+	} else
+	    return super.match(a.toString());
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof AddressStringTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/AddressTerm.java b/mail/src/main/java/javax/mail/search/AddressTerm.java
new file mode 100644
index 0000000..748ad33
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/AddressTerm.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Address;
+
+/**
+ * This class implements Message Address comparisons.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+
+public abstract class AddressTerm extends SearchTerm {
+    /**
+     * The address.
+     *
+     * @serial
+     */
+    protected Address address;
+
+    private static final long serialVersionUID = 2005405551929769980L;
+
+    protected AddressTerm(Address address) {
+	this.address = address;
+    }
+
+    /**
+     * Return the address to match with.
+     *
+     * @return	the adddress
+     */
+    public Address getAddress() {
+	return address;
+    }
+
+    /**
+     * Match against the argument Address.
+     *
+     * @param	a	the address to match
+     * @return	true if it matches
+     */
+    protected boolean match(Address a) {
+	return (a.equals(address));
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof AddressTerm))
+	    return false;
+	AddressTerm at = (AddressTerm)obj;
+	return at.address.equals(this.address);
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return address.hashCode();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/AndTerm.java b/mail/src/main/java/javax/mail/search/AndTerm.java
new file mode 100644
index 0000000..d0b935d
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/AndTerm.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+
+/**
+ * This class implements the logical AND operator on individual
+ * SearchTerms.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class AndTerm extends SearchTerm {
+
+    /**
+     * The array of terms on which the AND operator should be
+     * applied.
+     *
+     * @serial
+     */
+    private SearchTerm[] terms;
+
+    private static final long serialVersionUID = -3583274505380989582L;
+
+    /**
+     * Constructor that takes two terms.
+     * 
+     * @param t1 first term
+     * @param t2 second term
+     */
+    public AndTerm(SearchTerm t1, SearchTerm t2) {
+	terms = new SearchTerm[2];
+	terms[0] = t1;
+	terms[1] = t2;
+    }
+
+    /**
+     * Constructor that takes an array of SearchTerms.
+     * 
+     * @param t  array of terms
+     */
+    public AndTerm(SearchTerm[] t) {
+	terms = new SearchTerm[t.length]; // clone the array
+	for (int i = 0; i < t.length; i++)
+	    terms[i] = t[i];
+    }
+
+    /**
+     * Return the search terms.
+     *
+     * @return	the search terms
+     */
+    public SearchTerm[] getTerms() {
+	return terms.clone();
+    }
+
+    /**
+     * The AND operation. <p>
+     *
+     * The terms specified in the constructor are applied to
+     * the given object and the AND operator is applied to their results.
+     *
+     * @param msg	The specified SearchTerms are applied to this Message
+     *			and the AND operator is applied to their results.
+     * @return		true if the AND succeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	for (int i=0; i < terms.length; i++)
+	    if (!terms[i].match(msg))
+		return false;
+	return true;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof AndTerm))
+	    return false;
+	AndTerm at = (AndTerm)obj;
+	if (at.terms.length != terms.length)
+	    return false;
+	for (int i=0; i < terms.length; i++)
+	    if (!terms[i].equals(at.terms[i]))
+		return false;
+	return true;
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	int hash = 0;
+	for (int i=0; i < terms.length; i++)
+	    hash += terms[i].hashCode();
+	return hash;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/BodyTerm.java b/mail/src/main/java/javax/mail/search/BodyTerm.java
new file mode 100644
index 0000000..fa67893
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/BodyTerm.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import java.io.IOException;
+import javax.mail.*;
+
+/**
+ * This class implements searches on a message body.
+ * All parts of the message that are of MIME type "text/*" are searched.
+ * The pattern is a simple string that must appear as a substring in
+ * the message body.
+ * 
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class BodyTerm extends StringTerm {
+
+    private static final long serialVersionUID = -4888862527916911385L;
+
+    /**
+     * Constructor
+     * @param pattern	The String to search for
+     */
+    public BodyTerm(String pattern) {
+	// Note: comparison is case-insensitive
+	super(pattern);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	The pattern search is applied on this Message's body
+     * @return		true if the pattern is found; otherwise false 
+     */
+    @Override
+    public boolean match(Message msg) {
+	return matchPart(msg);
+    }
+
+    /**
+     * Search all the parts of the message for any text part
+     * that matches the pattern.
+     */
+    private boolean matchPart(Part p) {
+	try {
+	    /*
+	     * Using isMimeType to determine the content type avoids
+	     * fetching the actual content data until we need it.
+	     */
+	    if (p.isMimeType("text/*")) {
+		String s = (String)p.getContent();
+		if (s == null)
+		    return false;
+		/*
+		 * We invoke our superclass' (i.e., StringTerm) match method.
+		 * Note however that StringTerm.match() is not optimized 
+		 * for substring searches in large string buffers. We really
+		 * need to have a StringTerm subclass, say BigStringTerm, 
+		 * with its own match() method that uses a better algorithm ..
+		 * and then subclass BodyTerm from BigStringTerm.
+		 */ 
+		return super.match(s);
+	    } else if (p.isMimeType("multipart/*")) {
+		Multipart mp = (Multipart)p.getContent();
+		int count = mp.getCount();
+		for (int i = 0; i < count; i++)
+		    if (matchPart(mp.getBodyPart(i)))
+			return true;
+	    } else if (p.isMimeType("message/rfc822")) {
+		return matchPart((Part)p.getContent());
+	    }
+	} catch (MessagingException ex) {
+	} catch (IOException ex) {
+	} catch (RuntimeException ex) {
+	}
+	return false;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof BodyTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/ComparisonTerm.java b/mail/src/main/java/javax/mail/search/ComparisonTerm.java
new file mode 100644
index 0000000..57330ec
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/ComparisonTerm.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+/**
+ * This class models the comparison operator. This is an abstract
+ * class; subclasses implement comparisons for different datatypes.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public abstract class ComparisonTerm extends SearchTerm {
+    public static final int LE = 1;
+    public static final int LT = 2;
+    public static final int EQ = 3;
+    public static final int NE = 4;
+    public static final int GT = 5;
+    public static final int GE = 6;
+
+    /**
+     * The comparison.
+     *
+     * @serial
+     */
+    protected int comparison;
+
+    private static final long serialVersionUID = 1456646953666474308L;
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof ComparisonTerm))
+	    return false;
+	ComparisonTerm ct = (ComparisonTerm)obj;
+	return ct.comparison == this.comparison;
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return comparison;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/DateTerm.java b/mail/src/main/java/javax/mail/search/DateTerm.java
new file mode 100644
index 0000000..833ad90
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/DateTerm.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import java.util.Date;
+
+/**
+ * This class implements comparisons for Dates
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public abstract class DateTerm extends ComparisonTerm {
+    /**
+     * The date.
+     *
+     * @serial
+     */
+    protected Date date;
+
+    private static final long serialVersionUID = 4818873430063720043L;
+
+    /**
+     * Constructor.
+     * @param comparison the comparison type
+     * @param date  The Date to be compared against
+     */
+    protected DateTerm(int comparison, Date date) {
+	this.comparison = comparison;
+	this.date = date;
+    }
+
+    /**
+     * Return the Date to compare with.
+     *
+     * @return	the date
+     */
+    public Date getDate() {
+	return new Date(date.getTime());
+    }
+
+    /**
+     * Return the type of comparison.
+     *
+     * @return	the comparison type
+     */
+    public int getComparison() {
+	return comparison;
+    }
+
+    /**
+     * The date comparison method.
+     *
+     * @param d	the date in the constructor is compared with this date
+     * @return  true if the dates match, otherwise false
+     */
+    protected boolean match(Date d) {
+	switch (comparison) {
+	    case LE: 
+		return d.before(date) || d.equals(date);
+	    case LT:
+		return d.before(date);
+	    case EQ:
+		return d.equals(date);
+	    case NE:
+		return !d.equals(date);
+	    case GT:
+		return d.after(date);
+	    case GE:
+		return d.after(date) || d.equals(date);
+	    default:
+		return false;
+	}
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof DateTerm))
+	    return false;
+	DateTerm dt = (DateTerm)obj;
+	return dt.date.equals(this.date) && super.equals(obj);
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return date.hashCode() + super.hashCode();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/FlagTerm.java b/mail/src/main/java/javax/mail/search/FlagTerm.java
new file mode 100644
index 0000000..20897c6
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/FlagTerm.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.*;
+
+/**
+ * This class implements comparisons for Message Flags.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class FlagTerm extends SearchTerm {
+
+    /**
+     * Indicates whether to test for the presence or
+     * absence of the specified Flag. If <code>true</code>,
+     * then test whether all the specified flags are present, else
+     * test whether all the specified flags are absent.
+     *
+     * @serial
+     */
+    private boolean set;
+
+    /**
+     * Flags object containing the flags to test.
+     *
+     * @serial
+     */
+    private Flags flags;
+
+    private static final long serialVersionUID = -142991500302030647L;
+
+    /**
+     * Constructor.
+     *
+     * @param flags	Flags object containing the flags to check for
+     * @param set	the flag setting to check for
+     */
+    public FlagTerm(Flags flags, boolean set) {
+	this.flags = flags;
+	this.set = set;
+    }
+
+    /**
+     * Return the Flags to test.
+     *
+     * @return	the flags
+     */
+    public Flags getFlags() {
+	return (Flags)flags.clone();
+    }
+
+    /**
+     * Return true if testing whether the flags are set.
+     *
+     * @return	true if testing whether the flags are set
+     */
+    public boolean getTestSet() {
+	return set;
+    }
+
+    /**
+     * The comparison method.
+     *
+     * @param msg	The flag comparison is applied to this Message
+     * @return		true if the comparson succeeds, otherwise false.
+     */
+    @Override
+    public boolean match(Message msg) {
+
+	try {
+	    Flags f = msg.getFlags();
+	    if (set) { // This is easy
+		if (f.contains(flags))
+		    return true;
+		else 
+		    return false;
+	    }
+
+	    // Return true if ALL flags in the passed in Flags
+	    // object are NOT set in this Message.
+
+	    // Got to do this the hard way ...
+	    Flags.Flag[] sf = flags.getSystemFlags();
+
+	    // Check each flag in the passed in Flags object
+	    for (int i = 0; i < sf.length; i++) {
+		if (f.contains(sf[i]))
+		    // this flag IS set in this Message, get out.
+		    return false;
+	    }
+
+	    String[] s = flags.getUserFlags();
+
+	    // Check each flag in the passed in Flags object
+	    for (int i = 0; i < s.length; i++) {
+		if (f.contains(s[i]))
+		    // this flag IS set in this Message, get out.
+		    return false;
+	    }
+
+	    return true;
+
+	} catch (MessagingException e) {
+	    return false;
+	} catch (RuntimeException e) {
+	    return false;
+	}
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof FlagTerm))
+	    return false;
+	FlagTerm ft = (FlagTerm)obj;
+	return ft.set == this.set && ft.flags.equals(this.flags);
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return set ? flags.hashCode() : ~flags.hashCode();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/FromStringTerm.java b/mail/src/main/java/javax/mail/search/FromStringTerm.java
new file mode 100644
index 0000000..69ab926
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/FromStringTerm.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+import javax.mail.Address;
+
+/**
+ * This class implements string comparisons for the From Address
+ * header. <p>
+ *
+ * Note that this class differs from the <code>FromTerm</code> class
+ * in that this class does comparisons on address strings rather than Address
+ * objects. The string comparisons are case-insensitive.
+ *
+ * @since       JavaMail 1.1
+ */
+
+public final class FromStringTerm extends AddressStringTerm {
+
+    private static final long serialVersionUID = 5801127523826772788L;
+
+    /**
+     * Constructor.
+     *
+     * @param pattern   the address pattern to be compared.
+     */
+    public FromStringTerm(String pattern) {
+	super(pattern);
+    }
+
+    /**
+     * Check whether the address string specified in the constructor is
+     * a substring of the From address of this Message.
+     *
+     * @param   msg 	The comparison is applied to this Message's From
+     *		    	address.
+     * @return          true if the match succeeds, otherwise false.
+     */
+    @Override
+    public boolean match(Message msg) {
+	Address[] from;
+
+	try {
+	    from = msg.getFrom();
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (from == null)
+	    return false;
+	
+	for (int i=0; i < from.length; i++)
+	    if (super.match(from[i]))
+		return true;
+	return false;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof FromStringTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/FromTerm.java b/mail/src/main/java/javax/mail/search/FromTerm.java
new file mode 100644
index 0000000..6320a97
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/FromTerm.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+import javax.mail.Address;
+
+/**
+ * This class implements comparisons for the From Address header.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class FromTerm extends AddressTerm {
+
+    private static final long serialVersionUID = 5214730291502658665L;
+
+    /**
+     * Constructor
+     * @param address	The Address to be compared
+     */
+    public FromTerm(Address address) {
+	super(address);
+    }
+
+    /**
+     * The address comparator.
+     *
+     * @param msg	The address comparison is applied to this Message
+     * @return		true if the comparison succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	Address[] from;
+
+	try {
+	    from = msg.getFrom();
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (from == null)
+	    return false;
+
+	for (int i=0; i < from.length; i++)
+	    if (super.match(from[i]))
+		return true;
+	return false;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof FromTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/HeaderTerm.java b/mail/src/main/java/javax/mail/search/HeaderTerm.java
new file mode 100644
index 0000000..72b82de
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/HeaderTerm.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import java.util.Locale;
+import javax.mail.Message;
+
+/**
+ * This class implements comparisons for Message headers.
+ * The comparison is case-insensitive.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class HeaderTerm extends StringTerm {
+    /**
+     * The name of the header.
+     *
+     * @serial
+     */
+    private String headerName;
+
+    private static final long serialVersionUID = 8342514650333389122L;
+
+    /**
+     * Constructor.
+     *
+     * @param headerName The name of the header
+     * @param pattern    The pattern to search for
+     */
+    public HeaderTerm(String headerName, String pattern) {
+	super(pattern);
+	this.headerName = headerName;
+    }
+
+    /**
+     * Return the name of the header to compare with.
+     *
+     * @return	the name of the header
+     */
+    public String getHeaderName() {
+	return headerName;
+    }
+
+    /**
+     * The header match method.
+     *
+     * @param msg	The match is applied to this Message's header
+     * @return		true if the match succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	String[] headers;
+
+	try {
+	    headers = msg.getHeader(headerName);
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (headers == null)
+	    return false;
+
+	for (int i=0; i < headers.length; i++)
+	    if (super.match(headers[i]))
+		return true;
+	return false;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof HeaderTerm))
+	    return false;
+	HeaderTerm ht = (HeaderTerm)obj;
+	// XXX - depends on header comparisons being case independent
+	return ht.headerName.equalsIgnoreCase(headerName) && super.equals(ht);
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	// XXX - depends on header comparisons being case independent
+	return headerName.toLowerCase(Locale.ENGLISH).hashCode() +
+					super.hashCode();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/IntegerComparisonTerm.java b/mail/src/main/java/javax/mail/search/IntegerComparisonTerm.java
new file mode 100644
index 0000000..f890051
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/IntegerComparisonTerm.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+/**
+ * This class implements comparisons for integers.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public abstract class IntegerComparisonTerm extends ComparisonTerm {
+    /**
+     * The number.
+     *
+     * @serial
+     */
+    protected int number;
+
+    private static final long serialVersionUID = -6963571240154302484L;
+
+    protected IntegerComparisonTerm(int comparison, int number) {
+	this.comparison = comparison;
+	this.number = number;
+    }
+
+    /**
+     * Return the number to compare with.
+     *
+     * @return	the number
+     */
+    public int getNumber() {
+	return number;
+    }
+
+    /**
+     * Return the type of comparison.
+     *
+     * @return	the comparison type
+     */
+    public int getComparison() {
+	return comparison;
+    }
+
+    protected boolean match(int i) {
+	switch (comparison) {
+	    case LE: 
+		return i <= number;
+	    case LT:
+		return i < number;
+	    case EQ:
+		return i == number;
+	    case NE:
+		return i != number;
+	    case GT:
+		return i > number;
+	    case GE:
+		return i >= number;
+	    default:
+		return false;
+	}
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof IntegerComparisonTerm))
+	    return false;
+	IntegerComparisonTerm ict = (IntegerComparisonTerm)obj;
+	return ict.number == this.number && super.equals(obj);
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return number + super.hashCode();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/MessageIDTerm.java b/mail/src/main/java/javax/mail/search/MessageIDTerm.java
new file mode 100644
index 0000000..6762c04
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/MessageIDTerm.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+
+/**
+ * This term models the RFC822 "MessageId" - a message-id for 
+ * Internet messages that is supposed to be unique per message.
+ * Clients can use this term to search a folder for a message given
+ * its MessageId. <p>
+ *
+ * The MessageId is represented as a String.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class MessageIDTerm extends StringTerm {
+
+    private static final long serialVersionUID = -2121096296454691963L;
+
+    /**
+     * Constructor.
+     *
+     * @param msgid  the msgid to search for
+     */
+    public MessageIDTerm(String msgid) {
+	// Note: comparison is case-insensitive
+	super(msgid);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the match is applied to this Message's 
+     *			Message-ID header
+     * @return		true if the match succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	String[] s;
+
+	try {
+	    s = msg.getHeader("Message-ID");
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (s == null)
+	    return false;
+
+	for (int i=0; i < s.length; i++)
+	    if (super.match(s[i]))
+		return true;
+	return false;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof MessageIDTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/MessageNumberTerm.java b/mail/src/main/java/javax/mail/search/MessageNumberTerm.java
new file mode 100644
index 0000000..3ec0f28
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/MessageNumberTerm.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+
+/**
+ * This class implements comparisons for Message numbers.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class MessageNumberTerm extends IntegerComparisonTerm {
+
+    private static final long serialVersionUID = -5379625829658623812L;
+
+    /**
+     * Constructor.
+     *
+     * @param number  the Message number
+     */
+    public MessageNumberTerm(int number) {
+	super(EQ, number);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the Message number is matched with this Message
+     * @return		true if the match succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	int msgno;
+
+	try {
+	    msgno = msg.getMessageNumber();
+	} catch (Exception e) {
+	    return false;
+	}
+	
+	return super.match(msgno);
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof MessageNumberTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/NotTerm.java b/mail/src/main/java/javax/mail/search/NotTerm.java
new file mode 100644
index 0000000..b87c151
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/NotTerm.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+
+/**
+ * This class implements the logical NEGATION operator.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class NotTerm extends SearchTerm {
+    /**
+     * The search term to negate.
+     *
+     * @serial
+     */
+    private SearchTerm term;
+
+    private static final long serialVersionUID = 7152293214217310216L;
+
+    public NotTerm(SearchTerm t) {
+	term = t;
+    }
+
+    /**
+     * Return the term to negate.
+     *
+     * @return	the Term
+     */
+    public SearchTerm getTerm() {
+	return term;
+    }
+
+    /* The NOT operation */
+    @Override
+    public boolean match(Message msg) {
+	return !term.match(msg);
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof NotTerm))
+	    return false;
+	NotTerm nt = (NotTerm)obj;
+	return nt.term.equals(this.term);
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return term.hashCode() << 1;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/OrTerm.java b/mail/src/main/java/javax/mail/search/OrTerm.java
new file mode 100644
index 0000000..3df58f4
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/OrTerm.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+
+/**
+ * This class implements the logical OR operator on individual SearchTerms.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class OrTerm extends SearchTerm {
+
+    /**
+     * The array of terms on which the OR operator should
+     * be applied.
+     *
+     * @serial
+     */
+    private SearchTerm[] terms;
+
+    private static final long serialVersionUID = 5380534067523646936L;
+
+    /**
+     * Constructor that takes two operands.
+     *
+     * @param t1 first term
+     * @param t2 second term
+     */
+    public OrTerm(SearchTerm t1, SearchTerm t2) {
+	terms = new SearchTerm[2];
+	terms[0] = t1;
+	terms[1] = t2;
+    }
+
+    /**
+     * Constructor that takes an array of SearchTerms.
+     *
+     * @param t array of search terms
+     */
+    public OrTerm(SearchTerm[] t) {
+	terms = new SearchTerm[t.length];
+	for (int i = 0; i < t.length; i++)
+	    terms[i] = t[i];
+    }
+
+    /**
+     * Return the search terms.
+     *
+     * @return	the search terms
+     */
+    public SearchTerm[] getTerms() {
+	return terms.clone();
+    }
+
+    /**
+     * The OR operation. <p>
+     *
+     * The terms specified in the constructor are applied to
+     * the given object and the OR operator is applied to their results.
+     *
+     * @param msg	The specified SearchTerms are applied to this Message
+     *			and the OR operator is applied to their results.
+     * @return		true if the OR succeds, otherwise false
+     */
+
+    @Override
+    public boolean match(Message msg) {
+	for (int i=0; i < terms.length; i++)
+	    if (terms[i].match(msg))
+		return true;
+	return false;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof OrTerm))
+	    return false;
+	OrTerm ot = (OrTerm)obj;
+	if (ot.terms.length != terms.length)
+	    return false;
+	for (int i=0; i < terms.length; i++)
+	    if (!terms[i].equals(ot.terms[i]))
+		return false;
+	return true;
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	int hash = 0;
+	for (int i=0; i < terms.length; i++)
+	    hash += terms[i].hashCode();
+	return hash;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/ReceivedDateTerm.java b/mail/src/main/java/javax/mail/search/ReceivedDateTerm.java
new file mode 100644
index 0000000..b189d01
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/ReceivedDateTerm.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import java.util.Date;
+import javax.mail.Message;
+
+/**
+ * This class implements comparisons for the Message Received date
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class ReceivedDateTerm extends DateTerm {
+
+    private static final long serialVersionUID = -2756695246195503170L;
+
+    /**
+     * Constructor.
+     *
+     * @param comparison	the Comparison type
+     * @param date		the date to be compared
+     */
+    public ReceivedDateTerm(int comparison, Date date) {
+	super(comparison, date);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the date comparator is applied to this Message's
+     *			received date
+     * @return		true if the comparison succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	Date d;
+
+	try {
+	    d = msg.getReceivedDate();
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (d == null)
+	    return false;
+
+	return super.match(d);
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof ReceivedDateTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/RecipientStringTerm.java b/mail/src/main/java/javax/mail/search/RecipientStringTerm.java
new file mode 100644
index 0000000..60466cd
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/RecipientStringTerm.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+import javax.mail.Address;
+
+/**
+ * This class implements string comparisons for the Recipient Address
+ * headers. <p>
+ *
+ * Note that this class differs from the <code>RecipientTerm</code> class
+ * in that this class does comparisons on address strings rather than Address
+ * objects. The string comparisons are case-insensitive.
+ *
+ * @since       JavaMail 1.1
+ */
+
+public final class RecipientStringTerm extends AddressStringTerm {
+
+    /**
+     * The recipient type.
+     *
+     * @serial
+     */
+    private Message.RecipientType type;
+
+    private static final long serialVersionUID = -8293562089611618849L;
+
+    /**
+     * Constructor.
+     *
+     * @param type      the recipient type
+     * @param pattern   the address pattern to be compared.
+     */
+    public RecipientStringTerm(Message.RecipientType type, String pattern) {
+	super(pattern);
+	this.type = type;
+    }
+
+    /**
+     * Return the type of recipient to match with.
+     *
+     * @return	the recipient type
+     */
+    public Message.RecipientType getRecipientType() {
+	return type;
+    }
+
+    /**
+     * Check whether the address specified in the constructor is
+     * a substring of the recipient address of this Message.
+     *
+     * @param   msg 	The comparison is applied to this Message's recipient
+     *		    	address.
+     * @return          true if the match succeeds, otherwise false.
+     */
+    @Override
+    public boolean match(Message msg) {
+	Address[] recipients;
+
+	try {
+	    recipients = msg.getRecipients(type);
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (recipients == null)
+	    return false;
+	
+	for (int i=0; i < recipients.length; i++)
+	    if (super.match(recipients[i]))
+		return true;
+	return false;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof RecipientStringTerm))
+	    return false;
+	RecipientStringTerm rst = (RecipientStringTerm)obj;
+	return rst.type.equals(this.type) && super.equals(obj);
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return type.hashCode() + super.hashCode();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/RecipientTerm.java b/mail/src/main/java/javax/mail/search/RecipientTerm.java
new file mode 100644
index 0000000..7f2d149
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/RecipientTerm.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+import javax.mail.Address;
+
+/**
+ * This class implements comparisons for the Recipient Address headers.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class RecipientTerm extends AddressTerm {
+
+    /**
+     * The recipient type.
+     *
+     * @serial
+     */
+    private Message.RecipientType type;
+
+    private static final long serialVersionUID = 6548700653122680468L;
+
+    /**
+     * Constructor.
+     *
+     * @param type	the recipient type
+     * @param address	the address to match for
+     */
+    public RecipientTerm(Message.RecipientType type, Address address) {
+	super(address);
+	this.type = type;
+    }
+
+    /**
+     * Return the type of recipient to match with.
+     *
+     * @return	the recipient type
+     */
+    public Message.RecipientType getRecipientType() {
+	return type;
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	The address match is applied to this Message's recepient
+     *			address
+     * @return		true if the match succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	Address[] recipients;
+
+	try {
+ 	    recipients = msg.getRecipients(type);
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (recipients == null)
+	    return false;
+
+	for (int i=0; i < recipients.length; i++)
+	    if (super.match(recipients[i]))
+		return true;
+	return false;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof RecipientTerm))
+	    return false;
+	RecipientTerm rt = (RecipientTerm)obj;
+	return rt.type.equals(this.type) && super.equals(obj);
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return type.hashCode() + super.hashCode();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/SearchException.java b/mail/src/main/java/javax/mail/search/SearchException.java
new file mode 100644
index 0000000..5c4d460
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/SearchException.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.MessagingException;
+
+
+/**
+ * The exception thrown when a Search expression could not be handled.
+ *
+ * @author John Mani
+ */
+
+public class SearchException extends MessagingException {
+
+    private static final long serialVersionUID = -7092886778226268686L;
+
+    /**
+     * Constructs a SearchException with no detail message.
+     */
+    public SearchException() {
+	super();
+    }
+
+    /**
+     * Constructs a SearchException with the specified detail message.
+     * @param s		the detail message
+     */
+    public SearchException(String s) {
+	super(s);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/SearchTerm.java b/mail/src/main/java/javax/mail/search/SearchTerm.java
new file mode 100644
index 0000000..3194b63
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/SearchTerm.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import java.io.Serializable;
+
+import javax.mail.Message;
+
+/**
+ * Search criteria are expressed as a tree of search-terms, forming
+ * a parse-tree for the search expression. <p>
+ *
+ * Search-terms are represented by this class. This is an abstract
+ * class; subclasses implement specific match methods. <p>
+ *
+ * Search terms are serializable, which allows storing a search term
+ * between sessions.
+ *
+ * <strong>Warning:</strong>
+ * Serialized objects of this class may not be compatible with future
+ * JavaMail API releases.  The current serialization support is
+ * appropriate for short term storage. <p>
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public abstract class SearchTerm implements Serializable {
+
+    private static final long serialVersionUID = -6652358452205992789L;
+
+    /**
+     * This method applies a specific match criterion to the given
+     * message and returns the result.
+     *
+     * @param msg	The match criterion is applied on this message
+     * @return		true, it the match succeeds, false if the match fails
+     */
+
+    public abstract boolean match(Message msg);
+}
diff --git a/mail/src/main/java/javax/mail/search/SentDateTerm.java b/mail/src/main/java/javax/mail/search/SentDateTerm.java
new file mode 100644
index 0000000..2bd2fcb
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/SentDateTerm.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import java.util.Date;
+import javax.mail.Message;
+
+/**
+ * This class implements comparisons for the Message SentDate.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class SentDateTerm extends DateTerm {
+
+    private static final long serialVersionUID = 5647755030530907263L;
+
+    /**
+     * Constructor.
+     *
+     * @param comparison	the Comparison type
+     * @param date		the date to be compared
+     */
+    public SentDateTerm(int comparison, Date date) {
+	super(comparison, date);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the date comparator is applied to this Message's
+     *			sent date
+     * @return		true if the comparison succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	Date d;
+
+	try {
+	    d = msg.getSentDate();
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (d == null)
+	    return false;
+
+	return super.match(d);
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof SentDateTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/SizeTerm.java b/mail/src/main/java/javax/mail/search/SizeTerm.java
new file mode 100644
index 0000000..a940176
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/SizeTerm.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+
+/**
+ * This class implements comparisons for Message sizes.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class SizeTerm extends IntegerComparisonTerm {
+
+    private static final long serialVersionUID = -2556219451005103709L;
+
+    /**
+     * Constructor.
+     *
+     * @param comparison	the Comparison type
+     * @param size		the size
+     */
+    public SizeTerm(int comparison, int size) {
+	super(comparison, size);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the size comparator is applied to this Message's size
+     * @return		true if the size is equal, otherwise false 
+     */
+    @Override
+    public boolean match(Message msg) {
+	int size;
+
+	try {
+	    size = msg.getSize();
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (size == -1)
+	    return false;
+
+	return super.match(size);
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof SizeTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/StringTerm.java b/mail/src/main/java/javax/mail/search/StringTerm.java
new file mode 100644
index 0000000..51edfae
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/StringTerm.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+/**
+ * This class implements the match method for Strings. The current
+ * implementation provides only for substring matching. We
+ * could add comparisons (like strcmp ...).
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public abstract class StringTerm extends SearchTerm {
+    /**
+     * The pattern.
+     *
+     * @serial
+     */
+    protected String pattern;
+
+    /**
+     * Ignore case when comparing?
+     *
+     * @serial
+     */
+    protected boolean ignoreCase;
+
+    private static final long serialVersionUID = 1274042129007696269L;
+
+    /**
+     * Construct a StringTerm with the given pattern.
+     * Case will be ignored.
+     *
+     * @param	pattern		the pattern
+     */
+    protected StringTerm(String pattern) {
+	this.pattern = pattern;
+	ignoreCase = true;
+    }
+
+    /**
+     * Construct a StringTerm with the given pattern and ignoreCase flag.
+     *
+     * @param	pattern		the pattern
+     * @param	ignoreCase	should we ignore case?
+     */
+    protected StringTerm(String pattern, boolean ignoreCase) {
+	this.pattern = pattern;
+	this.ignoreCase = ignoreCase;
+    }
+
+    /**
+     * Return the string to match with.
+     *
+     * @return	the string to match
+     */
+    public String getPattern() {
+	return pattern;
+    }
+
+    /**
+     * Return true if we should ignore case when matching.
+     *
+     * @return	true if we should ignore case
+     */
+    public boolean getIgnoreCase() {
+	return ignoreCase;
+    }
+
+    protected boolean match(String s) {
+	int len = s.length() - pattern.length();
+	for (int i=0; i <= len; i++) {
+	    if (s.regionMatches(ignoreCase, i, 
+				pattern, 0, pattern.length()))
+		return true;
+	}
+	return false;
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof StringTerm))
+	    return false;
+	StringTerm st = (StringTerm)obj;
+	if (ignoreCase)
+	    return st.pattern.equalsIgnoreCase(this.pattern) &&
+		    st.ignoreCase == this.ignoreCase;
+	else
+	    return st.pattern.equals(this.pattern) &&
+		    st.ignoreCase == this.ignoreCase;
+    }
+
+    /**
+     * Compute a hashCode for this object.
+     */
+    @Override
+    public int hashCode() {
+	return ignoreCase ? pattern.hashCode() : ~pattern.hashCode();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/SubjectTerm.java b/mail/src/main/java/javax/mail/search/SubjectTerm.java
new file mode 100644
index 0000000..3326180
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/SubjectTerm.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import javax.mail.Message;
+
+/**
+ * This class implements comparisons for the message Subject header.
+ * The comparison is case-insensitive.  The pattern is a simple string
+ * that must appear as a substring in the Subject.
+ *
+ * @author Bill Shannon
+ * @author John Mani
+ */
+public final class SubjectTerm extends StringTerm {
+
+    private static final long serialVersionUID = 7481568618055573432L;
+
+    /**
+     * Constructor.
+     *
+     * @param pattern  the pattern to search for
+     */
+    public SubjectTerm(String pattern) {
+	// Note: comparison is case-insensitive
+	super(pattern);
+    }
+
+    /**
+     * The match method.
+     *
+     * @param msg	the pattern match is applied to this Message's 
+     *			subject header
+     * @return		true if the pattern match succeeds, otherwise false
+     */
+    @Override
+    public boolean match(Message msg) {
+	String subj;
+
+	try {
+	    subj = msg.getSubject();
+	} catch (Exception e) {
+	    return false;
+	}
+
+	if (subj == null)
+	    return false;
+
+	return super.match(subj);
+    }
+
+    /**
+     * Equality comparison.
+     */
+    @Override
+    public boolean equals(Object obj) {
+	if (!(obj instanceof SubjectTerm))
+	    return false;
+	return super.equals(obj);
+    }
+}
diff --git a/mail/src/main/java/javax/mail/search/package.html b/mail/src/main/java/javax/mail/search/package.html
new file mode 100644
index 0000000..fbab315
--- /dev/null
+++ b/mail/src/main/java/javax/mail/search/package.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>javax.mail.search package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+Message search terms for the JavaMail API.
+This package defines classes that can be used to construct a search
+expression to search a folder for messages matching the expression;
+see the {@link javax.mail.Folder#search search} method on
+{@link javax.mail.Folder javax.mail.Folder}.
+See {@link javax.mail.search.SearchTerm SearchTerm}.
+</P>
+<P>
+Note that the exact search capabilities depend on the protocol,
+provider, and server in use.  For the POP3 protocol, all searching is
+done on the client side using the JavaMail classes.  For IMAP, all
+searching is done on the server side and is limited by the search
+capabilities of the IMAP protocol and the IMAP server being used.
+For example, IMAP date based searches have only day granularity.
+</P>
+<P>
+In general, all of the string patterns supported by search terms are
+just simple strings; no regular expressions are supported.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/javax/mail/util/ByteArrayDataSource.java b/mail/src/main/java/javax/mail/util/ByteArrayDataSource.java
new file mode 100644
index 0000000..e4b9e5b
--- /dev/null
+++ b/mail/src/main/java/javax/mail/util/ByteArrayDataSource.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.util;
+
+import java.io.*;
+import javax.activation.*;
+import javax.mail.internet.*;
+
+/**
+ * A DataSource backed by a byte array.  The byte array may be
+ * passed in directly, or may be initialized from an InputStream
+ * or a String.
+ *
+ * @since JavaMail 1.4
+ * @author John Mani
+ * @author Bill Shannon
+ * @author Max Spivak
+ */
+public class ByteArrayDataSource implements DataSource {
+    private byte[] data;	// data
+    private int len = -1;
+    private String type;	// content-type
+    private String name = "";
+
+    static class DSByteArrayOutputStream extends ByteArrayOutputStream {
+	public byte[] getBuf() {
+	    return buf;
+	}
+
+	public int getCount() {
+	    return count;
+	}
+    }
+
+    /**
+     * Create a ByteArrayDataSource with data from the
+     * specified InputStream and with the specified MIME type.
+     * The InputStream is read completely and the data is
+     * stored in a byte array.
+     *
+     * @param	is	the InputStream
+     * @param	type	the MIME type
+     * @exception	IOException	errors reading the stream
+     */
+    public ByteArrayDataSource(InputStream is, String type) throws IOException {
+	DSByteArrayOutputStream os = new DSByteArrayOutputStream();
+	byte[] buf = new byte[8192];
+	int len;
+	while ((len = is.read(buf)) > 0)
+	    os.write(buf, 0, len);
+	this.data = os.getBuf();
+	this.len = os.getCount();
+
+	/*
+	 * ByteArrayOutputStream doubles the size of the buffer every time
+	 * it needs to expand, which can waste a lot of memory in the worst
+	 * case with large buffers.  Check how much is wasted here and if
+	 * it's too much, copy the data into a new buffer and allow the
+	 * old buffer to be garbage collected.
+	 */
+	if (this.data.length - this.len > 256*1024) {
+	    this.data = os.toByteArray();
+	    this.len = this.data.length;	// should be the same
+	}
+        this.type = type;
+    }
+
+    /**
+     * Create a ByteArrayDataSource with data from the
+     * specified byte array and with the specified MIME type.
+     *
+     * @param	data	the data
+     * @param	type	the MIME type
+     */
+    public ByteArrayDataSource(byte[] data, String type) {
+        this.data = data;
+	this.type = type;
+    }
+
+    /**
+     * Create a ByteArrayDataSource with data from the
+     * specified String and with the specified MIME type.
+     * The MIME type should include a <code>charset</code>
+     * parameter specifying the charset to be used for the
+     * string.  If the parameter is not included, the
+     * default charset is used.
+     *
+     * @param	data	the String
+     * @param	type	the MIME type
+     * @exception	IOException	errors reading the String
+     */
+    public ByteArrayDataSource(String data, String type) throws IOException {
+	String charset = null;
+	try {
+	    ContentType ct = new ContentType(type);
+	    charset = ct.getParameter("charset");
+	} catch (ParseException pex) {
+	    // ignore parse error
+	}
+	charset = MimeUtility.javaCharset(charset);
+	if (charset == null)
+	    charset = MimeUtility.getDefaultJavaCharset();
+	// XXX - could convert to bytes on demand rather than copying here
+	this.data = data.getBytes(charset);
+	this.type = type;
+    }
+
+    /**
+     * Return an InputStream for the data.
+     * Note that a new stream is returned each time
+     * this method is called.
+     *
+     * @return		the InputStream
+     * @exception	IOException	if no data has been set
+     */
+    @Override
+    public InputStream getInputStream() throws IOException {
+	if (data == null)
+	    throw new IOException("no data");
+	if (len < 0)
+	    len = data.length;
+	return new SharedByteArrayInputStream(data, 0, len);
+    }
+
+    /**
+     * Return an OutputStream for the data.
+     * Writing the data is not supported; an <code>IOException</code>
+     * is always thrown.
+     *
+     * @exception	IOException	always
+     */
+    @Override
+    public OutputStream getOutputStream() throws IOException {
+	throw new IOException("cannot do this");
+    }
+
+    /**
+     * Get the MIME content type of the data.
+     *
+     * @return	the MIME type
+     */
+    @Override
+    public String getContentType() {
+        return type;
+    }
+
+    /**
+     * Get the name of the data.
+     * By default, an empty string ("") is returned.
+     *
+     * @return	the name of this data
+     */
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Set the name of the data.
+     *
+     * @param	name	the name of this data
+     */
+    public void setName(String name) {
+	this.name = name;
+    }
+}
diff --git a/mail/src/main/java/javax/mail/util/SharedByteArrayInputStream.java b/mail/src/main/java/javax/mail/util/SharedByteArrayInputStream.java
new file mode 100644
index 0000000..0e13214
--- /dev/null
+++ b/mail/src/main/java/javax/mail/util/SharedByteArrayInputStream.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.util;
+
+import java.io.*;
+import javax.mail.internet.SharedInputStream;
+
+/**
+ * A ByteArrayInputStream that implements the SharedInputStream interface,
+ * allowing the underlying byte array to be shared between multiple readers.
+ *
+ * @author  Bill Shannon
+ * @since JavaMail 1.4
+ */
+
+public class SharedByteArrayInputStream extends ByteArrayInputStream
+				implements SharedInputStream {
+    /**
+     * Position within shared buffer that this stream starts at.
+     */
+    protected int start = 0;
+
+    /**
+     * Create a SharedByteArrayInputStream representing the entire
+     * byte array.
+     *
+     * @param	buf	the byte array
+     */
+    public SharedByteArrayInputStream(byte[] buf) {
+	super(buf);
+    }
+
+    /**
+     * Create a SharedByteArrayInputStream representing the part
+     * of the byte array from <code>offset</code> for <code>length</code>
+     * bytes.
+     *
+     * @param	buf	the byte array
+     * @param	offset	offset in byte array to first byte to include
+     * @param	length	number of bytes to include
+     */
+    public SharedByteArrayInputStream(byte[] buf, int offset, int length) {
+	super(buf, offset, length);
+	start = offset;
+    }
+
+    /**
+     * Return the current position in the InputStream, as an
+     * offset from the beginning of the InputStream.
+     *
+     * @return  the current position
+     */
+    @Override
+    public long getPosition() {
+	return pos - start;
+    }
+
+    /**
+     * Return a new InputStream representing a subset of the data
+     * from this InputStream, starting at <code>start</code> (inclusive)
+     * up to <code>end</code> (exclusive).  <code>start</code> must be
+     * non-negative.  If <code>end</code> is -1, the new stream ends
+     * at the same place as this stream.  The returned InputStream
+     * will also implement the SharedInputStream interface.
+     *
+     * @param	start	the starting position
+     * @param	end	the ending position + 1
+     * @return		the new stream
+     */
+    @Override
+    public InputStream newStream(long start, long end) {
+	if (start < 0)
+	    throw new IllegalArgumentException("start < 0");
+	if (end == -1)
+	    end = count - this.start;
+	return new SharedByteArrayInputStream(buf,
+				this.start + (int)start, (int)(end - start));
+    }
+}
diff --git a/mail/src/main/java/javax/mail/util/SharedFileInputStream.java b/mail/src/main/java/javax/mail/util/SharedFileInputStream.java
new file mode 100644
index 0000000..31df0da
--- /dev/null
+++ b/mail/src/main/java/javax/mail/util/SharedFileInputStream.java
@@ -0,0 +1,539 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.util;
+
+import java.io.*;
+import javax.mail.internet.SharedInputStream;
+
+/**
+ * A <code>SharedFileInputStream</code> is a
+ * <code>BufferedInputStream</code> that buffers
+ * data from the file and supports the <code>mark</code>
+ * and <code>reset</code> methods.  It also supports the
+ * <code>newStream</code> method that allows you to create
+ * other streams that represent subsets of the file.
+ * A <code>RandomAccessFile</code> object is used to
+ * access the file data. <p>
+ *
+ * Note that when the SharedFileInputStream is closed,
+ * all streams created with the <code>newStream</code>
+ * method are also closed.  This allows the creator of the
+ * SharedFileInputStream object to control access to the
+ * underlying file and ensure that it is closed when
+ * needed, to avoid leaking file descriptors.  Note also
+ * that this behavior contradicts the requirements of
+ * SharedInputStream and may change in a future release.
+ *
+ * @author  Bill Shannon
+ * @since   JavaMail 1.4
+ */
+public class SharedFileInputStream extends BufferedInputStream
+				implements SharedInputStream {
+
+    private static int defaultBufferSize = 2048;
+
+    /**
+     * The file containing the data.
+     * Shared by all related SharedFileInputStreams.
+     */
+    protected RandomAccessFile in;
+
+    /**
+     * The normal size of the read buffer.
+     */
+    protected int bufsize;
+
+    /**
+     * The file offset that corresponds to the first byte in
+     * the read buffer.
+     */
+    protected long bufpos;
+
+    /**
+     * The file offset of the start of data in this subset of the file.
+     */
+    protected long start = 0;
+
+    /**
+     * The amount of data in this subset of the file.
+     */
+    protected long datalen;
+
+    /**
+     * True if this is a top level stream created directly by "new".
+     * False if this is a derived stream created by newStream.
+     */
+    private boolean master = true;
+
+    /**
+     * A shared class that keeps track of the references
+     * to a particular file so it can be closed when the
+     * last reference is gone.
+     */
+    static class SharedFile {
+	private int cnt;
+	private RandomAccessFile in;
+
+	SharedFile(String file) throws IOException {
+	    this.in = new RandomAccessFile(file, "r");
+	}
+
+	SharedFile(File file) throws IOException {
+	    this.in = new RandomAccessFile(file, "r");
+	}
+
+	public synchronized RandomAccessFile open() {
+	    cnt++;
+	    return in;
+	}
+
+	public synchronized void close() throws IOException {
+	    if (cnt > 0 && --cnt <= 0)
+		in.close();
+	}
+
+	public synchronized void forceClose() throws IOException {
+	    if (cnt > 0) {
+		// normal case, close exceptions propagated
+		cnt = 0;
+		in.close();
+	    } else {
+		// should already be closed, ignore exception
+		try {
+		    in.close();
+		} catch (IOException ioex) { }
+	    }
+	}
+
+	@Override
+	protected void finalize() throws Throwable {
+	    try {
+		in.close();
+	    } finally {
+		super.finalize();
+	    }
+	}
+    }
+
+    private SharedFile sf;
+
+    /**
+     * Check to make sure that this stream has not been closed
+     */
+    private void ensureOpen() throws IOException {
+	if (in == null)
+	    throw new IOException("Stream closed");
+    }
+
+    /**
+     * Creates a <code>SharedFileInputStream</code>
+     * for the file.
+     *
+     * @param   file   the file
+     * @exception IOException for errors opening the file
+     */
+    public SharedFileInputStream(File file) throws IOException {
+	this(file, defaultBufferSize);
+    }
+
+    /**
+     * Creates a <code>SharedFileInputStream</code>
+     * for the named file
+     *
+     * @param   file   the file
+     * @exception IOException for errors opening the file
+     */
+    public SharedFileInputStream(String file) throws IOException {
+	this(file, defaultBufferSize);
+    }
+
+    /**
+     * Creates a <code>SharedFileInputStream</code>
+     * with the specified buffer size.
+     *
+     * @param   file	the file
+     * @param   size   the buffer size.
+     * @exception IOException for errors opening the file
+     * @exception IllegalArgumentException if size &le; 0.
+     */
+    public SharedFileInputStream(File file, int size) throws IOException {
+	super(null);	// XXX - will it NPE?
+        if (size <= 0)
+            throw new IllegalArgumentException("Buffer size <= 0");
+	init(new SharedFile(file), size);
+    }
+
+    /**
+     * Creates a <code>SharedFileInputStream</code>
+     * with the specified buffer size.
+     *
+     * @param   file	the file
+     * @param   size   the buffer size.
+     * @exception IOException for errors opening the file
+     * @exception IllegalArgumentException if size &le; 0.
+     */
+    public SharedFileInputStream(String file, int size) throws IOException {
+	super(null);	// XXX - will it NPE?
+        if (size <= 0)
+            throw new IllegalArgumentException("Buffer size <= 0");
+	init(new SharedFile(file), size);
+    }
+
+    private void init(SharedFile sf, int size) throws IOException {
+	this.sf = sf;
+	this.in = sf.open();
+	this.start = 0;
+	this.datalen = in.length();	// XXX - file can't grow
+	this.bufsize = size;
+	buf = new byte[size];
+    }
+
+    /**
+     * Used internally by the <code>newStream</code> method.
+     */
+    private SharedFileInputStream(SharedFile sf, long start, long len,
+				int bufsize) {
+	super(null);
+	this.master = false;
+	this.sf = sf;
+	this.in = sf.open();
+	this.start = start;
+	this.bufpos = start;
+	this.datalen = len;
+	this.bufsize = bufsize;
+	buf = new byte[bufsize];
+    }
+
+    /**
+     * Fills the buffer with more data, taking into account
+     * shuffling and other tricks for dealing with marks.
+     * Assumes that it is being called by a synchronized method.
+     * This method also assumes that all data has already been read in,
+     * hence pos > count.
+     */
+    private void fill() throws IOException {
+	if (markpos < 0) {
+	    pos = 0;		/* no mark: throw away the buffer */
+	    bufpos += count;
+	} else if (pos >= buf.length)	/* no room left in buffer */
+	    if (markpos > 0) {	/* can throw away early part of the buffer */
+		int sz = pos - markpos;
+		System.arraycopy(buf, markpos, buf, 0, sz);
+		pos = sz;
+		bufpos += markpos;
+		markpos = 0;
+	    } else if (buf.length >= marklimit) {
+		markpos = -1;	/* buffer got too big, invalidate mark */
+		pos = 0;	/* drop buffer contents */
+		bufpos += count;
+	    } else {		/* grow buffer */
+		int nsz = pos * 2;
+		if (nsz > marklimit)
+		    nsz = marklimit;
+		byte nbuf[] = new byte[nsz];
+		System.arraycopy(buf, 0, nbuf, 0, pos);
+		buf = nbuf;
+	    }
+        count = pos;
+	// limit to datalen
+	int len = buf.length - pos;
+	if (bufpos - start + pos + len > datalen)
+	    len = (int)(datalen - (bufpos - start + pos));
+	synchronized (in) {
+	    in.seek(bufpos + pos);
+	    int n = in.read(buf, pos, len);
+	    if (n > 0)
+		count = n + pos;
+	}
+    }
+
+    /**
+     * See the general contract of the <code>read</code>
+     * method of <code>InputStream</code>.
+     *
+     * @return     the next byte of data, or <code>-1</code> if the end of the
+     *             stream is reached.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public synchronized int read() throws IOException {
+        ensureOpen();
+	if (pos >= count) {
+	    fill();
+	    if (pos >= count)
+		return -1;
+	}
+	return buf[pos++] & 0xff;
+    }
+
+    /**
+     * Read characters into a portion of an array, reading from the underlying
+     * stream at most once if necessary.
+     */
+    private int read1(byte[] b, int off, int len) throws IOException {
+	int avail = count - pos;
+	if (avail <= 0) {
+	    if (false) {
+	    /* If the requested length is at least as large as the buffer, and
+	       if there is no mark/reset activity, do not bother to copy the
+	       bytes into the local buffer.  In this way buffered streams will
+	       cascade harmlessly. */
+	    if (len >= buf.length && markpos < 0) {
+		// XXX - seek, update bufpos - how?
+		return in.read(b, off, len);
+	    }
+	    }
+	    fill();
+	    avail = count - pos;
+	    if (avail <= 0) return -1;
+	}
+	int cnt = (avail < len) ? avail : len;
+	System.arraycopy(buf, pos, b, off, cnt);
+	pos += cnt;
+	return cnt;
+    }
+
+    /**
+     * Reads bytes from this stream into the specified byte array,
+     * starting at the given offset.
+     *
+     * <p> This method implements the general contract of the corresponding
+     * <code>{@link java.io.InputStream#read(byte[], int, int) read}</code>
+     * method of the <code>{@link java.io.InputStream}</code> class.
+     *
+     * @param      b     destination buffer.
+     * @param      off   offset at which to start storing bytes.
+     * @param      len   maximum number of bytes to read.
+     * @return     the number of bytes read, or <code>-1</code> if the end of
+     *             the stream has been reached.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public synchronized int read(byte b[], int off, int len)
+	throws IOException
+    {
+        ensureOpen();
+        if ((off | len | (off + len) | (b.length - (off + len))) < 0) {
+	    throw new IndexOutOfBoundsException();
+	} else if (len == 0) {
+	    return 0;
+	}
+
+	int n = read1(b, off, len);
+	if (n <= 0) return n;
+	while ((n < len) /* && (in.available() > 0) */) {
+	    int n1 = read1(b, off + n, len - n);
+	    if (n1 <= 0) break;
+	    n += n1;
+	}
+	return n;
+    }
+
+    /**
+     * See the general contract of the <code>skip</code>
+     * method of <code>InputStream</code>.
+     *
+     * @param      n   the number of bytes to be skipped.
+     * @return     the actual number of bytes skipped.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public synchronized long skip(long n) throws IOException {
+        ensureOpen();
+	if (n <= 0) {
+	    return 0;
+	}
+	long avail = count - pos;
+     
+        if (avail <= 0) {
+            // If no mark position set then don't keep in buffer
+	    /*
+            if (markpos <0) 
+                return in.skip(n);
+	    */
+            
+            // Fill in buffer to save bytes for reset
+            fill();
+            avail = count - pos;
+            if (avail <= 0)
+                return 0;
+        }
+        
+        long skipped = (avail < n) ? avail : n;
+        pos += skipped;
+        return skipped;
+    }
+
+    /**
+     * Returns the number of bytes that can be read from this input 
+     * stream without blocking. 
+     *
+     * @return     the number of bytes that can be read from this input
+     *             stream without blocking.
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public synchronized int available() throws IOException {
+        ensureOpen();
+	return (count - pos) + in_available();
+    }
+
+    private int in_available() throws IOException {
+	// XXX - overflow
+	return (int)((start + datalen) - (bufpos + count));
+    }
+
+    /** 
+     * See the general contract of the <code>mark</code>
+     * method of <code>InputStream</code>.
+     *
+     * @param   readlimit   the maximum limit of bytes that can be read before
+     *                      the mark position becomes invalid.
+     * @see     #reset()
+     */
+    @Override
+    public synchronized void mark(int readlimit) {
+	marklimit = readlimit;
+	markpos = pos;
+    }
+
+    /**
+     * See the general contract of the <code>reset</code>
+     * method of <code>InputStream</code>.
+     * <p>
+     * If <code>markpos</code> is <code>-1</code>
+     * (no mark has been set or the mark has been
+     * invalidated), an <code>IOException</code>
+     * is thrown. Otherwise, <code>pos</code> is
+     * set equal to <code>markpos</code>.
+     *
+     * @exception  IOException  if this stream has not been marked or
+     *               if the mark has been invalidated.
+     * @see        #mark(int)
+     */
+    @Override
+    public synchronized void reset() throws IOException {
+        ensureOpen();
+	if (markpos < 0)
+	    throw new IOException("Resetting to invalid mark");
+	pos = markpos;
+    }
+
+    /**
+     * Tests if this input stream supports the <code>mark</code> 
+     * and <code>reset</code> methods. The <code>markSupported</code> 
+     * method of <code>SharedFileInputStream</code> returns 
+     * <code>true</code>. 
+     *
+     * @return  a <code>boolean</code> indicating if this stream type supports
+     *          the <code>mark</code> and <code>reset</code> methods.
+     * @see     java.io.InputStream#mark(int)
+     * @see     java.io.InputStream#reset()
+     */
+    @Override
+    public boolean markSupported() {
+	return true;
+    }
+
+    /**
+     * Closes this input stream and releases any system resources 
+     * associated with the stream. 
+     *
+     * @exception  IOException  if an I/O error occurs.
+     */
+    @Override
+    public void close() throws IOException {
+        if (in == null)
+            return;
+	try {
+	    if (master)
+		sf.forceClose();
+	    else
+		sf.close();
+	} finally {
+	    sf = null;
+	    in = null;
+	    buf = null;
+	}
+    }
+
+    /**
+     * Return the current position in the InputStream, as an
+     * offset from the beginning of the InputStream.
+     *
+     * @return  the current position
+     */
+    @Override
+    public long getPosition() {
+//System.out.println("getPosition: start " + start + " pos " + pos 
+//	+ " bufpos " + bufpos + " = " + (bufpos + pos - start));
+	if (in == null)
+	    throw new RuntimeException("Stream closed");
+	return bufpos + pos - start;
+    }
+
+    /**
+     * Return a new InputStream representing a subset of the data
+     * from this InputStream, starting at <code>start</code> (inclusive)
+     * up to <code>end</code> (exclusive).  <code>start</code> must be
+     * non-negative.  If <code>end</code> is -1, the new stream ends
+     * at the same place as this stream.  The returned InputStream
+     * will also implement the SharedInputStream interface.
+     *
+     * @param	start	the starting position
+     * @param	end	the ending position + 1
+     * @return		the new stream
+     */
+    @Override
+    public synchronized InputStream newStream(long start, long end) {
+	if (in == null)
+	    throw new RuntimeException("Stream closed");
+	if (start < 0)
+	    throw new IllegalArgumentException("start < 0");
+	if (end == -1)
+	    end = datalen;
+	return new SharedFileInputStream(sf,
+			this.start + start, end - start, bufsize);
+    }
+
+    // for testing...
+    /*
+    public static void main(String[] argv) throws Exception {
+	SharedFileInputStream is = new SharedFileInputStream(argv[0]);
+	java.util.Random r = new java.util.Random();
+	int b;
+	while ((b = is.read()) >= 0) {
+	    System.out.write(b);
+	    if (r.nextDouble() < 0.3) {
+		InputStream is2 = is.newStream(is.getPosition(), -1);
+		int b2;
+		while ((b2 = is2.read()) >= 0)
+		    ;
+	    }
+	}
+    }
+    */
+
+    /**
+     * Force this stream to close.
+     */
+    @Override
+    protected void finalize() throws Throwable {
+	super.finalize();
+	close();
+    }
+}
diff --git a/mail/src/main/java/javax/mail/util/package.html b/mail/src/main/java/javax/mail/util/package.html
new file mode 100644
index 0000000..190a26c
--- /dev/null
+++ b/mail/src/main/java/javax/mail/util/package.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>javax.mail.util package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+JavaMail API utility classes.
+This package specifies utility classes that are useful with
+other JavaMail APIs.
+</P>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/java/overview.html b/mail/src/main/java/overview.html
new file mode 100644
index 0000000..411d023
--- /dev/null
+++ b/mail/src/main/java/overview.html
@@ -0,0 +1,347 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<TITLE>javax.mail package</TITLE>
+</HEAD>
+<BODY BGCOLOR="white">
+
+<P>
+The JavaMail&trade; API
+provides classes that model a mail system.
+The <code>javax.mail</code> package defines classes that are common to
+all mail systems.
+The <code>javax.mail.internet</code> package defines classes that are specific
+to mail systems based on internet standards such as MIME, SMTP, POP3, and IMAP.
+The JavaMail API includes the <code>javax.mail</code> package and subpackages.
+</P>
+<P>
+For an overview of the JavaMail API, read the
+<A HREF="https://javaee.github.io/javamail/docs/JavaMail-1.6.pdf" TARGET="_top">
+JavaMail specification</A>.
+</P>
+<P>
+The code to send a plain text message can be as simple as the following:
+</P>
+<PRE>
+    Properties props = new Properties();
+    props.put("mail.smtp.host", "my-mail-server");
+    Session session = Session.getInstance(props, null);
+
+    try {
+	MimeMessage msg = new MimeMessage(session);
+	msg.setFrom("me@example.com");
+	msg.setRecipients(Message.RecipientType.TO,
+			  "you@example.com");
+	msg.setSubject("JavaMail hello world example");
+	msg.setSentDate(new Date());
+	msg.setText("Hello, world!\n");
+	Transport.send(msg, "me@example.com", "my-password");
+    } catch (MessagingException mex) {
+	System.out.println("send failed, exception: " + mex);
+    }
+</PRE>
+<P>
+The JavaMail download bundle contains many more complete examples
+in the "demo" directory.
+</P>
+<P>
+Don't forget to see the
+<A HREF="https://javaee.github.io/javamail/FAQ.html" TARGET="_top">
+JavaMail API FAQ</A>
+for answers to the most common questions.
+The <A HREF="https://javaee.github.io/javamail/" TARGET="_top">
+JavaMail web site</A>
+contains many additional resources.
+</P>
+<A NAME="properties"><STRONG>Properties</STRONG></A>
+<P>
+The JavaMail API supports the following standard properties,
+which may be set in the <code>Session</code> object, or in the
+<code>Properties</code> object used to create the <code>Session</code> object.
+The properties are always set as strings; the Type column describes
+how the string is interpreted.  For example, use
+</P>
+<PRE>
+	props.put("mail.debug", "true");
+</PRE>
+<P>
+to set the <code>mail.debug</code> property, which is of type boolean.
+</P>
+<TABLE BORDER SUMMARY="JavaMail properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.debug">mail.debug</A></TD>
+<TD>boolean</TD>
+<TD>
+The initial debug mode.
+Default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.from">mail.from</A></TD>
+<TD>String</TD>
+<TD>
+The return email address of the current user, used by the
+<code>InternetAddress</code> method <code>getLocalAddress</code>.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.mime.address.strict">mail.mime.address.strict</A></TD>
+<TD>boolean</TD>
+<TD>
+The MimeMessage class uses the <code>InternetAddress</code> method
+<code>parseHeader</code> to parse headers in messages.  This property
+controls the strict flag passed to the <code>parseHeader</code>
+method.  The default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.host">mail.host</A></TD>
+<TD>String</TD>
+<TD>
+The default host name of the mail server for both Stores and Transports.
+Used if the <code>mail.<i>protocol</i>.host</code> property isn't set.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.store.protocol">mail.store.protocol</A></TD>
+<TD>String</TD>
+<TD>
+Specifies the default message access protocol.  The
+<code>Session</code> method <code>getStore()</code> returns a Store
+object that implements this protocol.  By default the first Store
+provider in the configuration files is returned.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.transport.protocol">mail.transport.protocol</A></TD>
+<TD>String</TD>
+<TD>
+Specifies the default message transport protocol.  The
+<code>Session</code> method <code>getTransport()</code> returns a Transport
+object that implements this protocol.  By default the first Transport
+provider in the configuration files is returned.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.user">mail.user</A></TD>
+<TD>String</TD>
+<TD>
+The default user name to use when connecting to the mail server.
+Used if the <code>mail.<i>protocol</i>.user</code> property isn't set.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.protocol.class">mail.<i>protocol</i>.class</A></TD>
+<TD>String</TD>
+<TD>
+Specifies the fully qualified class name of the provider for the
+specified protocol.  Used in cases where more than one provider
+for a given protocol exists; this property can be used to specify
+which provider to use by default.  The provider must still be listed
+in a configuration file.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.protocol.host">mail.<i>protocol</i>.host</A></TD>
+<TD>String</TD>
+<TD>
+The host name of the mail server for the specified protocol.
+Overrides the <code>mail.host</code> property.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.protocol.port">mail.<i>protocol</i>.port</A></TD>
+<TD>int</TD>
+<TD>
+The port number of the mail server for the specified protocol.
+If not specified the protocol's default port number is used.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.protocol.user">mail.<i>protocol</i>.user</A></TD>
+<TD>String</TD>
+<TD>
+The user name to use when connecting to mail servers
+using the specified protocol.
+Overrides the <code>mail.user</code> property.
+</TD>
+</TR>
+
+</TABLE>
+
+<P>
+The following properties are supported by the reference implementation (RI) of
+JavaMail, but are not currently a required part of the specification.
+The names, types, defaults, and semantics of these properties may
+change in future releases.
+</P>
+<TABLE BORDER SUMMARY="JavaMail RI properties">
+<TR>
+<TH>Name</TH>
+<TH>Type</TH>
+<TH>Description</TH>
+</TR>
+
+<TR>
+<TD><A NAME="mail.debug.auth">mail.debug.auth</A></TD>
+<TD>boolean</TD>
+<TD>
+Include protocol authentication commands (including usernames and passwords)
+in the debug output.
+Default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.debug.auth.username">mail.debug.auth.username</A></TD>
+<TD>boolean</TD>
+<TD>
+Include the user name in non-protocol debug output.
+Default is true.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.debug.auth.password">mail.debug.auth.password</A></TD>
+<TD>boolean</TD>
+<TD>
+Include the password in non-protocol debug output.
+Default is false.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.transport.protocol.address-type">mail.transport.protocol.<i>address-type</i></A></TD>
+<TD>String</TD>
+<TD>
+Specifies the default message transport protocol for the specified address type.
+The <code>Session</code> method <code>getTransport(Address)</code> returns a
+Transport object that implements this protocol when the address is of the
+specified type (e.g., "rfc822" for standard internet addresses).
+By default the first Transport configured for that address type is used.
+This property can be used to override the behavior of the
+{@link javax.mail.Transport#send send} method of the
+{@link javax.mail.Transport Transport} class so that (for example) the "smtps"
+protocol is used instead of the "smtp" protocol by setting the property
+<code>mail.transport.protocol.rfc822</code> to <code>"smtps"</code>.
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.event.scope">mail.event.scope</A></TD>
+<TD>String</TD>
+<TD>
+Controls the scope of events.  (See the javax.mail.event package.)
+By default, a separate event queue and thread is used for events for each
+Store, Transport, or Folder.
+If this property is set to "session", all such events are put in a single
+event queue processed by a single thread for the current session.
+If this property is set to "application", all such events are put in a single
+event queue processed by a single thread for the current application.
+(Applications are distinguished by their context class loader.)
+</TD>
+</TR>
+
+<TR>
+<TD><A NAME="mail.event.executor">mail.event.executor</A></TD>
+<TD>java.util.concurrent.Executor</TD>
+<TD>
+By default, a new Thread is created for each event queue.
+This thread is used to call the listeners for these events.
+If this property is set to an instance of an Executor, the
+Executor.execute method is used to run the event dispatcher
+for an event queue.  The event dispatcher runs until the
+event queue is no longer in use.
+</TD>
+</TR>
+
+</TABLE>
+
+<P>
+The JavaMail API also supports several System properties;
+see the {@link javax.mail.internet} package documentation
+for details.
+</P>
+<P>
+The JavaMail reference
+implementation includes protocol providers in subpackages of
+<code>com.sun.mail</code>.  Note that the APIs to these protocol
+providers are not part of the standard JavaMail API.  Portable
+programs will not use these APIs.
+</P>
+<P>
+Nonportable programs may use the APIs of the protocol providers
+by (for example) casting a returned <code>Folder</code> object to a
+<code>com.sun.mail.imap.IMAPFolder</code> object.  Similarly for
+<code>Store</code> and <code>Message</code> objects returned from the
+standard JavaMail APIs.
+</P>
+<P>
+The protocol providers also support properties that are specific to
+those providers.  The package documentation for the
+{@link com.sun.mail.imap IMAP}, {@link com.sun.mail.pop3 POP3},
+and {@link com.sun.mail.smtp SMTP} packages provide details.
+</P>
+<P>
+In addition to printing debugging output as controlled by the
+{@link javax.mail.Session Session} configuration, the current
+implementation of classes in this package log the same information using
+{@link java.util.logging.Logger} as described in the following table:
+</P>
+<TABLE BORDER SUMMARY="JavaMail Loggers">
+<TR>
+<TH>Logger Name</TH>
+<TH>Logging Level</TH>
+<TH>Purpose</TH>
+</TR>
+
+<TR>
+<TD>javax.mail</TD>
+<TD>CONFIG</TD>
+<TD>Configuration of the Session</TD>
+</TR>
+
+<TR>
+<TD>javax.mail</TD>
+<TD>FINE</TD>
+<TD>General debugging output</TD>
+</TR>
+</TABLE>
+
+</BODY>
+</HTML>
diff --git a/mail/src/main/resources/META-INF/LICENSE.txt b/mail/src/main/resources/META-INF/LICENSE.txt
new file mode 100644
index 0000000..5de3d1b
--- /dev/null
+++ b/mail/src/main/resources/META-INF/LICENSE.txt
@@ -0,0 +1,637 @@
+# Eclipse Public License - v 2.0
+
+        THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+        PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
+        OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+    1. DEFINITIONS
+
+    "Contribution" means:
+
+      a) in the case of the initial Contributor, the initial content
+         Distributed under this Agreement, and
+
+      b) in the case of each subsequent Contributor: 
+         i) changes to the Program, and 
+         ii) additions to the Program;
+      where such changes and/or additions to the Program originate from
+      and are Distributed by that particular Contributor. A Contribution
+      "originates" from a Contributor if it was added to the Program by
+      such Contributor itself or anyone acting on such Contributor's behalf.
+      Contributions do not include changes or additions to the Program that
+      are not Modified Works.
+
+    "Contributor" means any person or entity that Distributes the Program.
+
+    "Licensed Patents" mean patent claims licensable by a Contributor which
+    are necessarily infringed by the use or sale of its Contribution alone
+    or when combined with the Program.
+
+    "Program" means the Contributions Distributed in accordance with this
+    Agreement.
+
+    "Recipient" means anyone who receives the Program under this Agreement
+    or any Secondary License (as applicable), including Contributors.
+
+    "Derivative Works" shall mean any work, whether in Source Code or other
+    form, that is based on (or derived from) the Program and for which the
+    editorial revisions, annotations, elaborations, or other modifications
+    represent, as a whole, an original work of authorship.
+
+    "Modified Works" shall mean any work in Source Code or other form that
+    results from an addition to, deletion from, or modification of the
+    contents of the Program, including, for purposes of clarity any new file
+    in Source Code form that contains any contents of the Program. Modified
+    Works shall not include works that contain only declarations,
+    interfaces, types, classes, structures, or files of the Program solely
+    in each case in order to link to, bind by name, or subclass the Program
+    or Modified Works thereof.
+
+    "Distribute" means the acts of a) distributing or b) making available
+    in any manner that enables the transfer of a copy.
+
+    "Source Code" means the form of a Program preferred for making
+    modifications, including but not limited to software source code,
+    documentation source, and configuration files.
+
+    "Secondary License" means either the GNU General Public License,
+    Version 2.0, or any later versions of that license, including any
+    exceptions or additional permissions as identified by the initial
+    Contributor.
+
+    2. GRANT OF RIGHTS
+
+      a) Subject to the terms of this Agreement, each Contributor hereby
+      grants Recipient a non-exclusive, worldwide, royalty-free copyright
+      license to reproduce, prepare Derivative Works of, publicly display,
+      publicly perform, Distribute and sublicense the Contribution of such
+      Contributor, if any, and such Derivative Works.
+
+      b) Subject to the terms of this Agreement, each Contributor hereby
+      grants Recipient a non-exclusive, worldwide, royalty-free patent
+      license under Licensed Patents to make, use, sell, offer to sell,
+      import and otherwise transfer the Contribution of such Contributor,
+      if any, in Source Code or other form. This patent license shall
+      apply to the combination of the Contribution and the Program if, at
+      the time the Contribution is added by the Contributor, such addition
+      of the Contribution causes such combination to be covered by the
+      Licensed Patents. The patent license shall not apply to any other
+      combinations which include the Contribution. No hardware per se is
+      licensed hereunder.
+
+      c) Recipient understands that although each Contributor grants the
+      licenses to its Contributions set forth herein, no assurances are
+      provided by any Contributor that the Program does not infringe the
+      patent or other intellectual property rights of any other entity.
+      Each Contributor disclaims any liability to Recipient for claims
+      brought by any other entity based on infringement of intellectual
+      property rights or otherwise. As a condition to exercising the
+      rights and licenses granted hereunder, each Recipient hereby
+      assumes sole responsibility to secure any other intellectual
+      property rights needed, if any. For example, if a third party
+      patent license is required to allow Recipient to Distribute the
+      Program, it is Recipient's responsibility to acquire that license
+      before distributing the Program.
+
+      d) Each Contributor represents that to its knowledge it has
+      sufficient copyright rights in its Contribution, if any, to grant
+      the copyright license set forth in this Agreement.
+
+      e) Notwithstanding the terms of any Secondary License, no
+      Contributor makes additional grants to any Recipient (other than
+      those set forth in this Agreement) as a result of such Recipient's
+      receipt of the Program under the terms of a Secondary License
+      (if permitted under the terms of Section 3).
+
+    3. REQUIREMENTS
+
+    3.1 If a Contributor Distributes the Program in any form, then:
+
+      a) the Program must also be made available as Source Code, in
+      accordance with section 3.2, and the Contributor must accompany
+      the Program with a statement that the Source Code for the Program
+      is available under this Agreement, and informs Recipients how to
+      obtain it in a reasonable manner on or through a medium customarily
+      used for software exchange; and
+
+      b) the Contributor may Distribute the Program under a license
+      different than this Agreement, provided that such license:
+         i) effectively disclaims on behalf of all other Contributors all
+         warranties and conditions, express and implied, including
+         warranties or conditions of title and non-infringement, and
+         implied warranties or conditions of merchantability and fitness
+         for a particular purpose;
+
+         ii) effectively excludes on behalf of all other Contributors all
+         liability for damages, including direct, indirect, special,
+         incidental and consequential damages, such as lost profits;
+
+         iii) does not attempt to limit or alter the recipients' rights
+         in the Source Code under section 3.2; and
+
+         iv) requires any subsequent distribution of the Program by any
+         party to be under a license that satisfies the requirements
+         of this section 3.
+
+    3.2 When the Program is Distributed as Source Code:
+
+      a) it must be made available under this Agreement, or if the
+      Program (i) is combined with other material in a separate file or
+      files made available under a Secondary License, and (ii) the initial
+      Contributor attached to the Source Code the notice described in
+      Exhibit A of this Agreement, then the Program may be made available
+      under the terms of such Secondary Licenses, and
+
+      b) a copy of this Agreement must be included with each copy of
+      the Program.
+
+    3.3 Contributors may not remove or alter any copyright, patent,
+    trademark, attribution notices, disclaimers of warranty, or limitations
+    of liability ("notices") contained within the Program from any copy of
+    the Program which they Distribute, provided that Contributors may add
+    their own appropriate notices.
+
+    4. COMMERCIAL DISTRIBUTION
+
+    Commercial distributors of software may accept certain responsibilities
+    with respect to end users, business partners and the like. While this
+    license is intended to facilitate the commercial use of the Program,
+    the Contributor who includes the Program in a commercial product
+    offering should do so in a manner which does not create potential
+    liability for other Contributors. Therefore, if a Contributor includes
+    the Program in a commercial product offering, such Contributor
+    ("Commercial Contributor") hereby agrees to defend and indemnify every
+    other Contributor ("Indemnified Contributor") against any losses,
+    damages and costs (collectively "Losses") arising from claims, lawsuits
+    and other legal actions brought by a third party against the Indemnified
+    Contributor to the extent caused by the acts or omissions of such
+    Commercial Contributor in connection with its distribution of the Program
+    in a commercial product offering. The obligations in this section do not
+    apply to any claims or Losses relating to any actual or alleged
+    intellectual property infringement. In order to qualify, an Indemnified
+    Contributor must: a) promptly notify the Commercial Contributor in
+    writing of such claim, and b) allow the Commercial Contributor to control,
+    and cooperate with the Commercial Contributor in, the defense and any
+    related settlement negotiations. The Indemnified Contributor may
+    participate in any such claim at its own expense.
+
+    For example, a Contributor might include the Program in a commercial
+    product offering, Product X. That Contributor is then a Commercial
+    Contributor. If that Commercial Contributor then makes performance
+    claims, or offers warranties related to Product X, those performance
+    claims and warranties are such Commercial Contributor's responsibility
+    alone. Under this section, the Commercial Contributor would have to
+    defend claims against the other Contributors related to those performance
+    claims and warranties, and if a court requires any other Contributor to
+    pay any damages as a result, the Commercial Contributor must pay
+    those damages.
+
+    5. NO WARRANTY
+
+    EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+    PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
+    BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+    IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
+    TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+    PURPOSE. Each Recipient is solely responsible for determining the
+    appropriateness of using and distributing the Program and assumes all
+    risks associated with its exercise of rights under this Agreement,
+    including but not limited to the risks and costs of program errors,
+    compliance with applicable laws, damage to or loss of data, programs
+    or equipment, and unavailability or interruption of operations.
+
+    6. DISCLAIMER OF LIABILITY
+
+    EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+    PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
+    SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+    PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+    ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
+    EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+    POSSIBILITY OF SUCH DAMAGES.
+
+    7. GENERAL
+
+    If any provision of this Agreement is invalid or unenforceable under
+    applicable law, it shall not affect the validity or enforceability of
+    the remainder of the terms of this Agreement, and without further
+    action by the parties hereto, such provision shall be reformed to the
+    minimum extent necessary to make such provision valid and enforceable.
+
+    If Recipient institutes patent litigation against any entity
+    (including a cross-claim or counterclaim in a lawsuit) alleging that the
+    Program itself (excluding combinations of the Program with other software
+    or hardware) infringes such Recipient's patent(s), then such Recipient's
+    rights granted under Section 2(b) shall terminate as of the date such
+    litigation is filed.
+
+    All Recipient's rights under this Agreement shall terminate if it
+    fails to comply with any of the material terms or conditions of this
+    Agreement and does not cure such failure in a reasonable period of
+    time after becoming aware of such noncompliance. If all Recipient's
+    rights under this Agreement terminate, Recipient agrees to cease use
+    and distribution of the Program as soon as reasonably practicable.
+    However, Recipient's obligations under this Agreement and any licenses
+    granted by Recipient relating to the Program shall continue and survive.
+
+    Everyone is permitted to copy and distribute copies of this Agreement,
+    but in order to avoid inconsistency the Agreement is copyrighted and
+    may only be modified in the following manner. The Agreement Steward
+    reserves the right to publish new versions (including revisions) of
+    this Agreement from time to time. No one other than the Agreement
+    Steward has the right to modify this Agreement. The Eclipse Foundation
+    is the initial Agreement Steward. The Eclipse Foundation may assign the
+    responsibility to serve as the Agreement Steward to a suitable separate
+    entity. Each new version of the Agreement will be given a distinguishing
+    version number. The Program (including Contributions) may always be
+    Distributed subject to the version of the Agreement under which it was
+    received. In addition, after a new version of the Agreement is published,
+    Contributor may elect to Distribute the Program (including its
+    Contributions) under the new version.
+
+    Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+    receives no rights or licenses to the intellectual property of any
+    Contributor under this Agreement, whether expressly, by implication,
+    estoppel or otherwise. All rights in the Program not expressly granted
+    under this Agreement are reserved. Nothing in this Agreement is intended
+    to be enforceable by any entity that is not a Contributor or Recipient.
+    No third-party beneficiary rights are created under this Agreement.
+
+    Exhibit A - Form of Secondary Licenses Notice
+
+    "This Source Code may also be made available under the following 
+    Secondary Licenses when the conditions for such availability set forth 
+    in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+    version(s), and exceptions or additional permissions here}."
+
+      Simply including a copy of this Agreement, including this Exhibit A
+      is not sufficient to license the Source Code under Secondary Licenses.
+
+      If it is not possible or desirable to put the notice in a particular
+      file, then You may include the notice in a location (such as a LICENSE
+      file in a relevant directory) where a recipient would be likely to
+      look for such a notice.
+
+      You may add additional accurate notices of copyright ownership.
+
+---
+
+##    The GNU General Public License (GPL) Version 2, June 1991
+
+    Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+    51 Franklin Street, Fifth Floor
+    Boston, MA 02110-1335
+    USA
+
+    Everyone is permitted to copy and distribute verbatim copies
+    of this license document, but changing it is not allowed.
+
+    Preamble
+
+    The licenses for most software are designed to take away your freedom to
+    share and change it. By contrast, the GNU General Public License is
+    intended to guarantee your freedom to share and change free software--to
+    make sure the software is free for all its users. This General Public
+    License applies to most of the Free Software Foundation's software and
+    to any other program whose authors commit to using it. (Some other Free
+    Software Foundation software is covered by the GNU Library General
+    Public License instead.) You can apply it to your programs, too.
+
+    When we speak of free software, we are referring to freedom, not price.
+    Our General Public Licenses are designed to make sure that you have the
+    freedom to distribute copies of free software (and charge for this
+    service if you wish), that you receive source code or can get it if you
+    want it, that you can change the software or use pieces of it in new
+    free programs; and that you know you can do these things.
+
+    To protect your rights, we need to make restrictions that forbid anyone
+    to deny you these rights or to ask you to surrender the rights. These
+    restrictions translate to certain responsibilities for you if you
+    distribute copies of the software, or if you modify it.
+
+    For example, if you distribute copies of such a program, whether gratis
+    or for a fee, you must give the recipients all the rights that you have.
+    You must make sure that they, too, receive or can get the source code.
+    And you must show them these terms so they know their rights.
+
+    We protect your rights with two steps: (1) copyright the software, and
+    (2) offer you this license which gives you legal permission to copy,
+    distribute and/or modify the software.
+
+    Also, for each author's protection and ours, we want to make certain
+    that everyone understands that there is no warranty for this free
+    software. If the software is modified by someone else and passed on, we
+    want its recipients to know that what they have is not the original, so
+    that any problems introduced by others will not reflect on the original
+    authors' reputations.
+
+    Finally, any free program is threatened constantly by software patents.
+    We wish to avoid the danger that redistributors of a free program will
+    individually obtain patent licenses, in effect making the program
+    proprietary. To prevent this, we have made it clear that any patent must
+    be licensed for everyone's free use or not licensed at all.
+
+    The precise terms and conditions for copying, distribution and
+    modification follow.
+
+    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+    0. This License applies to any program or other work which contains a
+    notice placed by the copyright holder saying it may be distributed under
+    the terms of this General Public License. The "Program", below, refers
+    to any such program or work, and a "work based on the Program" means
+    either the Program or any derivative work under copyright law: that is
+    to say, a work containing the Program or a portion of it, either
+    verbatim or with modifications and/or translated into another language.
+    (Hereinafter, translation is included without limitation in the term
+    "modification".) Each licensee is addressed as "you".
+
+    Activities other than copying, distribution and modification are not
+    covered by this License; they are outside its scope. The act of running
+    the Program is not restricted, and the output from the Program is
+    covered only if its contents constitute a work based on the Program
+    (independent of having been made by running the Program). Whether that
+    is true depends on what the Program does.
+
+    1. You may copy and distribute verbatim copies of the Program's source
+    code as you receive it, in any medium, provided that you conspicuously
+    and appropriately publish on each copy an appropriate copyright notice
+    and disclaimer of warranty; keep intact all the notices that refer to
+    this License and to the absence of any warranty; and give any other
+    recipients of the Program a copy of this License along with the Program.
+
+    You may charge a fee for the physical act of transferring a copy, and
+    you may at your option offer warranty protection in exchange for a fee.
+
+    2. You may modify your copy or copies of the Program or any portion of
+    it, thus forming a work based on the Program, and copy and distribute
+    such modifications or work under the terms of Section 1 above, provided
+    that you also meet all of these conditions:
+
+        a) You must cause the modified files to carry prominent notices
+        stating that you changed the files and the date of any change.
+
+        b) You must cause any work that you distribute or publish, that in
+        whole or in part contains or is derived from the Program or any part
+        thereof, to be licensed as a whole at no charge to all third parties
+        under the terms of this License.
+
+        c) If the modified program normally reads commands interactively
+        when run, you must cause it, when started running for such
+        interactive use in the most ordinary way, to print or display an
+        announcement including an appropriate copyright notice and a notice
+        that there is no warranty (or else, saying that you provide a
+        warranty) and that users may redistribute the program under these
+        conditions, and telling the user how to view a copy of this License.
+        (Exception: if the Program itself is interactive but does not
+        normally print such an announcement, your work based on the Program
+        is not required to print an announcement.)
+
+    These requirements apply to the modified work as a whole. If
+    identifiable sections of that work are not derived from the Program, and
+    can be reasonably considered independent and separate works in
+    themselves, then this License, and its terms, do not apply to those
+    sections when you distribute them as separate works. But when you
+    distribute the same sections as part of a whole which is a work based on
+    the Program, the distribution of the whole must be on the terms of this
+    License, whose permissions for other licensees extend to the entire
+    whole, and thus to each and every part regardless of who wrote it.
+
+    Thus, it is not the intent of this section to claim rights or contest
+    your rights to work written entirely by you; rather, the intent is to
+    exercise the right to control the distribution of derivative or
+    collective works based on the Program.
+
+    In addition, mere aggregation of another work not based on the Program
+    with the Program (or with a work based on the Program) on a volume of a
+    storage or distribution medium does not bring the other work under the
+    scope of this License.
+
+    3. You may copy and distribute the Program (or a work based on it,
+    under Section 2) in object code or executable form under the terms of
+    Sections 1 and 2 above provided that you also do one of the following:
+
+        a) Accompany it with the complete corresponding machine-readable
+        source code, which must be distributed under the terms of Sections 1
+        and 2 above on a medium customarily used for software interchange; or,
+
+        b) Accompany it with a written offer, valid for at least three
+        years, to give any third party, for a charge no more than your cost
+        of physically performing source distribution, a complete
+        machine-readable copy of the corresponding source code, to be
+        distributed under the terms of Sections 1 and 2 above on a medium
+        customarily used for software interchange; or,
+
+        c) Accompany it with the information you received as to the offer to
+        distribute corresponding source code. (This alternative is allowed
+        only for noncommercial distribution and only if you received the
+        program in object code or executable form with such an offer, in
+        accord with Subsection b above.)
+
+    The source code for a work means the preferred form of the work for
+    making modifications to it. For an executable work, complete source code
+    means all the source code for all modules it contains, plus any
+    associated interface definition files, plus the scripts used to control
+    compilation and installation of the executable. However, as a special
+    exception, the source code distributed need not include anything that is
+    normally distributed (in either source or binary form) with the major
+    components (compiler, kernel, and so on) of the operating system on
+    which the executable runs, unless that component itself accompanies the
+    executable.
+
+    If distribution of executable or object code is made by offering access
+    to copy from a designated place, then offering equivalent access to copy
+    the source code from the same place counts as distribution of the source
+    code, even though third parties are not compelled to copy the source
+    along with the object code.
+
+    4. You may not copy, modify, sublicense, or distribute the Program
+    except as expressly provided under this License. Any attempt otherwise
+    to copy, modify, sublicense or distribute the Program is void, and will
+    automatically terminate your rights under this License. However, parties
+    who have received copies, or rights, from you under this License will
+    not have their licenses terminated so long as such parties remain in
+    full compliance.
+
+    5. You are not required to accept this License, since you have not
+    signed it. However, nothing else grants you permission to modify or
+    distribute the Program or its derivative works. These actions are
+    prohibited by law if you do not accept this License. Therefore, by
+    modifying or distributing the Program (or any work based on the
+    Program), you indicate your acceptance of this License to do so, and all
+    its terms and conditions for copying, distributing or modifying the
+    Program or works based on it.
+
+    6. Each time you redistribute the Program (or any work based on the
+    Program), the recipient automatically receives a license from the
+    original licensor to copy, distribute or modify the Program subject to
+    these terms and conditions. You may not impose any further restrictions
+    on the recipients' exercise of the rights granted herein. You are not
+    responsible for enforcing compliance by third parties to this License.
+
+    7. If, as a consequence of a court judgment or allegation of patent
+    infringement or for any other reason (not limited to patent issues),
+    conditions are imposed on you (whether by court order, agreement or
+    otherwise) that contradict the conditions of this License, they do not
+    excuse you from the conditions of this License. If you cannot distribute
+    so as to satisfy simultaneously your obligations under this License and
+    any other pertinent obligations, then as a consequence you may not
+    distribute the Program at all. For example, if a patent license would
+    not permit royalty-free redistribution of the Program by all those who
+    receive copies directly or indirectly through you, then the only way you
+    could satisfy both it and this License would be to refrain entirely from
+    distribution of the Program.
+
+    If any portion of this section is held invalid or unenforceable under
+    any particular circumstance, the balance of the section is intended to
+    apply and the section as a whole is intended to apply in other
+    circumstances.
+
+    It is not the purpose of this section to induce you to infringe any
+    patents or other property right claims or to contest validity of any
+    such claims; this section has the sole purpose of protecting the
+    integrity of the free software distribution system, which is implemented
+    by public license practices. Many people have made generous
+    contributions to the wide range of software distributed through that
+    system in reliance on consistent application of that system; it is up to
+    the author/donor to decide if he or she is willing to distribute
+    software through any other system and a licensee cannot impose that choice.
+
+    This section is intended to make thoroughly clear what is believed to be
+    a consequence of the rest of this License.
+
+    8. If the distribution and/or use of the Program is restricted in
+    certain countries either by patents or by copyrighted interfaces, the
+    original copyright holder who places the Program under this License may
+    add an explicit geographical distribution limitation excluding those
+    countries, so that distribution is permitted only in or among countries
+    not thus excluded. In such case, this License incorporates the
+    limitation as if written in the body of this License.
+
+    9. The Free Software Foundation may publish revised and/or new
+    versions of the General Public License from time to time. Such new
+    versions will be similar in spirit to the present version, but may
+    differ in detail to address new problems or concerns.
+
+    Each version is given a distinguishing version number. If the Program
+    specifies a version number of this License which applies to it and "any
+    later version", you have the option of following the terms and
+    conditions either of that version or of any later version published by
+    the Free Software Foundation. If the Program does not specify a version
+    number of this License, you may choose any version ever published by the
+    Free Software Foundation.
+
+    10. If you wish to incorporate parts of the Program into other free
+    programs whose distribution conditions are different, write to the
+    author to ask for permission. For software which is copyrighted by the
+    Free Software Foundation, write to the Free Software Foundation; we
+    sometimes make exceptions for this. Our decision will be guided by the
+    two goals of preserving the free status of all derivatives of our free
+    software and of promoting the sharing and reuse of software generally.
+
+    NO WARRANTY
+
+    11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO
+    WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+    EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+    OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND,
+    EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+    ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH
+    YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
+    NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+    12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+    WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+    AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR
+    DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL
+    DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM
+    (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED
+    INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF
+    THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR
+    OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+    END OF TERMS AND CONDITIONS
+
+    How to Apply These Terms to Your New Programs
+
+    If you develop a new program, and you want it to be of the greatest
+    possible use to the public, the best way to achieve this is to make it
+    free software which everyone can redistribute and change under these terms.
+
+    To do so, attach the following notices to the program. It is safest to
+    attach them to the start of each source file to most effectively convey
+    the exclusion of warranty; and each file should have at least the
+    "copyright" line and a pointer to where the full notice is found.
+
+        One line to give the program's name and a brief idea of what it does.
+        Copyright (C) <year> <name of author>
+
+        This program 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.
+
+        This program 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 program; if not, write to the Free Software
+        Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA
+
+    Also add information on how to contact you by electronic and paper mail.
+
+    If the program is interactive, make it output a short notice like this
+    when it starts in an interactive mode:
+
+        Gnomovision version 69, Copyright (C) year name of author
+        Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type
+        `show w'. This is free software, and you are welcome to redistribute
+        it under certain conditions; type `show c' for details.
+
+    The hypothetical commands `show w' and `show c' should show the
+    appropriate parts of the General Public License. Of course, the commands
+    you use may be called something other than `show w' and `show c'; they
+    could even be mouse-clicks or menu items--whatever suits your program.
+
+    You should also get your employer (if you work as a programmer) or your
+    school, if any, to sign a "copyright disclaimer" for the program, if
+    necessary. Here is a sample; alter the names:
+
+        Yoyodyne, Inc., hereby disclaims all copyright interest in the
+        program `Gnomovision' (which makes passes at compilers) written by
+        James Hacker.
+
+        signature of Ty Coon, 1 April 1989
+        Ty Coon, President of Vice
+
+    This General Public License does not permit incorporating your program
+    into proprietary programs. If your program is a subroutine library, you
+    may consider it more useful to permit linking proprietary applications
+    with the library. If this is what you want to do, use the GNU Library
+    General Public License instead of this License.
+
+---
+
+## CLASSPATH EXCEPTION
+
+    Linking this library statically or dynamically with other modules is
+    making a combined work based on this library.  Thus, the terms and
+    conditions of the GNU General Public License version 2 cover the whole
+    combination.
+
+    As a special exception, the copyright holders of this library give you
+    permission to link this library with independent modules to produce an
+    executable, regardless of the license terms of these independent
+    modules, and to copy and distribute the resulting executable under
+    terms of your choice, provided that you also meet, for each linked
+    independent module, the terms and conditions of the license of that
+    module.  An independent module is a module which is not derived from or
+    based on this library.  If you modify this library, you may extend this
+    exception to your version of the library, but you are not obligated to
+    do so.  If you do not wish to do so, delete this exception statement
+    from your version.
diff --git a/mail/src/main/resources/META-INF/MANIFEST.MF b/mail/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..db5acf6
--- /dev/null
+++ b/mail/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Extension-Name: javax.mail
+Specification-Title: JavaMail(TM) API Design Specification
+Specification-Version: ${mail.spec.version}
+Specification-Vendor: ${project.organization.name}
+Implementation-Title: javax.mail
+Implementation-Version: ${mail.version}
+Implementation-Vendor: ${project.organization.name}
+Implementation-Vendor-Id: com.sun
diff --git a/mail/src/main/resources/META-INF/gfprobe-provider.xml b/mail/src/main/resources/META-INF/gfprobe-provider.xml
new file mode 100644
index 0000000..b511959
--- /dev/null
+++ b/mail/src/main/resources/META-INF/gfprobe-provider.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<probe-providers>
+  <probe-provider moduleProviderName="glassfish" moduleName="javamail"
+      probeProviderName="smtp-transport"
+      class="com.sun.mail.smtp.SMTPTransport">
+    <probe name="sendMessageStart">
+      <method>sendMessageStart</method>
+      <probe-param type="java.lang.String" name="subject" />
+      <return-param type="void" />
+    </probe>
+    <probe name="sendMessageEnd">
+      <method>sendMessageEnd</method>
+      <return-param type="void" />
+    </probe>
+  </probe-provider>
+  <probe-provider moduleProviderName="glassfish" moduleName="javamail"
+      probeProviderName="iap-protocol"
+      class="com.sun.mail.iap.Protocol">
+    <probe name="commandStart">
+      <method>commandStart</method>
+      <probe-param type="java.lang.String" name="command" />
+      <return-param type="void" />
+    </probe>
+    <probe name="commandEnd">
+      <method>commandEnd</method>
+      <return-param type="void" />
+    </probe>
+  </probe-provider>
+  <probe-provider moduleProviderName="glassfish" moduleName="javamail"
+      probeProviderName="pop3-protocol"
+      class="com.sun.mail.pop3.Protocol">
+    <probe name="simpleCommandStart">
+      <method>simpleCommandStart</method>
+      <probe-param type="java.lang.String" name="command" />
+      <return-param type="void" />
+    </probe>
+    <probe name="simpleCommandEnd">
+      <method>simpleCommandEnd</method>
+      <return-param type="void" />
+    </probe>
+    <probe name="multilineCommandStart">
+      <method>multilineCommandStart</method>
+      <probe-param type="java.lang.String" name="command" />
+      <return-param type="void" />
+    </probe>
+    <probe name="multilineCommandEnd">
+      <method>multilineCommandEnd</method>
+      <return-param type="void" />
+    </probe>
+  </probe-provider>
+</probe-providers>
diff --git a/mail/src/main/resources/META-INF/hk2-locator/default b/mail/src/main/resources/META-INF/hk2-locator/default
new file mode 100644
index 0000000..226ff1d
--- /dev/null
+++ b/mail/src/main/resources/META-INF/hk2-locator/default
@@ -0,0 +1,9 @@
+#
+# This metadata allows com.sun.mail.util.logging.MailHandler to be
+# configured in the logging.properties file and used in GlassFish.
+# This file was created by hand to avoid a compile time dependency
+# on the HK2 annotations.
+#
+[com.sun.mail.util.logging.MailHandler]
+contract={java.util.logging.Handler}
+scope=javax.inject.Singleton
diff --git a/mail/src/main/resources/META-INF/javamail.charset.map b/mail/src/main/resources/META-INF/javamail.charset.map
new file mode 100644
index 0000000..cc58d4f
--- /dev/null
+++ b/mail/src/main/resources/META-INF/javamail.charset.map
@@ -0,0 +1,77 @@
+### JDK-to-MIME charset mapping table ####
+### This should be the first mapping table ###
+
+8859_1		ISO-8859-1
+iso8859_1	ISO-8859-1
+ISO8859-1	ISO-8859-1
+
+8859_2		ISO-8859-2
+iso8859_2	ISO-8859-2
+ISO8859-2	ISO-8859-2
+
+8859_3		ISO-8859-3
+iso8859_3	ISO-8859-3
+ISO8859-3	ISO-8859-3
+
+8859_4		ISO-8859-4
+iso8859_4	ISO-8859-4
+ISO8859-4	ISO-8859-4
+
+8859_5		ISO-8859-5
+iso8859_5	ISO-8859-5
+ISO8859-5	ISO-8859-5
+
+8859_6		ISO-8859-6
+iso8859_6	ISO-8859-6
+ISO8859-6	ISO-8859-6
+
+8859_7		ISO-8859-7
+iso8859_7	ISO-8859-7
+ISO8859-7	ISO-8859-7
+
+8859_8		ISO-8859-8
+iso8859_8	ISO-8859-8
+ISO8859-8	ISO-8859-8
+
+8859_9		ISO-8859-9
+iso8859_9	ISO-8859-9
+ISO8859-9	ISO-8859-9
+
+SJIS		Shift_JIS
+JIS		ISO-2022-JP
+ISO2022JP	ISO-2022-JP
+EUC_JP		euc-jp
+KOI8_R		koi8-r
+EUC_CN		euc-cn
+EUC_TW		euc-tw
+EUC_KR		euc-kr
+
+--DIVIDER: this line *must* start with "--" and end with "--" --
+
+#### XXX-to-JDK charset mapping table ####
+
+iso-2022-cn     ISO2022CN
+iso-2022-kr     ISO2022KR
+utf-8           UTF8
+utf8		UTF8
+ja_jp.iso2022-7 ISO2022JP
+ja_jp.eucjp     EUCJIS
+
+# these two are not needed in 1.1.6.  (since EUC_KR exists
+# and KSC5601 will map to the correct converter)
+euc-kr          KSC5601
+euckr           KSC5601
+
+# in JDK 1.1.6 we will no longer need the "us-ascii" convert
+us-ascii        ISO-8859-1
+x-us-ascii      ISO-8859-1
+
+# Chinese charsets are a mess and widely misrepresented.
+# gb18030 is a superset of gbk, which is a supserset of cp936/ms936,
+# which is a superset of gb2312.
+# https://bugzilla.gnome.org/show_bug.cgi?id=446783
+# map all of these to gb18030.
+gb2312		GB18030
+cp936		GB18030
+ms936		GB18030
+gbk		GB18030
diff --git a/mail/src/main/resources/META-INF/javamail.default.address.map b/mail/src/main/resources/META-INF/javamail.default.address.map
new file mode 100644
index 0000000..4ab5572
--- /dev/null
+++ b/mail/src/main/resources/META-INF/javamail.default.address.map
@@ -0,0 +1 @@
+rfc822=smtp
diff --git a/mail/src/main/resources/META-INF/javamail.default.providers b/mail/src/main/resources/META-INF/javamail.default.providers
new file mode 100644
index 0000000..bedc076
--- /dev/null
+++ b/mail/src/main/resources/META-INF/javamail.default.providers
@@ -0,0 +1,9 @@
+# JavaMail IMAP provider Oracle
+protocol=imap; type=store; class=com.sun.mail.imap.IMAPStore; vendor=Oracle;
+protocol=imaps; type=store; class=com.sun.mail.imap.IMAPSSLStore; vendor=Oracle;
+# JavaMail SMTP provider Oracle
+protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=Oracle;
+protocol=smtps; type=transport; class=com.sun.mail.smtp.SMTPSSLTransport; vendor=Oracle;
+# JavaMail POP3 provider Oracle
+protocol=pop3; type=store; class=com.sun.mail.pop3.POP3Store; vendor=Oracle;
+protocol=pop3s; type=store; class=com.sun.mail.pop3.POP3SSLStore; vendor=Oracle;
diff --git a/mail/src/main/resources/META-INF/mailcap b/mail/src/main/resources/META-INF/mailcap
new file mode 100644
index 0000000..5259ae7
--- /dev/null
+++ b/mail/src/main/resources/META-INF/mailcap
@@ -0,0 +1,16 @@
+#
+#
+# Default mailcap file for the JavaMail System.
+#
+# JavaMail content-handlers:
+#
+text/plain;;		x-java-content-handler=com.sun.mail.handlers.text_plain
+text/html;;		x-java-content-handler=com.sun.mail.handlers.text_html
+text/xml;;		x-java-content-handler=com.sun.mail.handlers.text_xml
+multipart/*;;		x-java-content-handler=com.sun.mail.handlers.multipart_mixed; x-java-fallback-entry=true
+message/rfc822;;	x-java-content-handler=com.sun.mail.handlers.message_rfc822
+#
+# can't support image types because java.awt.Toolkit doesn't work on servers
+#
+#image/gif;;		x-java-content-handler=com.sun.mail.handlers.image_gif
+#image/jpeg;;		x-java-content-handler=com.sun.mail.handlers.image_jpeg
diff --git a/mail/src/main/resources/META-INF/services/javax.mail.Provider b/mail/src/main/resources/META-INF/services/javax.mail.Provider
new file mode 100644
index 0000000..4688e5a
--- /dev/null
+++ b/mail/src/main/resources/META-INF/services/javax.mail.Provider
@@ -0,0 +1,6 @@
+com.sun.mail.imap.IMAPProvider
+com.sun.mail.imap.IMAPSSLProvider
+com.sun.mail.smtp.SMTPProvider
+com.sun.mail.smtp.SMTPSSLProvider
+com.sun.mail.pop3.POP3Provider
+com.sun.mail.pop3.POP3SSLProvider
diff --git a/mail/src/main/resources/javax/mail/Version.java b/mail/src/main/resources/javax/mail/Version.java
new file mode 100644
index 0000000..e1d9899
--- /dev/null
+++ b/mail/src/main/resources/javax/mail/Version.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+/**
+ * Package-private class that defines the version of JavaMail.
+ * This file is a template for the class that is generated
+ * at build time.
+ */
+class Version {
+    public static final String version = "${mail.version}";
+}
diff --git a/mail/src/oldtest/java/javax/mail/internet/decodetest b/mail/src/oldtest/java/javax/mail/internet/decodetest
new file mode 100644
index 0000000..5d7f496
--- /dev/null
+++ b/mail/src/oldtest/java/javax/mail/internet/decodetest
@@ -0,0 +1,4 @@
+#!/bin/sh
+java decodetext < encodedheaders.data > x1
+java -Dmail.mime.decodetext.strict=false decodetext < encodedheaders.data > x2
+diff -c x1 x2
diff --git a/mail/src/oldtest/java/javax/mail/internet/encodedheaders.data b/mail/src/oldtest/java/javax/mail/internet/encodedheaders.data
new file mode 100644
index 0000000..e853736
--- /dev/null
+++ b/mail/src/oldtest/java/javax/mail/internet/encodedheaders.data
@@ -0,0 +1,28 @@
+=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
+ =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= Congrats!
+Hello World
+  Hello   
+	World
+=?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>
+=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>
+=?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= <paf@nada.kth.se>
+=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=
+=?ISO-2022-JP?B?GyRCQmdCPCEhWEYwbE86GyhK?=
+[mizuki 1007] Re: Hajimemasite!!
+Kazuyuki Murata <hiroko@hamakko.or.jp>
+[mizuki 1009] =?ISO-2022-JP?B?GyRCJUYlbCVTPVAxaT5wSnMbKEI=?=
+=?ISO-8859-1?Q?a?=
+=?ISO-8859-1?Q?a?= b
+=?ISO-8859-1?Q?a?= =?ISO-8859-1?Q?b?=
+=?ISO-8859-1?Q?a?=  =?ISO-8859-1?Q?b?=
+=?ISO-8859-1?Q?a_b?=
+=?ISO-8859-1?Q?a?= =?ISO-8859-2?Q?_b?=
+=?US-ASCII?Q?Keith_Moore?=
+=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=
+=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=
+=?ISO-8859-1?Q?Olle_J=E4rnefors?=
+
+babababa=?ISO-2022-JP?B?GyRCMj0kMSRrGyhK?=
+babababa=?ISO-2022-JP?B?GyRCMj0kMSRrGyhK?=babababa
+bababa =?ISO-2022-JP?B?IBskQjI9JDEkaxsoSg==?=
+=?ISO-8859-1?Q?a?==?ISO-8859-1?Q?b?=
diff --git a/mail/src/oldtest/java/javax/mail/internet/messagecachetest.java b/mail/src/oldtest/java/javax/mail/internet/messagecachetest.java
new file mode 100644
index 0000000..34e56a2
--- /dev/null
+++ b/mail/src/oldtest/java/javax/mail/internet/messagecachetest.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+/*
+ * Test IMAP message cache.
+ *
+ * @author Bill Shannon
+ */
+
+public class messagecachetest {
+
+    static String protocol;
+    static String host = null;
+    static String user = null;
+    static String password = null;
+    static String mbox = null;
+    static String url = null;
+    static int port = -1;
+    static boolean verbose = false;
+    static boolean debug = false;
+    static Session session;
+
+    public static void main(String argv[]) {
+	int nummsg = 256;
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-v")) {
+		verbose = true;
+	    } else if (argv[optind].equals("-D")) {
+		debug = true;
+	    } else if (argv[optind].equals("-f")) {
+		mbox = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-p")) {
+		port = Integer.parseInt(argv[++optind]);
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+"Usage: messagecachetest [-L url] [-T protocol] [-H host] [-p port] [-U user]");
+		System.out.println(
+"\t[-P password] [-f mailbox] [msgnum] [-v] [-D]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+	    if (optind < argv.length)
+		 nummsg = Integer.parseInt(argv[optind]);
+
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    session = Session.getInstance(props, null);
+	    session.setDebug(debug);
+
+	    // Get a Store object
+	    Store store = null;
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, port, user, password);
+		else
+		    store.connect();
+	    }
+	    
+
+	    // Open the Folder
+
+	    Folder folder = store.getDefaultFolder();
+	    if (folder == null) {
+		System.out.println("No default folder");
+		System.exit(1);
+	    }
+
+	    if (mbox == null)
+		mbox = "messagecachetest";
+	    folder = folder.getFolder(mbox);
+	    if (folder == null) {
+		System.out.println("Invalid folder");
+		System.exit(1);
+	    }
+
+	    Message[] msgs = createMessages(nummsg);
+	    if (folder.exists())
+		folder.delete(false);
+	    folder.create(Folder.HOLDS_MESSAGES);
+
+	    folder.open(Folder.READ_WRITE);
+	    if (verbose)
+		System.out.println("fill folder");
+	    folder.appendMessages(msgs);
+	    folder.close(false);
+
+	    folder.open(Folder.READ_WRITE);
+	    if (verbose)
+		System.out.println("test message number");
+	    testMessageNumber(folder);
+	    folder.close(false);
+	    folder.open(Folder.READ_WRITE);
+	    if (verbose)
+		System.out.println("test expunge forward");
+	    testExpungeForward(folder);
+	    folder.close(false);
+
+	    folder.open(Folder.READ_WRITE);
+	    folder.appendMessages(msgs);
+	    folder.close(false);
+	    folder.open(Folder.READ_WRITE);
+	    if (verbose)
+		System.out.println("test expunge reverse");
+	    testExpungeReverse(folder);
+	    folder.close(false);
+
+	    folder.open(Folder.READ_WRITE);
+	    folder.appendMessages(msgs);
+	    folder.close(false);
+	    folder.open(Folder.READ_WRITE);
+	    if (verbose)
+		System.out.println("test expunge random");
+	    testExpungeRandom(folder);
+	    folder.close(false);
+
+	    folder.open(Folder.READ_WRITE);
+	    folder.appendMessages(msgs);
+	    folder.close(false);
+	    folder.open(Folder.READ_WRITE);
+	    if (verbose)
+		System.out.println("test expunge other");
+	    testExpungeOther(folder);
+	    folder.close(false);
+	    store.close();
+	} catch (Exception ex) {
+	    System.out.println("Oops, got exception! " + ex.getMessage());
+	    ex.printStackTrace();
+	    System.exit(1);
+	}
+	System.exit(0);
+    }
+
+    private static Message[] createMessages(int num) throws MessagingException {
+	Message[] msgs = new Message[num];
+	for (int i = 1; i <= num; i++) {
+	    MimeMessage msg = new MimeMessage(session);
+	    msg.setSentDate(new Date());
+	    msg.setFrom();
+	    msg.setRecipients(Message.RecipientType.TO, "nobody@nowhere.com");
+	    msg.setSubject(Integer.toString(i));
+	    msg.setText(i + "\n");
+	    msg.saveChanges();
+	    msgs[i-1] = msg;
+	}
+	return msgs;
+    }
+
+    private static void testMessageNumber(Folder folder)
+				throws MessagingException {
+	int nummsg = folder.getMessageCount();
+	Message msgs[] = new Message[nummsg];
+	for (int i = 1; i <= nummsg; i++) {
+	    Message msg = folder.getMessage(i);
+	    msgs[i-1] = msg;
+	    if (Integer.valueOf(msg.getSubject()) != i) {
+		System.out.println("FAIL: Wrong message! Got " +
+				msg.getSubject() + ", Expected " + i);
+	    }
+	}
+	for (int i = 1; i <= nummsg; i++) {
+	    Message msg = folder.getMessage(i);
+	    if (msgs[i-1] != msg || msg.getMessageNumber() != i) {
+		System.out.println("FAIL: Wrong message! Got " +
+				msg.getMessageNumber() + ", Expected " + i);
+	    }
+	}
+    }
+
+    private static void testExpungeForward(Folder folder)
+				throws MessagingException {
+	int nummsg = folder.getMessageCount();
+	for (int i = 1; i <= nummsg; i++) {
+	    Message msg = folder.getMessage(1);
+	    msg.setFlag(Flags.Flag.DELETED, true);
+	    folder.expunge();
+	    for (int j = 1; j < nummsg - i; j++) {
+		msg = folder.getMessage(j);
+		if (msg.getMessageNumber() != j) {
+		    System.out.println("FAIL: Wrong message! Got " +
+				    msg.getMessageNumber() + ", Expected " + j);
+		}
+	    }
+	}
+    }
+
+    private static void testExpungeReverse(Folder folder)
+				throws MessagingException {
+	int nummsg = folder.getMessageCount();
+	for (int i = nummsg; i >= 1; i--) {
+	    Message msg = folder.getMessage(i);
+	    msg.setFlag(Flags.Flag.DELETED, true);
+	    folder.expunge();
+	    for (int j = 1; j < i; j++) {
+		msg = folder.getMessage(j);
+		if (msg.getMessageNumber() != j) {
+		    System.out.println("FAIL: Wrong message! Got " +
+				    msg.getMessageNumber() + ", Expected " + j);
+		}
+	    }
+	}
+    }
+
+    private static void testExpungeRandom(Folder folder)
+				throws MessagingException {
+	int nummsg = folder.getMessageCount();
+	Random rnd = new Random(System.currentTimeMillis());
+	while (nummsg > 0) {
+	    Message msg = folder.getMessage(rnd.nextInt(nummsg) + 1);
+	    msg.setFlag(Flags.Flag.DELETED, true);
+	    folder.expunge();
+	    nummsg--;
+	    for (int j = 1; j <= nummsg; j++) {
+		msg = folder.getMessage(j);
+		if (msg.getMessageNumber() != j) {
+		    System.out.println("FAIL: Wrong message! Got " +
+				    msg.getMessageNumber() + ", Expected " + j);
+		}
+	    }
+	}
+    }
+
+    private static void testExpungeOther(Folder folder)
+				throws MessagingException {
+	Folder other = folder.getStore().getFolder(folder.getFullName());
+	other.open(Folder.READ_WRITE);
+	Message msg = other.getMessage(2);
+	msg.setFlag(Flags.Flag.DELETED, true);
+	other.expunge();
+	System.out.println("waiting for expunge notification");
+	try { Thread.sleep(2000); } catch (InterruptedException ex) { }
+	folder.isOpen();
+	try {
+	    msg = folder.getMessage(2);
+	    msg.getFlags();
+	    if (!msg.isExpunged()) {
+		System.out.println("FAIL: Message 2 is not expunged!");
+	    }
+	} catch (MessageRemovedException mex) {
+	    System.out.println("SUCCESS: message 2 is removed!");
+	}
+	msg = folder.getMessage(1);
+	if (Integer.valueOf(msg.getSubject()) != 1) {
+	    System.out.println("FAIL: Message 1 is wrong!");
+	}
+	msg = folder.getMessage(3);
+	if (Integer.valueOf(msg.getSubject()) != 3) {
+	    System.out.println("FAIL: Message 3 is wrong!");
+	}
+	other.close(false);
+    }
+}
diff --git a/mail/src/oldtest/java/javax/mail/internet/socketfactory/DummySSLSocketFactory.java b/mail/src/oldtest/java/javax/mail/internet/socketfactory/DummySSLSocketFactory.java
new file mode 100644
index 0000000..12e3834
--- /dev/null
+++ b/mail/src/oldtest/java/javax/mail/internet/socketfactory/DummySSLSocketFactory.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.*;
+
+
+/**
+ * DummySSLSocketFactory
+ */
+public class DummySSLSocketFactory extends SSLSocketFactory {
+    private SSLSocketFactory factory;
+
+    public DummySSLSocketFactory() {
+	try {
+	    SSLContext sslcontext = SSLContext.getInstance("TLS");
+	    sslcontext.init(null,
+				 new TrustManager[] { new DummyTrustManager()},
+				 null);
+	    factory = (SSLSocketFactory)sslcontext.getSocketFactory();
+	} catch(Exception ex) {
+	    // ignore
+	}
+    }
+
+    public static SocketFactory getDefault() {
+	return new DummySSLSocketFactory();
+    }
+
+    public Socket createSocket() throws IOException {
+	TestResult.success();
+	return factory.createSocket();
+    }
+
+    public Socket createSocket(Socket socket, String s, int i, boolean flag)
+				throws IOException {
+	TestResult.success();
+	return factory.createSocket(socket, s, i, flag);
+    }
+
+    public Socket createSocket(InetAddress inaddr, int i,
+				InetAddress inaddr1, int j) throws IOException {
+	TestResult.success();
+	return factory.createSocket(inaddr, i, inaddr1, j);
+    }
+
+    public Socket createSocket(InetAddress inaddr, int i)
+				throws IOException {
+	TestResult.success();
+	return factory.createSocket(inaddr, i);
+    }
+
+    public Socket createSocket(String s, int i, InetAddress inaddr, int j)
+				throws IOException {
+	TestResult.success();
+	return factory.createSocket(s, i, inaddr, j);
+    }
+
+    public Socket createSocket(String s, int i) throws IOException {
+	TestResult.success();
+	return factory.createSocket(s, i);
+    }
+
+    public String[] getDefaultCipherSuites() {
+	return factory.getDefaultCipherSuites();
+    }
+
+    public String[] getSupportedCipherSuites() {
+	return factory.getSupportedCipherSuites();
+    }
+}
diff --git a/mail/src/oldtest/java/javax/mail/internet/socketfactory/DummySocketFactory.java b/mail/src/oldtest/java/javax/mail/internet/socketfactory/DummySocketFactory.java
new file mode 100644
index 0000000..aa24722
--- /dev/null
+++ b/mail/src/oldtest/java/javax/mail/internet/socketfactory/DummySocketFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import javax.net.SocketFactory;
+
+
+/**
+ * DummySocketFactory
+ */
+public class DummySocketFactory extends SocketFactory {
+    private SocketFactory factory;
+
+    public DummySocketFactory() {
+	factory = SocketFactory.getDefault();
+    }
+
+    public static SocketFactory getDefault() {
+	return new DummySocketFactory();
+    }
+
+    public Socket createSocket() throws IOException {
+	TestResult.success();
+	return factory.createSocket();
+    }
+
+    public Socket createSocket(InetAddress inaddr, int i,
+				InetAddress inaddr1, int j) throws IOException {
+	TestResult.success();
+	return factory.createSocket(inaddr, i, inaddr1, j);
+    }
+
+    public Socket createSocket(InetAddress inaddr, int i)
+				throws IOException {
+	TestResult.success();
+	return factory.createSocket(inaddr, i);
+    }
+
+    public Socket createSocket(String s, int i, InetAddress inaddr, int j)
+				throws IOException {
+	TestResult.success();
+	return factory.createSocket(s, i, inaddr, j);
+    }
+
+    public Socket createSocket(String s, int i) throws IOException {
+	TestResult.success();
+	return factory.createSocket(s, i);
+    }
+}
diff --git a/mail/src/oldtest/java/javax/mail/internet/socketfactory/DummyTrustManager.java b/mail/src/oldtest/java/javax/mail/internet/socketfactory/DummyTrustManager.java
new file mode 100644
index 0000000..4307623
--- /dev/null
+++ b/mail/src/oldtest/java/javax/mail/internet/socketfactory/DummyTrustManager.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.X509Certificate;
+
+
+/**
+ * DummyTrustManager
+ */
+public class DummyTrustManager implements X509TrustManager {
+
+    public void checkClientTrusted(X509Certificate[] cert, String authType) {
+	// everything is trusted
+    }
+
+    public void checkServerTrusted(X509Certificate[] cert, String authType) {
+	// everything is trusted
+    }
+
+    public X509Certificate[] getAcceptedIssuers() {
+	return new X509Certificate[0];
+    }
+}
diff --git a/mail/src/oldtest/java/javax/mail/internet/socketfactory/TestResult.java b/mail/src/oldtest/java/javax/mail/internet/socketfactory/TestResult.java
new file mode 100644
index 0000000..1946d0c
--- /dev/null
+++ b/mail/src/oldtest/java/javax/mail/internet/socketfactory/TestResult.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+public class TestResult {
+    private static boolean pass = false;
+
+    private TestResult() { }	// no public constructor
+
+    public static void reset() {
+	pass = false;
+    }
+
+    public static void success() {
+	pass = true;
+    }
+
+    public static void print(String s) {
+	System.out.println((pass ? "SUCCESS: " : "FAIL: ") + s);
+    }
+}
diff --git a/mail/src/oldtest/java/javax/mail/internet/socketfactory/socketfactorytest.java b/mail/src/oldtest/java/javax/mail/internet/socketfactory/socketfactorytest.java
new file mode 100644
index 0000000..caf94aa
--- /dev/null
+++ b/mail/src/oldtest/java/javax/mail/internet/socketfactory/socketfactorytest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import java.util.Properties;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import com.sun.mail.util.MailSSLSocketFactory;
+
+/**
+ * Test socket factory properties.
+ * Needs to be run with an SMTP server that supports both SSL
+ * connections and the STARTTLS command, e.g., mail-sfbay.sun.com.
+ *
+ * @author Bill Shannon
+ */
+
+public class socketfactorytest {
+
+    public static void main(String[] argv) {
+	String mailhost = "mail-sfbay.sun.com";
+	String user = null, password = null;
+	boolean debug = false;
+	boolean auth = false;
+	int optind;
+
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-M")) {
+		mailhost = argv[++optind];
+	    } else if (argv[optind].equals("-d")) {
+		debug = true;
+	    } else if (argv[optind].equals("-A")) {
+		auth = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+			"Usage: socketfactorytest [-U user] [-P passwd]]");
+		System.out.println("\t[-M transport-host] [-d] [-A]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	try {
+
+	    // first, no factories
+
+	    Properties props = new Properties();
+	    props.put("mail.smtp.host", mailhost);
+	    Session session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    Transport t = session.getTransport("smtp");
+	    try {
+		if (auth)
+		    t.connect(mailhost, user, password);
+		else
+		    t.connect();
+		TestResult.success();
+	    } finally {
+		t.close();
+	    }
+	    TestResult.print("no factories");
+
+	    // socket factory property
+
+	    TestResult.reset();
+	    props = new Properties();
+	    props.put("mail.smtp.host", mailhost);
+	    props.put("mail.smtp.socketFactory.class", "DummySocketFactory");
+	    session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    t = session.getTransport("smtp");
+	    try {
+		if (auth)
+		    t.connect(mailhost, user, password);
+		else
+		    t.connect();
+	    } finally {
+		t.close();
+	    }
+	    TestResult.print("socket factory property");
+
+	    // socket factory object
+
+	    TestResult.reset();
+	    props = new Properties();
+	    props.put("mail.smtp.host", mailhost);
+	    props.put("mail.smtp.socketFactory", new DummySocketFactory());
+	    session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    t = session.getTransport("smtp");
+	    try {
+		if (auth)
+		    t.connect(mailhost, user, password);
+		else
+		    t.connect();
+	    } finally {
+		t.close();
+	    }
+	    TestResult.print("socket factory object");
+
+	    // SSL socket factory property
+
+	    TestResult.reset();
+	    props = new Properties();
+	    props.put("mail.smtp.host", mailhost);
+	    props.put("mail.smtp.ssl.enable", "true");
+	    props.put("mail.smtp.ssl.checkserveridentity", "true");
+	    props.put("mail.smtp.ssl.socketFactory.class",
+						"DummySSLSocketFactory");
+	    session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    t = session.getTransport("smtp");
+	    try {
+		if (auth)
+		    t.connect(mailhost, user, password);
+		else
+		    t.connect();
+	    } finally {
+		t.close();
+	    }
+	    TestResult.print("SSL socket factory property");
+
+	    // SSL socket factory object
+
+	    TestResult.reset();
+	    props = new Properties();
+	    props.put("mail.smtp.host", mailhost);
+	    props.put("mail.smtp.ssl.enable", "true");
+	    props.put("mail.smtp.ssl.checkserveridentity", "true");
+	    props.put("mail.smtp.ssl.socketFactory",
+						new DummySSLSocketFactory());
+	    session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    t = session.getTransport("smtp");
+	    try {
+		if (auth)
+		    t.connect(mailhost, user, password);
+		else
+		    t.connect();
+	    } finally {
+		t.close();
+	    }
+	    TestResult.print("SSL socket factory object");
+
+	    // STARTTLS no socket factory
+
+	    TestResult.reset();
+	    props = new Properties();
+	    props.put("mail.smtp.host", mailhost);
+	    props.put("mail.smtp.starttls.enable", "true");
+	    session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    t = session.getTransport("smtp");
+	    try {
+		if (auth)
+		    t.connect(mailhost, user, password);
+		else
+		    t.connect();
+		TestResult.success();
+	    } finally {
+		t.close();
+	    }
+	    TestResult.print("STARTTLS no socket factory");
+
+	    // STARTTLS SSL socket factory property
+
+	    TestResult.reset();
+	    props = new Properties();
+	    props.put("mail.smtp.host", mailhost);
+	    props.put("mail.smtp.starttls.enable", "true");
+	    props.put("mail.smtp.ssl.checkserveridentity", "true");
+	    props.put("mail.smtp.ssl.socketFactory.class",
+						"DummySSLSocketFactory");
+	    session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    t = session.getTransport("smtp");
+	    try {
+		if (auth)
+		    t.connect(mailhost, user, password);
+		else
+		    t.connect();
+	    } finally {
+		t.close();
+	    }
+	    TestResult.print("STARTTLS SSL socket factory property");
+
+	    // STARTTLS SSL socket factory object
+
+	    TestResult.reset();
+	    props = new Properties();
+	    props.put("mail.smtp.host", mailhost);
+	    props.put("mail.smtp.starttls.enable", "true");
+	    props.put("mail.smtp.ssl.checkserveridentity", "true");
+	    props.put("mail.smtp.ssl.socketFactory",
+						new DummySSLSocketFactory());
+	    session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    t = session.getTransport("smtp");
+	    try {
+		if (auth)
+		    t.connect(mailhost, user, password);
+		else
+		    t.connect();
+	    } finally {
+		t.close();
+	    }
+	    TestResult.print("STARTTLS SSL socket factory object");
+
+	    // Mail SSL socket factory object
+
+	    /*
+	    TestResult.reset();
+	    props = new Properties();
+	    props.put("mail.imap.host", mailhost);
+	    props.put("mail.imap.ssl.enable", "true");
+	    props.put("mail.imap.ssl.checkserveridentity", "true");
+	    MailSSLSocketFactory sf = new MailSSLSocketFactory("TLS");
+	    //sf.setTrustAllHosts(true);
+	    sf.setTrustedHosts(new String[] { "loghost" });
+	    props.put("mail.imap.ssl.socketFactory", sf);
+	    props.put("mail.imap.socketFactory.fallback", "false");
+	    session = Session.getInstance(props, null);
+	    if (debug)
+		session.setDebug(true);
+
+	    Store st = session.getStore("imap");
+	    try {
+		if (auth)
+		    st.connect(mailhost, user, password);
+		else
+		    st.connect();
+		TestResult.success();
+	    } finally {
+		st.close();
+	    }
+	    TestResult.print("Mail SSL socket factory object");
+	    */
+
+	} catch (Exception e) {
+	    System.out.println("Exception: " + e);
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/handlers/TextXmlTest.java b/mail/src/test/java/com/sun/mail/handlers/TextXmlTest.java
new file mode 100644
index 0000000..26c8123
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/handlers/TextXmlTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.handlers;
+
+import java.io.*;
+import java.util.*;
+import java.awt.datatransfer.DataFlavor;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.util.ByteArrayDataSource;
+import javax.xml.transform.stream.*;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test the text/xml DataContentHandler.
+ *
+ * XXX - should test other Source objects in addition to StreamSource.
+ *
+ * @author Bill Shannon
+ */
+
+public class TextXmlTest {
+
+    private static String xml = "<test><foo>bar</foo></test>\n";
+    private static byte[] xmlBytes = xml.getBytes();
+
+    // test InputStream to String
+    @Test
+    public void testStreamToStringTextXml() throws Exception {
+	testStreamToString("text/xml");
+    }
+
+    // test InputStream to String
+    @Test
+    public void testStreamToStringApplicationXml() throws Exception {
+	testStreamToString("application/xml");
+    }
+
+    private static void testStreamToString(String mimeType) throws Exception {
+	DataContentHandler dch = new text_xml();
+	DataFlavor df = new ActivationDataFlavor(String.class, mimeType, "XML");
+	DataSource ds = new ByteArrayDataSource(xmlBytes, mimeType);
+	Object content = dch.getContent(ds);
+	assertEquals(String.class, content.getClass());
+	assertEquals(xml, (String)content);
+	content = dch.getTransferData(df, ds);
+	assertEquals(String.class, content.getClass());
+	assertEquals(xml, (String)content);
+    }
+
+    // test InputStream to StreamSource
+    @Test
+    public void testStreamToSource() throws Exception {
+	DataContentHandler dch = new text_xml();
+	DataFlavor df = new ActivationDataFlavor(StreamSource.class,
+						    "text/xml", "XML stream");
+	DataSource ds = new ByteArrayDataSource(xmlBytes, "text/xml");
+	Object content = dch.getTransferData(df, ds);
+	assertEquals(StreamSource.class, content.getClass());
+	String sc = streamToString(((StreamSource)content).getInputStream());
+	assertEquals(xml, sc);
+    }
+
+    // test String to OutputStream
+    @Test
+    public void testStringToStream() throws Exception {
+	DataContentHandler dch = new text_xml();
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	dch.writeTo(xml, "text/xml", bos);
+	String sc = new String(bos.toByteArray(), "us-ascii");
+	assertEquals(xml, sc);
+    }
+
+    // test StreamSource to OutputStream
+    @Test
+    public void testSourceToStream() throws Exception {
+	DataContentHandler dch = new text_xml();
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	StreamSource ss = new StreamSource(new ByteArrayInputStream(xmlBytes));
+	dch.writeTo(ss, "text/xml", bos);
+	String sc = new String(bos.toByteArray(), "us-ascii");
+	// transformer adds an <?xml> header, so can't check for exact match
+	assertTrue(sc.indexOf(xml.trim()) >= 0);
+    }
+
+    /**
+     * Read a stream into a String.
+     */
+    private static String streamToString(InputStream is) {
+	try {
+	    StringBuilder sb = new StringBuilder();
+	    int c;
+	    while ((c = is.read()) > 0)
+		sb.append((char)c);
+	    return sb.toString();
+	} catch (IOException ex) {
+	    return "";
+	} finally {
+	    try {
+		is.close();
+	    } catch (IOException cex) { }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/iap/ProtocolTest.java b/mail/src/test/java/com/sun/mail/iap/ProtocolTest.java
new file mode 100644
index 0000000..53f57b2
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/iap/ProtocolTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.*;
+import java.util.Properties;
+
+import com.sun.mail.test.NullOutputStream;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the Protocol class.
+ */
+public final class ProtocolTest {
+
+    private static final byte[] noBytes = new byte[0];
+    private static final PrintStream nullps =
+				    new PrintStream(new NullOutputStream());
+    private static final ByteArrayInputStream nullis =
+				    new ByteArrayInputStream(noBytes);
+
+    /**
+     * Test that the tag prefix is computed properly.
+     */
+    @Test
+    public void testTagPrefix() throws IOException, ProtocolException {
+	Protocol.tagNum.set(0);		// reset for testing
+	String tag = newProtocolTag();
+	assertEquals("A0", tag);
+	for (int i = 1; i < 26; i++)
+	    tag = newProtocolTag();
+	assertEquals("Z0", tag);
+	tag = newProtocolTag();
+	assertEquals("AA0", tag);
+	for (int i = 26 + 1; i < (26*26 + 26); i++)
+	    tag = newProtocolTag();
+	assertEquals("ZZ0", tag);
+	tag = newProtocolTag();
+	assertEquals("AAA0", tag);
+	for (int i = 26*26 + 26 + 1; i < (26*26*26 + 26*26 + 26); i++)
+	    tag = newProtocolTag();
+	assertEquals("ZZZ0", tag);
+	tag = newProtocolTag();
+	// did it wrap around?
+	assertEquals("A0", tag);
+    }
+
+    private String newProtocolTag() throws IOException, ProtocolException {
+	Properties props = new Properties();
+	Protocol p = new Protocol(nullis, nullps, props, false);
+	String tag = p.writeCommand("CMD", null);
+	return tag;
+    }
+
+    /**
+     * Test that the tag prefix is reused.
+     */
+    @Test
+    public void testTagPrefixReuse() throws IOException, ProtocolException {
+	Properties props = new Properties();
+	props.setProperty("mail.imap.reusetagprefix", "true");
+	Protocol p = new Protocol(nullis, nullps, props, false);
+	String tag = p.writeCommand("CMD", null);
+	assertEquals("A0", tag);
+	p = new Protocol(nullis, nullps, props, false);
+	tag = p.writeCommand("CMD", null);
+	assertEquals("A0", tag);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/iap/ResponseInputStreamTest.java b/mail/src/test/java/com/sun/mail/iap/ResponseInputStreamTest.java
new file mode 100644
index 0000000..dfdb0c3
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/iap/ResponseInputStreamTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+/**
+ * Test ResponseInputStream.
+ */
+public class ResponseInputStreamTest {
+
+    /**
+     * Test that an EOF while reading a literal throws an IOException.
+     */
+    @Test
+    public void testEofWhileReadingLiteral() throws Exception {
+	ByteArrayInputStream bis = new ByteArrayInputStream(
+	    "test{1}\r\n".getBytes("ISO-8859-1"));
+	ResponseInputStream ris = new ResponseInputStream(bis);
+	try {
+	    ris.readResponse();
+	} catch (IOException ex) {
+	    // success!
+	    return;
+	}
+	fail("no exception");
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/iap/ResponseTest.java b/mail/src/test/java/com/sun/mail/iap/ResponseTest.java
new file mode 100644
index 0000000..d75ead4
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/iap/ResponseTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.iap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+
+/**
+ * Test response parsing.
+ */
+public class ResponseTest {
+    // timeout the test in case of infinite loop
+    @Rule
+    public Timeout timeout = Timeout.seconds(5);
+
+    private static String[] atomTests = {
+	"atom", "atom ", "atom(", "atom)", "atom{", "atom*", "atom%",
+	"atom\"", "atom\\ ", "atom]", "atom\001", "atom\177"
+    };
+
+    private static String[] astringTests = {
+	"atom", "atom ", "atom(", "atom)", "atom{", "atom*", "atom%",
+	"atom\"", "atom\\ ", "atom\001", "atom\177", "\"atom\"",
+	"{4}\r\natom"
+    };
+
+    /**
+     * Test parsing atoms.
+     */
+    @Test
+    public void testAtom() throws Exception {
+	for (String s : atomTests) {
+	    Response r = new Response("* " + s);
+	    assertEquals("atom", r.readAtom());
+	}
+	for (String s : atomTests) {
+	    Response r = new Response("* " + s + " ");
+	    assertEquals("atom", r.readAtom());
+	}
+    }
+
+    /**
+     * Test parsing astrings.
+     */
+    @Test
+    public void testAString() throws Exception {
+	for (String s : astringTests) {
+	    Response r = new Response("* " + s);
+	    assertEquals("atom", r.readAtomString());
+	}
+	for (String s : astringTests) {
+	    Response r = new Response("* " + s + " ");
+	    assertEquals("atom", r.readAtomString());
+	}
+    }
+
+    /**
+     * Test the special case where an astring can include ']'.
+     */
+    @Test
+    public void testAStringSpecial() throws Exception {
+	Response r = new Response("* " + "atom] ");
+	assertEquals("atom]", r.readAtomString());
+    }
+
+    /**
+     * Test astring lists.
+     */
+    @Test
+    public void testAStringList() throws Exception {
+	Response r = new Response("* " + "(A B C)");
+	assertArrayEquals(new String[] { "A", "B", "C" },
+			    r.readAtomStringList());
+    }
+
+    @Test
+    public void testAStringListInitialSpace() throws Exception {
+	Response r = new Response("* " + "( A B C)");
+	assertArrayEquals(new String[] { "A", "B", "C" },
+			    r.readAtomStringList());
+    }
+
+    @Test
+    public void testAStringListTrailingSpace() throws Exception {
+	Response r = new Response("* " + "(A B C )");
+	assertArrayEquals(new String[] { "A", "B", "C" },
+			    r.readAtomStringList());
+    }
+
+    @Test
+    public void testAStringListInitialAndTrailingSpace() throws Exception {
+	Response r = new Response("* " + "( A B C )");
+	assertArrayEquals(new String[] { "A", "B", "C" },
+			    r.readAtomStringList());
+    }
+
+    @Test
+    public void testAStringListMultipleSpaces() throws Exception {
+	Response r = new Response("* " + "(A  B    C)");
+	assertArrayEquals(new String[] { "A", "B", "C" },
+			    r.readAtomStringList());
+    }
+
+    @Test
+    public void testAStringListQuoted() throws Exception {
+	Response r = new Response("* " + "(A B \"C\")");
+	assertArrayEquals(new String[] { "A", "B", "C" },
+			    r.readAtomStringList());
+    }
+
+    /**
+     * Test astring lists with more data following.
+     */
+    @Test
+    public void testAStringListMore() throws Exception {
+	Response r = new Response("* " + "(A B \"C\") atom");
+	assertArrayEquals(new String[] { "A", "B", "C" },
+			    r.readAtomStringList());
+	assertEquals("atom", r.readAtomString());
+    }
+
+    /**
+     * Test empty astring lists.
+     */
+    @Test
+    public void testAStringListEmpty() throws Exception {
+	Response r = new Response("* " + "()");
+	assertArrayEquals(new String[0], r.readAtomStringList());
+    }
+
+    /**
+     * Test empty astring lists with more data following.
+     */
+    @Test
+    public void testAStringListEmptyMore() throws Exception {
+	Response r = new Response("* " + "() atom");
+	assertArrayEquals(new String[0], r.readAtomStringList());
+	assertEquals("atom", r.readAtomString());
+    }
+
+    /**
+     * Test readStringList
+     */
+    @Test
+    public void testBadStringList() throws Exception {
+	Response response = new Response(
+			    "* (\"name\", \"test\", \"version\", \"1.0\")");
+        String[] list = response.readStringList();
+	// anything other than an infinite loop timeout is considered success
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPAlertTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPAlertTest.java
new file mode 100644
index 0000000..cb351d6
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPAlertTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.event.StoreListener;
+import javax.mail.event.StoreEvent;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test alerts.
+ */
+public final class IMAPAlertTest {
+
+    private volatile boolean gotAlert = false;
+
+    @Test
+    public void test() {
+        TestServer server = null;
+        try {
+            final IMAPHandler handler = new IMAPHandlerAlert();
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+	    final CountDownLatch latch = new CountDownLatch(1);
+
+            final Store store = session.getStore("imap");
+	    store.addStoreListener(new StoreListener() {
+		@Override
+		public void notification(StoreEvent e) {
+		    String s;
+		    if (e.getMessageType() == StoreEvent.ALERT) {
+			s = "ALERT: ";
+			gotAlert = true;
+			latch.countDown();
+		    } else
+			s = "NOTICE: ";
+		    //System.out.println(s + e.getMessage());
+		}
+	    });
+            try {
+                store.connect("test", "test");
+		// time for event to be delivered
+		latch.await(5, TimeUnit.SECONDS);
+		assertTrue(gotAlert);
+
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.  Returns an alert message at login.
+     */
+    private static final class IMAPHandlerAlert extends IMAPHandler {
+	@Override
+        public void login() throws IOException {
+	    untagged("OK [ALERT] account is over quota");
+	    super.login();
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPAuthDebugTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPAuthDebugTest.java
new file mode 100644
index 0000000..a1ff4fb
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPAuthDebugTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Store;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that authentication information is only included in
+ * the debug output when explicitly requested by setting the
+ * property "mail.debug.auth" to "true".
+ *
+ * XXX - should test all authentication types, but that requires
+ *	 more work in the dummy test server.
+ */
+public final class IMAPAuthDebugTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    /**
+     * Test that authentication information isn't included in the debug output.
+     */
+    @Test
+    public void testNoAuthDefault() {
+	final Properties properties = new Properties();
+	assertFalse("LOGIN in debug output", test(properties, "LOGIN"));
+    }
+
+    @Test
+    public void testNoAuth() {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.debug.auth", "false");
+	assertFalse("LOGIN in debug output", test(properties, "LOGIN"));
+    }
+
+    /**
+     * Test that authentication information *is* included in the debug output.
+     */
+    @Test
+    public void testAuth() {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.debug.auth", "true");
+	assertTrue("LOGIN in debug output", test(properties, "LOGIN"));
+    }
+
+    /**
+     * Create a test server, connect to it, and collect the debug output.
+     * Scan the debug output looking for "expect", return true if found.
+     */
+    public boolean test(Properties properties, String expect) {
+	TestServer server = null;
+	try {
+	    final IMAPHandler handler = new IMAPHandler();
+	    server = new TestServer(handler);
+	    server.start();
+
+	    properties.setProperty("mail.imap.host", "localhost");
+	    properties.setProperty("mail.imap.port", "" + server.getPort());
+	    final Session session = Session.getInstance(properties);
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	    PrintStream ps = new PrintStream(bos);
+	    session.setDebugOut(ps);
+	    session.setDebug(true);
+
+	    final Store store = session.getStore("imap");
+	    try {
+		store.connect("test", "test");
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		store.close();
+	    }
+
+	    ps.close();
+	    bos.close();
+	    ByteArrayInputStream bis =
+		new ByteArrayInputStream(bos.toByteArray());
+	    BufferedReader r = new BufferedReader(
+					new InputStreamReader(bis, "us-ascii"));
+	    String line;
+	    boolean found = false;
+	    while ((line = r.readLine()) != null) {
+		if (line.startsWith("DEBUG"))
+		    continue;
+		if (line.startsWith("*"))
+		    continue;
+		if (line.indexOf(expect) >= 0)
+		    found = true;
+	    }
+	    r.close();
+	    return found;
+	} catch (final Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	    return false;	// XXX - doesn't matter
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPCloseFailureTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPCloseFailureTest.java
new file mode 100644
index 0000000..eacb68f
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPCloseFailureTest.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Folder;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that failures while closing a folder are handled properly.
+ */
+public final class IMAPCloseFailureTest {
+
+    private static final String HOST = "localhost";
+
+    static class NoIMAPHandler extends IMAPHandler {
+	static boolean first = true;
+
+	@Override
+	public void examine(String line) throws IOException {
+	    if (first)
+		no("mailbox gone");
+	    else
+		super.examine(line);
+	    first = false;
+	}
+    }
+
+    static class BadIMAPHandler extends IMAPHandler {
+	static boolean first = true;
+
+	@Override
+	public void examine(String line) throws IOException {
+	    if (first)
+		bad("mailbox gone");
+	    else
+		super.examine(line);
+	    first = false;
+	}
+    }
+
+    @Test
+    public void testCloseNo() {
+	testClose(new NoIMAPHandler());
+    }
+
+    @Test
+    public void testCloseBad() {
+	testClose(new BadIMAPHandler());
+    }
+
+    public void testClose(IMAPHandler handler) {
+	TestServer server = null;
+	try {
+	    server = new TestServer(handler);
+	    server.start();
+
+	    Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", HOST);
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+	    Session session = Session.getInstance(properties);
+	    //session.setDebug(true);
+
+	    Store store = session.getStore("imap");
+	    try {
+		store.connect("test", "test");
+		Folder f = store.getFolder("INBOX");
+		f.open(Folder.READ_WRITE);
+		f.close(false);
+		// Make sure that failure while closing doesn't leave us
+		// with a connection that can't be used to open a folder.
+		f.open(Folder.READ_WRITE);
+		f.close(false);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		if (store.isConnected())
+		    store.close();
+	    }
+
+	} catch (Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPConnectFailureTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPConnectFailureTest.java
new file mode 100644
index 0000000..d6827df
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPConnectFailureTest.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+import java.util.Properties;
+import java.net.ServerSocket;
+
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.MessagingException;
+
+import com.sun.mail.iap.ConnectionException;
+
+import com.sun.mail.test.TestServer;
+import com.sun.mail.util.MailConnectException;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test connect failure.
+ */
+public final class IMAPConnectFailureTest {
+
+    private static final String HOST = "localhost";
+    private static final int CTO = 20;
+
+    @Test
+    public void testNoServer() {
+	try {
+	    // verify that port is not being used
+	    ServerSocket ss = new ServerSocket(0);
+	    int port = ss.getLocalPort();
+	    ss.close();
+
+	    Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", HOST);
+            properties.setProperty("mail.imap.port", "" + port);
+            properties.setProperty("mail.imap.connectiontimeout", "" + CTO);
+	    Session session = Session.getInstance(properties);
+	    //session.setDebug(true);
+
+	    Store store = session.getStore("imap");
+	    try {
+		store.connect("test", "test");
+		fail("Connected!");
+		// failure!
+	    } catch (MailConnectException mcex) {
+		// success!
+		assertEquals(HOST, mcex.getHost());
+		assertEquals(port, mcex.getPort());
+		assertEquals(CTO, mcex.getConnectionTimeout());
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		if (store.isConnected())
+		    store.close();
+	    }
+
+	} catch (Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	}
+    }
+
+    /**
+     * Test that a disconnect after issuing the CAPABILITY command
+     * results in a ConnectionException.
+     */
+    @Test
+    public void testCapabilityDisconnect() {
+	TestServer server = null;
+	try {
+	    final IMAPHandler handler = new IMAPHandler() {
+		@Override
+		public void sendGreetings() throws IOException {
+		    untagged("OK IMAPHandler");
+		}
+
+		@Override
+		public void capability() throws IOException {
+		    exit();
+		}
+	    };
+	    server = new TestServer(handler);
+	    server.start();
+
+	    Properties properties = new Properties();
+	    properties.setProperty("mail.imap.host", "localhost");
+	    properties.setProperty("mail.imap.port", "" + server.getPort());
+	    final Session session = Session.getInstance(properties);
+	    //session.setDebug(true);
+
+	    final Store store = session.getStore("imap");
+	    try {
+		store.connect("test", "test");
+		fail("connect did not fail");
+	    } catch (MessagingException mex) {
+		// this is what we expect, now check that it was caused by
+		// the right exception
+		assertTrue(mex.getCause() instanceof ConnectionException);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		store.close();
+	    }
+
+	} catch (final Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPFetchProfileTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPFetchProfileTest.java
new file mode 100644
index 0000000..2b933d9
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPFetchProfileTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Set;
+import java.util.HashSet;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.FetchProfile;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test IMAP FetchProfile items.
+ */
+public final class IMAPFetchProfileTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    private static final String RDATE = "23-Jun-2004 06:26:26 -0700";
+    private static final String ENVELOPE =
+	"(\"Wed, 23 Jun 2004 18:56:42 +0530\" \"test\" " +
+	"((\"JavaMail\" NIL \"testuser\" \"example.com\")) " +
+	"((\"JavaMail\" NIL \"testuser\" \"example.com\")) " +
+	"((\"JavaMail\" NIL \"testuser\" \"example.com\")) " +
+	"((NIL NIL \"testuser\" \"example.com\")) NIL NIL NIL " +
+	"\"<40D98512.9040803@example.com>\")";
+
+    public static interface IMAPTest {
+	public void test(Folder folder, IMAPHandlerFetch handler)
+				    throws MessagingException;
+    }
+
+    @Test
+    public void testINTERNALDATEFetch() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerFetch handler)
+				    throws MessagingException {
+		    FetchProfile fp = new FetchProfile();
+		    fp.add(IMAPFolder.FetchProfileItem.INTERNALDATE);
+		    Message m = folder.getMessage(1);
+		    folder.fetch(new Message[] { m }, fp);
+		    assertTrue(handler.saw("INTERNALDATE"));
+		    handler.reset();
+		    assertTrue(m.getReceivedDate() != null);
+		    assertFalse(handler.saw("INTERNALDATE"));
+		}
+	    },
+	    new IMAPHandlerFetch() {
+		@Override
+		public void fetch(String line) throws IOException {
+		    if (line.indexOf("INTERNALDATE") >= 0)
+			saw.add("INTERNALDATE");
+		    untagged("1 FETCH (INTERNALDATE \"" + RDATE + "\")");
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testINTERNALDATEFetchEnvelope() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerFetch handler)
+				    throws MessagingException {
+		    FetchProfile fp = new FetchProfile();
+		    fp.add(FetchProfile.Item.ENVELOPE);
+		    Message m = folder.getMessage(1);
+		    folder.fetch(new Message[] { m }, fp);
+		    assertTrue(handler.saw("INTERNALDATE"));
+		    handler.reset();
+		    assertTrue(m.getReceivedDate() != null);
+		    assertFalse(handler.saw("INTERNALDATE"));
+		}
+	    },
+	    new IMAPHandlerFetch() {
+		@Override
+		public void fetch(String line) throws IOException {
+		    if (line.indexOf("INTERNALDATE") >= 0)
+			saw.add("INTERNALDATE");
+		    untagged("1 FETCH (INTERNALDATE \"" + RDATE + "\")");
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testINTERNALDATENoFetch() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerFetch handler)
+				    throws MessagingException {
+		    Message m = folder.getMessage(1);
+		    assertTrue(m.getReceivedDate() != null);
+		    assertTrue(handler.saw("INTERNALDATE"));
+		}
+	    },
+	    new IMAPHandlerFetch() {
+		@Override
+		public void fetch(String line) throws IOException {
+		    if (line.indexOf("INTERNALDATE") >= 0)
+			saw.add("INTERNALDATE");
+		    untagged("1 FETCH (ENVELOPE " + ENVELOPE +
+			" INTERNALDATE \"" + RDATE + "\" RFC822.SIZE 0)");
+		    ok();
+		}
+	    });
+    }
+
+    public void testWithHandler(IMAPTest test, IMAPHandlerFetch handler) {
+        TestServer server = null;
+        try {
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+                folder = store.getFolder("INBOX");
+                folder.open(Folder.READ_WRITE);
+		test.test(folder, handler);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		if (folder != null)
+		    folder.close(false);
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.
+     */
+    private static class IMAPHandlerFetch extends IMAPHandler {
+	// must be static because handler is cloned for each connection
+	protected static Set<String> saw = new HashSet<>();
+
+	@Override
+        public void select(String line) throws IOException {
+	    numberOfMessages = 1;
+	    super.select(line);
+	}
+
+	public boolean saw(String item) {
+	    return saw.contains(item);
+	}
+
+	public void reset() {
+	    saw.clear();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPFolderTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPFolderTest.java
new file mode 100644
index 0000000..8f2c9c5
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPFolderTest.java
@@ -0,0 +1,527 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.MessagingException;
+import javax.mail.UIDFolder;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test IMAPFolder methods.
+ */
+public final class IMAPFolderTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    private static final String utf8Folder = "test\u03b1";
+    private static final String utf7Folder = "test&A7E-";
+
+    public static abstract class IMAPTest {
+	public void init(Properties props) { };
+	public abstract void test(Store store, IMAPHandler handler)
+				    throws Exception;
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-7
+     * encoded name for the CREATE command.
+     */
+    @Test
+    public void testUtf7FolderNameCreate() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    assertTrue(test.create(Folder.HOLDS_MESSAGES));
+		}
+	    },
+	    new IMAPHandler() {
+		@Override
+		public void create(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "CREATE"
+		    String name = st.nextToken();
+		    if (name.equals(utf7Folder))
+			ok();
+		    else
+			no("wrong name");
+		}
+
+		@Override
+		public void list(String line) throws IOException {
+		    untagged("LIST (\\HasNoChildren) \"/\" " + utf7Folder);
+		    ok();
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-8
+     * unencoded name for the CREATE command.
+     */
+    @Test
+    public void testUtf8FolderNameCreate() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    assertTrue(test.create(Folder.HOLDS_MESSAGES));
+		}
+	    },
+	    new IMAPUtf8Handler() {
+		@Override
+		public void create(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "CREATE"
+		    String name = unquote(st.nextToken());
+		    if (name.equals(utf8Folder))
+			ok();
+		    else
+			no("wrong name");
+		}
+
+		@Override
+		public void list(String line) throws IOException {
+		    untagged("LIST (\\HasNoChildren) \"/\" \"" +
+							utf8Folder + "\"");
+		    ok();
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-7
+     * encoded name for the DELETE command.
+     */
+    @Test
+    public void testUtf7FolderNameDelete() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    assertTrue(test.delete(false));
+		}
+	    },
+	    new IMAPHandler() {
+		@Override
+		public void delete(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "DELETE"
+		    String name = st.nextToken();
+		    if (name.equals(utf7Folder))
+			ok();
+		    else
+			no("wrong name");
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-8
+     * unencoded name for the DELETE command.
+     */
+    @Test
+    public void testUtf8FolderNameDelete() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    assertTrue(test.delete(false));
+		}
+	    },
+	    new IMAPUtf8Handler() {
+		@Override
+		public void delete(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "DELETE"
+		    String name = unquote(st.nextToken());
+		    if (name.equals(utf8Folder))
+			ok();
+		    else
+			no("wrong name");
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-7
+     * encoded name for the SELECT command.
+     */
+    @Test
+    public void testUtf7FolderNameSelect() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    test.open(Folder.READ_WRITE);
+		    test.close(true);
+		    // no exception means success
+		}
+	    },
+	    new IMAPHandler() {
+		@Override
+		public void select(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "SELECT"
+		    String name = st.nextToken();
+		    if (name.equals(utf7Folder))
+			ok();
+		    else
+			no("wrong name");
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-8
+     * unencoded name for the SELECT command.
+     */
+    @Test
+    public void testUtf8FolderNameSelect() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    test.open(Folder.READ_WRITE);
+		    test.close(true);
+		    // no exception means success
+		}
+	    },
+	    new IMAPUtf8Handler() {
+		@Override
+		public void select(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "SELECT"
+		    String name = unquote(st.nextToken());
+		    if (name.equals(utf8Folder))
+			ok();
+		    else
+			no("wrong name");
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-7
+     * encoded name for the EXAMINE command.
+     */
+    @Test
+    public void testUtf7FolderNameExamine() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    test.open(Folder.READ_ONLY);
+		    test.close(true);
+		    // no exception means success
+		}
+	    },
+	    new IMAPHandler() {
+		@Override
+		public void examine(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "EXAMINE"
+		    String name = st.nextToken();
+		    if (name.equals(utf7Folder))
+			ok();
+		    else
+			no("wrong name");
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-8
+     * unencoded name for the EXAMINE command.
+     */
+    @Test
+    public void testUtf8FolderNameExamine() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    test.open(Folder.READ_ONLY);
+		    test.close(true);
+		    // no exception means success
+		}
+	    },
+	    new IMAPUtf8Handler() {
+		@Override
+		public void examine(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "EXAMINE"
+		    String name = unquote(st.nextToken());
+		    if (name.equals(utf8Folder))
+			ok();
+		    else
+			no("wrong name");
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-7
+     * encoded name for the STATUS command.
+     */
+    @Test
+    public void testUtf7FolderNameStatus() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    assertEquals(123, ((UIDFolder)test).getUIDValidity());
+		}
+	    },
+	    new IMAPHandler() {
+		@Override
+		public void status(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "STATUS"
+		    String name = st.nextToken();
+		    if (name.equals(utf7Folder)) {
+			untagged("STATUS " + utf7Folder +
+				    " (UIDVALIDITY 123)");
+			ok();
+		    } else
+			no("wrong name");
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-8
+     * unencoded name for the STATUS command.
+     */
+    @Test
+    public void testUtf8FolderNameStatus() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder(utf8Folder);
+		    assertEquals(123, ((UIDFolder)test).getUIDValidity());
+		}
+	    },
+	    new IMAPUtf8Handler() {
+		@Override
+		public void status(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "STATUS"
+		    String name = unquote(st.nextToken());
+		    if (name.equals(utf8Folder)) {
+			untagged("STATUS \"" + utf8Folder +
+				    "\" (UIDVALIDITY 123)");
+			ok();
+		    } else
+			no("wrong name");
+		}
+	    });
+    }
+
+    /**
+     * Test that UIDNOTSTICKY is false in the formal case.
+     */
+    @Test
+    public void testUidNotStickyFalse() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder("test");
+		    try {
+			test.open(Folder.READ_WRITE);
+			assertFalse(((IMAPFolder)test).getUIDNotSticky());
+		    } finally {
+			test.close();
+		    }
+		}
+	    },
+	    new IMAPHandler());
+    }
+
+    /**
+     * Test that UIDNOTSTICKY is true when the untagged response is included.
+     */
+    @Test
+    public void testUidNotStickyTrue() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder("test");
+		    try {
+			test.open(Folder.READ_WRITE);
+			assertTrue(((IMAPFolder)test).getUIDNotSticky());
+		    } finally {
+			test.close();
+		    }
+		}
+	    },
+	    new IMAPHandler() {
+		@Override
+		public void select(String line) throws IOException {
+		    untagged("NO [UIDNOTSTICKY]");
+		    super.select(line);
+		}
+	    });
+    }
+
+    /**
+     * Test that EXPUNGE responses with out-of-range message numbers
+     * are ignored.
+     */
+    @Test
+    public void testExpungeOutOfRange() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, IMAPHandler handler)
+				    throws MessagingException, IOException {
+		    Folder test = store.getFolder("test");
+		    try {
+			test.open(Folder.READ_WRITE);
+			// no way to force a noop without waiting so do this
+			assertEquals(0, test.getUnreadMessageCount());
+			assertEquals(0, test.getMessageCount());
+		    } finally {
+			test.close();
+		    }
+		}
+	    },
+	    new IMAPHandler() {
+		@Override
+		public void search(String line) throws IOException {
+		    untagged("1 EXPUNGE");
+		    untagged("0 EXISTS");
+		    super.search(line);
+		}
+	    });
+    }
+
+    private void testWithHandler(IMAPTest test, IMAPHandler handler) {
+        TestServer server = null;
+        try {
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+	    test.init(properties);
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("imap");
+            try {
+                store.connect("test", "test");
+		test.test(store, handler);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    private static String unquote(String s) {
+	if (s.startsWith("\"") && s.endsWith("\"") && s.length() > 1) {
+	    s = s.substring(1, s.length() - 1);
+	    // check for any escaped characters
+	    if (s.indexOf('\\') >= 0) {
+		StringBuilder sb = new StringBuilder(s.length());	// approx
+		for (int i = 0; i < s.length(); i++) {
+		    char c = s.charAt(i);
+		    if (c == '\\' && i < s.length() - 1)
+			c = s.charAt(++i);
+		    sb.append(c);
+		}
+		s = sb.toString();
+	    }
+	}
+	return s;
+    }
+
+    /**
+     * An IMAPHandler that enables UTF-8 support.
+     */
+    private static class IMAPUtf8Handler extends IMAPHandler {
+	{{ capabilities += " ENABLE UTF8=ACCEPT"; }}
+
+	@Override
+	public void enable(String line) throws IOException {
+	    ok();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPHandler.java b/mail/src/test/java/com/sun/mail/imap/IMAPHandler.java
new file mode 100644
index 0000000..7ebe4d3
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPHandler.java
@@ -0,0 +1,586 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.util.StringTokenizer;
+import java.util.logging.Level;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.mail.util.BASE64EncoderStream;
+
+import com.sun.mail.test.ProtocolHandler;
+
+/**
+ * Handle IMAP connection.
+ *
+ * @author Bill Shannon
+ */
+public class IMAPHandler extends ProtocolHandler {
+
+    /** Current line. */
+    private String currentLine;
+
+    /** Tag for current command */
+    protected String tag;
+
+    /** IMAP capabilities supported */
+    protected String capabilities = "IMAP4REV1 IDLE ID";
+
+    /** Number of messages */
+    protected int numberOfMessages = 0;
+
+    /** Number of recent messages */
+    protected int numberOfRecentMessages = 0;
+
+    /**
+     * Send greetings.
+     *
+     * @throws IOException unable to write to socket
+     */
+    @Override
+    public void sendGreetings() throws IOException {
+        untagged("OK [CAPABILITY " + capabilities + "] IMAPHandler");
+    }
+
+    /**
+     * Send String to socket.
+     *
+     * @param str String to send
+     * @throws IOException unable to write to socket
+     */
+    public void println(final String str) throws IOException {
+        writer.print(str);
+	writer.print("\r\n");
+        writer.flush();
+    }
+
+    /**
+     * Send a tagged response.
+     *
+     * @param resp the response to send
+     * @throws IOException unable to read/write to socket
+     */
+    public void tagged(final String resp) throws IOException {
+	println(tag + " " + resp);
+    }
+
+    /**
+     * Send an untagged response.
+     *
+     * @param resp the response to send
+     * @throws IOException unable to read/write to socket
+     */
+    public void untagged(final String resp) throws IOException {
+	println("* " + resp);
+    }
+
+    /**
+     * Send a tagged OK response.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void ok() throws IOException {
+	tagged("OK");
+    }
+
+    /**
+     * Send a tagged OK response with a message.
+     *
+     * @param msg the message to send
+     * @throws IOException unable to read/write to socket
+     */
+    public void ok(final String msg) throws IOException {
+	tagged("OK " + (msg != null ? msg : ""));
+    }
+
+    /**
+     * Send a tagged NO response with a message.
+     *
+     * @param msg the message to send
+     * @throws IOException unable to read/write to socket
+     */
+    public void no(final String msg) throws IOException {
+	tagged("NO " + (msg != null ? msg : ""));
+    }
+
+    /**
+     * Send a tagged BAD response with a message.
+     *
+     * @param msg the message to send
+     * @throws IOException unable to read/write to socket
+     */
+    public void bad(final String msg) throws IOException {
+	tagged("BAD " + (msg != null ? msg : ""));
+    }
+
+    /**
+     * Send an untagged BYE response with a message, then exit.
+     *
+     * @param msg the message to send
+     * @throws IOException unable to read/write to socket
+     */
+    public void bye(final String msg) throws IOException {
+	untagged("BYE " + (msg != null ? msg : ""));
+	exit();
+    }
+
+    /**
+     * Send a "continue" command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void cont() throws IOException {
+	println("+ please continue");
+    }
+
+    /**
+     * Send a "continue" command with a message.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void cont(String msg) throws IOException {
+	println("+ " + (msg != null ? msg : ""));
+    }
+
+    /**
+     * Handle command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    @Override
+    public void handleCommand() throws IOException {
+        currentLine = super.readLine();
+
+        if (currentLine == null) {
+	    // probably just EOF because the socket was closed
+            //LOGGER.severe("Current line is null!");
+            exit();
+            return;
+        }
+
+        StringTokenizer ct = new StringTokenizer(currentLine, " ");
+	if (!ct.hasMoreTokens()) {
+            LOGGER.log(Level.SEVERE, "ERROR no command tag: {0}",
+							escape(currentLine));
+            bad("no command tag");
+	    return;
+	}
+	tag = ct.nextToken();
+	if (!ct.hasMoreTokens()) {
+            LOGGER.log(Level.SEVERE, "ERROR no command: {0}",
+							escape(currentLine));
+            bad("no command");
+	    return;
+	}
+        final String commandName = ct.nextToken().toUpperCase();
+        if (commandName == null) {
+            LOGGER.severe("Command name is empty!");
+            exit();
+            return;
+        }
+
+        if (commandName.equals("LOGIN")) {
+            login();
+        } else if (commandName.equals("AUTHENTICATE")) {
+	    String mech = ct.nextToken().toUpperCase();
+	    String ir = null;
+	    if (ct.hasMoreTokens())
+	    	ir = ct.nextToken();
+            authenticate(mech, ir);
+        } else if (commandName.equals("CAPABILITY")) {
+            capability();
+        } else if (commandName.equals("NOOP")) {
+            noop();
+        } else if (commandName.equals("SELECT")) {
+            select(currentLine);
+        } else if (commandName.equals("EXAMINE")) {
+            examine(currentLine);
+        } else if (commandName.equals("LIST")) {
+            list(currentLine);
+        } else if (commandName.equals("IDLE")) {
+            idle();
+        } else if (commandName.equals("FETCH")) {
+            fetch(currentLine);
+        } else if (commandName.equals("STORE")) {
+            store(currentLine);
+        } else if (commandName.equals("SEARCH")) {
+            search(currentLine);
+        } else if (commandName.equals("APPEND")) {
+            append(currentLine);
+        } else if (commandName.equals("CLOSE")) {
+            close();
+        } else if (commandName.equals("LOGOUT")) {
+            logout();
+        } else if (commandName.equals("UID")) {
+	    String subcommandName = ct.nextToken().toUpperCase();
+	    if (subcommandName.equals("FETCH")) {
+		uidfetch(currentLine);
+	    } else if (subcommandName.equals("STORE")) {
+		uidstore(currentLine);
+	    } else {
+		LOGGER.log(Level.SEVERE, "ERROR UID command unknown: {0}",
+								subcommandName);
+		bad("unknown UID command");
+	    }
+        } else if (commandName.equals("ID")) {
+	    id(currentLine);
+        } else if (commandName.equals("ENABLE")) {
+            enable(currentLine);
+        } else if (commandName.equals("CREATE")) {
+            create(currentLine);
+        } else if (commandName.equals("DELETE")) {
+            delete(currentLine);
+        } else if (commandName.equals("STATUS")) {
+            status(currentLine);
+        } else if (commandName.equals("NAMESPACE")) {
+            namespace();
+        } else {
+            LOGGER.log(Level.SEVERE, "ERROR command unknown: {0}",
+							escape(currentLine));
+            bad("unknown command");
+        }
+    }
+
+    /**
+     * LOGIN command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void login() throws IOException {
+        ok("[CAPABILITY " + capabilities + "]");
+    }
+
+    /**
+     * AUTHENTICATE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void authenticate(String mech, String ir) throws IOException {
+	if (mech.equals("LOGIN"))
+	    authlogin(ir);
+	else if (mech.equals("PLAIN"))
+	    authplain(ir);
+	else
+	    bad("AUTHENTICATE not supported");
+    }
+
+    /**
+     * AUTHENTICATE LOGIN command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void authlogin(String ir) throws IOException {
+	if (ir != null)
+	    bad("AUTHENTICATE LOGIN does not support initial response");
+	cont(base64encode("Username"));
+	String username = readLine();
+	cont(base64encode("Password"));
+	String password = readLine();
+        ok("[CAPABILITY " + capabilities + "]");
+    }
+
+    /**
+     * AUTHENTICATE PLAIN command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void authplain(String ir) throws IOException {
+	if (ir == null) {
+	    cont("");
+	    String resp = readLine();
+	}
+        ok("[CAPABILITY " + capabilities + "]");
+    }
+
+    /**
+     * CAPABILITY command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void capability() throws IOException {
+	untagged("CAPABILITY " + capabilities);
+        ok();
+    }
+
+    /**
+     * SELECT command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void select(String line) throws IOException {
+	untagged(numberOfMessages + " EXISTS");
+	untagged(numberOfRecentMessages + " RECENT");
+        ok();
+    }
+
+    /**
+     * EXAMINE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void examine(String line) throws IOException {
+	untagged(numberOfMessages + " EXISTS");
+	untagged(numberOfRecentMessages + " RECENT");
+        ok();
+    }
+
+    /**
+     * LIST command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void list(String line) throws IOException {
+        ok();
+    }
+
+    /**
+     * IDLE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void idle() throws IOException {
+        cont();
+	idleWait();
+	ok();
+    }
+
+    @Override
+    protected String readLine() throws IOException {
+        currentLine = super.readLine();
+        if (currentLine == null) {
+            LOGGER.severe("Current line is null!");
+            exit();
+        }
+	return currentLine;
+    }
+
+    protected void idleWait() throws IOException {
+        String line = readLine();
+
+        if (line != null && !line.equalsIgnoreCase("DONE")) {
+            LOGGER.severe("Didn't get DONE response to IDLE");
+            exit();
+            return;
+        }
+    }
+
+    /**
+     * FETCH command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void fetch(String line) throws IOException {
+        ok();	// XXX
+    }
+
+    /**
+     * STORE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void store(String line) throws IOException {
+        ok();	// XXX
+    }
+
+    /**
+     * SEARCH command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void search(String line) throws IOException {
+	untagged("SEARCH");
+        ok();	// XXX
+    }
+
+    /**
+     * UID FETCH command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void uidfetch(String line) throws IOException {
+        ok();	// XXX
+    }
+
+    /**
+     * UID STORE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void uidstore(String line) throws IOException {
+        ok();	// XXX
+    }
+
+    /**
+     * APPEND command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void append(String line) throws IOException {
+	int left = line.lastIndexOf('{');
+	int right = line.indexOf('}', left);
+	int bytes = Integer.parseInt(line.substring(left + 1, right));
+	cont("waiting for message");
+	collectMessage(bytes);
+        ok();	// XXX
+    }
+
+    /**
+     * ID command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void id(String line) throws IOException {
+	untagged("ID NIL");
+        ok();
+    }
+
+    /**
+     * ENABLE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void enable(String line) throws IOException {
+        no("can't enable");
+    }
+
+    /**
+     * CREATE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void create(String line) throws IOException {
+        no("can't create");
+    }
+
+    /**
+     * DELETE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void delete(String line) throws IOException {
+        no("can't delete");
+    }
+
+    /**
+     * STATUS command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void status(String line) throws IOException {
+        no("can't get status");
+    }
+
+    /**
+     * NAMESPACE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void namespace() throws IOException {
+        no("no namespaces");
+    }
+
+    /**
+     * Collect "bytes" worth of data for the message being appended.
+     */
+    protected void collectMessage(int bytes) throws IOException {
+	readLiteral(bytes);	// read the data and throw it away
+	super.readLine();	// data followed by a newline
+    }
+
+    /**
+     * Read a literal of "bytes" bytes and return it as a UTF-8 string.
+     */
+    protected String readLiteral(int bytes) throws IOException {
+	println("+");
+	byte[] data = new byte[bytes];
+	int len = data.length;
+	int off = 0;
+	int n;
+	while (len > 0 && (n = in.read(data, off, len)) > 0) {
+	    off += n;
+	    len -= n;
+	}
+	return new String(data, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * CLOSE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void close() throws IOException {
+        ok();
+    }
+
+    /**
+     * NOOP command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void noop() throws IOException {
+        ok();
+    }
+
+    /**
+     * LOGOUT command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void logout() throws IOException {
+        ok();
+        exit();
+    }
+
+    /**
+     * Base64 encode the string.
+     */
+    protected String base64encode(String s) throws IOException {
+	return new String(BASE64EncoderStream.encode(s.getBytes("US-ASCII")),
+			"US-ASCII");
+    }
+
+    /**
+     * Escape any non-printable characters in "s",
+     * limiting total length to about 100 characters.
+     */
+    private String escape(String s) {
+	StringBuilder sb = new StringBuilder();
+	for (int i = 0; i < s.length(); i++) {
+	    if (sb.length() >= 100) {
+		sb.append("...");
+		break;
+	    }
+	    char c = s.charAt(i);
+	    if (c < ' ' || c == '\177') {
+		if (c == '\r')
+		    sb.append("\\r");
+		else if (c == '\n')
+		    sb.append("\\n");
+		else if (c == '\t')
+		    sb.append("\\t");
+		else
+		    sb.append('\\').append(String.format("%03o", (int)c));
+	    } else if (c >= '\200') {
+		    sb.append("\\u").append(String.format("%04x", (int)c));
+	    } else
+		sb.append(c);
+	}
+	return sb.toString();
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPIDTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPIDTest.java
new file mode 100644
index 0000000..e6ce7c2
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPIDTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Map;
+
+import javax.mail.Session;
+import javax.mail.Store;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the IMAP ID command.
+ */
+public final class IMAPIDTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    @Test
+    public void testIDNIL() {
+        TestServer server = null;
+        try {
+            final IMAPHandler handler = new IMAPHandlerID();
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final IMAPStore store = (IMAPStore)session.getStore("imap");
+            try {
+                store.connect("test", "test");
+		Map<String,String> id = store.id(null);
+		assertEquals("true", id.get("test"));
+
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.
+     */
+    private static final class IMAPHandlerID extends IMAPHandler {
+
+	@Override
+        public void id(String line) throws IOException {
+	    StringTokenizer st = new StringTokenizer(line);
+	    String tag = st.nextToken();
+	    String cmd = st.nextToken();
+	    String arg = st.nextToken();
+	    untagged("ID (\"test\" \"" + arg.equals("NIL") + "\")");
+	    ok();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPIdleManagerTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPIdleManagerTest.java
new file mode 100644
index 0000000..dbff132
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPIdleManagerTest.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Folder;
+import javax.mail.FetchProfile;
+import javax.mail.MessagingException;
+import javax.mail.event.ConnectionAdapter;
+import javax.mail.event.ConnectionEvent;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test IdleManager.
+ */
+public final class IMAPIdleManagerTest {
+
+    private static final int TIMEOUT = 1000;	// 1 second
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.millis(10 * TIMEOUT);
+
+    /**
+     * Test that IdleManager handles multiple responses in a single packet.
+     */
+    @Test
+    public void testDone() {
+	testSuccess(new IMAPHandlerIdleDone());
+    }
+
+    @Test
+    public void testExists() {
+	testSuccess(new IMAPHandlerIdleExists());
+    }
+
+    private void testSuccess(IMAPHandlerIdle handler) {
+        TestServer server = null;
+	IdleManager idleManager = null;
+        try {
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            properties.setProperty("mail.imap.usesocketchannels", "true");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+	    ExecutorService executor = Executors.newCachedThreadPool();
+	    idleManager = new IdleManager(session, executor);
+
+            final IMAPStore store = (IMAPStore)session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+		folder = store.getFolder("INBOX");
+		folder.open(Folder.READ_WRITE);
+		idleManager.watch(folder);
+		handler.waitForIdle();
+
+		// now do something that is sure to touch the server
+		FetchProfile fp = new FetchProfile();
+		fp.add(FetchProfile.Item.ENVELOPE);
+		folder.fetch(folder.getMessages(), fp);
+
+		// check that the new message was seen
+		int count = folder.getMessageCount();
+		folder.close(true);
+
+		assertEquals(3, count);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		try {
+		    folder.close(false);
+		} catch (Exception ex2) { }
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+	    if (idleManager != null)
+		idleManager.stop();
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Test that IdleManager handles timeouts.
+     */
+    @Test
+    public void testBeforeIdleTimeout() {
+	testFailure(new IMAPHandlerBeforeIdleTimeout(), true);
+    }
+
+    @Test
+    public void testIdleTimeout() {
+	testFailure(new IMAPHandlerIdleTimeout(), true);
+    }
+
+    @Test
+    public void testDoneTimeout() {
+	testFailure(new IMAPHandlerDoneTimeout(), true);
+    }
+
+    /**
+     * Test that IdleManager handles connection failures.
+     */
+    @Test
+    public void testBeforeIdleDrop() {
+	testFailure(new IMAPHandlerBeforeIdleDrop(), false);
+    }
+
+    @Test
+    public void testIdleDrop() {
+	testFailure(new IMAPHandlerIdleDrop(), false);
+    }
+
+    @Test
+    public void testDoneDrop() {
+	testFailure(new IMAPHandlerDoneDrop(), false);
+    }
+
+    private void testFailure(IMAPHandlerIdle handler, boolean setTimeout) {
+        TestServer server = null;
+	IdleManager idleManager = null;
+        try {
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+	    if (setTimeout)
+		properties.setProperty("mail.imap.timeout", "" + TIMEOUT);
+            properties.setProperty("mail.imap.usesocketchannels", "true");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+	    ExecutorService executor = Executors.newCachedThreadPool();
+	    idleManager = new IdleManager(session, executor);
+
+            final IMAPStore store = (IMAPStore)session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+		folder = store.getFolder("INBOX");
+		folder.open(Folder.READ_WRITE);
+		idleManager.watch(folder);
+		handler.waitForIdle();
+
+		// now do something that is sure to touch the server
+		FetchProfile fp = new FetchProfile();
+		fp.add(FetchProfile.Item.ENVELOPE);
+		folder.fetch(folder.getMessages(), fp);
+
+		fail("No exception");
+	    } catch (MessagingException mex) {
+		// success!
+	    } catch (Exception ex) {
+		System.out.println("Failed with exception: " + ex);
+		ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		try {
+		    folder.close(false);
+		} catch (Exception ex2) { }
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+	    if (idleManager != null)
+		idleManager.stop();
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    @Test
+    public void testNotOpened() {
+        TestServer server = null;
+	IdleManager idleManager = null;
+        try {
+            server = new TestServer(new IMAPHandler());
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            properties.setProperty("mail.imap.usesocketchannels", "true");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+	    ExecutorService executor = Executors.newCachedThreadPool();
+	    idleManager = new IdleManager(session, executor);
+
+            final IMAPStore store = (IMAPStore)session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+		folder = store.getFolder("INBOX");
+		idleManager.watch(folder);
+
+		fail("No exception");
+	    } catch (MessagingException mex) {
+		// make sure we get the expected exception
+		assertTrue(mex.getMessage().contains("open"));
+		// success!
+	    } catch (Exception ex) {
+		System.out.println("Failed with exception: " + ex);
+		ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		try {
+		    folder.close(false);
+		} catch (Exception ex2) { }
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+	    if (idleManager != null)
+		idleManager.stop();
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    @Test
+    public void testNoSocketChannel() {
+        TestServer server = null;
+	IdleManager idleManager = null;
+        try {
+            server = new TestServer(new IMAPHandler());
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+	    ExecutorService executor = Executors.newCachedThreadPool();
+	    idleManager = new IdleManager(session, executor);
+
+            final IMAPStore store = (IMAPStore)session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+		folder = store.getFolder("INBOX");
+		folder.open(Folder.READ_WRITE);
+		idleManager.watch(folder);
+
+		fail("No exception");
+	    } catch (MessagingException mex) {
+		// make sure we get the expected exception
+		assertTrue(!mex.getMessage().contains("open"));
+		// success!
+	    } catch (Exception ex) {
+		System.out.println("Failed with exception: " + ex);
+		ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		try {
+		    folder.close(false);
+		} catch (Exception ex2) { }
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+	    if (idleManager != null)
+		idleManager.stop();
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Base class for custom handler.
+     */
+    private static abstract class IMAPHandlerIdle extends IMAPHandler {
+	@Override
+        public void select(String line) throws IOException {
+	    numberOfMessages = 1;
+	    super.select(line);
+	}
+
+	public abstract void waitForIdle() throws InterruptedException;
+    }
+
+    /**
+     * Custom handler.  Respond to DONE with a single packet containing
+     * EXISTS and OK.
+     */
+    private static final class IMAPHandlerIdleDone extends IMAPHandlerIdle {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void idle() throws IOException {
+	    cont();
+	    latch.countDown();
+	    idleWait();
+	    println("* 3 EXISTS\r\n" + tag + " OK");
+        }
+
+	@Override
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+
+    /**
+     * Custom handler.  Send two EXISTS responses in a single packet.
+     */
+    private static final class IMAPHandlerIdleExists extends IMAPHandlerIdle {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void idle() throws IOException {
+	    cont();
+	    latch.countDown();
+	    idleWait();
+	    println("* 2 EXISTS\r\n* 3 EXISTS");
+	    ok();
+        }
+
+	@Override
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+
+    /**
+     * Custom handler.  Delay long enough before IDLE starts to force a timeout.
+     */
+    private static final class IMAPHandlerBeforeIdleTimeout
+						    extends IMAPHandlerIdle {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void idle() throws IOException {
+	    try {
+		Thread.sleep(2 * TIMEOUT);
+	    } catch (InterruptedException ex) { }
+	    cont();
+	    latch.countDown();
+	    idleWait();
+	    ok();
+        }
+
+	@Override
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+
+    /**
+     * Custom handler.  Delay long enough after IDLE starts to force a timeout.
+     */
+    private static final class IMAPHandlerIdleTimeout extends IMAPHandlerIdle {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void idle() throws IOException {
+	    cont();
+	    latch.countDown();
+	    try {
+		Thread.sleep(2 * TIMEOUT);
+	    } catch (InterruptedException ex) { }
+	    idleWait();
+	    ok();
+        }
+
+	@Override
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+
+    /**
+     * Custom handler.  Delay long enough after DONE received to force a
+     * timeout.
+     */
+    private static final class IMAPHandlerDoneTimeout extends IMAPHandlerIdle {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void idle() throws IOException {
+	    cont();
+	    latch.countDown();
+	    idleWait();
+	    try {
+		Thread.sleep(2 * TIMEOUT);
+	    } catch (InterruptedException ex) { }
+	    ok();
+        }
+
+	@Override
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+
+    /**
+     * Custom handler.  Drop the connection before IDLE started.
+     */
+    private static final class IMAPHandlerBeforeIdleDrop extends
+							    IMAPHandlerIdle {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void idle() throws IOException {
+	    latch.countDown();
+	    exit();
+        }
+
+	@Override
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+
+    /**
+     * Custom handler.  Drop the connection after IDLE started.
+     */
+    private static final class IMAPHandlerIdleDrop extends IMAPHandlerIdle {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void idle() throws IOException {
+	    cont();
+	    latch.countDown();
+	    exit();
+        }
+
+	@Override
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+
+    /**
+     * Custom handler.  Drop the connection after DONE received.
+     */
+    private static final class IMAPHandlerDoneDrop extends IMAPHandlerIdle {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void idle() throws IOException {
+	    cont();
+	    latch.countDown();
+	    idleWait();
+	    exit();
+        }
+
+	@Override
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPIdleStateTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPIdleStateTest.java
new file mode 100644
index 0000000..4ef95aa
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPIdleStateTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+
+import javax.mail.Session;
+import javax.mail.Store;
+
+import com.sun.mail.imap.IMAPStore;
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that IMAP idle state is handled properly.
+ */
+public final class IMAPIdleStateTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    @Test
+    public void test() {
+        TestServer server = null;
+        try {
+            final IMAPHandlerIdleBye handler = new IMAPHandlerIdleBye();
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final IMAPStore store = (IMAPStore)session.getStore("imap");
+            try {
+                store.connect("test", "test");
+
+		// create a thread to run the IDLE command on the Store
+		Thread t = new Thread() {
+		    @Override
+		    public void run() {
+			try {
+			    store.idle();
+			} catch (Exception ex) {
+			}
+		    }
+		};
+		t.start();
+		handler.waitForIdle();
+
+		// Now break it out of idle.
+		// Need to use a method that doesn't check that the Store
+		// is connected first.
+		store.hasCapability("XXX");
+		// no NullPointerException means the bug is fixed!
+
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.  Simulates the server sending a BYE response
+     * to abort an IDLE.
+     */
+    private static final class IMAPHandlerIdleBye extends IMAPHandler {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void idle() throws IOException {
+	    cont();
+	    latch.countDown();
+	    // don't wait for DONE, just close the connection now
+	    bye("closing");
+        }
+
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPIdleUntaggedResponseTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPIdleUntaggedResponseTest.java
new file mode 100644
index 0000000..36ebbfd
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPIdleUntaggedResponseTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.FetchProfile;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test untagged responses before IDLE continuation.
+ */
+public final class IMAPIdleUntaggedResponseTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    @Test
+    public void test() {
+        TestServer server = null;
+        try {
+            final IMAPHandlerIdleExists handler = new IMAPHandlerIdleExists();
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("imap");
+	    Folder folder0 = null;
+            try {
+                store.connect("test", "test");
+                final Folder folder = store.getFolder("INBOX");
+		folder0 = folder;
+                folder.open(Folder.READ_ONLY);
+
+		// create a thread to make sure we're kicked out of idle
+		Thread t = new Thread() {
+		    @Override
+		    public void run() {
+			try {
+			    handler.waitForIdle();
+			    // now do something that is sure to touch the server
+			    FetchProfile fp = new FetchProfile();
+			    fp.add(FetchProfile.Item.ENVELOPE);
+			    folder.fetch(folder.getMessages(), fp);
+			} catch (Exception ex) {
+			}
+		    }
+		};
+		t.start();
+
+		((com.sun.mail.imap.IMAPFolder)folder).idle();
+
+		assertEquals("message count", 1, folder.getMessageCount());
+
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		if (folder0 != null)
+		    folder0.close(false);
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.  Returns untagged responses before continuation,
+     * followed by a flag change for one of the new messages, to make
+     * sure the notification of the new message is seen.
+     */
+    private static final class IMAPHandlerIdleExists extends IMAPHandler {
+	// must be static because handler is cloned for each connection
+	private static CountDownLatch latch = new CountDownLatch(1);
+
+	@Override
+        public void examine(String line) throws IOException {
+	    numberOfMessages = 1;
+	    super.examine(line);
+	}
+
+	@Override
+        public void idle() throws IOException {
+            untagged("1 EXISTS");
+            untagged("1 RECENT");
+	    cont();
+            untagged("1 FETCH (FLAGS (\\Recent \\Seen))");
+	    latch.countDown();
+	    idleWait();
+	    ok();
+        }
+
+	public void waitForIdle() throws InterruptedException {
+	    latch.await();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPLoginCapabilitiesTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPLoginCapabilitiesTest.java
new file mode 100644
index 0000000..17fc5e9
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPLoginCapabilitiesTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Store;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test that capabilities are updated after login.
+ */
+public final class IMAPLoginCapabilitiesTest {
+
+    private static final String NEWCAP = "NEWCAP";
+
+    private static final int TIMEOUT = 1000;	// 1 second
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.millis(5 * TIMEOUT);
+
+    /**
+     * Test untagged CAPABILITY response after LOGIN.
+     * This is illegal, but mail.ru and AOL do it.
+     */
+    @Test
+    public void testUntaggedCapabilityAfterLogin() {
+	test(new IMAPHandler() {
+			@Override
+			public void login() throws IOException {
+			    untagged("CAPABILITY " + capabilities +
+								" " + NEWCAP);
+			    ok("LOGIN completed");
+			}
+		    });
+    }
+
+    /**
+     * Test multiple untagged CAPABILITY responses after LOGIN.
+     * This should NEVER happen, but we handle it just in case.
+     */
+    @Test
+    public void testMultipleUntaggedCapabilityAfterLogin() {
+	test(new IMAPHandler() {
+			@Override
+			public void login() throws IOException {
+			    untagged("CAPABILITY " + capabilities);
+			    untagged("CAPABILITY " + NEWCAP);
+			    ok("LOGIN completed");
+			}
+		    });
+    }
+
+    /**
+     * Test untagged CAPABILITY response after AUTHENTICATE.
+     */
+    @Test
+    public void testUntaggedCapabilityAfterAuthenticate() {
+	test(new IMAPHandler() {
+			{{ capabilities += " AUTH=PLAIN"; }}
+			@Override
+			public void authplain(String ir) throws IOException {
+			    untagged("CAPABILITY " + capabilities +
+								" " + NEWCAP);
+			    ok("AUTHENTICATE completed");
+			}
+		    });
+    }
+
+    private void test(IMAPHandler handler) {
+	TestServer server = null;
+	try {
+	    server = new TestServer(handler);
+	    server.start();
+
+	    final Properties properties = new Properties();
+	    properties.setProperty("mail.imap.host", "localhost");
+	    properties.setProperty("mail.imap.port", "" + server.getPort());
+	    //properties.setProperty("mail.debug.auth", "true");
+	    final Session session = Session.getInstance(properties);
+	    //session.setDebug(true);
+
+	    final IMAPStore store = (IMAPStore)session.getStore("imap");
+	    try {
+		store.connect("test", "test");
+		assertTrue(store.hasCapability(NEWCAP));
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		store.close();
+	    }
+	} catch (final Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPLoginFailureTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPLoginFailureTest.java
new file mode 100644
index 0000000..4a5b282
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPLoginFailureTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.MessagingException;
+
+import com.sun.mail.test.SavedSocketFactory;
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that login failures are handled correctly.
+ */
+public final class IMAPLoginFailureTest {
+
+    /**
+     * Test that login failures when no login methods are supported
+     * cause the socket to be closed.
+     */
+    @Test
+    public void testSocketClosed() {
+	TestServer server = null;
+	try {
+	    final IMAPHandler handler = new IMAPHandler() {
+		@Override
+		public void sendGreetings() throws IOException {
+		    capabilities = "IMAP4REV1 LOGINDISABLED";
+		    super.sendGreetings();
+		}
+	    };
+	    server = new TestServer(handler);
+	    server.start();
+
+	    SavedSocketFactory ssf = new SavedSocketFactory();
+	    Properties properties = new Properties();
+	    properties.setProperty("mail.imap.host", "localhost");
+	    properties.setProperty("mail.imap.port", "" + server.getPort());
+	    properties.put("mail.imap.socketFactory", ssf);
+	    final Session session = Session.getInstance(properties);
+	    //session.setDebug(true);
+
+	    final Store store = session.getStore("imap");
+	    try {
+		store.connect("test", "test");
+		fail("login did not fail");
+	    } catch (MessagingException mex) {
+		// this is what we expect, now check that the socket is closed
+		assertTrue(ssf.getSocket().isClosed());
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		store.close();
+	    }
+
+	} catch (final Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPLoginHandler.java b/mail/src/test/java/com/sun/mail/imap/IMAPLoginHandler.java
new file mode 100644
index 0000000..132a72f
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPLoginHandler.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.mail.util.BASE64DecoderStream;
+
+/**
+ * Handle IMAP connection with LOGIN authentication.
+ *
+ * @author Bill Shannon
+ */
+public class IMAPLoginHandler extends IMAPHandler {
+
+    protected String username = "test";
+    protected String password = "test";
+
+    public IMAPLoginHandler() {
+	capabilities += " LOGINDISABLED AUTH=LOGIN";
+    }
+
+    /**
+     * AUTHENTICATE LOGIN command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    public void authlogin(String ir) throws IOException {
+	if (ir != null)
+	    bad("AUTHENTICATE LOGIN does not support initial response");
+	cont(base64encode("Username"));
+	String resp = readLine();
+	String u = new String(BASE64DecoderStream.decode(
+				    resp.getBytes(StandardCharsets.US_ASCII)),
+				StandardCharsets.UTF_8);
+	cont(base64encode("Password"));
+	resp = readLine();
+	String p = new String(BASE64DecoderStream.decode(
+				    resp.getBytes(StandardCharsets.US_ASCII)),
+				StandardCharsets.UTF_8);
+	//System.out.printf("USER: %s, PASSWORD: %s%n", u, p);
+	if (!u.equals(username) || !p.equals(password)) {
+	    no("authentication failed");
+	    return;
+	}
+        ok("[CAPABILITY " + capabilities + "]");
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPLoginReferralTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPLoginReferralTest.java
new file mode 100644
index 0000000..4f9cc63
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPLoginReferralTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Store;
+
+import com.sun.mail.imap.ReferralException;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test IMAP login referrals (RFC 2221).
+ */
+public final class IMAPLoginReferralTest {
+
+    private static final String REFERRAL_URL = "imap://test@server/";
+    private static final String REFERRAL_MSG = "try server";
+    private static final String REFERRAL =
+			    "[REFERRAL " + REFERRAL_URL + "] " + REFERRAL_MSG;
+
+    private static final int TIMEOUT = 1000;	// 1 second
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.millis(5 * TIMEOUT);
+
+    /**
+     * Test referral in BYE when connecting.
+     */
+    @Test
+    public void testConnectReferral() {
+	test(new IMAPHandler() {
+			@Override
+			public void sendGreetings() throws IOException {
+			    bye(REFERRAL);
+			}
+		    });
+    }
+
+    /**
+     * Test referral in NO response to LOGIN.
+     */
+    @Test
+    public void testLoginReferral() {
+	test(new IMAPHandler() {
+			{{ capabilities += " LOGIN-REFERRALS"; }}
+			@Override
+			public void login() throws IOException {
+			    no(REFERRAL);
+			}
+		    });
+    }
+
+    /**
+     * Test referral in OK response to LOGIN.
+     */
+    @Test
+    public void testLoginOkReferral() {
+	test(new IMAPHandler() {
+			{{ capabilities += " LOGIN-REFERRALS"; }}
+			@Override
+			public void login() throws IOException {
+			    ok(REFERRAL);
+			}
+		    });
+    }
+
+    /**
+     * Test referral in NO response to AUTHENTICATE PLAIN.
+     */
+    @Test
+    public void testPlainReferral() {
+	test(new IMAPHandler() {
+			{{ capabilities += " LOGIN-REFERRALS AUTH=PLAIN"; }}
+			@Override
+			public void authplain(String ir) throws IOException {
+			    no(REFERRAL);
+			}
+		    });
+    }
+
+    /**
+     * Test referral in OK response to AUTHENTICATE PLAIN.
+     */
+    @Test
+    public void testPlainOkReferral() {
+	test(new IMAPHandler() {
+			{{ capabilities += " LOGIN-REFERRALS AUTH=PLAIN"; }}
+			@Override
+			public void authplain(String ir) throws IOException {
+			    if (ir == null) {
+				cont("");
+				String resp = readLine();
+			    }
+			    ok(REFERRAL);
+			}
+		    });
+    }
+
+    private void test(IMAPHandler handler) {
+	TestServer server = null;
+	try {
+	    server = new TestServer(handler);
+	    server.start();
+
+	    final Properties properties = new Properties();
+	    properties.setProperty("mail.imap.host", "localhost");
+	    properties.setProperty("mail.imap.port", "" + server.getPort());
+	    properties.setProperty("mail.imap.referralexception", "true");
+	    final Session session = Session.getInstance(properties);
+	    //session.setDebug(true);
+
+	    final IMAPStore store = (IMAPStore)session.getStore("imap");
+	    try {
+		store.connect("test", "test");
+		fail("connect succeeded");
+	    } catch (ReferralException ex) {
+		// success!
+		assertEquals(ex.getUrl(), REFERRAL_URL);
+		assertEquals(ex.getText(), REFERRAL_MSG);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		store.close();
+	    }
+	} catch (final Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPMessageNumberOutOfRangeTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPMessageNumberOutOfRangeTest.java
new file mode 100644
index 0000000..688bef2
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPMessageNumberOutOfRangeTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.Flags;
+import javax.mail.search.FlagTerm;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test FETCH and SEARCH responses with a message number that's out of range.
+ */
+public final class IMAPMessageNumberOutOfRangeTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    @Test
+    public void test() {
+        TestServer server = null;
+        try {
+            final IMAPHandlerBad handler = new IMAPHandlerBad();
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+                folder = store.getFolder("INBOX");
+                folder.open(Folder.READ_ONLY);
+		Message msg = folder.getMessage(1);
+		Flags f = msg.getFlags();
+		Message[] msgs = folder.search(
+			    new FlagTerm(new Flags(Flags.Flag.RECENT), true));
+		assertEquals(1, msgs.length);
+		assertEquals(msg, msgs[0]);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		if (folder != null)
+		    folder.close(false);
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.  Returns responses for messages that don't
+     * exist in the folder.
+     */
+    private static final class IMAPHandlerBad extends IMAPHandler {
+
+	@Override
+        public void examine(String line) throws IOException {
+	    numberOfMessages = 1;
+	    numberOfRecentMessages = 1;
+	    super.examine(line);
+	}
+
+	@Override
+        public void search(String line) throws IOException {
+            untagged("SEARCH 1 2");
+	    ok();
+        }
+
+	@Override
+        public void fetch(String line) throws IOException {
+            untagged("1 FETCH (FLAGS (\\Recent))");
+            untagged("2 FETCH (FLAGS (\\Deleted))");
+	    ok();
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPMessageTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPMessageTest.java
new file mode 100644
index 0000000..3f2e670
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPMessageTest.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.Set;
+import java.util.HashSet;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.Multipart;
+import javax.mail.BodyPart;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeUtility;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test IMAPMessage methods.
+ */
+public final class IMAPMessageTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    private static final String RDATE = "23-Jun-2004 06:26:26 -0700";
+    private static final String ENV_DATE =
+	"\"Wed, 23 Jun 2004 18:56:42 +0530\"";
+    private static final String ENV_SUBJECT = "\"test\"";
+    private static final String ENV_UTF8_ENCODED_SUBJECT =
+      "=?UTF-8?B?VVRGOCB0ZXN0OiDgsqzgsr4g4LKH4LKy4LON4LKy4LK/IOCyuOCygg==?= " +
+      "=?UTF-8?B?4LKt4LK14LK/4LK44LOBIOCyh+CyguCypuCzhuCyqA==?= " +
+      "=?UTF-8?B?4LON4LKoIOCyueCzg+CypuCyr+CypuCysuCyvyA=?=";
+    private static final String ENV_ADDRS =
+	"((\"JavaMail\" NIL \"testuser\" \"example.com\")) " +
+	"((\"JavaMail\" NIL \"testuser\" \"example.com\")) " +
+	"((\"JavaMail\" NIL \"testuser\" \"example.com\")) " +
+	"((NIL NIL \"testuser\" \"example.com\")) NIL NIL NIL " +
+	"\"<40D98512.9040803@example.com>\"";
+    private static final String ENVELOPE =
+	"(" + ENV_DATE + " " + ENV_SUBJECT + " " + ENV_ADDRS + ")";
+
+    public static abstract class IMAPTest {
+	public void init(Properties props) { };
+	public abstract void test(Folder folder, IMAPHandlerMessage handler)
+				    throws Exception;
+    }
+
+    /**
+     * Test that a small message size is returned correctly.
+     */
+    @Test
+    public void testSizeSmall() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerMessage handler)
+				    throws MessagingException {
+		    Message m = folder.getMessage(1);
+		    assertEquals(123, m.getSize());
+		}
+	    },
+	    new IMAPHandlerMessage() {
+		{{ size = 123; }}
+	    });
+    }
+
+    /**
+     * Test that a large message size is returned as Integer.MAX_VALUE
+     * from MimeMessage.getSize and returned as the actual value from
+     * IMAPMessage.getSizeLong.
+     */
+    @Test
+    public void testSizeLarge() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerMessage handler)
+				    throws MessagingException {
+		    Message m = folder.getMessage(1);
+		    assertEquals(Integer.MAX_VALUE, m.getSize());
+		    assertEquals((long)Integer.MAX_VALUE + 1,
+				    ((IMAPMessage)m).getSizeLong());
+		}
+	    },
+	    new IMAPHandlerMessage() {
+		{{ size = (long)Integer.MAX_VALUE + 1; }}
+	    });
+    }
+
+    /**
+     * Test that returning NIL instead of an empty string for the content
+     * of the message works correctly.
+     */
+    @Test
+    public void testEmptyBody() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void init(Properties props) {
+		    props.setProperty("mail.imap.partialfetch","false");
+		}
+
+		@Override
+		public void test(Folder folder, IMAPHandlerMessage handler)
+				    throws MessagingException, IOException {
+		    Message m = folder.getMessage(1);
+		    String t = (String)m.getContent();
+		    assertEquals("", t);
+		}
+	    },
+	    new IMAPHandlerMessage() {
+		@Override
+		public void fetch(String line) throws IOException {
+		    if (line.indexOf("BODYSTRUCTURE") >= 0)
+			untagged("1 FETCH (BODYSTRUCTURE " +
+			    "(\"text\" \"plain\" (\"charset\" \"us-ascii\") " +
+				"NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL)" +
+			    ")");
+		    else if (line.indexOf("BODY[TEXT]") >= 0)
+			untagged("1 FETCH (BODY[TEXT] NIL " +
+				    "FLAGS (\\Seen \\Recent))");
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testAttachementFileName() {
+        testWithHandler(
+                new IMAPTest() {
+                    @Override
+                    public void test(Folder folder, IMAPHandlerMessage handler) throws MessagingException, IOException {
+                        Message m = folder.getMessage(1);
+                        Multipart mp = (Multipart)m.getContent();
+                        BodyPart bp = mp.getBodyPart(1);
+                        assertEquals("filename.csv", MimeUtility.decodeText(bp.getFileName()));
+                    }
+                },
+                new IMAPHandlerMessage() {
+                    @Override
+                    public void fetch(String line) throws IOException {
+                        untagged("1 FETCH (BODYSTRUCTURE (" +
+                                "(\"text\" \"html\" (\"charset\" \"utf-8\") NIL NIL \"base64\" 402 6 NIL NIL NIL NIL)" +
+                                "(\"application\" \"octet-stream\" (\"name\" \"=?utf-8?B?ZmlsZW5hbWU=?= =?utf-8?B?LmNzdg==?=\") NIL NIL \"base64\" 658 NIL " +
+                                "(\"attachment\" (\"filename\" \"\")) NIL NIL) \"mixed\" " +
+                                "(\"boundary\" \"--boundary_539_27806e16-2599-4612-b98a-69335bedd206\") NIL NIL NIL))"
+                        );
+                        ok();
+                    }
+                }
+        );
+    }
+
+    /**
+     * Test that returning NIL instead of an empty string for the content
+     * of an empty body part works correctly.
+     */
+    @Test
+    public void testEmptyBodyAttachment() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void init(Properties props) {
+		    props.setProperty("mail.imap.partialfetch","false");
+		}
+
+		@Override
+		public void test(Folder folder, IMAPHandlerMessage handler)
+				    throws MessagingException, IOException {
+		    Message m = folder.getMessage(1);
+		    Multipart mp = (Multipart)m.getContent();
+		    BodyPart bp = mp.getBodyPart(1);
+		    String t = (String)bp.getContent();
+		    assertEquals("", t);
+		}
+	    },
+	    new IMAPHandlerMessage() {
+		@Override
+		public void fetch(String line) throws IOException {
+		    if (line.indexOf("BODYSTRUCTURE") >= 0)
+			untagged("1 FETCH (BODYSTRUCTURE (" +
+			    "(\"text\" \"plain\" (\"charset\" \"us-ascii\") " +
+				"NIL NIL \"7bit\" 4 0 NIL NIL NIL NIL)" +
+			    "(\"text\" \"plain\" (\"charset\" \"us-ascii\") " +
+				"NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL)" +
+			    " \"mixed\" (\"boundary\" \"----=_x\") NIL NIL))");
+		    else if (line.indexOf("BODY[2]") >= 0)
+			untagged("1 FETCH (BODY[2] NIL " +
+				    "FLAGS (\\Seen \\Recent))");
+		    ok();
+		}
+	    });
+    }
+
+    /**
+     * Test that returning NIL instead of an empty string for the content
+     * of an empty body part works correctly.
+     * This is a bug in office365.com.  Note the space in "base64 ".
+     */
+    @Test
+    public void testBadEncoding() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void init(Properties props) {
+		    props.setProperty("mail.imap.partialfetch","false");
+		}
+
+		@Override
+		public void test(Folder folder, IMAPHandlerMessage handler)
+				    throws MessagingException, IOException {
+		    Message m = folder.getMessage(1);
+		    Multipart mp = (Multipart)m.getContent();
+		    BodyPart bp = mp.getBodyPart(1);
+		    StringBuilder sb = new StringBuilder();
+		    try (InputStream is = bp.getInputStream()) {
+			int c;
+			while ((c = is.read()) != -1)
+			    sb.append((char)c);
+		    }
+		    assertEquals("test", sb.toString());
+		}
+	    },
+	    new IMAPHandlerMessage() {
+		@Override
+		public void fetch(String line) throws IOException {
+		    if (line.indexOf("BODYSTRUCTURE") >= 0)
+			untagged("1 FETCH (BODYSTRUCTURE (" +
+			    "(\"text\" \"plain\" (\"charset\" \"us-ascii\") " +
+				"NIL NIL \"7bit\" 0 0 NIL NIL NIL NIL)" +
+			    "(\"application\" \"octet-stream\" " +
+				"(\"name\" \"test.txt\") NIL NIL \"base64 \" " +
+				"8 NIL NIL NIL NIL) " +
+			    "\"mixed\" (\"boundary\" \"=_x\") NIL NIL))");
+		    else if (line.indexOf("BODY[2]") >= 0)
+			untagged("1 FETCH (BODY[2] \"dGVzdA==\" " +
+				    "FLAGS (\\Seen \\Recent))");
+		    ok();
+		}
+	    });
+    }
+
+
+    /**
+     * Test that a UTF-8 encoded Subject is decoded properly.
+     */
+    @Test
+    public void testUtf8SubjectEncoded() {
+	String s = null;
+	try {
+	    s = MimeUtility.decodeText(ENV_UTF8_ENCODED_SUBJECT);
+	} catch (UnsupportedEncodingException ex) {
+	}
+	final String subject = s;
+
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerMessage handler)
+				    throws MessagingException {
+		    Message m = folder.getMessage(1);
+		    assertEquals(subject, m.getSubject());
+		}
+	    },
+	    new IMAPHandlerMessage() {
+		{{
+		    envelope = "(" + ENV_DATE + " \"" +
+				ENV_UTF8_ENCODED_SUBJECT + "\" " +
+				ENV_ADDRS + ")";
+		}}
+	    });
+    }
+
+    /**
+     * Test that a UTF-8 Subject is decoded properly.
+     */
+    @Test
+    public void testUtf8Subject() {
+	String s = null;
+	try {
+	    s = MimeUtility.decodeText(ENV_UTF8_ENCODED_SUBJECT);
+	} catch (UnsupportedEncodingException ex) {
+	}
+	final String subject = s;
+
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerMessage handler)
+				    throws MessagingException {
+		    Message m = folder.getMessage(1);
+		    assertEquals(subject, m.getSubject());
+		}
+	    },
+	    new IMAPHandlerMessage() {
+		{{
+		    envelope = "(" + ENV_DATE + " \"" + subject + "\" " +
+				    ENV_ADDRS + ")";
+		    capabilities += " ENABLE UTF8=ACCEPT";
+		}}
+
+		@Override
+		public void enable(String line) throws IOException {
+		    ok();
+		}
+	    });
+    }
+
+    private void testWithHandler(IMAPTest test, IMAPHandlerMessage handler) {
+        TestServer server = null;
+        try {
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+	    test.init(properties);
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+                folder = store.getFolder("INBOX");
+                folder.open(Folder.READ_ONLY);
+		test.test(folder, handler);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		if (folder != null)
+		    folder.close(false);
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.
+     */
+    private static class IMAPHandlerMessage extends IMAPHandler {
+
+	String rdate = RDATE;
+	String envelope = ENVELOPE;
+	long size = 0;
+
+	@Override
+        public void examine(String line) throws IOException {
+	    numberOfMessages = 1;
+	    super.examine(line);
+	}
+
+	@Override
+	public void fetch(String line) throws IOException {
+	    untagged("1 FETCH (ENVELOPE " + envelope +
+		" INTERNALDATE \"" + rdate + "\" " +
+		"RFC822.SIZE " + size + ")");
+	    ok();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPPlainHandler.java b/mail/src/test/java/com/sun/mail/imap/IMAPPlainHandler.java
new file mode 100644
index 0000000..e0514f8
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPPlainHandler.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.mail.util.BASE64DecoderStream;
+
+/**
+ * Handle IMAP connection with PLAIN authentication.
+ *
+ * @author Bill Shannon
+ */
+public class IMAPPlainHandler extends IMAPHandler {
+
+    protected String username = "test";
+    protected String password = "test";
+
+    public IMAPPlainHandler() {
+	capabilities += " LOGINDISABLED AUTH=PLAIN";
+    }
+
+    /**
+     * AUTHENTICATE PLAIN command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    @Override
+    public void authplain(String ir) throws IOException {
+	if (ir == null) {
+	    cont("");
+	    ir = readLine();
+	}
+	String auth = new String(BASE64DecoderStream.decode(
+				    ir.getBytes(StandardCharsets.US_ASCII)),
+				StandardCharsets.UTF_8);
+	String[] ap = auth.split("\000");
+	String u = ap[1];
+	String p = ap[2];
+	//System.out.printf("USER: %s, PASSWORD: %s%n", u, p);
+	if (!u.equals(username) || !p.equals(password)) {
+	    no("authentication failed");
+	    return;
+	}
+        ok("[CAPABILITY " + capabilities + "]");
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPResponseEventTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPResponseEventTest.java
new file mode 100644
index 0000000..c2e3396
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPResponseEventTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.event.StoreListener;
+import javax.mail.event.StoreEvent;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test IMAP response events.
+ */
+public final class IMAPResponseEventTest {
+
+    private volatile boolean gotResponse;
+
+    /**
+     * Test that response events are sent for the LOGIN command.
+     */
+    @Test
+    public void testLoginResponseEvent() {
+	testLogin("");
+    }
+
+    /**
+     * Test that response events are sent for the AUTHENTICATE LOGIN command.
+     */
+    @Test
+    public void testAuthLoginResponseEvent() {
+	testLogin("LOGINDISABLED AUTH=LOGIN");
+    }
+
+    /**
+     * Test that response events are sent for the AUTHENTICATE PLAIN command.
+     */
+    @Test
+    public void testAuthPlainResponseEvent() {
+	testLogin("LOGINDISABLED AUTH=PLAIN");
+    }
+
+    private void testLogin(String type) {
+        TestServer server = null;
+        try {
+            final IMAPHandler handler = new IMAPHandlerLogin(type);
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            properties.setProperty("mail.imap.enableresponseevents", "true");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+	    final CountDownLatch latch = new CountDownLatch(1);
+
+            final Store store = session.getStore("imap");
+	    store.addStoreListener(new StoreListener() {
+		@Override
+		public void notification(StoreEvent e) {
+		    String s;
+		    if (e.getMessageType() == IMAPStore.RESPONSE) {
+			s = "RESPONSE: ";
+			// is this the expected AUTHENTICATE response?
+			if (e.getMessage().indexOf("X-LOGIN-SUCCESS") >= 0)
+			    gotResponse = true;
+			latch.countDown();
+		    } else
+			s = "OTHER: ";
+		    //System.out.println(s + e.getMessage());
+		}
+	    });
+	    gotResponse = false;
+            try {
+                store.connect("test", "test");
+		// time for event to be delivered
+		latch.await(5, TimeUnit.SECONDS);
+		assertTrue(gotResponse);
+
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.  Forces use of specific login type and includes
+     * a fake capability to be included in the OK response that we
+     * will check for success.
+     */
+    private static final class IMAPHandlerLogin extends IMAPHandler {
+	public IMAPHandlerLogin(String type) {
+	    capabilities += " " + type + " X-LOGIN-SUCCESS";
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPSaslHandler.java b/mail/src/test/java/com/sun/mail/imap/IMAPSaslHandler.java
new file mode 100644
index 0000000..b8c1062
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPSaslHandler.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.StringTokenizer;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+
+import javax.security.sasl.*;
+import javax.security.auth.callback.*;
+
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.BASE64DecoderStream;
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * Handle IMAP connection with SASL authentication.
+ *
+ * @author Bill Shannon
+ */
+public class IMAPSaslHandler extends IMAPHandler {
+
+    public IMAPSaslHandler() {
+	capabilities += " LOGINDISABLED AUTH=DIGEST-MD5";
+    }
+
+    /**
+     * AUTHENTICATE command.
+     *
+     * @throws IOException unable to read/write to socket
+     */
+    @Override
+    public void authenticate(String mech, String ir) throws IOException {
+	final String u = "test";
+	final String p = "test";
+	final String realm = "test";
+
+	CallbackHandler cbh = new CallbackHandler() {
+	    @Override
+	    public void handle(Callback[] callbacks) {
+		if (LOGGER.isLoggable(Level.FINE))
+		    LOGGER.fine("SASL callback length: " + callbacks.length);
+		for (int i = 0; i < callbacks.length; i++) {
+		    if (LOGGER.isLoggable(Level.FINE))
+			LOGGER.fine("SASL callback " + i + ": " + callbacks[i]);
+		    if (callbacks[i] instanceof NameCallback) {
+			NameCallback ncb = (NameCallback)callbacks[i];
+			ncb.setName(u);
+		    } else if (callbacks[i] instanceof PasswordCallback) {
+			PasswordCallback pcb = (PasswordCallback)callbacks[i];
+			pcb.setPassword(p.toCharArray());
+		    } else if (callbacks[i] instanceof AuthorizeCallback) {
+			AuthorizeCallback ac = (AuthorizeCallback)callbacks[i];
+			if (LOGGER.isLoggable(Level.FINE))
+			    LOGGER.fine("SASL authorize: " +
+				"authn: " + ac.getAuthenticationID() + ", " +
+				"authz: " + ac.getAuthorizationID() + ", " +
+				"authorized: " + ac.getAuthorizedID());
+			ac.setAuthorized(true);
+		    } else if (callbacks[i] instanceof RealmCallback) {
+			RealmCallback rcb = (RealmCallback)callbacks[i];
+			rcb.setText(realm != null ?
+				    realm : rcb.getDefaultText());
+		    } else if (callbacks[i] instanceof RealmChoiceCallback) {
+			RealmChoiceCallback rcb =
+			    (RealmChoiceCallback)callbacks[i];
+			if (realm == null)
+			    rcb.setSelectedIndex(rcb.getDefaultChoice());
+			else {
+			    // need to find specified realm in list
+			    String[] choices = rcb.getChoices();
+			    for (int k = 0; k < choices.length; k++) {
+				if (choices[k].equals(realm)) {
+				    rcb.setSelectedIndex(k);
+				    break;
+				}
+			    }
+			}
+		    }
+		}
+	    }
+	};
+
+	SaslServer ss;
+	try {
+	    ss = Sasl.createSaslServer(mech, "imap", "localhost", null, cbh);
+	} catch (SaslException sex) {
+	    LOGGER.log(Level.FINE, "Failed to create SASL server", sex);
+	    no("Failed to create SASL server");
+	    return;
+	}
+	if (ss == null) {
+	    LOGGER.fine("No SASL support");
+	    no("No SASL support");
+	    return;
+	}
+	if (LOGGER.isLoggable(Level.FINE))
+	    LOGGER.fine("SASL server " + ss.getMechanismName());
+
+	byte[] response = new byte[0];
+	while (!ss.isComplete()) {
+	    try {
+		byte[] chal = ss.evaluateResponse(response);
+		if (ss.isComplete()) {
+		    break;
+		} else {
+		    // send challenge
+		    if (LOGGER.isLoggable(Level.FINE))
+			LOGGER.fine("SASL challenge: " +
+			    ASCIIUtility.toString(chal, 0, chal.length));
+		    byte[] ba = BASE64EncoderStream.encode(chal);
+		    if (ba.length > 0)
+			cont(ASCIIUtility.toString(ba, 0, ba.length));
+		    else
+			cont();
+		    // read response
+		    String resp = readLine();
+		    response = resp.getBytes();
+		    response = BASE64DecoderStream.decode(response);
+		}
+	    } catch (SaslException ex) {
+		no(ex.toString());
+		break;
+	    }
+	}
+
+	if (ss.isComplete() /*&& status == SUCCESS*/) {
+	    String qop = (String)ss.getNegotiatedProperty(Sasl.QOP);
+	    if (qop != null && (qop.equalsIgnoreCase("auth-int") ||
+				qop.equalsIgnoreCase("auth-conf"))) {
+		// XXX - NOT SUPPORTED!!!
+		LOGGER.fine(
+			"SASL Mechanism requires integrity or confidentiality");
+		no("SASL Mechanism requires integrity or confidentiality");
+		return;
+	    }
+	}
+
+        ok("[CAPABILITY " + capabilities + "]");
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPSaslLoginTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPSaslLoginTest.java
new file mode 100644
index 0000000..f8c27f1
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPSaslLoginTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.*;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.MessagingException;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import static org.junit.Assert.fail;
+
+/**
+ * Test login using a SASL mechanism.
+ */
+public final class IMAPSaslLoginTest {
+
+    /**
+     * Test that login using a SASL mechanism works.
+     */
+    @Test
+    public void testSaslLogin() {
+	TestServer server = null;
+	try {
+	    IMAPHandler handler = new IMAPSaslHandler();
+	    server = new TestServer(handler);
+	    server.start();
+
+	    Properties properties = new Properties();
+	    properties.setProperty("mail.imap.host", "localhost");
+	    properties.setProperty("mail.imap.port", "" + server.getPort());
+	    properties.setProperty("mail.imap.sasl.enable", "true");
+	    properties.setProperty("mail.imap.sasl.mechanisms", "DIGEST-MD5");
+	    Session session = Session.getInstance(properties);
+	    //session.setDebug(true);
+
+	    Store store = session.getStore("imap");
+	    try {
+		store.connect("test", "test");
+		// success!
+	    } catch (MessagingException mex) {
+		fail("login failed");
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		store.close();
+	    }
+
+	} catch (Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPSearchTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPSearchTest.java
new file mode 100644
index 0000000..be262a7
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPSearchTest.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.search.*;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the search method.
+ */
+public final class IMAPSearchTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    @Test
+    public void testWithinNotSupported() {
+        TestServer server = null;
+        try {
+            server = new TestServer(new IMAPHandler() {
+		@Override
+		public void search(String line) throws IOException {
+		    bad("WITHIN not supported");
+		}
+	    });
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            properties.setProperty("mail.imap.throwsearchexception", "true");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+                folder = store.getFolder("INBOX");
+                folder.open(Folder.READ_ONLY);
+		Message[] msgs = folder.search(new YoungerTerm(1));
+		fail("search didn't fail");
+	    } catch (SearchException ex) {
+		// success!
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		if (folder != null)
+		    folder.close(false);
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Test that when the server supports UTF8 and the client enables it,
+     * the client doesn't issue a SEARCH CHARSET command even if the search
+     * term includes a non-ASCII character.
+     * (see RFC 6855, section 3, last paragraph)
+     */
+    @Test
+    public void testUtf8Search() {
+        TestServer server = null;
+        try {
+            server = new TestServer(new IMAPUtf8Handler() {
+		@Override
+		public void search(String line) throws IOException {
+		    if (line.contains("CHARSET"))
+			bad("CHARSET not supported");
+		    else
+			ok();
+		}
+	    });
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+                folder = store.getFolder("INBOX");
+                folder.open(Folder.READ_ONLY);
+		Message[] msgs = folder.search(new SubjectTerm("\u2019"));
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		if (folder != null)
+		    folder.close(false);
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * An IMAPHandler that enables UTF-8 support.
+     */
+    private static class IMAPUtf8Handler extends IMAPHandler {
+	{{ capabilities += " ENABLE UTF8=ACCEPT"; }}
+
+	@Override
+	public void enable(String line) throws IOException {
+	    ok();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPStoreTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPStoreTest.java
new file mode 100644
index 0000000..5bb6312
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPStoreTest.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.StringTokenizer;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.MessagingException;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test IMAPStore methods.
+ */
+public final class IMAPStoreTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    private static final String utf8Folder = "test\u03b1";
+    private static final String utf7Folder = "test&A7E-";
+
+    public static abstract class IMAPTest {
+	public void init(Properties props) { };
+	public void test(Store store, TestServer server) throws Exception { };
+    }
+
+    /**
+     * Test that UTF-8 user name works with LOGIN authentication.
+     */
+    @Test
+    public void testUtf8UsernameLogin() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, TestServer server)
+				    throws MessagingException, IOException {
+		    store.connect(utf8Folder, utf8Folder);
+		}
+	    },
+	    new IMAPLoginHandler() {
+		@Override
+		public void authlogin(String ir)
+					throws IOException {
+		    username = utf8Folder;
+		    password = utf8Folder;
+		    super.authlogin(ir);
+		}
+	    });
+    }
+
+    /**
+     * Test that UTF-8 user name works with PLAIN authentication.
+     */
+    @Test
+    public void testUtf8UsernamePlain() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, TestServer server)
+				    throws MessagingException, IOException {
+		    store.connect(utf8Folder, utf8Folder);
+		}
+	    },
+	    new IMAPPlainHandler() {
+		@Override
+		public void authplain(String ir)
+					throws IOException {
+		    username = utf8Folder;
+		    password = utf8Folder;
+		    super.authplain(ir);
+		}
+	    });
+    }
+
+    /**
+     * Test that UTF-7 folder names in the NAMESPACE command are
+     * decoded properly.
+     */
+    @Test
+    public void testUtf7Namespaces() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, TestServer server)
+				    throws MessagingException, IOException {
+		    store.connect("test", "test");
+		    Folder[] pub = ((IMAPStore)store).getSharedNamespaces();
+		    assertEquals(utf8Folder, pub[0].getName());
+		}
+	    },
+	    new IMAPHandler() {
+		{{ capabilities += " NAMESPACE"; }}
+		@Override
+		public void namespace() throws IOException {
+		    untagged("NAMESPACE ((\"\" \"/\")) ((\"~\" \"/\")) " +
+			"((\"" + utf7Folder + "/\" \"/\"))");
+		    ok();
+		}
+	    });
+    }
+
+    /**
+     * Test that using a UTF-8 folder name results in the proper UTF-8
+     * unencoded name for the CREATE command.
+     */
+    @Test
+    public void testUtf8FolderNameCreate() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, TestServer server)
+				    throws MessagingException, IOException {
+		    store.connect("test", "test");
+		    Folder test = store.getFolder(utf8Folder);
+		    assertTrue(test.create(Folder.HOLDS_MESSAGES));
+		}
+	    },
+	    new IMAPUtf8Handler() {
+		@Override
+		public void create(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip tag
+		    st.nextToken();	// skip "CREATE"
+		    String name = unquote(st.nextToken());
+		    if (name.equals(utf8Folder))
+			ok();
+		    else
+			no("wrong name");
+		}
+
+		@Override
+		public void list(String line) throws IOException {
+		    untagged("LIST (\\HasNoChildren) \"/\" \"" +
+							utf8Folder + "\"");
+		    ok();
+		}
+	    });
+    }
+
+    /**
+     * Test that Store.close also closes open Folders.
+     */
+    @Test
+    public void testCloseClosesFolder() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Store store, TestServer server)
+				    throws MessagingException, IOException {
+		    store.connect("test", "test");
+		    Folder test = store.getFolder("INBOX");
+		    test.open(Folder.READ_ONLY);
+		    store.close();
+		    assertFalse(test.isOpen());
+		    assertEquals(1, server.clientCount());
+		    server.waitForClients(1);
+		    // test will timeout if clients don't terminate
+		}
+	    },
+	    new IMAPHandler() {
+	    });
+    }
+
+    /**
+     * Test that Store.close closes connections in the pool.
+     */
+    @Test
+    public void testCloseEmptiesPool() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void init(Properties props) {
+		    props.setProperty("mail.imap.connectionpoolsize", "2");
+		}
+
+		@Override
+		public void test(Store store, TestServer server)
+				    throws MessagingException, IOException {
+		    store.connect("test", "test");
+		    Folder test = store.getFolder("INBOX");
+		    test.open(Folder.READ_ONLY);
+		    Folder test2 = store.getFolder("INBOX");
+		    test2.open(Folder.READ_ONLY);
+		    test.close(false);
+		    test2.close(false);
+		    store.close();
+		    assertEquals(2, server.clientCount());
+		    server.waitForClients(2);
+		    // test will timeout if clients don't terminate
+		}
+	    },
+	    new IMAPHandler() {
+	    });
+    }
+
+    /**
+     * Test that Store failures don't close Folders.
+     */
+    @Test
+    public void testStoreFailureDoesNotCloseFolder() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void init(Properties props) {
+		    props.setProperty(
+			"mail.imap.closefoldersonstorefailure", "false");
+		}
+
+		@Override
+		public void test(Store store, TestServer server)
+				    throws MessagingException, IOException {
+		    store.connect("test", "test");
+		    Folder test = store.getFolder("INBOX");
+		    test.open(Folder.READ_ONLY);
+		    try {
+			((IMAPStore)store).getSharedNamespaces();
+			fail("MessagingException expected");
+		    } catch (MessagingException mex) {
+			// expected
+		    }
+		    assertTrue(test.isOpen());
+		    store.close();
+		    assertFalse(test.isOpen());
+		    assertEquals(2, server.clientCount());
+		    server.waitForClients(2);
+		    // test will timeout if clients don't terminate
+		}
+	    },
+	    new IMAPHandler() {
+		{{ capabilities += " NAMESPACE"; }}
+
+		@Override
+		public void namespace() throws IOException {
+		    exit();
+		}
+	    });
+    }
+
+    /**
+     * Test that Store.close after Store failure will close all Folders
+     * and empty the connectin pool.
+     */
+    @Test
+    public void testCloseAfterFailure() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void init(Properties props) {
+		    props.setProperty(
+			"mail.imap.closefoldersonstorefailure", "false");
+		}
+
+		@Override
+		public void test(Store store, TestServer server)
+				    throws MessagingException, IOException {
+		    store.connect("test", "test");
+		    Folder test = store.getFolder("INBOX");
+		    test.open(Folder.READ_ONLY);
+		    try {
+			((IMAPStore)store).getSharedNamespaces();
+			fail("MessagingException expected");
+		    } catch (MessagingException mex) {
+			// expected
+		    }
+		    assertTrue(test.isOpen());
+		    test.close();	// put it back in the pool
+		    store.close();
+		    assertEquals(2, server.clientCount());
+		    server.waitForClients(2);
+		    // test will timeout if clients don't terminate
+		}
+	    },
+	    new IMAPHandler() {
+		{{ capabilities += " NAMESPACE"; }}
+
+		@Override
+		public void namespace() throws IOException {
+		    exit();
+		}
+	    });
+    }
+
+    /**
+     * Test that Store failures do close Folders.
+     */
+    @Test
+    public void testStoreFailureDoesCloseFolder() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void init(Properties props) {
+		    props.setProperty(
+			// the default, but just to be sure...
+			"mail.imap.closefoldersonstorefailure", "true");
+		}
+
+		@Override
+		public void test(Store store, TestServer server)
+				    throws MessagingException, IOException {
+		    store.connect("test", "test");
+		    Folder test = store.getFolder("INBOX");
+		    test.open(Folder.READ_ONLY);
+		    try {
+			((IMAPStore)store).getSharedNamespaces();
+			fail("MessagingException expected");
+		    } catch (MessagingException mex) {
+			// expected
+		    }
+		    assertFalse(test.isOpen());
+		    store.close();
+		    assertEquals(2, server.clientCount());
+		    server.waitForClients(2);
+		    // test will timeout if clients don't terminate
+		}
+	    },
+	    new IMAPHandler() {
+		{{ capabilities += " NAMESPACE"; }}
+
+		@Override
+		public void namespace() throws IOException {
+		    exit();
+		}
+	    });
+    }
+
+    private void testWithHandler(IMAPTest test, IMAPHandler handler) {
+        TestServer server = null;
+        try {
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+	    test.init(properties);
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("imap");
+            try {
+		test.test(store, server);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    private static String unquote(String s) {
+	if (s.startsWith("\"") && s.endsWith("\"") && s.length() > 1) {
+	    s = s.substring(1, s.length() - 1);
+	    // check for any escaped characters
+	    if (s.indexOf('\\') >= 0) {
+		StringBuilder sb = new StringBuilder(s.length());	// approx
+		for (int i = 0; i < s.length(); i++) {
+		    char c = s.charAt(i);
+		    if (c == '\\' && i < s.length() - 1)
+			c = s.charAt(++i);
+		    sb.append(c);
+		}
+		s = sb.toString();
+	    }
+	}
+	return s;
+    }
+
+    /**
+     * An IMAPHandler that enables UTF-8 support.
+     */
+    private static class IMAPUtf8Handler extends IMAPHandler {
+	{{ capabilities += " ENABLE UTF8=ACCEPT"; }}
+
+	@Override
+	public void enable(String line) throws IOException {
+	    ok();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/IMAPUidExpungeTest.java b/mail/src/test/java/com/sun/mail/imap/IMAPUidExpungeTest.java
new file mode 100644
index 0000000..bdbc4e1
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/IMAPUidExpungeTest.java
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.UIDFolder;
+import javax.mail.MessagingException;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test EXPUNGE responses during UID FETCH.
+ */
+public final class IMAPUidExpungeTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    public static interface IMAPTest {
+	public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException;
+    }
+
+    @Test
+    public void testUIDSingle() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message m = ((UIDFolder)folder).getMessageByUID(2);
+		    m.getFlags();
+		    assertEquals(1, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("1 EXPUNGE");
+		    untagged("3 EXISTS");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDSingle2() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message m = ((UIDFolder)folder).getMessageByUID(2);
+		    m.getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 EXPUNGE");
+		    untagged("3 EXISTS");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDRange() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(2, 4);
+		    assertTrue(msgs[1] == null || msgs[1].isExpunged());
+		    msgs[0].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[2].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 FETCH (UID 3)");
+		    untagged("4 FETCH (UID 4)");
+		    untagged("3 EXPUNGE");
+		    untagged("3 EXISTS");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDRange2() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(2, 4);
+		    assertTrue(msgs[1] == null || msgs[1].isExpunged());
+		    msgs[0].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[2].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 FETCH (UID 3)");
+		    untagged("3 EXPUNGE");
+		    untagged("3 EXISTS");
+		    untagged("3 FETCH (UID 4)");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDRange3() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(2, 4);
+		    // UID 3 is unknown and not returned
+		    assertEquals(2, msgs.length);
+		    msgs[0].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[1].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 EXPUNGE");
+		    untagged("3 EXISTS");
+		    untagged("3 FETCH (UID 4)");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDRange4() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(1, 3);
+		    assertEquals(3, msgs.length);
+		    msgs[0].getFlags();
+		    assertEquals(1, handler.getSeqNum());
+		    msgs[1].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[2].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("1 FETCH (UID 1)");
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 FETCH (UID 3)");
+		    untagged("4 EXPUNGE");
+		    untagged("3 EXISTS");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDRange5() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(2, 4);
+		    assertEquals(3, msgs.length);
+		    msgs[0].getFlags();
+		    assertEquals(1, handler.getSeqNum());
+		    msgs[1].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[2].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 FETCH (UID 3)");
+		    untagged("4 FETCH (UID 4)");
+		    untagged("1 EXPUNGE");
+		    untagged("3 EXISTS");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDList() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(
+							new long[] { 2, 3, 4 });
+		    assertTrue(msgs[1] == null || msgs[1].isExpunged());
+		    msgs[0].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[2].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 FETCH (UID 3)");
+		    untagged("4 FETCH (UID 4)");
+		    untagged("3 EXPUNGE");
+		    untagged("3 EXISTS");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDList2() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(
+							new long[] { 2, 3, 4 });
+		    assertTrue(msgs[1] == null || msgs[1].isExpunged());
+		    msgs[0].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[2].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 FETCH (UID 3)");
+		    untagged("3 EXPUNGE");
+		    untagged("3 EXISTS");
+		    untagged("3 FETCH (UID 4)");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDList3() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(
+							new long[] { 2, 3, 4 });
+		    assertTrue(msgs[1] == null || msgs[1].isExpunged());
+		    msgs[0].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[2].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 EXPUNGE");
+		    untagged("3 EXISTS");
+		    untagged("3 FETCH (UID 4)");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDList4() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(
+							new long[] { 1, 2, 3 });
+		    assertEquals(3, msgs.length);
+		    msgs[0].getFlags();
+		    assertEquals(1, handler.getSeqNum());
+		    msgs[1].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[2].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("1 FETCH (UID 1)");
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 FETCH (UID 3)");
+		    untagged("4 EXPUNGE");
+		    untagged("3 EXISTS");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    @Test
+    public void testUIDList5() {
+	testWithHandler(
+	    new IMAPTest() {
+		@Override
+		public void test(Folder folder, IMAPHandlerExpunge handler)
+				    throws MessagingException {
+		    Message[] msgs = ((UIDFolder)folder).getMessagesByUID(
+							new long[] { 2, 3, 4 });
+		    assertEquals(3, msgs.length);
+		    msgs[0].getFlags();
+		    assertEquals(1, handler.getSeqNum());
+		    msgs[1].getFlags();
+		    assertEquals(2, handler.getSeqNum());
+		    msgs[2].getFlags();
+		    assertEquals(3, handler.getSeqNum());
+		}
+	    },
+	    new IMAPHandlerExpunge() {
+		@Override
+		public void uidfetch(String line) throws IOException {
+		    untagged("2 FETCH (UID 2)");
+		    untagged("3 FETCH (UID 3)");
+		    untagged("4 FETCH (UID 4)");
+		    untagged("1 EXPUNGE");
+		    untagged("3 EXISTS");
+		    numberOfMessages--;
+		    ok();
+		}
+	    });
+    }
+
+    public void testWithHandler(IMAPTest test, IMAPHandlerExpunge handler) {
+        TestServer server = null;
+        try {
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.imap.host", "localhost");
+            properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("imap");
+	    Folder folder = null;
+            try {
+                store.connect("test", "test");
+                folder = store.getFolder("INBOX");
+                folder.open(Folder.READ_WRITE);
+		test.test(folder, handler);
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+		if (folder != null)
+		    folder.close(false);
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.
+     */
+    private static class IMAPHandlerExpunge extends IMAPHandler {
+	// must be static because handler is cloned for each connection
+	private static int seqnum;
+
+	@Override
+        public void select(String line) throws IOException {
+	    numberOfMessages = 4;
+	    super.select(line);
+	}
+
+	@Override
+        public void fetch(String line) throws IOException {
+	    StringTokenizer st = new StringTokenizer(line, " ");
+	    String tag = st.nextToken();
+	    String command = st.nextToken();
+	    seqnum = Integer.parseInt(st.nextToken());
+	    ok();
+        }
+
+	public int getSeqNum() {
+	    return seqnum;
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/MessageCacheTest.java b/mail/src/test/java/com/sun/mail/imap/MessageCacheTest.java
new file mode 100644
index 0000000..89344f2
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/MessageCacheTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+/**
+ * Test the IMAP MessageCache.
+ */
+public class MessageCacheTest {
+    /**
+     * Test that when a message is expunged and a new message is added,
+     * the new message has the expected sequence number.
+     */
+    @Test
+    public void testExpungeAdd() throws Exception {
+	// test a range of values to find boundary condition errors
+	for (int n = 1; n <= 100; n++) {
+	    //System.out.println("MessageCache.testExpungeAdd: test " + n);
+	    // start with one message
+	    MessageCache mc = new MessageCache(1, false);
+	    // add the remaining messages (eat into SLOP)
+	    mc.addMessages(n - 1, 2);
+	    // now expunge a message to cause the seqnums array to be created
+	    mc.expungeMessage(1);
+	    // and add one more message
+	    mc.addMessages(1, n);
+	    //System.out.println("  new seqnum " + mc.seqnumOf(n + 1));
+	    // does the new message have the expected sequence number?
+	    assertEquals(mc.seqnumOf(n + 1), n);
+	}
+    }
+
+    /**
+     * Test that when a message is expunged and new messages are added,
+     * the new messages have the expected sequence number.  Similar to
+     * the above, but the seqnums array is created first, then expanded.
+     */
+    @Test
+    public void testExpungeAddExpand() throws Exception {
+	// test a range of values to find boundary condition errors
+	for (int n = 2; n <= 100; n++) {
+	    //System.out.println("MessageCache.testExpungeAdd: test " + n);
+	    // start with two messages
+	    MessageCache mc = new MessageCache(2, false);
+	    // now expunge a message to cause the seqnums array to be created
+	    mc.expungeMessage(1);
+	    // add the remaining messages (eat into SLOP)
+	    mc.addMessages(n - 1, 2);
+	    //System.out.println("  new seqnum " + mc.seqnumOf(n + 1));
+	    // does the new message have the expected sequence number?
+	    assertEquals(mc.seqnumOf(n + 1), n);
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/protocol/BODYSTRUCTURETest.java b/mail/src/test/java/com/sun/mail/imap/protocol/BODYSTRUCTURETest.java
new file mode 100644
index 0000000..2bd0a0d
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/protocol/BODYSTRUCTURETest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import javax.mail.internet.ParameterList;
+
+import com.sun.mail.iap.Response;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+/**
+ * Test the BODYSTRUCTURE class.
+ */
+public class BODYSTRUCTURETest {
+    /**
+     * Test workaround for Exchange bug that returns NIL instead of ""
+     * for a parameter with an empty value (name="").
+     */
+    @Test
+    public void testExchangeEmptyParameterValueBug() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+    "* 3 FETCH (BODYSTRUCTURE ((\"text\" \"plain\" (\"charset\" \"UTF-8\") " +
+    "NIL NIL \"quoted-printable\" 512 13 NIL (\"inline\" NIL) NIL NIL)" +
+    "(\"text\" \"html\" (\"charset\" \"UTF-8\") NIL NIL \"quoted-printable\" " +
+    "784 11 NIL (\"inline\" NIL) NIL NIL) \"alternative\" " +
+    "(\"boundary\" \"__139957996218379.example.com\" \"name\" NIL) NIL NIL))");
+    // here's the incorrect NIL that should be "" ............^
+	FetchResponse fr = new FetchResponse(response);
+	BODYSTRUCTURE bs = fr.getItem(BODYSTRUCTURE.class);
+	ParameterList p = bs.cParams;
+	assertNotNull(p.get("name"));
+    }
+
+    /**
+     * Test workaround for Exchange bug that returns the Content-Description
+     * header value instead of the Content-Disposition for some kinds of
+     * (formerly S/MIME encrypted?) messages.
+     */
+    @Test
+    public void testExchangeBadDisposition() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+    "* 1 FETCH (BODYSTRUCTURE (" +
+	"(\"text\" \"plain\" (\"charset\" \"us-ascii\") NIL NIL \"7bit\" " +
+	    "21 0 NIL (\"inline\" NIL) NIL NIL)" +
+	"(\"application\" \"octet-stream\" (\"name\" \"private.txt\") " +
+	    "NIL NIL \"base64\" 690 NIL " +
+		"(\"attachment\" (\"filename\" \"private.txt\")) NIL NIL) " +
+    "\"mixed\" (\"boundary\" \"----=_Part_0_-1731707885.1504253815584\") " +
+	"\"S/MIME Encrypted Message\" NIL))");
+    //    ^^^^^^^ here's the string that should be the disposition
+	FetchResponse fr = new FetchResponse(response);
+	BODYSTRUCTURE bs = fr.getItem(BODYSTRUCTURE.class);
+	assertEquals("S/MIME Encrypted Message", bs.description);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/protocol/EnvelopeTest.java b/mail/src/test/java/com/sun/mail/imap/protocol/EnvelopeTest.java
new file mode 100644
index 0000000..ef93ba8
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/protocol/EnvelopeTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.Response;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+/**
+ * Test the ENVELOPE class.
+ */
+public class EnvelopeTest {
+    /**
+     * Test workaround for Yahoo IMAP bug that returns a bogus space
+     * character when one of the recipients is "undisclosed-recipients".
+     */
+    @Test
+    public void testYahooUndisclosedRecipientsBug() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+    "* 2 FETCH (INTERNALDATE \"24-Apr-2012 20:28:58 +0000\" " +
+    "RFC822.SIZE 155937 " +
+    "ENVELOPE (\"Wed, 28 Sep 2011 11:16:17 +0100\" \"test\" " +
+    "((NIL NIL \"xxx\" \"tju.edu.cn\")) " +
+    "((NIL NIL \"xxx\" \"gmail.com\")) " +
+    "((NIL NIL \"xxx\" \"tju.edu.cn\")) " +
+    "((\"undisclosed-recipients\" NIL " +
+	"\"\\\"undisclosed-recipients\\\"\" NIL )) " +
+    // here's the space inserted by Yahoo IMAP ^
+    "NIL NIL NIL " +
+    "\"<xxx@mail.gmail.com>\"))");
+	FetchResponse fr = new FetchResponse(response);
+	// no exception means it worked
+    }
+
+    /**
+     * Test workaround for Yahoo IMAP bug that returns an empty list
+     * instad of NIL for some addresses in ENVELOPE response.
+     */
+    @Test
+    public void testYahooEnvelopeAddressListBug() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+    "* 2 FETCH (RFC822.SIZE 2567 INTERNALDATE \"29-Apr-2011 13:49:01 +0000\" " +
+    "ENVELOPE (\"Fri, 29 Apr 2011 19:19:01 +0530\" \"test\" " +
+    "((\"xxx\" NIL \"xxx\" \"milium.com.br\")) " +
+    "((\"xxx\" NIL \"xxx\" \"milium.com.br\")) " +
+    "((NIL NIL \"xxx\" \"live.hk\")) () NIL NIL NIL " +
+    "\"<20110429134718.70333732030A@mail2.milium.com.br>\"))");
+	FetchResponse fr = new FetchResponse(response);
+	// no exception means it worked
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/protocol/IMAPProtocolTest.java b/mail/src/test/java/com/sun/mail/imap/protocol/IMAPProtocolTest.java
new file mode 100644
index 0000000..e561388
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/protocol/IMAPProtocolTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.Properties;
+import com.sun.mail.test.AsciiStringInputStream;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+/**
+ * Test the IMAPProtocol class.
+ */
+public class IMAPProtocolTest {
+    private static final boolean debug = false;
+    private static final String content = "aXQncyBteSB0ZXN0IG1haWwNCg0K\r\n";
+    private static final String response =
+	    "* 1 FETCH (UID 127 BODY[1.1.MIME] {82}\r\n" +
+	    "Content-Type: text/plain;\r\n" +
+	    "\tcharset=\"utf-8\"\r\n" +
+	    "Content-Transfer-Encoding: base64\r\n" +
+	    "\r\n" +
+	    " ENVELOPE (\"Mon, 17 Mar 2014 14:03:08 +0100\" \"test invoice\"" +
+	    " ((\"Joe User\" NIL \"joe.user\" \"example.com\"))" +
+	    " ((\"Joe User\" NIL \"joe.user\" \"example.com\"))" +
+	    " ((\"Joe User\" NIL \"joe.user\" \"example.com\"))" +
+	    " ((\"Joe User\" NIL \"joe.user\" \"example.com\"))" + 
+	    " NIL NIL NIL \"<1234@example.com>\") BODY[1.1]<0> " +
+	    "{" + content.length() + "}\r\n" + content + 
+	    ")\r\n" +
+	    "A0 OK FETCH completed.\r\n";
+
+    /**
+     * Test that a response containing multiple BODY elements
+     * returns the correct one.  Derived from a customer bug
+     * with Exchange 2003.  Normally this would never happen,
+     * but it's a valid IMAP response and JavaMail needs to
+     * handle it properly.
+     */
+    @Test
+    public void testMultipleBodyResponses() throws Exception {
+	Properties props = new Properties();
+	props.setProperty("mail.imap.reusetagprefix", "true");
+	IMAPProtocol p = new IMAPProtocol(
+	    new AsciiStringInputStream(response),
+	    new PrintStream(new ByteArrayOutputStream()),
+	    props,
+	    debug);
+	BODY b = p.fetchBody(1, "1.1");
+	assertEquals("section number", "1.1", b.getSection());
+	//System.out.println(b);
+	//System.out.write(b.getByteArray().getNewBytes());
+	String result = new String(b.getByteArray().getNewBytes(), "us-ascii");
+	assertEquals("getByteArray.getNewBytes", content, result);
+	InputStream is = b.getByteArrayInputStream();
+	byte[] ba = new byte[is.available()];
+	is.read(ba);
+	result = new String(ba, "us-ascii");
+	assertEquals("getByteArrayInputStream", content, result);
+    }
+
+    /**
+     * Same test as above, but using a different fetchBody method.
+     */
+    @Test
+    public void testMultipleBodyResponses2() throws Exception {
+	Properties props = new Properties();
+	props.setProperty("mail.imap.reusetagprefix", "true");
+	IMAPProtocol p = new IMAPProtocol(
+	    new AsciiStringInputStream(response),
+	    new PrintStream(new ByteArrayOutputStream()),
+	    props,
+	    debug);
+	BODY b = p.fetchBody(1, "1.1", 0, content.length(), null);
+	assertEquals("section number", "1.1", b.getSection());
+	//System.out.println(b);
+	//System.out.write(b.getByteArray().getNewBytes());
+	String result = new String(b.getByteArray().getNewBytes(), "us-ascii");
+	assertEquals("getByteArray.getNewBytes", content, result);
+	InputStream is = b.getByteArrayInputStream();
+	byte[] ba = new byte[is.available()];
+	is.read(ba);
+	result = new String(ba, "us-ascii");
+	assertEquals("getByteArrayInputStream", content, result);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/protocol/MODSEQTest.java b/mail/src/test/java/com/sun/mail/imap/protocol/MODSEQTest.java
new file mode 100644
index 0000000..e3769fe
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/protocol/MODSEQTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.ParsingException;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+/**
+ * Test the MODSEQ class.
+ */
+public class MODSEQTest {
+    /**
+     * Test an example MODSEQ response.
+     */
+    @Test
+    public void testAll() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+	    "* 1 FETCH (MODSEQ (624140003))");
+	FetchResponse fr = new FetchResponse(response);
+	MODSEQ m = fr.getItem(MODSEQ.class);
+	assertEquals(1, m.seqnum);
+	assertEquals(624140003, m.modseq);
+    }
+
+    /**
+     * Test an example MODSEQ response with unnecessary spaces.
+     */
+    @Test
+    public void testSpaces() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+	    "* 1 FETCH ( MODSEQ ( 624140003 ) )");
+	FetchResponse fr = new FetchResponse(response);
+	MODSEQ m = fr.getItem(MODSEQ.class);
+	assertEquals(1, m.seqnum);
+	assertEquals(624140003, m.modseq);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/protocol/NamespacesTest.java b/mail/src/test/java/com/sun/mail/imap/protocol/NamespacesTest.java
new file mode 100644
index 0000000..04e834e
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/protocol/NamespacesTest.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.ParsingException;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+/**
+ * Test the Namespaces class.
+ */
+public class NamespacesTest {
+    private static final String utf8Folder = "#public\u03b1/";
+    private static final String utf7Folder = "#public&A7E-/";
+
+    /**
+     * Test an example NAMESPACE response.
+     */
+    @Test
+    public void testAll() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+	    "* NAMESPACE ((\"\" \"/\")) " +	// personal
+	    "((\"~\" \"/\")) " +		// other users
+	    "((\"#shared/\" \"/\")" +		// shared
+		"(\"#public/\" \"/\")" +
+		"(\"#ftp/\" \"/\")" +
+		"(\"#news.\" \".\"))");
+	Namespaces ns = new Namespaces(response);
+	assertEquals(1, ns.personal.length);
+	assertEquals("", ns.personal[0].prefix);
+	assertEquals('/', ns.personal[0].delimiter);
+	assertEquals(1, ns.otherUsers.length);
+	assertEquals("~", ns.otherUsers[0].prefix);
+	assertEquals('/', ns.otherUsers[0].delimiter);
+	assertEquals(4, ns.shared.length);
+	assertEquals("#shared/", ns.shared[0].prefix);
+	assertEquals('/', ns.shared[0].delimiter);
+	assertEquals("#public/", ns.shared[1].prefix);
+	assertEquals('/', ns.shared[1].delimiter);
+	assertEquals("#ftp/", ns.shared[2].prefix);
+	assertEquals('/', ns.shared[2].delimiter);
+	assertEquals("#news.", ns.shared[3].prefix);
+	assertEquals('.', ns.shared[3].delimiter);
+    }
+
+    /**
+     * Test an example NAMESPACE response with unnecessary spaces.
+     */
+    @Test
+    public void testSpaces() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+	    "* NAMESPACE ((\"\" \"/\")) " +	// personal
+	    "( ( \"~\" \"/\" ) ) " +		// other users
+	    "(( \"#shared/\" \"/\" )" +		// shared
+		"( \"#public/\" \"/\" )" +
+		"( \"#ftp/\" \"/\" )" +
+		" (\"#news.\" \".\" ))");
+	Namespaces ns = new Namespaces(response);
+	assertEquals(1, ns.personal.length);
+	assertEquals("", ns.personal[0].prefix);
+	assertEquals('/', ns.personal[0].delimiter);
+	assertEquals(1, ns.otherUsers.length);
+	assertEquals("~", ns.otherUsers[0].prefix);
+	assertEquals('/', ns.otherUsers[0].delimiter);
+	assertEquals(4, ns.shared.length);
+	assertEquals("#shared/", ns.shared[0].prefix);
+	assertEquals('/', ns.shared[0].delimiter);
+	assertEquals("#public/", ns.shared[1].prefix);
+	assertEquals('/', ns.shared[1].delimiter);
+	assertEquals("#ftp/", ns.shared[2].prefix);
+	assertEquals('/', ns.shared[2].delimiter);
+	assertEquals("#news.", ns.shared[3].prefix);
+	assertEquals('.', ns.shared[3].delimiter);
+    }
+
+    /**
+     * Test a NAMESPACE response with a UTF-7 folder name.
+     */
+    @Test
+    public void testUtf7() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+	    "* NAMESPACE ((\"\" \"/\")) " +	// personal
+	    "((\"~\" \"/\")) " +		// other users
+	    "((\"#shared/\" \"/\")" +		// shared
+		"(\"" + utf7Folder + "\" \"/\")" +
+		"(\"#ftp/\" \"/\")" +
+		"(\"#news.\" \".\"))",
+	    false);
+	Namespaces ns = new Namespaces(response);
+	assertEquals(1, ns.personal.length);
+	assertEquals("", ns.personal[0].prefix);
+	assertEquals('/', ns.personal[0].delimiter);
+	assertEquals(1, ns.otherUsers.length);
+	assertEquals("~", ns.otherUsers[0].prefix);
+	assertEquals('/', ns.otherUsers[0].delimiter);
+	assertEquals(4, ns.shared.length);
+	assertEquals("#shared/", ns.shared[0].prefix);
+	assertEquals('/', ns.shared[0].delimiter);
+	assertEquals(utf8Folder, ns.shared[1].prefix);
+	assertEquals('/', ns.shared[1].delimiter);
+	assertEquals("#ftp/", ns.shared[2].prefix);
+	assertEquals('/', ns.shared[2].delimiter);
+	assertEquals("#news.", ns.shared[3].prefix);
+	assertEquals('.', ns.shared[3].delimiter);
+    }
+
+    /**
+     * Test a NAMESPACE response with a UTF-8 folder name.
+     */
+    @Test
+    public void testUtf8() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+	    "* NAMESPACE ((\"\" \"/\")) " +	// personal
+	    "((\"~\" \"/\")) " +		// other users
+	    "((\"#shared/\" \"/\")" +		// shared
+		"(\"" + utf8Folder + "\" \"/\")" +
+		"(\"#ftp/\" \"/\")" +
+		"(\"#news.\" \".\"))",
+	    true);
+	Namespaces ns = new Namespaces(response);
+	assertEquals(1, ns.personal.length);
+	assertEquals("", ns.personal[0].prefix);
+	assertEquals('/', ns.personal[0].delimiter);
+	assertEquals(1, ns.otherUsers.length);
+	assertEquals("~", ns.otherUsers[0].prefix);
+	assertEquals('/', ns.otherUsers[0].delimiter);
+	assertEquals(4, ns.shared.length);
+	assertEquals("#shared/", ns.shared[0].prefix);
+	assertEquals('/', ns.shared[0].delimiter);
+	assertEquals(utf8Folder, ns.shared[1].prefix);
+	assertEquals('/', ns.shared[1].delimiter);
+	assertEquals("#ftp/", ns.shared[2].prefix);
+	assertEquals('/', ns.shared[2].delimiter);
+	assertEquals("#news.", ns.shared[3].prefix);
+	assertEquals('.', ns.shared[3].delimiter);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/protocol/StatusTest.java b/mail/src/test/java/com/sun/mail/imap/protocol/StatusTest.java
new file mode 100644
index 0000000..1cc4478
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/protocol/StatusTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.ParsingException;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+/**
+ * Test the Status class.
+ */
+public class StatusTest {
+    /**
+     * Test that the returned mailbox name is decoded.
+     */
+    @Test
+    public void testMailboxDecode() throws Exception {
+	String mbox = "Entw\u00fcrfe";
+	IMAPResponse response = new IMAPResponse(
+	    "* STATUS " +
+	    BASE64MailboxEncoder.encode(mbox) +
+	    " (MESSAGES 231 UIDNEXT 44292)", false);
+	Status s = new Status(response);
+	assertEquals(mbox, s.mbox);
+	assertEquals(231, s.total);
+	assertEquals(44292, s.uidnext);
+    }
+
+    /**
+     * Test that the returned mailbox name is correct when using UTF-8.
+     */
+    @Test
+    public void testMailboxUtf8() throws Exception {
+	String mbox = "Entw\u00fcrfe";
+	IMAPResponse response = new IMAPResponse(
+	    "* STATUS " +
+	    mbox +
+	    " (MESSAGES 231 UIDNEXT 44292)", true);
+	Status s = new Status(response);
+	assertEquals(mbox, s.mbox);
+	assertEquals(231, s.total);
+	assertEquals(44292, s.uidnext);
+    }
+
+    /**
+     * Test that spaces in the response don't confuse it.
+     */
+    @Test
+    public void testSpaces() throws Exception {
+	IMAPResponse response = new IMAPResponse(
+	    "* STATUS  test  ( MESSAGES  231  UIDNEXT  44292 )");
+	Status s = new Status(response);
+	assertEquals("test", s.mbox);
+	assertEquals(231, s.total);
+	assertEquals(44292, s.uidnext);
+    }
+
+    /**
+     * Test that a bad response throws a ParsingException
+     */
+    @Test(expected = ParsingException.class)
+    public void testBadResponseNoAttrList() throws Exception {
+	String mbox = "test";
+	IMAPResponse response = new IMAPResponse("* STATUS test ");
+	Status s = new Status(response);
+    }
+
+    /**
+     * Test that a bad response throws a ParsingException
+     */
+    @Test(expected = ParsingException.class)
+    public void testBadResponseNoAttrs() throws Exception {
+	String mbox = "test";
+	IMAPResponse response = new IMAPResponse("* STATUS test (");
+	Status s = new Status(response);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/protocol/StratoImapBugfixTest.java b/mail/src/test/java/com/sun/mail/imap/protocol/StratoImapBugfixTest.java
new file mode 100644
index 0000000..2f9dd97
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/protocol/StratoImapBugfixTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import com.sun.mail.iap.ParsingException;
+import com.sun.mail.iap.Response;
+import com.sun.mail.imap.protocol.Status;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+/**
+ * @author tkrammer
+ */
+public class StratoImapBugfixTest {
+    @Test
+    public void testValidStatusResponseLeadingSpaces() throws Exception {
+	final Response response =
+		new Response("STATUS \"  Sent Items  \" (UIDNEXT 1)");
+	final Status status = new Status(response);
+
+	assertEquals("  Sent Items  ", status.mbox);
+	assertEquals(1, status.uidnext);
+    }
+
+    @Test
+    public void testValidStatusResponse() throws Exception {
+	final Response response =
+		new Response("STATUS \"Sent Items\" (UIDNEXT 1)");
+	final Status status = new Status(response);
+
+	assertEquals("Sent Items", status.mbox);
+	assertEquals(1, status.uidnext);
+    }
+
+    @Test
+    public void testInvalidStatusResponse() throws Exception {
+	Response response = new Response("STATUS Sent Items (UIDNEXT 1)");
+	final Status status = new Status(response);
+
+	assertEquals("Sent Items", status.mbox);
+	assertEquals(1, status.uidnext);
+    }
+
+    @Test
+    public void testMissingBracket() throws Exception {
+	final Response response =
+		new Response("STATUS \"Sent Items\" UIDNEXT 1)");
+
+	try {
+	    new Status(response);
+	    fail("Must throw exception");
+	} catch(ParsingException e) {
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/imap/protocol/UIDSetTest.java b/mail/src/test/java/com/sun/mail/imap/protocol/UIDSetTest.java
new file mode 100644
index 0000000..26b1c84
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/imap/protocol/UIDSetTest.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.imap.protocol;
+
+import java.io.*;
+import java.util.*;
+
+import org.junit.Test;
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test UIDSet.
+ *
+ * @author Bill Shannon
+ */
+
+@RunWith(Parameterized.class)
+public class UIDSetTest {
+    private TestData data;
+
+    private static boolean gen_test_input = false;	// output good
+    private static int errors = 0;		// number of errors detected
+
+    private static boolean junit;
+
+    static class TestData {
+	public String name;
+	public String uids;
+	public long max;
+	public String maxuids;
+	public long[] expect;
+    }
+
+    public UIDSetTest(TestData t) {
+	data = t;
+    }
+
+    @Test
+    public void testData() {
+	test(data);
+    }
+
+    @Parameters
+    public static Collection<TestData[]> data() throws Exception {
+	junit = true;
+	// XXX - gratuitous array requirement
+	List<TestData[]> testData = new ArrayList<>();
+	BufferedReader in = new BufferedReader(new InputStreamReader(
+	    UIDSetTest.class.getResourceAsStream("uiddata")));
+	TestData t;
+	while ((t = parse(in)) != null)
+	    testData.add(new TestData[] { t });
+	return testData;
+    }
+
+    public static void main(String argv[]) throws Exception {
+	int optind;
+	// XXX - all options currently ignored
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-")) {
+		// ignore
+	    } else if (argv[optind].equals("-g")) {
+		gen_test_input = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+		    "Usage: uidtest [-g] [-]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	// read from stdin
+	BufferedReader in =
+	    new BufferedReader(new InputStreamReader(System.in));
+
+	TestData t;
+	while ((t = parse(in)) != null)
+	    test(t);
+	System.exit(errors);
+    }
+
+    /*
+     * Parse the input, returning a test case.
+     */
+    public static TestData parse(BufferedReader in) throws Exception {
+
+	String line = null;
+	for (;;) {
+	    line = in.readLine();
+	    if (line == null)
+		return null;
+	    if (line.length() == 0 || line.startsWith("#"))
+		continue;
+
+	    if (!line.startsWith("TEST"))
+		throw new Exception("Bad test data format");
+	    break;
+	}
+
+	TestData t = new TestData();
+	int i = line.indexOf(' ');	// XXX - crude
+	t.name = line.substring(i + 1);
+
+	line = in.readLine();
+	StringTokenizer st = new StringTokenizer(line);
+	String tok = st.nextToken();
+	if (!tok.equals("DATA"))
+	    throw new Exception("Bad test data format: " + line);
+	tok = st.nextToken();
+	if (tok.equals("NULL"))
+	    t.uids = null;
+	else if (tok.equals("EMPTY"))
+	    t.uids = "";
+	else
+	    t.uids = tok;
+
+	line = in.readLine();
+	st = new StringTokenizer(line);
+	tok = st.nextToken();
+	if (tok.equals("MAX")) {
+	    tok = st.nextToken();
+	    try {
+		t.max = Long.valueOf(tok);
+	    } catch (NumberFormatException ex) {
+		throw new Exception("Bad MAX value in line: " + line);
+	    }
+	    if (st.hasMoreTokens())
+		t.maxuids = st.nextToken();
+	    else
+		t.maxuids = t.uids;
+	    line = in.readLine();
+	    st = new StringTokenizer(line);
+	    tok = st.nextToken();
+	}
+	List<Long> uids = new ArrayList<>();
+	if (!tok.equals("EXPECT"))
+	    throw new Exception("Bad test data format: " + line);
+	while (st.hasMoreTokens()) {
+	    tok = st.nextToken();
+	    if (tok.equals("NULL"))
+		t.expect = null;
+	    else if (tok.equals("EMPTY"))
+		t.expect = new long[0];
+	    else {
+		try {
+		    uids.add(Long.valueOf(tok));
+		} catch (NumberFormatException ex) {
+		    throw new Exception("Bad DATA option in line: " + line);
+		}
+	    }
+	}
+	if (uids.size() > 0) {
+	    t.expect = new long[uids.size()];
+	    i = 0;
+	    for (Long l : uids)
+		t.expect[i++] = l.longValue();
+	}
+
+	return t;
+    }
+
+    /**
+     * Test the data in the test case.
+     */
+    public static void test(TestData t) {
+	// XXX - handle nulls
+
+	// first, test string to array
+	UIDSet[] uidset = UIDSet.parseUIDSets(t.uids);
+	long[] uids;
+	if (t.max > 0)
+	    uids = UIDSet.toArray(uidset, t.max);
+	else
+	    uids = UIDSet.toArray(uidset);
+	if (junit)
+	    Assert.assertArrayEquals(t.expect, uids);
+	else if (!arrayEquals(t.expect, uids)) {
+	    System.out.println("Test: " + t.name);
+	    System.out.println("FAIL");
+	    errors++;
+	}
+
+	// now, test the reverse
+	UIDSet[] uidset2 = UIDSet.createUIDSets(uids);
+	String suid = UIDSet.toString(uidset2);
+	String euid = t.max > 0 ? t.maxuids : t.uids;
+	if (junit)
+	    Assert.assertEquals(euid, suid);
+	else if (!euid.equals(suid)) {
+	    System.out.println("Test: " + t.name);
+	    System.out.println("FAIL2");
+	    errors++;
+	}
+    }
+
+    private static boolean arrayEquals(long[] a,long[] b) {
+	if (a == b)
+	    return true;
+	if (a == null || b == null)
+	    return false;
+	if (a.length != b.length)
+	    return false;
+	for (int i = 0; i < a.length; i++)
+	    if (a[i] != b[i])
+		return false;
+	return true;
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/pop3/POP3AuthDebugTest.java b/mail/src/test/java/com/sun/mail/pop3/POP3AuthDebugTest.java
new file mode 100644
index 0000000..3ff667d
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/pop3/POP3AuthDebugTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.*;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Store;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that authentication information is only included in
+ * the debug output when explicitly requested by setting the
+ * property "mail.debug.auth" to "true".
+ *
+ * XXX - should test all authentication types, but that requires
+ *	 more work in the dummy test server.
+ */
+public final class POP3AuthDebugTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    /**
+     * Test that authentication information isn't included in the debug output.
+     */
+    @Test
+    public void testNoAuthDefault() {
+	final Properties properties = new Properties();
+	assertFalse("PASS in debug output", test(properties, "PASS"));
+    }
+
+    @Test
+    public void testNoAuth() {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.debug.auth", "false");
+	assertFalse("PASS in debug output", test(properties, "PASS"));
+    }
+
+    /**
+     * Test that authentication information *is* included in the debug output.
+     */
+    @Test
+    public void testAuth() {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.debug.auth", "true");
+	assertTrue("PASS in debug output", test(properties, "PASS"));
+    }
+
+    /**
+     * Create a test server, connect to it, and collect the debug output.
+     * Scan the debug output looking for "expect", return true if found.
+     */
+    public boolean test(Properties properties, String expect) {
+	TestServer server = null;
+	try {
+	    final POP3Handler handler = new POP3Handler();
+	    server = new TestServer(handler);
+	    server.start();
+	    Thread.sleep(1000);
+
+	    properties.setProperty("mail.pop3.host", "localhost");
+	    properties.setProperty("mail.pop3.port", "" + server.getPort());
+	    final Session session = Session.getInstance(properties);
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	    PrintStream ps = new PrintStream(bos);
+	    session.setDebugOut(ps);
+	    session.setDebug(true);
+
+	    final Store store = session.getStore("pop3");
+	    try {
+		store.connect("test", "test");
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		store.close();
+	    }
+
+	    ps.close();
+	    bos.close();
+	    ByteArrayInputStream bis =
+		new ByteArrayInputStream(bos.toByteArray());
+	    BufferedReader r = new BufferedReader(
+					new InputStreamReader(bis, "us-ascii"));
+	    String line;
+	    boolean found = false;
+	    while ((line = r.readLine()) != null) {
+		if (line.startsWith("DEBUG"))
+		    continue;
+		if (line.startsWith("*"))
+		    continue;
+		if (line.indexOf(expect) >= 0)
+		    found = true;
+	    }
+	    r.close();
+	    return found;
+	} catch (final Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	    return false;	// XXX - doesn't matter
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/pop3/POP3FolderClosedExceptionTest.java b/mail/src/test/java/com/sun/mail/pop3/POP3FolderClosedExceptionTest.java
new file mode 100644
index 0000000..3269e9b
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/pop3/POP3FolderClosedExceptionTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.FolderClosedException;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that FolderClosedException is thrown when server times out connection.
+ * This test is derived from real failures seen with Hotmail.
+ *
+ * @author sbo
+ * @author Bill Shannon
+ */
+public final class POP3FolderClosedExceptionTest {
+
+    /**
+     * Test that FolderClosedException is thrown when the timeout occurs
+     * when reading the message body.
+     */
+    @Test
+    public void testFolderClosedExceptionBody() {
+	TestServer server = null;
+	try {
+	    final POP3Handler handler = new POP3HandlerTimeoutBody();
+	    server = new TestServer(handler);
+	    server.start();
+	    Thread.sleep(1000);
+
+	    final Properties properties = new Properties();
+	    properties.setProperty("mail.pop3.host", "localhost");
+	    properties.setProperty("mail.pop3.port", "" + server.getPort());
+	    final Session session = Session.getInstance(properties);
+	    //session.setDebug(true);
+
+	    final Store store = session.getStore("pop3");
+	    try {
+		store.connect("test", "test");
+		final Folder folder = store.getFolder("INBOX");
+		folder.open(Folder.READ_ONLY);
+		Message msg = folder.getMessage(1);
+		try {
+		    msg.getContent();
+		} catch (IOException ioex) {
+		    // expected
+		    // first attempt detects error return from server
+		}
+		// second attempt detects closed connection from server
+		msg.getContent();
+
+		// Check
+		assertFalse(folder.isOpen());
+	    } catch (FolderClosedException ex) {
+		// success!
+	    } finally {
+		store.close();
+	    }
+	} catch (final Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+
+    /**
+     * Custom handler.  Returns ERR for RETR the first time,
+     * then closes the connection the second time.
+     */
+    private static final class POP3HandlerTimeoutBody extends POP3Handler {
+
+	private boolean first = true;
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public void retr(String arg) throws IOException {
+	    if (first) {
+		println("-ERR Server timeout");
+		first = false;
+	    } else
+		exit();
+	}
+    }
+
+    /**
+     * Test that FolderClosedException is thrown when the timeout occurs
+     * when reading the headers.
+     */
+    @Test
+    public void testFolderClosedExceptionHeaders() {
+	TestServer server = null;
+	try {
+	    final POP3Handler handler = new POP3HandlerTimeoutHeader();
+	    server = new TestServer(handler);
+	    server.start();
+	    Thread.sleep(1000);
+
+	    final Properties properties = new Properties();
+	    properties.setProperty("mail.pop3.host", "localhost");
+	    properties.setProperty("mail.pop3.port", "" + server.getPort());
+	    final Session session = Session.getInstance(properties);
+	    //session.setDebug(true);
+
+	    final Store store = session.getStore("pop3");
+	    try {
+		store.connect("test", "test");
+		final Folder folder = store.getFolder("INBOX");
+		folder.open(Folder.READ_ONLY);
+		Message msg = folder.getMessage(1);
+		msg.getSubject();
+
+		// Check
+		assertFalse(folder.isOpen());
+	    } catch (FolderClosedException ex) {
+		// success!
+	    } finally {
+		store.close();
+	    }
+	} catch (final Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+
+    /**
+     * Custom handler.  Returns ERR for TOP, then closes connection.
+     */
+    private static final class POP3HandlerTimeoutHeader extends POP3Handler {
+
+	/**
+	 * {@inheritDoc}
+	 */
+	@Override
+	public void top(String arg) throws IOException {
+	    println("-ERR Server timeout");
+	    exit();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/pop3/POP3Handler.java b/mail/src/test/java/com/sun/mail/pop3/POP3Handler.java
new file mode 100644
index 0000000..0713ff1
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/pop3/POP3Handler.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.IOException;
+import java.util.StringTokenizer;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+
+import com.sun.mail.test.ProtocolHandler;
+
+/**
+ * Handle connection.
+ *
+ * @author sbo
+ */
+public class POP3Handler extends ProtocolHandler {
+
+    /** Current line. */
+    private String currentLine;
+
+    /** First test message. */
+    private String top1 =
+	    "Mime-Version: 1.0\r\n" +
+	    "From: joe@example.com\r\n" +
+	    "To: bob@example.com\r\n" +
+	    "Subject: Example\r\n" +
+	    "Content-Type: text/plain\r\n" +
+	    "\r\n";
+    private String msg1 = top1 +
+	    "plain text\r\n";
+
+    /** Second test message. */
+    private String top2 =
+	    "Mime-Version: 1.0\r\n" +
+	    "From: joe@example.com\r\n" +
+	    "To: bob@example.com\r\n" +
+	    "Subject: Multipart Example\r\n" +
+	    "Content-Type: multipart/mixed; boundary=\"xxx\"\r\n" +
+	    "\r\n";
+    private String msg2 = top2 +
+	    "preamble\r\n" +
+	    "--xxx\r\n" +
+	    "\r\n" +
+	    "first part\r\n" +
+	    "\r\n" +
+	    "--xxx\r\n" +
+	    "\r\n" +
+	    "second part\r\n" +
+	    "\r\n" +
+	    "--xxx--\r\n";
+
+    /**
+     * Send greetings.
+     *
+     * @throws IOException
+     *             unable to write to socket
+     */
+    @Override
+    public void sendGreetings() throws IOException {
+        this.println("+OK POP3 CUSTOM");
+    }
+
+    /**
+     * Send String to socket.
+     *
+     * @param str
+     *            String to send
+     * @throws IOException
+     *             unable to write to socket
+     */
+    public void println(final String str) throws IOException {
+        this.writer.print(str);
+	this.writer.print("\r\n");
+        this.writer.flush();
+    }
+
+    /**
+     * Handle command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    @Override
+    public void handleCommand() throws IOException {
+        this.currentLine = readLine();
+
+        if (this.currentLine == null) {
+	    // probably just EOF because the socket was closed
+            //LOGGER.severe("Current line is null!");
+            this.exit();
+            return;
+        }
+
+        final StringTokenizer st = new StringTokenizer(this.currentLine, " ");
+        final String commandName = st.nextToken().toUpperCase();
+        final String arg = st.hasMoreTokens() ? st.nextToken() : null;
+        if (commandName == null) {
+            LOGGER.severe("Command name is empty!");
+            this.exit();
+            return;
+        }
+
+        if (commandName.equals("STAT")) {
+            this.stat();
+        } else if (commandName.equals("LIST")) {
+            this.list();
+        } else if (commandName.equals("RETR")) {
+            this.retr(arg);
+        } else if (commandName.equals("DELE")) {
+            this.dele();
+        } else if (commandName.equals("NOOP")) {
+            this.noop();
+        } else if (commandName.equals("RSET")) {
+            this.rset();
+        } else if (commandName.equals("QUIT")) {
+            this.quit();
+        } else if (commandName.equals("TOP")) {
+            this.top(arg);
+        } else if (commandName.equals("UIDL")) {
+            this.uidl();
+        } else if (commandName.equals("USER")) {
+            this.user();
+        } else if (commandName.equals("PASS")) {
+            this.pass();
+        } else if (commandName.equals("CAPA")) {
+            this.println("-ERR CAPA not supported");
+        } else {
+            LOGGER.log(Level.SEVERE, "ERROR command unknown: {0}", commandName);
+            this.println("-ERR unknown command");
+        }
+    }
+
+    /**
+     * STAT command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void stat() throws IOException {
+        this.println("+OK 2 " + (msg1.length() + msg2.length()));
+    }
+
+    /**
+     * LIST command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void list() throws IOException {
+        this.writer.println("+OK");
+        this.writer.println("1 " + msg1.length());
+        this.writer.println("2 " + msg2.length());
+        this.println(".");
+    }
+
+    /**
+     * RETR command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void retr(String arg) throws IOException {
+	String msg;
+	if (arg.equals("1"))
+	    msg = msg1;
+	else
+	    msg = msg2;
+        this.println("+OK " + msg.length() + " octets");
+	this.writer.write(msg);
+	this.println(".");
+    }
+
+    /**
+     * DELE command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void dele() throws IOException {
+	this.println("-ERR DELE not supported");
+    }
+
+    /**
+     * NOOP command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void noop() throws IOException {
+        this.println("+OK");
+    }
+
+    /**
+     * RSET command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void rset() throws IOException {
+        this.println("+OK");
+    }
+
+    /**
+     * QUIT command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void quit() throws IOException {
+        this.println("+OK");
+        this.exit();
+    }
+
+    /**
+     * TOP command.
+     * XXX - ignores number of lines argument
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void top(String arg) throws IOException {
+	String top;
+	if (arg.equals("1"))
+	    top = top1;
+	else
+	    top = top2;
+        this.println("+OK " + top.length() + " octets");
+	this.writer.write(top);
+	this.println(".");
+    }
+
+    /**
+     * UIDL command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void uidl() throws IOException {
+        this.writer.println("+OK");
+        this.writer.println("1 1");
+        this.writer.println("2 2");
+        this.println(".");
+    }
+
+    /**
+     * USER command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void user() throws IOException {
+        this.println("+OK");
+    }
+
+    /**
+     * PASS command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void pass() throws IOException {
+        this.println("+OK");
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/pop3/POP3MessageTest.java b/mail/src/test/java/com/sun/mail/pop3/POP3MessageTest.java
new file mode 100644
index 0000000..2dc38b7
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/pop3/POP3MessageTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.Multipart;
+import javax.mail.Part;
+import javax.mail.MessagingException;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.BeforeClass;
+import org.junit.AfterClass;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that we can read POP3 messages.
+ *
+ * @author Bill Shannon
+ */
+public final class POP3MessageTest {
+
+    private static TestServer server = null;
+    private static Store store;
+    private static Folder folder;
+
+    private static void startServer(boolean cached) {
+        try {
+            final POP3Handler handler = new POP3Handler();
+            server = new TestServer(handler);
+            server.start();
+            Thread.sleep(1000);
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.pop3.host", "localhost");
+            properties.setProperty("mail.pop3.port", "" + server.getPort());
+	    if (cached)
+		properties.setProperty("mail.pop3.filecache.enable", "true");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            store = session.getStore("pop3");
+	    store.connect("test", "test");
+	    folder = store.getFolder("INBOX");
+	    folder.open(Folder.READ_ONLY);
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    private static void stopServer() {
+	try {
+	    if (folder != null)
+		folder.close(false);
+	    if (store != null)
+		store.close();
+	} catch (MessagingException ex) {
+	    // ignore it
+	} finally {
+	    if (server != null)
+		server.quit();
+	}
+    }
+
+    /**
+     * Test that we can read the content of a message twice.
+     * A bug caused POP3Message to return the same stream the
+     * second time, instead of a new stream positioned at the
+     * beginning of the data.  This caused multipart parsing
+     * to fail.
+     */
+    @Test
+    public void testReadTwice() throws Exception {
+	readTwice(false);
+    }
+
+    /**
+     * Now test it using the file cache.
+     */
+    @Test
+    public void testReadTwiceCached() throws Exception {
+	readTwice(true);
+    }
+
+    private void readTwice(boolean cached) throws Exception {
+	startServer(cached);
+	try {
+	    Message[] msgs = folder.getMessages();
+	    for (int i = 0; i < msgs.length; i++) {
+		loadMail(msgs[i]);
+		loadMail(msgs[i]);	
+	    }
+	} finally {
+	    stopServer();
+	}
+	// no exception is success!
+    }
+
+    private void loadMail(Part p) throws Exception {
+	Object content = p.getContent();
+	if (content instanceof Multipart) {
+	    Multipart mp = (Multipart)content;
+	    int cnt = mp.getCount();
+	    for (int i = 0; i < cnt; i++)
+		loadMail(mp.getBodyPart(i));
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/pop3/POP3ReadableMimeTest.java b/mail/src/test/java/com/sun/mail/pop3/POP3ReadableMimeTest.java
new file mode 100644
index 0000000..ad6dd71
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/pop3/POP3ReadableMimeTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Properties;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Message;
+import javax.mail.Part;
+import javax.mail.MessagingException;
+
+import com.sun.mail.util.ReadableMime;
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.BeforeClass;
+import org.junit.AfterClass;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test ReadableMime support for POP3.
+ *
+ * @author Bill Shannon
+ */
+public final class POP3ReadableMimeTest {
+
+    private static TestServer server = null;
+    private static Store store;
+    private static Folder folder;
+
+    private static void startServer(boolean cached) {
+        try {
+            final POP3Handler handler = new POP3Handler();
+            server = new TestServer(handler);
+            server.start();
+            Thread.sleep(1000);
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.pop3.host", "localhost");
+            properties.setProperty("mail.pop3.port", "" + server.getPort());
+	    if (cached)
+		properties.setProperty("mail.pop3.filecache.enable", "true");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            store = session.getStore("pop3");
+	    store.connect("test", "test");
+	    folder = store.getFolder("INBOX");
+	    folder.open(Folder.READ_ONLY);
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+
+    private static void stopServer() {
+	try {
+	    if (folder != null)
+		folder.close(false);
+	    if (store != null)
+		store.close();
+	} catch (MessagingException ex) {
+	    // ignore it
+	} finally {
+	    if (server != null)
+		server.quit();
+	}
+    }
+
+    /**
+     * Test that the data returned by the getMimeStream method
+     * is exactly the same data as produced by the writeTo method.
+     */
+    @Test
+    public void testReadableMime() throws Exception {
+	test(false);
+    }
+
+    /**
+     * Now test it using the file cache.
+     */
+    @Test
+    public void testReadableMimeCached() throws Exception {
+	test(true);
+    }
+
+    private void test(boolean cached) throws Exception {
+	startServer(cached);
+	try {
+	    Message[] msgs = folder.getMessages();
+	    for (int i = 0; i < msgs.length; i++)
+		verifyData(msgs[i]);
+	} finally {
+	    stopServer();
+	}
+	// no exception is success!
+    }
+
+    private void verifyData(Part p) throws MessagingException, IOException {
+	assertTrue("ReadableMime", p instanceof ReadableMime);
+	InputStream is = null;
+	try {
+	    ReadableMime rp = (ReadableMime)p;
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	    p.writeTo(bos);
+	    bos.close();
+	    byte[] buf = bos.toByteArray();
+	    is = rp.getMimeStream();
+	    int i, b;
+	    for (i = 0; (b = is.read()) != -1; i++)
+		assertTrue("message data", b == (buf[i] & 0xff));
+	    assertTrue("data size", i == buf.length);
+	} finally {
+	    try {
+		is.close();
+	    } catch (IOException ex) { }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/pop3/POP3StoreTest.java b/mail/src/test/java/com/sun/mail/pop3/POP3StoreTest.java
new file mode 100644
index 0000000..b4d1082
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/pop3/POP3StoreTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.pop3;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.Folder;
+import javax.mail.Session;
+import javax.mail.Store;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test POP3Store.
+ *
+ * @author sbo
+ * @author Bill Shannon
+ */
+public final class POP3StoreTest {
+
+    /**
+     * Check is connected.
+     */
+    @Test
+    public void testIsConnected() {
+        TestServer server = null;
+        try {
+            final POP3Handler handler = new POP3HandlerNoopErr();
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.pop3.host", "localhost");
+            properties.setProperty("mail.pop3.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("pop3");
+            try {
+                store.connect("test", "test");
+                final Folder folder = store.getFolder("INBOX");
+                folder.open(Folder.READ_ONLY);
+
+                // Check
+                assertFalse(folder.isOpen());
+            } finally {
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Check that enabling APOP with a server that doesn't support APOP
+     * (and doesn't return any information in the greeting) doesn't fail.
+     */
+    @Test
+    public void testApopNotSupported() {
+        TestServer server = null;
+        try {
+            final POP3Handler handler = new POP3HandlerNoGreeting();
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.pop3.host", "localhost");
+            properties.setProperty("mail.pop3.port", "" + server.getPort());
+            properties.setProperty("mail.pop3.apop.enable", "true");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Store store = session.getStore("pop3");
+            try {
+                store.connect("test", "test");
+		// success!
+            } finally {
+                store.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler. Returns ERR for NOOP.
+     *
+     * @author sbo
+     */
+    private static final class POP3HandlerNoopErr extends POP3Handler {
+
+        /**
+         * {@inheritDoc}
+         */
+	@Override
+        public void noop() throws IOException {
+            this.println("-ERR");
+        }
+    }
+
+    /**
+     * Custom handler.  Don't include any extra information in the greeting.
+     *
+     * @author Bill Shannon
+     */
+    private static final class POP3HandlerNoGreeting extends POP3Handler {
+
+        /**
+         * {@inheritDoc}
+         */
+	@Override
+        public void sendGreetings() throws IOException {
+            this.println("+OK");
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/NopServer.java b/mail/src/test/java/com/sun/mail/smtp/NopServer.java
new file mode 100644
index 0000000..033deca
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/NopServer.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * A server that does nothing, but keeps track of
+ * whether the client socket was closed.
+ *
+ * Inspired by, and derived from, POP3Server by sbo.
+ *
+ * @author Bill Shannon
+ */
+public final class NopServer extends Thread {
+
+    /** Server socket. */
+    private ServerSocket serverSocket;
+
+    /** Keep on? */
+    private volatile boolean keepOn;
+
+    /** Did we get EOF on the client socket? */
+    private volatile boolean gotEOF = false;
+
+    /**
+     * Nop server.
+     */
+    public NopServer() throws IOException {
+	serverSocket = new ServerSocket(0);
+    }
+
+    /**
+     * Return the port the server is listening on.
+     */
+    public int getPort() {
+	return serverSocket.getLocalPort();
+    }
+
+    /**
+     * Exit Nop server.
+     */
+    public void quit() {
+        try {
+            keepOn = false;
+            if (serverSocket != null && !serverSocket.isClosed()) {
+                serverSocket.close();
+                serverSocket = null;
+            }
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public boolean eof() {
+	return gotEOF;
+    }
+
+    @Override
+    public void run() {
+        try {
+            keepOn = true;
+
+            while (keepOn) {
+                try {
+                    final Socket clientSocket = serverSocket.accept();
+		    /*
+		     * Do nothing but consume any input and throw it away.
+		     * When we see EOF, remember it.
+		     */
+		    InputStream is = clientSocket.getInputStream();
+		    while (is.read() >= 0)
+			;
+		    gotEOF = true;
+                } catch (final IOException e) {
+                    //e.printStackTrace();
+                }
+            }
+        } finally {
+            quit();
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPAuthDebugTest.java b/mail/src/test/java/com/sun/mail/smtp/SMTPAuthDebugTest.java
new file mode 100644
index 0000000..60340af
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPAuthDebugTest.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Transport;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that authentication information is only included in
+ * the debug output when explicitly requested by setting the
+ * property "mail.debug.auth" to "true".
+ *
+ * XXX - should test all authentication types, but that requires
+ *	 more work in the dummy test server.
+ */
+public final class SMTPAuthDebugTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    /**
+     * Test that authentication information isn't included in the debug output.
+     */
+    @Test
+    public void testNoAuthDefault() {
+	final Properties properties = new Properties();
+	assertFalse("AUTH in debug output", test(properties, "AUTH"));
+    }
+
+    @Test
+    public void testNoAuth() {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.debug.auth", "false");
+	assertFalse("AUTH in debug output", test(properties, "AUTH"));
+    }
+
+    /**
+     * Test that authentication information *is* included in the debug output.
+     */
+    @Test
+    public void testAuth() {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.debug.auth", "true");
+	assertTrue("AUTH in debug output", test(properties, "AUTH"));
+    }
+
+    /**
+     * Create a test server, connect to it, and collect the debug output.
+     * Scan the debug output looking for "expect", return true if found.
+     */
+    public boolean test(Properties properties, String expect) {
+	TestServer server = null;
+	try {
+	    final SMTPHandler handler = new SMTPHandler();
+	    server = new TestServer(handler);
+	    server.start();
+	    Thread.sleep(1000);
+
+	    properties.setProperty("mail.smtp.host", "localhost");
+	    properties.setProperty("mail.smtp.port", "" + server.getPort());
+	    final Session session = Session.getInstance(properties);
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	    PrintStream ps = new PrintStream(bos);
+	    session.setDebugOut(ps);
+	    session.setDebug(true);
+
+	    final Transport t = session.getTransport("smtp");
+	    try {
+		t.connect("test", "test");
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+	    } finally {
+		t.close();
+	    }
+
+	    ps.close();
+	    bos.close();
+	    ByteArrayInputStream bis =
+		new ByteArrayInputStream(bos.toByteArray());
+	    BufferedReader r = new BufferedReader(
+					new InputStreamReader(bis, "us-ascii"));
+	    String line;
+	    boolean found = false;
+	    while ((line = r.readLine()) != null) {
+		if (line.startsWith("DEBUG"))
+		    continue;
+		if (line.startsWith("*"))
+		    continue;
+		if (line.startsWith(expect))
+		    found = true;
+	    }
+	    r.close();
+	    return found;
+	} catch (final Exception e) {
+	    e.printStackTrace();
+	    fail(e.getMessage());
+	    return false;	// XXX - doesn't matter
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPBdatTest.java b/mail/src/test/java/com/sun/mail/smtp/SMTPBdatTest.java
new file mode 100644
index 0000000..64fb3f0
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPBdatTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.io.ByteArrayOutputStream;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the BDAT command.
+ */
+public final class SMTPBdatTest {
+
+    private byte[] message;
+
+    @Test
+    public void testBdatSuccess() throws Exception {
+        TestServer server = null;
+        try {
+	    SMTPHandler handler = new SMTPHandler() {
+		{{ extensions.add("CHUNKING"); }}
+
+		@Override
+		public void setMessage(byte[] msg) {
+		    message = msg;
+		}
+	    };
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.chunksize", "128");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Transport t = session.getTransport("smtp");
+            try {
+		MimeMessage msg = new MimeMessage(session);
+		msg.setRecipients(Message.RecipientType.TO, "joe@example.com");
+		msg.setSubject("test");
+		msg.setText("test\r\n");
+                t.connect();
+		t.sendMessage(msg, msg.getAllRecipients());
+		ByteArrayOutputStream bos = new ByteArrayOutputStream();
+		msg.writeTo(bos);
+		bos.close();
+		byte[] orig = bos.toByteArray();
+		assertArrayEquals(orig, message);
+	    } catch (MessagingException ex) {
+		fail(ex.getMessage());
+            } finally {
+                t.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+		// wait for handler to exit
+		server.join();
+            }
+        }
+    }
+
+    @Test
+    public void testBdatFailure() throws Exception {
+        TestServer server = null;
+        try {
+	    SMTPHandler handler = new SMTPHandler() {
+		{{ extensions.add("CHUNKING"); }}
+
+		@Override
+		public void bdat(String line) throws IOException {
+		    String[] tok = line.split("\\s+");
+		    int bytes = Integer.parseInt(tok[1]);
+		    boolean last = tok.length > 2 &&
+				    tok[2].equalsIgnoreCase("LAST");
+		    readBdatMessage(bytes, last);
+		    println("444 failed");
+		}
+	    };
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.chunksize", "128");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Transport t = session.getTransport("smtp");
+            try {
+		MimeMessage msg = new MimeMessage(session);
+		msg.setRecipients(Message.RecipientType.TO, "joe@example.com");
+		msg.setSubject("test");
+		msg.setText("test\r\n");
+                t.connect();
+		t.sendMessage(msg, msg.getAllRecipients());
+		fail("no exception");
+	    } catch (MessagingException ex) {
+		// expect it to fail
+            } finally {
+                t.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+		// wait for handler to exit
+		server.join();
+            }
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPCloseTest.java b/mail/src/test/java/com/sun/mail/smtp/SMTPCloseTest.java
new file mode 100644
index 0000000..2d26cb8
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPCloseTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Transport;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that the socket is closed when connect times out.
+ */
+public final class SMTPCloseTest {
+
+    // timeout the test in case of failure
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(5);
+
+    @Test
+    public void test() {
+        NopServer server = null;
+        try {
+            server = new NopServer();
+            server.start();
+            Thread.sleep(1000);
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.timeout", "100");
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Transport t = session.getTransport("smtp");
+            try {
+                t.connect();
+	    } catch (Exception ex) {
+		// expect an exception when connect times out
+            } finally {
+                t.close();
+            }
+	    // give the server thread a chance to detect the close
+	    for (int i = 0; i < 10 && !server.eof(); i++)
+		Thread.sleep(100);
+	    assertTrue("socket closed", server.eof());
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPConnectFailureTest.java b/mail/src/test/java/com/sun/mail/smtp/SMTPConnectFailureTest.java
new file mode 100644
index 0000000..2f5b7d0
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPConnectFailureTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.util.Properties;
+import java.net.ServerSocket;
+
+import javax.mail.Session;
+import javax.mail.Transport;
+
+import com.sun.mail.util.MailConnectException;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test connect failures.
+ */
+public class SMTPConnectFailureTest {
+
+    private static final String HOST = "localhost";
+    private static final int CTO = 20;
+
+    @Test
+    public void testNoServer() {
+        try {
+	    // verify that port is not being used
+	    ServerSocket ss = new ServerSocket(0);
+	    int port = ss.getLocalPort();
+	    ss.close();
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", HOST);
+            properties.setProperty("mail.smtp.port", "" + port);
+            properties.setProperty("mail.smtp.connectiontimeout", "" + CTO);
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect("test", "test");
+		fail("Connected!");
+		// failure!
+	    } catch (MailConnectException mcex) {
+		// success!
+		assertEquals(HOST, mcex.getHost());
+		assertEquals(port, mcex.getPort());
+		assertEquals(CTO, mcex.getConnectionTimeout());
+	    } catch (Exception ex) {
+		// expect an exception when connect times out
+		fail(ex.toString());
+            } finally {
+		if (t.isConnected())
+		    t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPHandler.java b/mail/src/test/java/com/sun/mail/smtp/SMTPHandler.java
new file mode 100644
index 0000000..3217b50
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPHandler.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+
+import com.sun.mail.test.ProtocolHandler;
+
+/**
+ * Handle connection.
+ *
+ * @author Bill Shannon
+ */
+public class SMTPHandler extends ProtocolHandler {
+
+    /** Current line. */
+    private String currentLine;
+
+    /** A message being accumulated. */
+    private ByteArrayOutputStream messageStream;
+
+    /** SMTP extensions supported. */
+    protected Set<String> extensions = new HashSet<String>();
+
+    /**
+     * Send greetings.
+     *
+     * @throws IOException
+     *             unable to write to socket
+     */
+    @Override
+    public void sendGreetings() throws IOException {
+        println("220 localhost dummy server ready");
+    }
+
+    /**
+     * Send String to socket.
+     *
+     * @param str
+     *            String to send
+     * @throws IOException
+     *             unable to write to socket
+     */
+    public void println(final String str) throws IOException {
+        writer.print(str);
+	writer.print("\r\n");
+        writer.flush();
+    }
+
+    /**
+     * Handle command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    @Override
+    public void handleCommand() throws IOException {
+        currentLine = readLine();
+
+        if (currentLine == null)
+            return;
+
+        final StringTokenizer st = new StringTokenizer(currentLine, " ");
+        final String commandName = st.nextToken().toUpperCase();
+        if (commandName == null) {
+            LOGGER.severe("Command name is empty!");
+            exit();
+            return;
+        }
+
+        if (commandName.equals("HELO")) {
+            helo();
+        } else if (commandName.equals("EHLO")) {
+            ehlo();
+        } else if (commandName.equals("MAIL")) {
+            mail(currentLine);
+        } else if (commandName.equals("RCPT")) {
+            rcpt(currentLine);
+        } else if (commandName.equals("DATA")) {
+            data();
+        } else if (commandName.equals("BDAT")) {
+            bdat(currentLine);
+        } else if (commandName.equals("NOOP")) {
+            noop();
+        } else if (commandName.equals("RSET")) {
+            rset();
+        } else if (commandName.equals("QUIT")) {
+            quit();
+        } else if (commandName.equals("AUTH")) {
+            auth(currentLine);
+        } else {
+            LOGGER.log(Level.SEVERE, "ERROR command unknown: {0}", commandName);
+            println("-ERR unknown command");
+        }
+    }
+
+    protected String readLine() throws IOException {
+        currentLine = super.readLine();
+
+        if (currentLine == null) {
+	    // XXX - often happens when shutting down
+            //LOGGER.severe("Current line is null!");
+            exit();
+        }
+	return currentLine;
+    }
+
+    /**
+     * HELO command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void helo() throws IOException {
+        println("220 Ok");
+    }
+
+    /**
+     * EHLO command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void ehlo() throws IOException {
+        println("250-hello");
+	for (String ext : extensions)
+	    println("250-" + ext);
+        println("250 AUTH PLAIN");	// PLAIN is simplest to fake
+    }
+
+    /**
+     * MAIL command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void mail(String line) throws IOException {
+	ok();
+    }
+
+    /**
+     * RCPT command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void rcpt(String line) throws IOException {
+	ok();
+    }
+
+    /**
+     * DATA command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void data() throws IOException {
+        println("354 go ahead");
+	readMessage();
+	ok();
+    }
+
+    /**
+     * BDAT command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void bdat(String line) throws IOException {
+        StringTokenizer st = new StringTokenizer(line, " ");
+        String commandName = st.nextToken();
+	int bytes = Integer.parseInt(st.nextToken());
+	boolean last = st.hasMoreTokens() &&
+			st.nextToken().equalsIgnoreCase("LAST");
+	readBdatMessage(bytes, last);
+	ok();
+    }
+
+    /**
+     * Allow subclasses to override to save the message.
+     */
+    protected void setMessage(byte[] msg) {
+    }
+
+    /**
+     * Consume the message and save it.
+     */
+    protected void readMessage() throws IOException {
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	PrintWriter pw = new PrintWriter(new OutputStreamWriter(bos, "utf-8"));
+	String line;
+	while ((line = super.readLine()) != null) {
+	    if (line.equals("."))
+		break;
+	    if (line.startsWith("."))
+		line = line.substring(1);
+	    pw.print(line);
+	    pw.print("\r\n");
+	}
+	pw.close();
+	setMessage(bos.toByteArray());
+    }
+
+    /**
+     * Consume a chunk of the message and save it.
+     * Save the entire message when the last chunk is received.
+     */
+    protected void readBdatMessage(int bytes, boolean last) throws IOException {
+	byte[] data = new byte[bytes];
+	int len = data.length;
+	int off = 0;
+	int n;
+	while (len > 0 && (n = in.read(data, off, len)) > 0) {
+	    off += n;
+	    len -= n;
+	}
+	if (messageStream == null)
+	    messageStream = new ByteArrayOutputStream();
+	messageStream.write(data);
+	if (last) {
+	    setMessage(messageStream.toByteArray());
+	    messageStream = null;
+	}
+    }
+
+    /**
+     * NOOP command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void noop() throws IOException {
+        ok();
+    }
+
+    /**
+     * RSET command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void rset() throws IOException {
+        ok();
+    }
+
+    /**
+     * QUIT command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void quit() throws IOException {
+        println("221 BYE");
+        exit();
+    }
+
+    /**
+     * AUTH command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    public void auth(String line) throws IOException {
+        println("235 Authorized");
+    }
+
+    protected void ok() throws IOException {
+	println("250 OK");
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPIOExceptionTest.java b/mail/src/test/java/com/sun/mail/smtp/SMTPIOExceptionTest.java
new file mode 100644
index 0000000..bc80496
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPIOExceptionTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.CountDownLatch;
+
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.mail.event.ConnectionAdapter;
+import javax.mail.event.ConnectionEvent;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that the connection is closed when an IOException is detected.
+ */
+public final class SMTPIOExceptionTest {
+
+    // timeout the test in case of failure
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(5);
+
+    private boolean closed = false;
+
+    private static final int TIMEOUT = 200;	// I/O timeout, in millis
+
+    @Test
+    public void test() throws Exception {
+        TestServer server = null;
+	final CountDownLatch closedLatch = new CountDownLatch(1);
+        try {
+	    SMTPHandler handler = new SMTPHandler() {
+		@Override
+		public void rcpt(String line) throws IOException {
+		    try {
+			// delay long enough to cause timeout
+			Thread.sleep(2 * TIMEOUT);
+		    } catch (Exception ex) { }
+		    super.rcpt(line);
+		}
+	    };
+            server = new TestServer(handler);
+            server.start();
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.timeout", "" + TIMEOUT);
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Transport t = session.getTransport("smtp");
+	    /*
+	     * Use a listener to detect the connection being closed
+	     * because if we called isConnected() and the connection
+	     * wasn't already closed, it will issue a command that
+	     * might detect that the connection was closed, even
+	     * though it wasn't closed already.
+	     */
+	    t.addConnectionListener(new ConnectionAdapter() {
+		@Override
+		public void closed(ConnectionEvent e) {
+		    closedLatch.countDown();
+		}
+	    });
+            try {
+		MimeMessage msg = new MimeMessage(session);
+		msg.setRecipients(Message.RecipientType.TO, "joe@example.com");
+		msg.setSubject("test");
+		msg.setText("test");
+                t.connect();
+		t.sendMessage(msg, msg.getAllRecipients());
+	    } catch (MessagingException ex) {
+		// expect an exception from sendMessage
+		closedLatch.await();	// wait for the listener to run
+		// if we get here, the listener was called - SUCCESS
+            } finally {
+                t.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+		// wait for handler to exit
+		server.join();
+            }
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPLoginHandler.java b/mail/src/test/java/com/sun/mail/smtp/SMTPLoginHandler.java
new file mode 100644
index 0000000..0b294ea
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPLoginHandler.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.StringTokenizer;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.BASE64DecoderStream;
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * Handle connection with LOGIN or PLAIN authentication.
+ *
+ * @author Bill Shannon
+ */
+public class SMTPLoginHandler extends SMTPHandler {
+    protected String username = "test";
+    protected String password = "test";
+
+    /**
+     * EHLO command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    @Override
+    public void ehlo() throws IOException {
+        println("250-hello");
+        println("250-SMTPUTF8");
+        println("250-8BITMIME");
+        println("250 AUTH PLAIN LOGIN");
+    }
+
+    /**
+     * AUTH command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    @Override
+    public void auth(String line) throws IOException {
+        StringTokenizer ct = new StringTokenizer(line, " ");
+        String commandName = ct.nextToken().toUpperCase();
+	String mech = ct.nextToken().toUpperCase();
+	String ir = "";
+	if (ct.hasMoreTokens())
+	    ir = ct.nextToken();
+
+	if (LOGGER.isLoggable(Level.FINE))
+	    LOGGER.fine(line);
+	if (mech.equalsIgnoreCase("PLAIN"))
+	    plain(ir);
+	else if (mech.equalsIgnoreCase("LOGIN"))
+	    login(ir);
+	else
+	    println("501 bad AUTH mechanism");
+    }
+
+    /**
+     * AUTH LOGIN
+     */
+    private void login(String ir) throws IOException {
+	println("334");
+	// read user name
+	String resp = readLine();
+	if (!isBase64(resp)) {
+	    println("501 response not base64");
+	    return;
+	}
+	byte[] response = resp.getBytes(StandardCharsets.US_ASCII);
+	response = BASE64DecoderStream.decode(response);
+	String u = new String(response, StandardCharsets.UTF_8);
+	if (LOGGER.isLoggable(Level.FINE))
+	    LOGGER.fine("USER: " + u);
+	println("334");
+
+	// read password
+	resp = readLine();
+	if (!isBase64(resp)) {
+	    println("501 response not base64");
+	    return;
+	}
+	response = resp.getBytes(StandardCharsets.US_ASCII);
+	response = BASE64DecoderStream.decode(response);
+	String p = new String(response, StandardCharsets.UTF_8);
+	if (LOGGER.isLoggable(Level.FINE))
+	    LOGGER.fine("PASSWORD: " + p);
+
+	//System.out.printf("USER: %s, PASSWORD: %s%n", u, p);
+	if (!u.equals(username) || !p.equals(password)) {
+	    println("535 authentication failed");
+	    return;
+	}
+
+	println("235 Authenticated");
+    }
+
+    /**
+     * AUTH PLAIN
+     */
+    private void plain(String ir) throws IOException {
+	String auth = new String(BASE64DecoderStream.decode(
+				    ir.getBytes(StandardCharsets.US_ASCII)),
+				StandardCharsets.UTF_8);
+	String[] ap = auth.split("\000");
+	String u = ap[1];
+	String p = ap[2];
+	//System.out.printf("USER: %s, PASSWORD: %s%n", u, p);
+	if (!u.equals(username) || !p.equals(password)) {
+	    println("535 authentication failed");
+	    return;
+	}
+	println("235 Authenticated");
+    }
+
+    /**
+     * Is every character in the string a base64 character?
+     */
+    private boolean isBase64(String s) {
+	int len = s.length();
+	if (s.endsWith("=="))
+	    len -= 2;
+	else if (s.endsWith("="))
+	    len--;
+	for (int i = 0; i < len; i++) {
+	    char c = s.charAt(i);
+	    if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+		    (c >= '0' && c <= '9') || c == '+' || c == '/'))
+		return false;
+	}
+	return true;
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPSaslHandler.java b/mail/src/test/java/com/sun/mail/smtp/SMTPSaslHandler.java
new file mode 100644
index 0000000..2ab6a2b
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPSaslHandler.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.util.StringTokenizer;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+
+import javax.security.sasl.*;
+import javax.security.auth.callback.*;
+
+import com.sun.mail.util.BASE64EncoderStream;
+import com.sun.mail.util.BASE64DecoderStream;
+import com.sun.mail.util.ASCIIUtility;
+
+/**
+ * Handle connection with SASL authentication.
+ *
+ * @author Bill Shannon
+ */
+public class SMTPSaslHandler extends SMTPHandler {
+
+    /**
+     * EHLO command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    @Override
+    public void ehlo() throws IOException {
+        println("250-hello");
+        println("250 AUTH DIGEST-MD5");
+    }
+
+    /**
+     * AUTH command.
+     *
+     * @throws IOException
+     *             unable to read/write to socket
+     */
+    @Override
+    public void auth(String line) throws IOException {
+        StringTokenizer ct = new StringTokenizer(line, " ");
+        String commandName = ct.nextToken().toUpperCase();
+	String mech = ct.nextToken().toUpperCase();
+	String ir = "";
+	if (ct.hasMoreTokens())
+	    ir = ct.nextToken();
+
+	final String u = "test";
+	final String p = "test";
+	final String realm = "test";
+
+	CallbackHandler cbh = new CallbackHandler() {
+	    @Override
+	    public void handle(Callback[] callbacks) {
+		if (LOGGER.isLoggable(Level.FINE))
+		    LOGGER.fine("SASL callback length: " + callbacks.length);
+		for (int i = 0; i < callbacks.length; i++) {
+		    if (LOGGER.isLoggable(Level.FINE))
+			LOGGER.fine("SASL callback " + i + ": " + callbacks[i]);
+		    if (callbacks[i] instanceof NameCallback) {
+			NameCallback ncb = (NameCallback)callbacks[i];
+			ncb.setName(u);
+		    } else if (callbacks[i] instanceof PasswordCallback) {
+			PasswordCallback pcb = (PasswordCallback)callbacks[i];
+			pcb.setPassword(p.toCharArray());
+		    } else if (callbacks[i] instanceof AuthorizeCallback) {
+			AuthorizeCallback ac = (AuthorizeCallback)callbacks[i];
+			if (LOGGER.isLoggable(Level.FINE))
+			    LOGGER.fine("SASL authorize: " +
+				"authn: " + ac.getAuthenticationID() + ", " +
+				"authz: " + ac.getAuthorizationID() + ", " +
+				"authorized: " + ac.getAuthorizedID());
+			ac.setAuthorized(true);
+		    } else if (callbacks[i] instanceof RealmCallback) {
+			RealmCallback rcb = (RealmCallback)callbacks[i];
+			rcb.setText(realm != null ?
+				    realm : rcb.getDefaultText());
+		    } else if (callbacks[i] instanceof RealmChoiceCallback) {
+			RealmChoiceCallback rcb =
+			    (RealmChoiceCallback)callbacks[i];
+			if (realm == null)
+			    rcb.setSelectedIndex(rcb.getDefaultChoice());
+			else {
+			    // need to find specified realm in list
+			    String[] choices = rcb.getChoices();
+			    for (int k = 0; k < choices.length; k++) {
+				if (choices[k].equals(realm)) {
+				    rcb.setSelectedIndex(k);
+				    break;
+				}
+			    }
+			}
+		    }
+		}
+	    }
+	};
+
+	SaslServer ss;
+	try {
+	    ss = Sasl.createSaslServer(mech, "smtp", "localhost", null, cbh);
+	} catch (SaslException sex) {
+	    LOGGER.log(Level.FINE, "Failed to create SASL server", sex);
+	    println("501 Failed to create SASL server");
+	    return;
+	}
+	if (ss == null) {
+	    LOGGER.fine("No SASL support");
+	    println("501 No SASL support");
+	    return;
+	}
+	if (LOGGER.isLoggable(Level.FINE))
+	    LOGGER.fine("SASL server " + ss.getMechanismName());
+
+	byte[] response = ir.getBytes();
+	while (!ss.isComplete()) {
+	    try {
+		byte[] chal = ss.evaluateResponse(response);
+		// send challenge
+		if (LOGGER.isLoggable(Level.FINE))
+		    LOGGER.fine("SASL challenge: " +
+			ASCIIUtility.toString(chal, 0, chal.length));
+		byte[] ba = BASE64EncoderStream.encode(chal);
+		if (ba.length > 0)
+		    println("334 " +
+			    ASCIIUtility.toString(ba, 0, ba.length));
+		else
+		    println("334");
+		// read response
+		String resp = readLine();
+		if (!isBase64(resp)) {
+		    println("501 response not base64");
+		break;
+		}
+		response = resp.getBytes();
+		response = BASE64DecoderStream.decode(response);
+	    } catch (SaslException ex) {
+		println("501 " + ex.toString());
+		break;
+	    }
+	}
+
+	if (ss.isComplete() /*&& status == SUCCESS*/) {
+	    String qop = (String)ss.getNegotiatedProperty(Sasl.QOP);
+	    if (qop != null && (qop.equalsIgnoreCase("auth-int") ||
+				qop.equalsIgnoreCase("auth-conf"))) {
+		// XXX - NOT SUPPORTED!!!
+		LOGGER.fine(
+			"SASL Mechanism requires integrity or confidentiality");
+		println("501 " +
+			"SASL Mechanism requires integrity or confidentiality");
+		return;
+	    }
+	}
+
+	println("235 Authenticated");
+    }
+
+    /**
+     * Is every character in the string a base64 character?
+     */
+    private boolean isBase64(String s) {
+	int len = s.length();
+	if (s.endsWith("=="))
+	    len -= 2;
+	else if (s.endsWith("="))
+	    len--;
+	for (int i = 0; i < len; i++) {
+	    char c = s.charAt(i);
+	    if (!((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') ||
+		    (c >= '0' && c <= '9') || c == '+' || c == '/'))
+		return false;
+	}
+	return true;
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPSaslLoginTest.java b/mail/src/test/java/com/sun/mail/smtp/SMTPSaslLoginTest.java
new file mode 100644
index 0000000..fe8b5ba
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPSaslLoginTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.AuthenticationFailedException;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test login using a SASL mechanism on the server
+ * with SASL and non-SASL on the client.
+ */
+public class SMTPSaslLoginTest {
+
+    /**
+     * Test using non-SASL DIGEST-MD5.
+     */
+    @Test
+    public void testSuccess() {
+        TestServer server = null;
+        try {
+            server = new TestServer(new SMTPSaslHandler());
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect("test", "test");
+		// success!
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+
+    /**
+     * Test using non-SASL DIGEST-MD5 with incorrect password.
+     */
+    @Test
+    public void testFailure() {
+        TestServer server = null;
+        try {
+            server = new TestServer(new SMTPSaslHandler());
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect("test", "xtest");
+		// should have failed
+		fail("wrong password succeeded");
+	    } catch (AuthenticationFailedException ex) {
+		// success!
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+
+    /**
+     * Test using SASL DIGEST-MD5.
+     */
+    @Test
+    public void testSaslSuccess() {
+        TestServer server = null;
+        try {
+            server = new TestServer(new SMTPSaslHandler());
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.sasl.enable", "true");
+            properties.setProperty("mail.smtp.sasl.mechanisms", "DIGEST-MD5");
+            properties.setProperty("mail.smtp.auth.digest-md5.disable", "true");
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect("test", "test");
+		// success!
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+
+    /**
+     * Test using SASL DIGEST-MD5 with incorrect password.
+     */
+    @Test
+    public void testSaslFailure() {
+        TestServer server = null;
+        try {
+            server = new TestServer(new SMTPSaslHandler());
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.sasl.enable", "true");
+            properties.setProperty("mail.smtp.sasl.mechanisms", "DIGEST-MD5");
+            properties.setProperty("mail.smtp.auth.digest-md5.disable", "true");
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect("test", "xtest");
+		// should have failed
+		fail("wrong password succeeded");
+	    } catch (AuthenticationFailedException ex) {
+		// success!
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+
+    /**
+     * Test that AUTH with no mechanisms fails.
+     */
+    @Test
+    public void testAuthNoParam() {
+        TestServer server = null;
+        try {
+            server = new TestServer(new SMTPSaslHandler() {
+		@Override
+		public void ehlo() throws IOException {
+		    println("250-hello");
+		    println("250 AUTH");
+		}
+	    });
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect("test", "test");
+		fail("Connect didn't fail");
+	    } catch (AuthenticationFailedException ex) {
+		// success
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+
+    /**
+     * Test that no AUTH succeeds by skipping authentication entirely.
+     */
+    @Test
+    public void testNoAuth() {
+        TestServer server = null;
+        try {
+            server = new TestServer(new SMTPSaslHandler() {
+		@Override
+		public void ehlo() throws IOException {
+		    println("250-hello");
+		    println("250 XXX");
+		}
+		@Override
+		public void auth(String line) throws IOException {
+		    println("501 Authentication failed");
+		}
+	    });
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect("test", "test");
+		// success
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPUtf8Test.java b/mail/src/test/java/com/sun/mail/smtp/SMTPUtf8Test.java
new file mode 100644
index 0000000..07d2386
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPUtf8Test.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import javax.mail.Session;
+import javax.mail.Message;
+import javax.mail.Transport;
+import javax.mail.AuthenticationFailedException;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test UTF-8 user names and recipients.
+ */
+public class SMTPUtf8Test {
+
+    /**
+     * Test using UTF-8 user name.
+     */
+    @Test
+    public void testUtf8UserName() {
+        TestServer server = null;
+	final String user = "test\u03b1";
+        try {
+            server = new TestServer(new SMTPLoginHandler() {
+		@Override
+		public void auth(String line) throws IOException {
+		    username = user;
+		    password = user;
+		    super.auth(line);
+		}
+	    });
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.auth.mechanisms", "LOGIN");
+	    properties.setProperty("mail.mime.allowutf8",  "true");
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect(user, user);
+		// success!
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+
+    /**
+     * Test using UTF-8 user name but without mail.mime.allowutf8.
+     */
+    @Test
+    public void testUtf8UserNameNoAllowUtf8() {
+        TestServer server = null;
+	final String user = "test\u03b1";
+        try {
+            server = new TestServer(new SMTPLoginHandler() {
+		@Override
+		public void auth(String line) throws IOException {
+		    username = user;
+		    password = user;
+		    super.auth(line);
+		}
+	    });
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.auth.mechanisms", "LOGIN");
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect(user, user);
+		// success!
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+
+    /**
+     * Test using UTF-8 user name and PLAIN but without mail.mime.allowutf8.
+     */
+    @Test
+    public void testUtf8UserNamePlain() {
+        TestServer server = null;
+	final String user = "test\u03b1";
+        try {
+            server = new TestServer(new SMTPLoginHandler() {
+		@Override
+		public void auth(String line) throws IOException {
+		    username = user;
+		    password = user;
+		    super.auth(line);
+		}
+	    });
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.auth.mechanisms", "PLAIN");
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+                t.connect(user, user);
+		// success!
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+    }
+
+    private static class Envelope {
+	public String from;
+	public String to;
+    }
+
+    /**
+     * Test using UTF-8 From and To address.
+     */
+    @Test
+    public void testUtf8From() {
+        TestServer server = null;
+	final String test = "test\u03b1";
+	final String saddr = test + "@" + test + ".com";
+	final Envelope env = new Envelope();
+        try {
+            server = new TestServer(new SMTPHandler() {
+		@Override
+		public void ehlo() throws IOException {
+		    println("250-hello");
+		    println("250-SMTPUTF8");
+		    println("250 AUTH PLAIN");
+		}
+
+		@Override
+		public void mail(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip "MAIL"
+		    env.from = st.nextToken().
+				    replaceFirst("FROM:<(.*)>", "$1");
+		    if (!st.hasMoreTokens() ||
+			    !st.nextToken().equals("SMTPUTF8"))
+			println("500 fail");
+		    else
+			ok();
+		}
+
+		@Override
+		public void rcpt(String line) throws IOException {
+		    StringTokenizer st = new StringTokenizer(line);
+		    st.nextToken();	// skip "RCPT"
+		    env.to = st.nextToken().
+				    replaceFirst("TO:<(.*)>", "$1");
+		    ok();
+		}
+	    });
+            server.start();
+
+            Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+	    properties.setProperty("mail.mime.allowutf8",  "true");
+            //properties.setProperty("mail.debug.auth", "true");
+            Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            Transport t = session.getTransport("smtp");
+            try {
+		MimeMessage msg = new MimeMessage(session);
+		InternetAddress addr = new InternetAddress(saddr, test);
+		msg.setFrom(addr);
+		msg.setRecipient(Message.RecipientType.TO, addr);
+		msg.setSubject(test);
+		msg.setText(test + "\n");
+                t.connect("test", "test");
+		t.sendMessage(msg, msg.getAllRecipients());
+	    } catch (Exception ex) {
+		fail(ex.toString());
+            } finally {
+                t.close();
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+            }
+        }
+	// after we're sure the server is done
+	assertEquals(saddr, env.from);
+	assertEquals(saddr, env.to);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/smtp/SMTPWriteTimeoutTest.java b/mail/src/test/java/com/sun/mail/smtp/SMTPWriteTimeoutTest.java
new file mode 100644
index 0000000..d9fd759
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/smtp/SMTPWriteTimeoutTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.smtp;
+
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.activation.DataHandler;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.mail.util.ByteArrayDataSource;
+
+import com.sun.mail.test.TestServer;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.fail;
+
+/**
+ * Test that write timeouts work.
+ */
+public final class SMTPWriteTimeoutTest {
+
+    // timeout the test in case of failure
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(10);
+
+    private static final int TIMEOUT = 200;	// write timeout, in millis
+
+    @Test
+    public void test() throws Exception {
+        TestServer server = null;
+        try {
+	    SMTPHandler handler = new SMTPHandler() {
+		@Override
+		public void readMessage() throws IOException {
+		    try {
+			// delay long enough to cause timeout
+			Thread.sleep(5 * TIMEOUT);
+		    } catch (Exception ex) { }
+		    super.readMessage();
+		}
+	    };
+            server = new TestServer(handler);
+            server.start();
+            Thread.sleep(1000);
+
+            final Properties properties = new Properties();
+            properties.setProperty("mail.smtp.host", "localhost");
+            properties.setProperty("mail.smtp.port", "" + server.getPort());
+            properties.setProperty("mail.smtp.writetimeout", "" + TIMEOUT);
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+            final Transport t = session.getTransport("smtp");
+            try {
+		MimeMessage msg = new MimeMessage(session);
+		msg.setRecipients(Message.RecipientType.TO, "joe@example.com");
+		msg.setSubject("test");
+		byte[] bytes = new byte[16*1024*1024];
+		msg.setDataHandler(
+		    new DataHandler(new ByteArrayDataSource(bytes,
+				    "application/octet-stream")));
+                t.connect();
+		t.sendMessage(msg, msg.getAllRecipients());
+		fail("No exception");
+	    } catch (MessagingException ex) {
+		// expect an exception from sendMessage
+            } finally {
+                t.close();
+            }
+        } catch (final Exception e) {
+            e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+		server.interrupt();
+		// wait long enough for handler to exit
+		Thread.sleep(2 * TIMEOUT);
+            }
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/test/AsciiStringInputStream.java b/mail/src/test/java/com/sun/mail/test/AsciiStringInputStream.java
new file mode 100644
index 0000000..39caf7b
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/test/AsciiStringInputStream.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.test;
+
+import java.io.InputStream;
+
+/**
+ * Replacement for deprecated java.io.StringBufferInputStream
+ */
+public class AsciiStringInputStream extends InputStream {
+
+    private final String input;
+    private int position;
+
+    public AsciiStringInputStream(String input) {
+	this(input, true);
+    }
+
+    public AsciiStringInputStream(String input, boolean strict) {
+	if (strict) {
+	    for (int i = 0; i < input.length(); i++) {
+		if (input.charAt(i) > 0x7F) {
+		    throw new IllegalArgumentException("Not an ASCII string");
+		}
+	    }
+	}
+
+	this.input = input;
+    }
+
+    @Override
+    public int read() {
+	if (position < input.length()) {
+	    return input.charAt(position++) & 0xFF;
+	} else {
+	    return -1;
+	}
+    }
+
+}
diff --git a/mail/src/test/java/com/sun/mail/test/ClassLoaderSuite.java b/mail/src/test/java/com/sun/mail/test/ClassLoaderSuite.java
new file mode 100644
index 0000000..ceb4813
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/test/ClassLoaderSuite.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.test;
+
+import java.lang.annotation.*;
+import java.net.URL;
+import java.net.MalformedURLException;
+import java.net.URLClassLoader;
+import java.util.List;
+import java.util.ArrayList;
+
+import org.junit.runners.Suite;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerBuilder;
+import org.junit.runner.Runner;
+import org.junit.runner.notification.RunNotifier;
+
+/**
+ * A special test suite that loads each of the test classes
+ * in a separate class loader, along with the class under test.
+ * This allows the tests to test methods whose behavior depends on
+ * the value of a System property that's read at class initialization
+ * time; each test can set a different value of the System property
+ * and the corresponding class under test will be loaded in a
+ * separate class loader. <p>
+ *
+ * To use this class, create a test suite class:
+ *
+ * <pre>
+ * @RunWith(ClassLoaderSuite.class)
+ * @SuiteClasses({ MyTest1.class, MyTest2.class })
+ * @TestClass(ClassToTest.class)
+ * public class MyTestSuite {
+ * }
+ * </pre>
+ *
+ * The MyTest1 and MyTest2 classes are written as normal JUnit
+ * test classes.  Set the System property to test in the @BeforeClass
+ * method of these classes.
+ *
+ * @author Bill Shannon
+ */
+
+public class ClassLoaderSuite extends Suite {
+    /**
+     * An annotation to be used on the test suite class to indicate
+     * the class under test.  The class is used to find the classpath
+     * to allow loading the class under test in a separate class loader.
+     * Note that other classes in the same classpath will also be loaded
+     * in the separate class loader, along with the test classes.
+     */
+    @Retention(RetentionPolicy.RUNTIME)
+    @Target(ElementType.TYPE)
+    @Inherited
+    public @interface TestClass {
+	public Class<?> value();
+    }
+
+    /**
+     * A special class loader that loads classes from its own class path
+     * (specified via URLs) before delegating to the parent class loader.
+     * This is used to load the test classes in separate class loaders,
+     * even though those classes are also loaded in the parent class loader.
+     */
+    static class TestClassLoader extends URLClassLoader {
+	public TestClassLoader(URL[] urls, ClassLoader parent) {
+	    super(urls, parent);
+	}
+
+	@Override
+	public Class<?> loadClass(String name, boolean resolve)
+				    throws ClassNotFoundException {
+	    Class<?> c = null;
+	    try {
+		c = findLoadedClass(name);
+		if (c != null)
+		    return c;
+		c = findClass(name);
+		if (resolve)
+		    resolveClass(c);
+	    } catch (ClassNotFoundException cex) {
+		c = super.loadClass(name, resolve);
+	    }
+	    return c;
+	}
+    }
+
+    /**
+     * Constructor.
+     */
+    public ClassLoaderSuite(Class<?> klass, RunnerBuilder builder)
+				throws InitializationError {
+	super(builder, klass,
+	    reloadClasses(getTestClass(klass), getSuiteClasses(klass)));
+    }
+
+    /**
+     * Set the thread's context class loader to the class loader
+     * for the test class.
+     */
+    @Override
+    protected void runChild(Runner runner, RunNotifier notifier) {
+	// XXX - is it safe to assume it's always a ParentRunner?
+	ParentRunner<?> pr = (ParentRunner<?>)runner;
+	ClassLoader cl = null;
+	try {
+	    cl = Thread.currentThread().getContextClassLoader();
+	    Thread.currentThread().setContextClassLoader(
+		pr.getTestClass().getJavaClass().getClassLoader());
+	    super.runChild(runner, notifier);
+	} finally {
+	    Thread.currentThread().setContextClassLoader(cl);
+	}
+    }
+
+    /**
+     * Get the value of the SuiteClasses annotation.
+     */
+    private static Class<?>[] getSuiteClasses(Class<?> klass)
+				throws InitializationError {
+	SuiteClasses annotation = klass.getAnnotation(SuiteClasses.class);
+	if (annotation == null)
+	    throw new InitializationError("class '" + klass.getName() +
+		"' must have a SuiteClasses annotation");
+	return annotation.value();
+    }
+
+    /**
+     * Get the value of the TestClass annotation.
+     */
+    private static Class<?> getTestClass(Class<?> klass)
+				throws InitializationError {
+	TestClass annotation = klass.getAnnotation(TestClass.class);
+	if (annotation == null)
+	    throw new InitializationError("class '" + klass.getName() +
+		"' must have a TestClass annotation");
+	return annotation.value();
+    }
+
+    /**
+     * Reload the classes in a separate class loader.
+     */
+    private static Class<?>[] reloadClasses(Class<?> testClass,
+			Class<?>[] suiteClasses) throws InitializationError {
+	URL[] urls = new URL[] {
+	    classpathOf(testClass),
+	    classpathOf(ClassLoaderSuite.class)
+	};
+	Class<?> sc = null;
+	try {
+	    for (int i = 0; i < suiteClasses.length; i++) {
+		sc = suiteClasses[i];
+		ClassLoader cl = new TestClassLoader(urls,
+		    ClassLoaderSuite.class.getClassLoader());
+		suiteClasses[i] = cl.loadClass(sc.getName());
+	    }
+	    return suiteClasses;
+	} catch (ClassNotFoundException cex) {
+	    throw new InitializationError("could not reload class: " + sc);
+	}
+    }
+
+    /**
+     * Return the classpath entry used to load the named resource.
+     * XXX - Only handles file: and jar: URLs.
+     */
+    private static URL classpathOf(Class<?> c) {
+	String name = "/" + c.getName().replace('.', '/') + ".class";
+	try {
+	    URL url = ClassLoaderSuite.class.getResource(name);
+	    if (url.getProtocol().equals("file")) {
+		String file = url.getPath();
+		if (file.endsWith(name))	// has to be true?
+		    file = file.substring(0, file.length() - name.length() + 1);
+//System.out.println("file URL " + url + " has CLASSPATH " + file);
+		return new URL("file", null, file);
+	    } else if (url.getProtocol().equals("jar")) {
+		String file = url.getPath();
+		int i = file.lastIndexOf('!');
+		if (i >= 0)
+		    file = file.substring(0, i);
+//System.out.println("jar URL " + url + " has CLASSPATH " + file);
+		return new URL(file);
+	    } else
+		return url;
+	} catch (MalformedURLException mex) {
+	    return null;
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/test/NullOutputStream.java b/mail/src/test/java/com/sun/mail/test/NullOutputStream.java
new file mode 100644
index 0000000..d78fc87
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/test/NullOutputStream.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.test;
+
+import java.io.*;
+
+/**
+ * An OutputStream that throws away all data written to it.
+ */
+public class NullOutputStream extends OutputStream {
+
+    @Override
+    public void write(int b) throws IOException {
+    }
+
+    @Override
+    public void write(byte[] b) throws IOException {
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/test/ProtocolHandler.java b/mail/src/test/java/com/sun/mail/test/ProtocolHandler.java
new file mode 100644
index 0000000..434ff0a
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/test/ProtocolHandler.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.test;
+
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.io.PushbackInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.Socket;
+import java.net.SocketException;
+import java.nio.charset.StandardCharsets;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+import javax.net.ssl.SSLException;
+
+/**
+ * Handle protocol connection.
+ *
+ * Inspired by, and derived from, POP3Handler by sbo.
+ *
+ * @author sbo
+ * @author Bill Shannon
+ */
+public abstract class ProtocolHandler implements Runnable, Cloneable {
+
+    /** Logger for this class. */
+    protected final Logger LOGGER = Logger.getLogger(this.getClass().getName());
+
+    /** Client socket. */
+    protected Socket clientSocket;
+
+    /** Quit? */
+    protected boolean quit;
+
+    /** Writer to socket. */
+    protected PrintWriter writer;
+
+    /** Input from socket. */
+    protected InputStream in;
+
+    /**
+     * Sets the client socket.
+     *
+     * @param clientSocket	the client socket
+     */
+    public final void setClientSocket(final Socket clientSocket)
+				throws IOException {
+        this.clientSocket = clientSocket;
+	writer = new PrintWriter(new OutputStreamWriter(
+		    clientSocket.getOutputStream(), StandardCharsets.UTF_8));
+	in = new BufferedInputStream(clientSocket.getInputStream());
+    }
+
+    /**
+     * Optionally send a greeting when first connected.
+     */
+    public void sendGreetings() throws IOException {
+    }
+
+    /**
+     * Read and process a single command.
+     */
+    public abstract void handleCommand() throws IOException;
+
+    /**
+     * Read a single line terminated by newline or CRLF.
+     * Convert the UTF-8 bytes in the line (minus the line terminator)
+     * to a String.
+     */
+    protected String readLine() throws IOException {
+        byte[] buf = new byte[128];
+
+        int room = buf.length;
+        int offset = 0;
+        int c;
+
+	while ((c = in.read()) != -1) {
+	    if (c == '\n') {
+		break;
+	    } else if (c == '\r') {
+		int c2 = in.read();
+		if ((c2 != '\n') && (c2 != -1)) {
+		    if (!(in instanceof PushbackInputStream))
+			this.in = new PushbackInputStream(in);
+		    ((PushbackInputStream)in).unread(c2);
+		}
+		break;
+	    } else {
+		if (--room < 0) {
+		    byte[] nbuf = new byte[offset + 128];
+		    room = nbuf.length - offset - 1;
+		    System.arraycopy(buf, 0, nbuf, 0, offset);
+		    buf = nbuf;
+		}
+		buf[offset++] = (byte)c;
+	    }
+	}
+	if ((c == -1) && (offset == 0))
+	    return null;
+	return new String(buf, 0, offset, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public final void run() {
+        try {
+
+            sendGreetings();
+
+            while (!quit) {
+                handleCommand();
+            }
+
+            //clientSocket.close();
+	} catch (SocketException sex) {
+	    // ignore it, often get "connection reset" when client closes
+	} catch (SSLException sex) {
+	    // ignore it, often occurs when testing SSL
+        } catch (Exception e) {
+            LOGGER.log(Level.SEVERE, "Error", e);
+        } finally {
+            try {
+		if (clientSocket != null)
+		    clientSocket.close();
+            } catch (final IOException ioe) {
+                LOGGER.log(Level.SEVERE, "Error", ioe);
+            }
+        }
+    }
+
+    /**
+     * Quit.
+     */
+    public void exit() {
+        quit = true;
+        try {
+            if (clientSocket != null && !clientSocket.isClosed()) {
+                clientSocket.close();
+		clientSocket = null;
+            }
+        } catch (final IOException e) {
+            LOGGER.log(Level.SEVERE, "Error", e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Object clone() {
+        try {
+            return super.clone();
+        } catch (final CloneNotSupportedException e) {
+            throw new AssertionError(e);
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/test/SavedSocketFactory.java b/mail/src/test/java/com/sun/mail/test/SavedSocketFactory.java
new file mode 100644
index 0000000..095f2ce
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/test/SavedSocketFactory.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2012, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.test;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.net.InetAddress;
+import javax.net.SocketFactory;
+
+/**
+ * A SocketFactory that saves the Socket it creates so that it can be
+ * accessed later.  Useful for checking that sockets are closed properly.
+ */
+public class SavedSocketFactory extends SocketFactory {
+    private SocketFactory factory;
+    private Socket saved;
+
+    public SavedSocketFactory() {
+	super();
+	try {
+	    factory = SocketFactory.getDefault();
+	} catch(Exception ex) {
+	    // ignore
+	}
+    }
+
+    @Override
+    public Socket createSocket() throws IOException {
+	return save(factory.createSocket());
+    }
+
+    @Override
+    public Socket createSocket(InetAddress host, int port)
+				throws IOException {
+	return save(factory.createSocket(host, port));
+    }
+
+    @Override
+    public Socket createSocket(InetAddress address, int port,
+				InetAddress localAddress, int localPort)
+				throws IOException {
+	return save(factory.createSocket(
+				    address, port, localAddress, localPort));
+    }
+
+    @Override
+    public Socket createSocket(String host, int port) throws IOException {
+	return save(factory.createSocket(host, port));
+    }
+
+    @Override
+    public Socket createSocket(String host, int port,
+				InetAddress localHost, int localPort)
+				throws IOException {
+	return save(factory.createSocket(host, port, localHost, localPort));
+    }
+
+    public Socket getSocket() {
+	return saved;
+    }
+
+    private Socket save(Socket s) {
+	saved = s;
+	return saved;
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/test/TestSSLSocketFactory.java b/mail/src/test/java/com/sun/mail/test/TestSSLSocketFactory.java
new file mode 100644
index 0000000..de5a8b5
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/test/TestSSLSocketFactory.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.test;
+
+import java.io.IOException;
+import java.net.*;
+import java.security.GeneralSecurityException;
+
+import javax.net.ssl.*;
+
+/**
+ * An SSL socket factory for testing that tracks whether it's being used.
+ * <p>
+ *
+ * An instance of this factory can be set as the value of the
+ * <code>mail.&lt;protocol&gt;.ssl.socketFactory</code> property.
+ *
+ * @since	JavaMail 1.5.3
+ * @author	Stephan Sann
+ * @author	Bill Shannon
+ */
+public class TestSSLSocketFactory extends SSLSocketFactory {
+
+    /** Holds a SSLSocketFactory to pass all API-method-calls to */
+    private SSLSocketFactory defaultFactory = null;
+
+    /** Was a socket created? */
+    private boolean socketCreated;
+
+    /** Was a socket wrapped? */
+    private boolean socketWrapped;
+
+    private String[] suites;
+
+    /**
+     * Initializes a new TestSSLSocketFactory.
+     * 
+     * @throws  GeneralSecurityException for security errors
+     */
+    public TestSSLSocketFactory() throws GeneralSecurityException {
+	this("TLS");
+    }
+
+    /**
+     * Initializes a new TestSSLSocketFactory with a given protocol.
+     * Normally the protocol will be specified as "TLS".
+     * 
+     * @param   protocol  The protocol to use
+     * @throws  NoSuchAlgorithmException if given protocol is not supported
+     * @throws  GeneralSecurityException for security errors
+     */
+    public TestSSLSocketFactory(String protocol)
+				throws GeneralSecurityException {
+
+	// Get the default SSLSocketFactory to delegate all API-calls to.
+	defaultFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
+    }
+
+    /**
+     * Was a socket created using one of the createSocket methods?
+     */
+    public boolean getSocketCreated() {
+	return socketCreated;
+    }
+
+    /**
+     * Was a socket wrapped using the createSocket method that takes a Socket?
+     */
+    public boolean getSocketWrapped() {
+	return socketWrapped;
+    }
+
+    /**
+     * Set the default cipher suites to be applied to future sockets.
+     */
+    public void setDefaultCipherSuites(String[] suites) {
+	this.suites = suites;
+    }
+
+    /**
+     * Configure the socket to be returned.
+     */
+    private Socket configure(Socket socket) {
+	if (socket instanceof SSLSocket) {	// XXX - always true
+	    SSLSocket s = (SSLSocket)socket;
+	    if (suites != null)
+		s.setEnabledCipherSuites(suites);
+	}
+	return socket;
+    }
+
+
+    // SocketFactory methods
+
+    /* (non-Javadoc)
+     * @see javax.net.ssl.SSLSocketFactory#createSocket(java.net.Socket,
+     *						java.lang.String, int, boolean)
+     */
+    @Override
+    public synchronized Socket createSocket(Socket socket, String s, int i,
+				boolean flag) throws IOException {
+	Socket wrappedSocket = defaultFactory.createSocket(socket, s, i, flag);
+	socketWrapped = true;
+	return configure(wrappedSocket);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.ssl.SSLSocketFactory#getDefaultCipherSuites()
+     */
+    @Override
+    public synchronized String[] getDefaultCipherSuites() {
+	if (suites != null)
+	    return suites.clone();
+	else
+	    return defaultFactory.getDefaultCipherSuites();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.ssl.SSLSocketFactory#getSupportedCipherSuites()
+     */
+    @Override
+    public synchronized String[] getSupportedCipherSuites() {
+	return defaultFactory.getSupportedCipherSuites();
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket()
+     */
+    @Override
+    public synchronized Socket createSocket() throws IOException {
+	Socket socket = defaultFactory.createSocket();
+	socketCreated = true;
+	return configure(socket);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int,
+     *						java.net.InetAddress, int)
+     */
+    @Override
+    public synchronized Socket createSocket(InetAddress inetaddress, int i,
+			InetAddress inetaddress1, int j) throws IOException {
+	Socket socket =
+		defaultFactory.createSocket(inetaddress, i, inetaddress1, j);
+	socketCreated = true;
+	return configure(socket);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int)
+     */
+    @Override
+    public synchronized Socket createSocket(InetAddress inetaddress, int i)
+				throws IOException {
+	Socket socket = defaultFactory.createSocket(inetaddress, i);
+	socketCreated = true;
+	return configure(socket);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.lang.String, int,
+     *						java.net.InetAddress, int)
+     */
+    @Override
+    public synchronized Socket createSocket(String s, int i,
+				InetAddress inetaddress, int j)
+				throws IOException, UnknownHostException {
+	Socket socket = defaultFactory.createSocket(s, i, inetaddress, j);
+	socketCreated = true;
+	return configure(socket);
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.lang.String, int)
+     */
+    @Override
+    public synchronized Socket createSocket(String s, int i)
+				throws IOException, UnknownHostException {
+	Socket socket = defaultFactory.createSocket(s, i);
+	socketCreated = true;
+	return configure(socket);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/test/TestServer.java b/mail/src/test/java/com/sun/mail/test/TestServer.java
new file mode 100644
index 0000000..bf60350
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/test/TestServer.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.test;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.InetSocketAddress;
+import javax.net.ssl.*;
+
+/**
+ * A simple server for testing.
+ *
+ * Inspired by, and derived from, POP3Server by sbo.
+ *
+ * @author sbo
+ * @author Bill Shannon
+ */
+public final class TestServer extends Thread {
+
+    /** Server socket. */
+    private ServerSocket serverSocket;
+
+    /** Keep on? */
+    private volatile boolean keepOn;
+
+    /** Protocol handler. */
+    private final ProtocolHandler handler;
+
+    private List<Thread> clients = new ArrayList<Thread>();
+
+    /**
+     * Test server.
+     *
+     * @param handler	the protocol handler
+     */
+    public TestServer(final ProtocolHandler handler) throws IOException {
+	this(handler, false);
+    }
+
+    /**
+     * Test server.
+     *
+     * @param handler	the protocol handler
+     * @param isSSL	create SSL sockets?
+     */
+    public TestServer(final ProtocolHandler handler, final boolean isSSL)
+				throws IOException {
+        this.handler = handler;
+
+	/*
+	 * Allowing the JDK to pick a port number sometimes results in it
+	 * picking a number that's already in use by another process, but
+	 * no error is returned.  Picking it ourself allows us to make sure
+	 * that it's not used before we pick it.  Hopefully the socket
+	 * creation will fail if the port is already in use.
+	 *
+	 * XXX - perhaps we should use Random to choose a port number in
+	 * the emphemeral range, in case a lot of low port numbers are
+	 * already in use.
+	 */
+	for (int port = 49152; port < 50000 /*65535*/; port++) {
+	    /*
+	    if (isListening(port))
+		continue;
+	    */
+	    try {
+		serverSocket = createServerSocket(port, isSSL);
+		return;
+	    } catch (IOException ex) {
+		// ignore
+	    }
+	}
+	throw new RuntimeException("Can't find unused port");
+    }
+
+    private static ServerSocket createServerSocket(int port, boolean isSSL)
+				throws IOException {
+	ServerSocket ss;
+	if (isSSL) {
+	    SSLServerSocketFactory sf =
+		(SSLServerSocketFactory)SSLServerSocketFactory.getDefault();
+	    ss = sf.createServerSocket(port);
+	    // enable only the anonymous cipher suites so we don't have to
+	    // create a server certificate
+	    List<String> anon = new ArrayList<>();
+	    String[] suites = sf.getSupportedCipherSuites();
+	    for (int i = 0; i < suites.length; i++) {
+		if (suites[i].indexOf("_anon_") >= 0) {
+		    anon.add(suites[i]);
+		}
+	    }
+	    ((SSLServerSocket)ss).setEnabledCipherSuites(
+				    anon.toArray(new String[anon.size()]));
+	} else
+	    ss = new ServerSocket(port);
+	return ss;
+    }
+
+    /**
+     * Return the port the server is listening on.
+     */
+    public int getPort() {
+	return serverSocket.getLocalPort();
+    }
+
+    /**
+     * Exit server.
+     */
+    public void quit() {
+        try {
+            keepOn = false;
+            if (serverSocket != null && !serverSocket.isClosed()) {
+                serverSocket.close();
+                serverSocket = null;
+            }
+        } catch (final IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void start() {
+	super.start();
+	// don't return until server is really listening
+	// XXX - this might not be necessary
+	for (int tries = 0; tries < 10; tries++) {
+	    if (isListening(getPort())) {
+		return;
+	    }
+	    try {
+		Thread.sleep(100);
+	    } catch (InterruptedException ex) { }
+	}
+	throw new RuntimeException("Server isn't listening");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void run() {
+        try {
+            keepOn = true;
+
+            while (keepOn) {
+                try {
+                    final Socket clientSocket = serverSocket.accept();
+                    final ProtocolHandler pHandler =
+			(ProtocolHandler)handler.clone();
+                    pHandler.setClientSocket(clientSocket);
+                    Thread t = new Thread(pHandler);
+		    synchronized (clients) {
+			clients.add(t);
+		    }
+		    t.start();
+                } catch (final IOException e) {
+                    //e.printStackTrace();
+		} catch (NullPointerException nex) {
+		    // serverSocket can be set to null before we could check
+                }
+            }
+        } finally {
+            quit();
+        }
+    }
+
+    /**
+     * Return number of clients ever created.
+     */
+    public int clientCount() {
+	synchronized (clients) {
+	    // isListening creates a client that we don't count
+	    return clients.size() - 1;
+	}
+    }
+
+    /**
+     * Wait for at least n clients to terminate.
+     */
+    public void waitForClients(int n) {
+	if (n > clientCount())
+	    throw new RuntimeException("not that many clients");
+	for (;;) {
+	    int num = -1;	// ignore isListening client
+	    synchronized (clients) {
+		for (Thread t : clients) {
+		    if (!t.isAlive()) {
+			if (++num >= n)
+			    return;
+		    }
+		}
+	    }
+	    try {
+		Thread.sleep(100);
+	    } catch (InterruptedException ex) { }
+	}
+    }
+
+    private boolean isListening(int port) {
+	try {
+	    Socket s = new Socket();
+	    s.connect(new InetSocketAddress("localhost", port), 100);
+	    // it's listening!
+	    s.close();
+	    return true;
+	} catch (Exception ex) {
+	    //System.out.println(ex);
+	}
+	return false;
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/test/TestSocketFactory.java b/mail/src/test/java/com/sun/mail/test/TestSocketFactory.java
new file mode 100644
index 0000000..a0e751e
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/test/TestSocketFactory.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.test;
+
+import java.io.IOException;
+import java.net.*;
+
+import javax.net.SocketFactory;
+
+/**
+ * A socket factory for testing that tracks whether it's being used.
+ * <p>
+ *
+ * An instance of this factory can be set as the value of the
+ * <code>mail.&lt;protocol&gt;.socketFactory</code> property.
+ *
+ * @since	JavaMail 1.5.3
+ * @author	Stephan Sann
+ * @author	Bill Shannon
+ */
+public class TestSocketFactory extends SocketFactory {
+
+    /** Holds a SocketFactory to pass all API-method-calls to */
+    private SocketFactory defaultFactory = null;
+
+    /** Was a socket created? */
+    private boolean socketCreated;
+
+    /**
+     * Initializes a new TestSocketFactory.
+     */
+    public TestSocketFactory() {
+	// Get the default SocketFactory to delegate all API-calls to.
+	defaultFactory = SocketFactory.getDefault();
+    }
+
+    /**
+     * Was a socket created using one of the createSocket methods?
+     */
+    public boolean getSocketCreated() {
+	return socketCreated;
+    }
+
+
+    // SocketFactory methods
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket()
+     */
+    @Override
+    public synchronized Socket createSocket() throws IOException {
+	Socket socket = defaultFactory.createSocket();
+	socketCreated = true;
+	return socket;
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int,
+     *						java.net.InetAddress, int)
+     */
+    @Override
+    public synchronized Socket createSocket(InetAddress inetaddress, int i,
+			InetAddress inetaddress1, int j) throws IOException {
+	Socket socket =
+		defaultFactory.createSocket(inetaddress, i, inetaddress1, j);
+	socketCreated = true;
+	return socket;
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.net.InetAddress, int)
+     */
+    @Override
+    public synchronized Socket createSocket(InetAddress inetaddress, int i)
+				throws IOException {
+	Socket socket = defaultFactory.createSocket(inetaddress, i);
+	socketCreated = true;
+	return socket;
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.lang.String, int,
+     *						java.net.InetAddress, int)
+     */
+    @Override
+    public synchronized Socket createSocket(String s, int i,
+				InetAddress inetaddress, int j)
+				throws IOException, UnknownHostException {
+	Socket socket = defaultFactory.createSocket(s, i, inetaddress, j);
+	socketCreated = true;
+	return socket;
+    }
+
+    /* (non-Javadoc)
+     * @see javax.net.SocketFactory#createSocket(java.lang.String, int)
+     */
+    @Override
+    public synchronized Socket createSocket(String s, int i)
+				throws IOException, UnknownHostException {
+	Socket socket = defaultFactory.createSocket(s, i);
+	socketCreated = true;
+	return socket;
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/BASE64Test.java b/mail/src/test/java/com/sun/mail/util/BASE64Test.java
new file mode 100644
index 0000000..6a558b2
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/BASE64Test.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.util.*;
+import javax.mail.*;
+
+import org.junit.Test;
+import org.junit.Assert;
+
+/**
+ * Test base64 encoding/decoding.
+ *
+ * @author Bill Shannon
+ */
+
+public class BASE64Test {
+
+    @Test
+    public void test() throws IOException {
+	// test a range of buffer sizes
+	for (int bufsize = 1; bufsize < 100; bufsize++) {
+	    //System.out.println("Buffer size: " + bufsize);
+	    byte[] buf = new byte[bufsize];
+
+	    // test a set of patterns
+
+	    // first, all zeroes
+	    Arrays.fill(buf, (byte)0);
+	    test("Zeroes", buf);
+
+	    // now, all ones
+	    Arrays.fill(buf, (byte)0xff);
+	    test("Ones", buf);
+
+	    // now, small integers
+	    for (int i = 0; i < bufsize; i++)
+		buf[i] = (byte)i;
+	    test("Ints", buf);
+
+	    // finally, random numbers
+	    Random rnd = new Random();
+	    rnd.nextBytes(buf);
+	    test("Random", buf);
+	}
+    }
+
+    /**
+     * Encode and decode the buffer and check that we get back the
+     * same data.  Encoding is done both with the static encode
+     * method and using the encoding stream.  Likewise, decoding
+     * is done both with the static decode method and using the
+     * decoding stream.  Check all combinations.
+     */
+    private static void test(String name, byte[] buf) throws IOException {
+	// first encode and decode with method
+	byte[] encoded = BASE64EncoderStream.encode(buf);
+	byte[] nbuf = BASE64DecoderStream.decode(encoded);
+	compare(name, "method", buf, nbuf);
+
+	// encode with stream, compare with method encoded version
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	BASE64EncoderStream os =
+	    new BASE64EncoderStream(bos, Integer.MAX_VALUE);
+	os.write(buf);
+	os.flush();
+	os.close();
+	byte[] sbuf = bos.toByteArray();
+	compare(name, "encoded", encoded, sbuf);
+
+	// encode with stream, decode with method
+	nbuf = BASE64DecoderStream.decode(sbuf);
+	compare(name, "stream->method", buf, nbuf);
+
+	// encode with stream, decode with stream
+	ByteArrayInputStream bin = new ByteArrayInputStream(sbuf);
+	BASE64DecoderStream in = new BASE64DecoderStream(bin);
+	readAll(in, nbuf, nbuf.length);
+	compare(name, "stream", buf, nbuf);
+
+	// encode with method, decode with stream
+	for (int i = 1; i <= nbuf.length; i++) {
+	    bin = new ByteArrayInputStream(encoded);
+	    in = new BASE64DecoderStream(bin);
+	    readAll(in, nbuf, i);
+	    compare(name, "method->stream " + i, buf, nbuf);
+	}
+
+	// encode with stream, decode with stream, many buffers
+
+	// first, fill the output with multiple buffers, up to the limit
+	int limit = 10000;		// more than 8K
+	bos = new ByteArrayOutputStream();
+	os = new BASE64EncoderStream(bos);
+	for (int size = 0, blen = buf.length; size < limit; size += blen) {
+	    if (size + blen > limit) {
+		blen = limit - size;
+		// write out partial buffer, starting at non-zero offset
+		os.write(buf, buf.length - blen, blen);
+	    } else
+		os.write(buf);
+	}
+	os.flush();
+	os.close();
+
+	// read the encoded output and check the line length
+	String type = "big stream";		// for error messages below
+	sbuf = bos.toByteArray();
+	bin = new ByteArrayInputStream(sbuf);
+	byte[] inbuf = new byte[78];
+	for (int size = 0, blen = 76; size < limit; size += blen) {
+	    if (size + blen > limit) {
+		blen = limit - size;
+		int n = bin.read(inbuf, 0, blen);
+		Assert.assertEquals(name + ": " + type +
+		    " read wrong size at offset " + (size + blen), blen, n);
+	    } else {
+		int n = bin.read(inbuf, 0, blen + 2);
+		Assert.assertEquals(name + ": " + type +
+		    " read wrong size at offset " + (size + blen), blen + 2, n);
+		Assert.assertTrue(name + ": " + type +
+		    " no CRLF: at offset " + (size + blen),
+		    inbuf[blen] == (byte)'\r' && inbuf[blen+1] == (byte)'\n');
+	    }
+	}
+
+	// decode the output and check the data
+	bin = new ByteArrayInputStream(sbuf);
+	in = new BASE64DecoderStream(bin);
+	inbuf = new byte[buf.length];
+	for (int size = 0, blen = buf.length; size < limit; size += blen) {
+	    if (size + blen > limit)
+		blen = limit - size;
+	    int n = in.read(nbuf, 0, blen);
+	    Assert.assertEquals(name + ": " + type +
+		" read decoded wrong size at offset " + (size + blen), blen, n);
+	    if (blen != buf.length) {
+		// have to compare with end of original buffer
+		byte[] cbuf = new byte[blen];
+		System.arraycopy(buf, buf.length - blen, cbuf, 0, blen);
+		// need a version of the read buffer that's the right size
+		byte[] cnbuf = new byte[blen];
+		System.arraycopy(nbuf, 0, cnbuf, 0, blen);
+		compare(name, type, cbuf, cnbuf);
+	    } else {
+		compare(name, type, buf, nbuf);
+	    }
+	}
+    }
+
+    private static byte[] origLine;
+    private static byte[] encodedLine;
+    static {
+	try {
+	    origLine =
+		"000000000000000000000000000000000000000000000000000000000".
+		    getBytes("us-ascii");
+	    encodedLine =
+		("MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw" +
+		"MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw" + "\r\n").
+		    getBytes("us-ascii");
+	} catch (UnsupportedEncodingException uex) {
+	    // should never happen;
+	}
+    }
+
+    /**
+     * Test that CRLF is inserted at the right place.
+     * Test combinations of array writes of different sizes
+     * and single byte writes.
+     */
+    @Test
+    public void testLineLength() throws Exception {
+	for (int i = 0; i < origLine.length; i++) {
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+	    OutputStream os = new BASE64EncoderStream(bos);
+	    os.write(origLine, 0, i);
+	    os.write(origLine, i, origLine.length - i);
+	    os.write((byte)'0');
+	    os.flush();
+	    os.close();
+
+	    byte[] line = new byte[encodedLine.length];
+	    System.arraycopy(bos.toByteArray(), 0, line, 0, line.length);
+	    Assert.assertArrayEquals("encoded line " + i, encodedLine, line);
+	}
+
+	for (int i = 0; i < origLine.length; i++) {
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+	    OutputStream os = new BASE64EncoderStream(bos);
+	    os.write(origLine, 0, i);
+	    os.write(origLine, i, origLine.length - i);
+	    os.write(origLine);
+	    os.flush();
+	    os.close();
+
+	    byte[] line = new byte[encodedLine.length];
+	    System.arraycopy(bos.toByteArray(), 0, line, 0, line.length);
+	    Assert.assertArrayEquals("all arrays, encoded line " + i,
+					encodedLine, line);
+	}
+
+	for (int i = 1; i < 5; i++) {
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+	    OutputStream os = new BASE64EncoderStream(bos);
+	    for (int j = 0; j < i; j++)
+		os.write((byte)'0');
+	    os.write(origLine, i, origLine.length - i);
+	    os.write((byte)'0');
+	    os.flush();
+	    os.close();
+
+	    byte[] line = new byte[encodedLine.length];
+	    System.arraycopy(bos.toByteArray(), 0, line, 0, line.length);
+	    Assert.assertArrayEquals("single byte first encoded line " + i,
+					encodedLine, line);
+	}
+	for (int i = origLine.length - 5; i < origLine.length; i++) {
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+
+	    OutputStream os = new BASE64EncoderStream(bos);
+	    os.write(origLine, 0, i);
+	    for (int j = 0; j < origLine.length - i; j++)
+		os.write((byte)'0');
+	    os.write((byte)'0');
+	    os.flush();
+	    os.close();
+
+	    byte[] line = new byte[encodedLine.length];
+	    System.arraycopy(bos.toByteArray(), 0, line, 0, line.length);
+	    Assert.assertArrayEquals("single byte last encoded line " + i,
+					encodedLine, line);
+	}
+    }
+
+    /**
+     * Fill the buffer from the stream.
+     */
+    private static void readAll(InputStream in, byte[] buf, int readsize)
+				throws IOException {
+	int need = buf.length;
+	int off = 0; 
+	int got;
+	while (need > 0) {
+	    got = in.read(buf, off, need > readsize ? readsize : need);
+	    if (got <= 0)
+		break;
+	    off += got;
+	    need -= got;
+	}
+	if (need != 0)
+	    System.out.println("couldn't read all bytes");
+    }
+
+    /**
+     * Compare the two buffers.
+     */
+    private static void compare(String name, String type,
+				byte[] buf, byte[] nbuf) {
+	/*
+	if (nbuf.length != buf.length) {
+	    System.out.println(name + ": " + type +
+		" decoded array size wrong: " +
+		"got " + nbuf.length + ", expected " + buf.length);
+	    dump(name + " buf", buf);
+	    dump(name + " nbuf", nbuf);
+	}
+	*/
+	Assert.assertEquals(name + ": " + type + " decoded array size wrong",
+			    buf.length, nbuf.length);
+	for (int i = 0; i < buf.length; i++) {
+	    Assert.assertEquals(name + ": " + type + " data wrong: index " + i,
+		buf[i], nbuf[i]);
+	}
+    }
+
+    /**
+     * Dump the contents of the buffer.
+     */
+    private static void dump(String name, byte[] buf) {
+	System.out.println(name);
+	for (int i = 0; i < buf.length; i++)
+	    System.out.println(buf[i]);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/ContentTypeCleaner.java b/mail/src/test/java/com/sun/mail/util/ContentTypeCleaner.java
new file mode 100644
index 0000000..98b4581
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/ContentTypeCleaner.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.MessagingException;
+import javax.mail.BodyPart;
+import com.sun.mail.test.AsciiStringInputStream;
+import javax.mail.internet.MimePart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the "mail.mime.contenttypehandler" property.
+ */
+public class ContentTypeCleaner {
+ 
+    private static Session s = Session.getInstance(new Properties());
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("ContentTypeCleaner");
+	System.setProperty("mail.mime.contenttypehandler",
+	    ContentTypeCleaner.class.getName());
+    }
+
+    @Test
+    public void testGarbage() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	BodyPart bp = mp.getBodyPart(0);
+	assertEquals("text/plain", bp.getContentType());
+	assertEquals("first part\n", bp.getContent());
+    }
+
+    @Test
+    public void testValid() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	BodyPart bp = mp.getBodyPart(1);
+	assertEquals("text/plain; charset=iso-8859-1", bp.getContentType());
+	assertEquals("second part\n", bp.getContent());
+    }
+
+    @Test
+    public void testEmpty() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	BodyPart bp = mp.getBodyPart(2);
+	assertEquals("text/plain", bp.getContentType());
+	assertEquals("third part\n", bp.getContent());
+    }
+
+    public static String cleanContentType(MimePart mp, String contentType) {
+	if (contentType == null)
+	    return null;
+	if (contentType.equals("complete garbage"))
+	    return "text/plain";
+	return contentType;
+    }
+
+    private static MimeMessage createMessage() throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Subject: Example\n" +
+	    "Content-Type: multipart/mixed; boundary=\"-\"\n" +
+	    "\n" +
+	    "preamble\n" +
+	    "---\n" +
+	    "Content-Type: complete garbage\n" +
+	    "\n" +
+	    "first part\n" +
+	    "\n" +
+	    "---\n" +
+	    "Content-Type: text/plain; charset=iso-8859-1\n" +
+	    "\n" +
+	    "second part\n" +
+	    "\n" +
+	    "---\n" +
+	    "\n" +
+	    "third part\n" +
+	    "\n" +
+	    "-----\n";
+
+	return new MimeMessage(s, new AsciiStringInputStream(content));
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/LineInputStreamTest.java b/mail/src/test/java/com/sun/mail/util/LineInputStreamTest.java
new file mode 100644
index 0000000..2255f0a
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/LineInputStreamTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test handling of line terminators.
+ * LineInputStream handles these different line terminators:
+ *
+ *	NL		- Unix
+ *	CR LF		- Windows, MIME
+ *	CR		- old MacOS
+ *	CR CR LF	- broken internet servers
+ *
+ * @author Bill Shannon
+ */
+
+public class LineInputStreamTest {
+    private static final String[] lines = {
+	"line1\nline2\nline3\n",
+	"line1\r\nline2\r\nline3\r\n",
+	"line1\rline2\rline3\r",
+	"line1\r\r\nline2\r\r\nline3\r\r\n"
+    };
+
+    private static final String[] empty = {
+	"\n\n\n",
+	"\r\n\r\n\r\n",
+	"\r\r\r",
+	"\r\r\n\r\r\n\r\r\n"
+    };
+
+    private static final String[] mixed = {
+	"line1\n\nline3\n",
+	"line1\r\n\r\nline3\r\n",
+	"line1\r\rline3\r",
+	"line1\r\r\n\r\r\nline3\r\r\n"
+    };
+
+    @Test
+    public void testLines() throws IOException {
+	for (String s : lines) {
+	    LineInputStream is = createStream(s);
+	    assertEquals("line1", is.readLine());
+	    assertEquals("line2", is.readLine());
+	    assertEquals("line3", is.readLine());
+	    assertEquals(null, is.readLine());
+	}
+    }
+
+    @Test
+    public void testEmpty() throws IOException {
+	for (String s : empty) {
+	    LineInputStream is = createStream(s);
+	    assertEquals("", is.readLine());
+	    assertEquals("", is.readLine());
+	    assertEquals("", is.readLine());
+	    assertEquals(null, is.readLine());
+	}
+    }
+
+    @Test
+    public void testMixed() throws IOException {
+	for (String s : mixed) {
+	    LineInputStream is = createStream(s);
+	    assertEquals("line1", is.readLine());
+	    assertEquals("", is.readLine());
+	    assertEquals("line3", is.readLine());
+	    assertEquals(null, is.readLine());
+	}
+    }
+
+    private LineInputStream createStream(String s) {
+	try {
+	return new LineInputStream(
+	    new ByteArrayInputStream(s.getBytes("us-ascii")));
+	} catch (UnsupportedEncodingException ex) {
+	    return null;	// should never happen
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/MimeUtilTestSuite.java b/mail/src/test/java/com/sun/mail/util/MimeUtilTestSuite.java
new file mode 100644
index 0000000..06131fe
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/MimeUtilTestSuite.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite.SuiteClasses;
+
+import com.sun.mail.test.ClassLoaderSuite;
+import com.sun.mail.test.ClassLoaderSuite.TestClass;
+
+/**
+ * Suite of MimeUtil tests that need to be run in a separate class loader.
+ */
+@RunWith(ClassLoaderSuite.class)
+@TestClass(MimeUtil.class)
+@SuiteClasses( {
+    ContentTypeCleaner.class
+})
+public class MimeUtilTestSuite {
+}
diff --git a/mail/src/test/java/com/sun/mail/util/PropUtilTest.java b/mail/src/test/java/com/sun/mail/util/PropUtilTest.java
new file mode 100644
index 0000000..3d6695e
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/PropUtilTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.util.Properties;
+import javax.mail.Session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+/**
+ * Test that the PropUtil methods return the correct values,
+ * especially when defaults and non-String values are considered.
+ */
+public class PropUtilTest {
+    @Test
+    public void testInt() throws Exception {
+	Properties props = new Properties();
+	props.setProperty("test", "2");
+	assertEquals(PropUtil.getIntProperty(props, "test", 1), 2);
+    }
+
+    @Test
+    public void testIntDef() throws Exception {
+	Properties props = new Properties();
+	assertEquals(PropUtil.getIntProperty(props, "test", 1), 1);
+    }
+
+    @Test
+    public void testIntDefProp() throws Exception {
+	Properties defprops = new Properties();
+	defprops.setProperty("test", "2");
+	Properties props = new Properties(defprops);
+	assertEquals(PropUtil.getIntProperty(props, "test", 1), 2);
+    }
+
+    @Test
+    public void testInteger() throws Exception {
+	Properties props = new Properties();
+	props.put("test", 2);
+	assertEquals(PropUtil.getIntProperty(props, "test", 1), 2);
+    }
+
+    @Test
+    public void testBool() throws Exception {
+	Properties props = new Properties();
+	props.setProperty("test", "true");
+	assertTrue(PropUtil.getBooleanProperty(props, "test", false));
+    }
+
+    @Test
+    public void testBoolDef() throws Exception {
+	Properties props = new Properties();
+	assertTrue(PropUtil.getBooleanProperty(props, "test", true));
+    }
+
+    @Test
+    public void testBoolDefProp() throws Exception {
+	Properties defprops = new Properties();
+	defprops.setProperty("test", "true");
+	Properties props = new Properties(defprops);
+	assertTrue(PropUtil.getBooleanProperty(props, "test", false));
+    }
+
+    @Test
+    public void testBoolean() throws Exception {
+	Properties props = new Properties();
+	props.put("test", true);
+	assertTrue(PropUtil.getBooleanProperty(props, "test", false));
+    }
+
+
+    // the Session variants...
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testSessionInt() throws Exception {
+	Properties props = new Properties();
+	props.setProperty("test", "2");
+	Session sess = Session.getInstance(props, null);
+	assertEquals(PropUtil.getIntSessionProperty(sess, "test", 1), 2);
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testSessionIntDef() throws Exception {
+	Properties props = new Properties();
+	Session sess = Session.getInstance(props, null);
+	assertEquals(PropUtil.getIntSessionProperty(sess, "test", 1), 1);
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testSessionIntDefProp() throws Exception {
+	Properties defprops = new Properties();
+	defprops.setProperty("test", "2");
+	Properties props = new Properties(defprops);
+	Session sess = Session.getInstance(props, null);
+	assertEquals(PropUtil.getIntSessionProperty(sess, "test", 1), 2);
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testSessionInteger() throws Exception {
+	Properties props = new Properties();
+	props.put("test", 2);
+	Session sess = Session.getInstance(props, null);
+	assertEquals(PropUtil.getIntSessionProperty(sess, "test", 1), 2);
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testSessionBool() throws Exception {
+	Properties props = new Properties();
+	props.setProperty("test", "true");
+	Session sess = Session.getInstance(props, null);
+	assertTrue(PropUtil.getBooleanSessionProperty(sess, "test", false));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testSessionBoolDef() throws Exception {
+	Properties props = new Properties();
+	Session sess = Session.getInstance(props, null);
+	assertTrue(PropUtil.getBooleanSessionProperty(sess, "test", true));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testSessionBoolDefProp() throws Exception {
+	Properties defprops = new Properties();
+	defprops.setProperty("test", "true");
+	Properties props = new Properties(defprops);
+	Session sess = Session.getInstance(props, null);
+	assertTrue(PropUtil.getBooleanSessionProperty(sess, "test", false));
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testSessionBoolean() throws Exception {
+	Properties props = new Properties();
+	props.put("test", true);
+	Session sess = Session.getInstance(props, null);
+	assertTrue(PropUtil.getBooleanSessionProperty(sess, "test", false));
+    }
+
+
+    // the System variants...
+
+    @Test
+    public void testSystemBool() throws Exception {
+	System.setProperty("test", "true");
+	assertTrue(PropUtil.getBooleanSystemProperty("test", false));
+    }
+
+    @Test
+    public void testSystemBoolDef() throws Exception {
+	assertTrue(PropUtil.getBooleanSystemProperty("testnotset", true));
+    }
+
+    @Test
+    public void testSystemBoolean() throws Exception {
+	System.getProperties().put("testboolean", true);
+	assertTrue(PropUtil.getBooleanSystemProperty("testboolean", false));
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/QPEncoderStreamTest.java b/mail/src/test/java/com/sun/mail/util/QPEncoderStreamTest.java
new file mode 100644
index 0000000..245a768
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/QPEncoderStreamTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.ByteArrayOutputStream;
+import com.sun.mail.util.QPEncoderStream;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test quoted-printable encoder.
+ *
+ * @author Bill Shannon
+ */
+
+public class QPEncoderStreamTest {
+    /**
+     * Test that a trailing space is encoded in the output stream.
+     */
+    @Test
+    public void testTrailingSpace() throws Exception {
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	QPEncoderStream qs = new QPEncoderStream(bos);
+	qs.write("test ".getBytes("us-ascii"));
+	qs.flush();
+	String result = new String(bos.toByteArray(), "us-ascii");
+	assertEquals("test=20", result);
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/SocketFetcherTest.java b/mail/src/test/java/com/sun/mail/util/SocketFetcherTest.java
new file mode 100644
index 0000000..991d525
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/SocketFetcherTest.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.util.Properties;
+import java.nio.charset.StandardCharsets;
+
+import com.sun.mail.test.TestServer;
+import com.sun.mail.test.ProtocolHandler;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
+
+/**
+ * Test SocketFetcher.
+ */
+public final class SocketFetcherTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    /**
+     * Test connecting with proxy host and port.
+     */
+    @Test
+    public void testProxyHostPort() {
+	assertTrue("proxy host, port", testProxy("proxy", "localhost", "PPPP"));
+    }
+
+    /**
+     * Test connecting with proxy host and port and user name and password.
+     */
+    @Test
+    public void testProxyHostPortUserPassword() {
+	assertTrue("proxy host, port, user, password",
+	    testProxyUserPassword("proxy", "localhost", "PPPP", "user", "pwd"));
+    }
+
+    /**
+     * Test connecting with proxy host:port.
+     */
+    @Test
+    public void testProxyHostColonPort() {
+	assertTrue("proxy host:port", testProxy("proxy", "localhost:PPPP", null));
+    }
+
+    /**
+     * Test connecting with socks host and port.
+     */
+    @Test
+    public void testSocksHostPort() {
+	assertTrue("socks host, port", testProxy("socks", "localhost", "PPPP"));
+    }
+
+    /**
+     * Test connecting with socks host:port.
+     */
+    @Test
+    public void testSocksHostColonPort() {
+	assertTrue("socks host:port", testProxy("socks", "localhost:PPPP", null));
+    }
+
+    /**
+     * Test connecting with no proxy.
+     */
+    @Test
+    public void testNoProxy() {
+	assertFalse("no proxy", testProxy("none", "localhost", null));
+    }
+
+    /**
+     */
+    public boolean testProxy(String type, String host, String port) {
+	return testProxyUserPassword(type, host, port, null, null);
+    }
+
+    /**
+     */
+    public boolean testProxyUserPassword(String type, String host, String port,
+					String user, String pwd) {
+	TestServer server = null;
+	try {
+	    ProxyHandler handler = new ProxyHandler(type.equals("proxy"));
+	    server = new TestServer(handler);
+	    server.start();
+	    String sport = "" + server.getPort();
+
+	    //System.setProperty("mail.socket.debug", "true");
+	    Properties properties = new Properties();
+	    properties.setProperty("mail.test.host", "localhost");
+	    properties.setProperty("mail.test.port", "2");
+	    properties.setProperty("mail.test." + type + ".host",
+				    host.replace("PPPP", sport));
+	    if (port != null)
+		properties.setProperty("mail.test." + type + ".port",
+				    port.replace("PPPP", sport));
+	    if (user != null)
+		properties.setProperty("mail.test." + type + ".user", user);
+	    if (pwd != null)
+		properties.setProperty("mail.test." + type + ".password", pwd);
+
+	    Socket s = null;
+	    try {
+		s = SocketFetcher.getSocket("localhost", 2,
+					    properties, "mail.test", false);
+	    } catch (Exception ex) {
+		// ignore failure, which is expected
+		//System.out.println(ex);
+		//ex.printStackTrace();
+	    } finally {
+		if (s != null)
+		    s.close();
+	    }
+	    if (!handler.getConnected())
+		return false;
+	    if (user != null && pwd != null)
+		return (user + ":" + pwd).equals(handler.getUserPassword());
+	    else
+		return true;
+
+	} catch (final Exception e) {
+	    //e.printStackTrace();
+	    fail(e.getMessage());
+	    return false;	// XXX - doesn't matter
+	} finally {
+	    if (server != null) {
+		server.quit();
+	    }
+	}
+    }
+
+    /**
+     * Custom handler.  Remember whether any data was sent
+     * and save user/password string;
+     */
+    private static class ProxyHandler extends ProtocolHandler {
+	private boolean http;
+
+	// must be static because handler is cloned for each connection
+	private static volatile boolean connected;
+	private static volatile String userPassword;
+
+	public ProxyHandler(boolean http) {
+	    this.http = http;
+	    connected = false;
+	}
+
+	@Override
+	public void handleCommand() throws IOException {
+	    if (!http) {
+		int c = in.read();
+		if (c >= 0) {
+		    // any data means a real client connected
+		    connected = true;
+		}
+		exit();
+	    }
+
+	    // else, http...
+	    String line;
+	    while ((line = readLine()) != null) {
+		// any data means a real client connected
+		connected = true;
+		if (line.length() == 0)
+		    break;
+		if (line.startsWith("Proxy-Authorization:")) {
+		    int i = line.indexOf("Basic ") + 6;
+		    String up = line.substring(i);
+		    userPassword = new String(BASE64DecoderStream.decode(
+				    up.getBytes(StandardCharsets.US_ASCII)),
+				    StandardCharsets.UTF_8);
+		}
+	    }
+	    exit();
+	}
+
+	public boolean getConnected() {
+	    return connected;
+	}
+
+	public String getUserPassword() {
+	    return userPassword;
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/UUDecoderStreamTest.java b/mail/src/test/java/com/sun/mail/util/UUDecoderStreamTest.java
new file mode 100644
index 0000000..10c7942
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/UUDecoderStreamTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.io.*;
+import java.util.*;
+import com.sun.mail.util.UUDecoderStream;
+
+import org.junit.Test;
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test uudecoder.
+ *
+ * @author Bill Shannon
+ */
+
+@RunWith(Parameterized.class)
+public class UUDecoderStreamTest {
+    private TestData data;
+
+    private static boolean gen_test_input = false;	// output good
+    private static int errors = 0;		// number of errors detected
+
+    private static boolean junit;
+
+    static class TestData {
+	public String name;
+	public boolean ignoreErrors;
+	public boolean ignoreMissingBeginEnd;
+	public byte[] input;
+	public byte[] expectedOutput;
+	public String expectedException;
+    }
+
+    public UUDecoderStreamTest(TestData t) {
+	data = t;
+    }
+
+    @Test
+    public void testData() {
+	test(data);
+    }
+
+    @Parameters
+    public static Collection<TestData[]> data() throws Exception {
+	junit = true;
+	// XXX - gratuitous array requirement
+	List<TestData[]> testData = new ArrayList<>();
+	BufferedReader in = new BufferedReader(new InputStreamReader(
+	    UUDecoderStreamTest.class.getResourceAsStream("uudata")));
+	TestData t;
+	while ((t = parse(in)) != null)
+	    testData.add(new TestData[] { t });
+	return testData;
+    }
+
+    public static void main(String argv[]) throws Exception {
+	int optind;
+	// XXX - all options currently ignored
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-")) {
+		// ignore
+	    } else if (argv[optind].equals("-g")) {
+		gen_test_input = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+		    "Usage: uutest [-g] [-]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	// read from stdin
+	BufferedReader in =
+	    new BufferedReader(new InputStreamReader(System.in));
+
+	TestData t;
+	while ((t = parse(in)) != null)
+	    test(t);
+	System.exit(errors);
+    }
+
+    /*
+     * Parse the input, returning a test case.
+     */
+    public static TestData parse(BufferedReader in) throws Exception {
+
+	String line = null;
+	for (;;) {
+	    line = in.readLine();
+	    if (line == null)
+		return null;
+	    if (line.length() == 0 || line.startsWith("#"))
+		continue;
+
+	    if (!line.startsWith("TEST"))
+		throw new Exception("Bad test data format");
+	    break;
+	}
+
+	TestData t = new TestData();
+	int i = line.indexOf(' ');	// XXX - crude
+	t.name = line.substring(i + 1);
+
+	line = in.readLine();
+	StringTokenizer st = new StringTokenizer(line);
+	String tok = st.nextToken();
+	if (!tok.equals("DATA"))
+	    throw new Exception("Bad test data format: " + line);
+	while (st.hasMoreTokens()) {
+	    tok = st.nextToken();
+	    if (tok.equals("ignoreErrors"))
+		t.ignoreErrors = true;
+	    else if (tok.equals("ignoreMissingBeginEnd"))
+		t.ignoreMissingBeginEnd = true;
+	    else
+		throw new Exception("Bad DATA option in line: " + line);
+	}
+
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	Writer os = new OutputStreamWriter(bos, "us-ascii");
+	for (;;) {
+	    line = in.readLine();
+	    if (line.equals("EXPECT"))
+		break;
+	    os.write(line);
+	    os.write("\n");
+	}
+	os.close();
+	t.input = bos.toByteArray();
+
+	bos = new ByteArrayOutputStream();
+	os = new OutputStreamWriter(bos, "us-ascii");
+	for (;;) {
+	    line = in.readLine();
+	    if (line.startsWith("EXCEPTION")) {
+		i = line.indexOf(' ');	// XXX - crude
+		t.expectedException = line.substring(i + 1);
+	    } else if (line.equals("END"))
+		break;
+	    os.write(line);
+	    os.write("\n");
+	}
+	os.close();
+	if (t.expectedException == null)
+	    t.expectedOutput = bos.toByteArray();
+
+	return t;
+    }
+
+    /**
+     * Test the data in the test case.
+     */
+    public static void test(TestData t) {
+	InputStream in =
+	    new UUDecoderStream(new ByteArrayInputStream(t.input),
+				t.ignoreErrors, t.ignoreMissingBeginEnd);
+
+	// two cases - either we're expecting an exception or we're not
+	if (t.expectedException != null) {
+	    try {
+		int c;
+		while ((c = in.read()) >= 0)
+		    ;	// throw it away
+		// read all the data with no exception - fail
+		if (junit)
+		    Assert.fail("Didn't get expected exception: " +
+				    t.expectedException);
+		System.out.println("Test: " + t.name);
+		System.out.println("Got no Exception");
+		System.out.println("Expected Exception: " +
+				    t.expectedException);
+		errors++;
+	    } catch (Exception ex) {
+		if (junit)
+		    Assert.assertEquals("For expected exception",
+			ex.getClass().getName(), t.expectedException);
+		if (!ex.getClass().getName().equals(t.expectedException)) {
+		    System.out.println("Test: " + t.name);
+		    System.out.println("Got Exception: " + ex);
+		    System.out.println("Expected Exception: " +
+					t.expectedException);
+		    errors++;
+		}
+	    } finally {
+		try {
+		    in.close();
+		} catch (IOException ioex) { }
+	    }
+	} else {
+	    InputStream ein = new ByteArrayInputStream(t.expectedOutput);
+	    try {
+		int c, ec;
+		boolean gotError = false;
+		while ((c = in.read()) >= 0) {
+		    ec = ein.read();
+		    if (junit)
+			Assert.assertFalse("For expected EOF, got char " + c,
+					    ec < 0);
+		    if (ec < 0) {
+			System.out.println("Test: " + t.name);
+			System.out.println("Got char: " + c);
+			System.out.println("Expected EOF");
+			errors++;
+			gotError = true;
+			break;
+		    }
+		    if (junit)
+			Assert.assertEquals("For expected char " + ec +
+					    ", got char " + c, ec, c);
+		    if (c != ec) {
+			System.out.println("Test: " + t.name);
+			System.out.println("Got char: " + c);
+			System.out.println("Expected char: " + ec);
+			errors++;
+			gotError = true;
+			break;
+		    }
+		}
+		if (!gotError) {
+		    ec = ein.read();
+		    if (junit)
+			Assert.assertFalse("For expected char " + ec +
+					    ", got EOF", ec >= 0);
+		    if (ec >= 0) {
+			System.out.println("Test: " + t.name);
+			System.out.println("Got EOF");
+			System.out.println("Expected char: " + ec);
+			errors++;
+		    }
+		}
+	    } catch (Exception ex) {
+		if (junit)
+		    Assert.fail("Got exception: " + ex);
+		System.out.println("Test: " + t.name);
+		System.out.println("Got Exception: " + ex);
+		System.out.println("Expected no Exception");
+		errors++;
+	    } finally {
+		try {
+		    in.close();
+		} catch (IOException ioex) { }
+		try {
+		    ein.close();
+		} catch (IOException ioex) { }
+	    }
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/WriteTimeoutSocketTest.java b/mail/src/test/java/com/sun/mail/util/WriteTimeoutSocketTest.java
new file mode 100644
index 0000000..f688f50
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/WriteTimeoutSocketTest.java
@@ -0,0 +1,258 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.util;
+
+import java.lang.reflect.*;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.Properties;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.HashSet;
+
+import javax.mail.*;
+import javax.mail.internet.MimeMessage;
+import javax.mail.util.ByteArrayDataSource;
+import javax.activation.DataHandler;
+import javax.net.ssl.*;
+
+import com.sun.mail.imap.IMAPHandler;
+import com.sun.mail.test.TestServer;
+import com.sun.mail.test.TestSocketFactory;
+import com.sun.mail.test.TestSSLSocketFactory;
+
+import org.junit.Test;
+import org.junit.Rule;
+import org.junit.rules.Timeout;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test that write timeouts work.
+ */
+public final class WriteTimeoutSocketTest {
+
+    // timeout the test in case of deadlock
+    @Rule
+    public Timeout deadlockTimeout = Timeout.seconds(20);
+
+    private static final int TIMEOUT = 200;	// ms
+    private static final String data =
+	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+    /**
+     * Test write timeouts with plain sockets.
+     */
+    @Test
+    public void test() {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.imap.host", "localhost");
+	properties.setProperty("mail.imap.writetimeout", "" + TIMEOUT);
+	test(properties, false);
+    }
+
+    /**
+     * Test write timeouts with custom socket factory.
+     */
+    @Test
+    public void testSocketFactory() {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.imap.host", "localhost");
+	properties.setProperty("mail.imap.writetimeout", "" + TIMEOUT);
+	TestSocketFactory sf = new TestSocketFactory();
+	properties.put("mail.imap.socketFactory", sf);
+	properties.setProperty("mail.imap.socketFactory.fallback", "false");
+	test(properties, false);
+	// make sure our socket factory was actually used
+	assertTrue(sf.getSocketCreated());
+    }
+
+    /**
+     * Test write timeouts with SSL sockets.
+     */
+    @Test
+    public void testSSL() {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.imap.host", "localhost");
+	properties.setProperty("mail.imap.writetimeout", "" + TIMEOUT);
+	properties.setProperty("mail.imap.ssl.enable", "true");
+	// enable only the anonymous cipher suites since there's no
+	// server certificate
+	properties.setProperty("mail.imap.ssl.ciphersuites",
+						    getAnonCipherSuites());
+	test(properties, true);
+    }
+
+    /**
+     * Test write timeouts with a custom SSL socket factory.
+     */
+    @Test
+    public void testSSLSocketFactory() throws Exception {
+	final Properties properties = new Properties();
+	properties.setProperty("mail.imap.host", "localhost");
+	properties.setProperty("mail.imap.writetimeout", "" + TIMEOUT);
+	properties.setProperty("mail.imap.ssl.enable", "true");
+	TestSSLSocketFactory sf = new TestSSLSocketFactory();
+	sf.setDefaultCipherSuites(getAnonCipherSuitesArray());
+	properties.put("mail.imap.ssl.socketFactory", sf);
+	// don't fall back to non-SSL
+	properties.setProperty("mail.imap.socketFactory.fallback", "false");
+	// enable only the anonymous cipher suites since there's no
+	// server certificate
+	properties.setProperty("mail.imap.ssl.ciphersuites",
+						    getAnonCipherSuites());
+	test(properties, true);
+	// make sure our socket factory was actually used
+	assertTrue(sf.getSocketWrapped() || sf.getSocketCreated());
+    }
+
+    /**
+     * Test that WriteTimeoutSocket overrides all methods from Socket.
+     * XXX - this is kind of hacky since it depends on Method.toString
+     */
+    @Test
+    public void testOverrides() throws Exception {
+	Set<String> socketMethods = new HashSet<>();
+	Method[] m = java.net.Socket.class.getDeclaredMethods();
+	String className = java.net.Socket.class.getName() + ".";
+	for (int i = 0; i < m.length; i++) {
+	    if (Modifier.isPublic(m[i].getModifiers()) &&
+		!Modifier.isStatic(m[i].getModifiers())) {
+		String name = m[i].toString().
+				    replace("synchronized ", "").
+				    replace(className, "");
+		socketMethods.add(name);
+	    }
+	}
+	Set<String> wtsocketMethods = new HashSet<>();
+	m = WriteTimeoutSocket.class.getDeclaredMethods();
+	className = WriteTimeoutSocket.class.getName() + ".";
+	for (int i = 0; i < m.length; i++) {
+	    if (Modifier.isPublic(m[i].getModifiers())) {
+		String name = m[i].toString().
+				    replace("synchronized ", "").
+				    replace(className, "");
+		socketMethods.remove(name);
+	    }
+	}
+	for (String s : socketMethods)
+	    System.out.println("WriteTimeoutSocket did not override: " + s);
+	assertTrue(socketMethods.isEmpty());
+    }
+
+    private static String[] getAnonCipherSuitesArray() {
+	SSLSocketFactory sf = (SSLSocketFactory)SSLSocketFactory.getDefault();
+	List<String> anon = new ArrayList<>();
+	String[] suites = sf.getSupportedCipherSuites();
+	for (int i = 0; i < suites.length; i++) {
+	    if (suites[i].indexOf("_anon_") >= 0) {
+		anon.add(suites[i]);
+	    }
+	}
+	return anon.toArray(new String[anon.size()]);
+    }
+
+    private static String getAnonCipherSuites() {
+	SSLSocketFactory sf = (SSLSocketFactory)SSLSocketFactory.getDefault();
+	StringBuilder anon = new StringBuilder();
+	String[] suites = sf.getSupportedCipherSuites();
+	for (int i = 0; i < suites.length; i++) {
+	    if (suites[i].indexOf("_anon_") >= 0) {
+		if (anon.length() > 0)
+		    anon.append(" ");
+		anon.append(suites[i]);
+	    }
+	}
+	return anon.toString();
+    }
+
+    private void test(Properties properties, boolean isSSL) {
+        TestServer server = null;
+        try {
+            final TimeoutHandler handler = new TimeoutHandler();
+            server = new TestServer(handler, isSSL);
+            server.start();
+
+	    properties.setProperty("mail.imap.port", "" + server.getPort());
+            final Session session = Session.getInstance(properties);
+            //session.setDebug(true);
+
+	    MimeMessage msg = new MimeMessage(session);
+	    msg.setFrom("test@example.com");
+	    msg.setSubject("test");
+	    final int size = 8192000;	// enough data to fill network buffers
+	    byte[] part = new byte[size];
+	    for (int i = 0; i < size; i++) {
+		int j = i % 64;
+		if (j == 62)
+		    part[i] = (byte)'\r';
+		else if (j == 63)
+		    part[i] = (byte)'\n';
+		else
+		    part[i] = (byte)data.charAt((j + i / 64) % 62);
+	    }
+	    msg.setDataHandler(new DataHandler(
+		new ByteArrayDataSource(part, "text/plain")));
+	    msg.saveChanges();
+
+            final Store store = session.getStore("imap");
+            try {
+                store.connect("test", "test");
+		final Folder f = store.getFolder("test");
+		f.appendMessages(new Message[] { msg });
+		fail("No timeout");
+	    } catch (StoreClosedException scex) {
+		// success!
+	    } catch (Exception ex) {
+		System.out.println(ex);
+		//ex.printStackTrace();
+		fail(ex.toString());
+            } finally {
+                store.close();
+            }
+        } catch (final Exception e) {
+            //e.printStackTrace();
+            fail(e.getMessage());
+        } finally {
+            if (server != null) {
+                server.quit();
+            }
+        }
+    }
+
+    /**
+     * Custom handler.
+     */
+    private static final class TimeoutHandler extends IMAPHandler {
+	@Override
+        protected void collectMessage(int bytes) throws IOException {
+	    try {
+		// allow plenty of time for even slow machines to time out
+		Thread.sleep(TIMEOUT*20);
+	    } catch (InterruptedException ex) { }
+	    super.collectMessage(bytes);
+        }
+
+	@Override
+	public void list(String line) throws IOException {
+	    untagged("LIST () \"/\" test");
+	    ok();
+	}
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/logging/AbstractLogging.java b/mail/src/test/java/com/sun/mail/util/logging/AbstractLogging.java
new file mode 100644
index 0000000..0ef4d95
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/logging/AbstractLogging.java
@@ -0,0 +1,393 @@
+/*

+ * Copyright (c) 2016, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2016, 2018 Jason Mehrens. All rights reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0, which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * This Source Code may also be made available under the following Secondary

+ * Licenses when the conditions for such availability set forth in the

+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,

+ * version 2 with the GNU Classpath Exception, which is available at

+ * https://www.gnu.org/software/classpath/license.html.

+ *

+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

+ */

+package com.sun.mail.util.logging;

+

+import java.io.ByteArrayInputStream;

+import java.io.ByteArrayOutputStream;

+import java.io.IOException;

+import java.lang.annotation.Annotation;

+import java.lang.reflect.AccessibleObject;

+import java.lang.reflect.Constructor;

+import java.lang.reflect.Field;

+import java.lang.reflect.Method;

+import java.lang.reflect.Modifier;

+import java.util.ArrayList;

+import java.util.Arrays;

+import java.util.HashSet;

+import java.util.List;

+import java.util.Properties;

+import java.util.logging.Level;

+import java.util.logging.LogManager;

+import java.util.logging.LogRecord;

+import static org.junit.Assert.assertEquals;

+import static org.junit.Assert.assertFalse;

+import static org.junit.Assert.assertTrue;

+import static org.junit.Assert.fail;

+

+/**

+ * Common super class for the logging test suite.

+ *

+ * @author Jason Mehrens

+ * @since JavaMail 1.5.6

+ */

+abstract class AbstractLogging {

+

+    /**

+     * Used to print debug information about the given throwable.

+     *

+     * @param t the throwable.

+     * @throws NullPointerException if given throwable is null.

+     */

+    @SuppressWarnings({"CallToThreadDumpStack", "CallToPrintStackTrace"})

+    static void dump(final Throwable t) {

+        t.printStackTrace();

+    }

+

+    /**

+     * Gets all of the predefined Levels.

+     *

+     * @return an array of log levels.

+     */

+    static Level[] getAllLevels() {

+        final Field[] fields = Level.class.getFields();

+        List<Level> a = new ArrayList<>(fields.length);

+        for (Field field : fields) {

+            if (Modifier.isStatic(field.getModifiers())

+                    && Level.class.isAssignableFrom(field.getType())) {

+                try {

+                    a.add((Level) field.get((Object) null));

+                } catch (IllegalArgumentException | IllegalAccessException ex) {

+                    fail(ex.toString());

+                }

+            }

+        }

+        return a.toArray(new Level[a.size()]);

+    }

+

+    /**

+     * Determines if the given class is from the JavaMail API Reference

+     * Implementation {@code com.sun.mail} package.

+     *

+     * @param k the type to test.

+     * @return true if this is part of reference implementation but not part of

+     * the official API spec.

+     * @throws Exception if there is a problem.

+     */

+    final boolean isPrivateSpec(final Class<?> k) throws Exception {

+        return isFromJavaMail(k, false);

+    }

+

+    /**

+     * Reinitialize the logging properties using the given properties.

+     *

+     * @param manager the log manager.

+     * @param props the properties.

+     * @throws IOException if there is a problem.

+     * @throws NullPointerException if either argument is null.

+     */

+    final void read(LogManager manager, Properties props) throws IOException {

+        //JDK-4810637

+        ByteArrayOutputStream out = new ByteArrayOutputStream(512);

+        props.store(out, getClass().getName());

+        manager.readConfiguration(new ByteArrayInputStream(out.toByteArray()));

+    }

+

+    /**

+     * Sets the log record time using milliseconds from the epoch of

+     * 1970-01-01T00:00:00Z. Any nanosecond information is set to zero. This

+     * method is used to support JDK8 when running on JDK9 or newer.

+     *

+     * @param record the log record to adjust.

+     * @param epochMilli the time in milliseconds from epoch.

+     * @throws NullPointerException if the given record is null.

+     */

+    @SuppressWarnings("deprecation") //See JDK-8144262 and K7091.

+    static void setEpochMilli(final LogRecord record, final long epochMilli) {

+        record.setMillis(epochMilli);

+    }

+

+    /**

+     * Sets the log record time using the seconds and nanoseconds of the epoch

+     * from 1970-01-01T00:00:00Z.

+     *

+     * @param record the log record.

+     * @param epochSecond the seconds.

+     * @param nanoAdjustment the nano seconds.

+     * @throws ClassNotFoundException if running on pre JDK 8.

+     * @throws NoSuchMethodException if running on JDK 8.

+     * @throws Exception if there is a problem.

+     */

+    static void setEpochSecond(final LogRecord record, final long epochSecond,

+            final long nanoAdjustment) throws Exception {

+        final Class<?> k = Class.forName("java.time.Instant");

+        Method instant = k.getMethod("ofEpochSecond", long.class, long.class);

+        Method set = LogRecord.class.getMethod("setInstant", k);

+        set.invoke(record, instant.invoke(null, epochSecond, nanoAdjustment));

+    }

+

+    /**

+     * Determines if the {@code java.time} APIs are available for this JVM.

+     *

+     * @return true if the time classes can be loaded.

+     */

+    static boolean hasJavaTimeModule() {

+        try {

+            Class.forName("java.time.Duration");

+            Class.forName("java.time.Instant");

+            Class.forName("java.time.ZonedDateTime");

+            Class.forName("java.time.ZoneId");

+            return true;

+        } catch (final ClassNotFoundException | LinkageError notSupported) {

+        }

+        return false;

+    }

+

+    /**

+     * Fails if any declared types are outside of the logging-mailhandler.jar.

+     * This includes classes from the JavaMail spec.

+     *

+     * @param k the type to check for dependencies.

+     * @throws Exception if there is a problem.

+     */

+    final void testJavaMailLinkage(final Class<?> k) throws Exception {

+        testJavaMailLinkage(k, true);

+    }

+

+    /**

+     * Fails if any declared types are outside of the logging-mailhandler.jar.

+     *

+     * @param k the type to check for dependencies.

+     * @param includeSpec if true this includes official JavaMail spec classes.

+     * @throws Exception if there is a problem.

+     */

+    final void testJavaMailLinkage(final Class<?> k, final boolean includeSpec)

+            throws Exception {

+        assertFalse(k.getName(), isFromJavaMail(k, includeSpec));

+        for (Annotation an : k.getDeclaredAnnotations()) {

+            assertFalse(an.toString(),

+                    isFromJavaMail(an.annotationType(), includeSpec));

+        }

+

+        for (Method m : k.getDeclaredMethods()) {

+            assertFalse(m.getReturnType().getName(),

+                    isFromJavaMail(m.getReturnType(), includeSpec));

+            for (Class<?> p : m.getParameterTypes()) {

+                assertFalse(p.getName(), isFromJavaMail(p, includeSpec));

+            }

+

+            for (Class<?> e : m.getExceptionTypes()) {

+                assertFalse(e.getName(), isFromJavaMail(e, includeSpec));

+            }

+

+            for (Annotation an : m.getDeclaredAnnotations()) {

+                assertFalse(an.toString(),

+                        isFromJavaMail(an.annotationType(), includeSpec));

+            }

+        }

+

+        for (Constructor<?> c : k.getDeclaredConstructors()) {

+            for (Class<?> p : c.getParameterTypes()) {

+                assertFalse(p.getName(), isFromJavaMail(p, includeSpec));

+            }

+

+            for (Class<?> e : c.getExceptionTypes()) {

+                assertFalse(e.getName(), isFromJavaMail(e, includeSpec));

+            }

+

+            for (Annotation an : c.getDeclaredAnnotations()) {

+                assertFalse(an.toString(),

+                        isFromJavaMail(an.annotationType(), includeSpec));

+            }

+        }

+

+        for (Field f : k.getDeclaredFields()) {

+            for (Annotation an : k.getDeclaredAnnotations()) {

+                assertFalse(an.toString(),

+                        isFromJavaMail(an.annotationType(), includeSpec));

+            }

+            assertFalse(f.getName(), isFromJavaMail(f.getType(), includeSpec));

+        }

+    }

+

+    /**

+     * Tests that the private static loadDeclaredClasses method of the given

+     * type. Objects used by the MailHandler during a push might require

+     * declaring classes to be loaded on create since a push may happen after a

+     * class loader is shutdown.

+     *

+     * @param k the type to check never null.

+     * @throws Exception if there is a problem.

+     */

+    final void testLoadDeclaredClasses(Class<?> k) throws Exception {

+        Method m = k.getDeclaredMethod("loadDeclaredClasses");

+        assertTrue(Modifier.isStatic(m.getModifiers()));

+        assertTrue(Modifier.isPrivate(m.getModifiers()));

+        assertEquals(Class[].class, m.getReturnType());

+        m.setAccessible(true);

+        Class<?>[] named = (Class<?>[]) m.invoke((Object) null);

+        assertTrue(named.length != 0);

+        HashSet<Class<?>> declared = new HashSet<>(

+                Arrays.<Class<?>>asList(k.getDeclaredClasses()));

+        for (Class<?> c : named) {

+            assertEquals(c.toString(), k, c.getEnclosingClass());

+            assertTrue(c.getDeclaredClasses().length == 0);

+            declared.remove(c);

+        }

+        assertTrue(declared.toString(), declared.isEmpty());

+    }

+

+    /**

+     * Checks that the given class is visible to the LogManager.

+     *

+     * @param c the class to check.

+     * @throws Exception if there is a problem.

+     */

+    final void testLogManagerModifiers(final Class<?> c) throws Exception {

+        assertTrue(Modifier.isPublic(c.getModifiers()));

+        assertTrue(Modifier.isPublic(c.getConstructor().getModifiers()));

+    }

+

+    /**

+     * Checks that the given class is not dependent on the

+     * {@code javax.annotation} classes as they are not present in all

+     * environments.

+     *

+     * @param k the class to inspect.

+     * @throws Exception if there is a problem.

+     */

+    final void testNoDependencyOnJavaxAnnotations(Class<?> k) throws Exception {

+        for (Method m : k.getDeclaredMethods()) {

+            testNoJavaxAnnotation(m);

+        }

+

+        for (Field f : k.getDeclaredFields()) {

+            testNoJavaxAnnotation(f);

+        }

+

+        for (Constructor<?> c : k.getDeclaredConstructors()) {

+            testNoJavaxAnnotation(c);

+        }

+

+        for (Class<?> i : k.getInterfaces()) {

+            testNoJavaxAnnotation(i);

+        }

+

+        for (Class<?> d : k.getDeclaredClasses()) {

+            testNoDependencyOnJavaxAnnotations(d);

+        }

+    }

+

+    /**

+     * WebappClassLoader.clearReferencesStaticFinal() method will ignore fields

+     * that have type names that start with 'java.' or 'javax.'. This test

+     * checks that the given class conforms to this rule so it doesn't become a

+     * target that will be nullified by the WebappClassLoader.

+     *

+     * @param c the class to check.

+     * @throws Exception if there is a problem.

+     */

+    final void testWebappClassLoaderFieldNames(Class<?> c) throws Exception {

+        for (Field f : c.getDeclaredFields()) {

+            Class<?> k = f.getType();

+            while (k.isArray()) {

+                k = k.getComponentType();

+            }

+

+            /**

+             * The WebappClassLoader ignores primitives, non-static, and

+             * synthetic fields. For the logging API, this test is stricter than

+             * what the WebappClassLoader actually clears. This restricts the

+             * logging API to standard field types for both static and

+             * non-static fields. The idea is to try to stay forward compatible

+             * with WebappClassLoader.

+             */

+            if (f.getName().indexOf('$') < 0 && !k.isPrimitive()

+                    && !k.getName().startsWith("java.")

+                    && !k.getName().startsWith("javax.")) {

+                fail(f.toString());

+            }

+        }

+

+        for (Class<?> ic : c.getDeclaredClasses()) {

+            testWebappClassLoaderFieldNames(ic);

+        }

+    }

+

+    /**

+     * Blocks the current thread until current time has elapsed by one

+     * millisecond.

+     *

+     * @throws InterruptedException if the current thread is interrupted.

+     */

+    static void tickMilli() throws InterruptedException {

+        tickMilli(1L);

+    }

+

+    /**

+     * Blocks the current thread until current time has elapsed by the given

+     * delay in milliseconds.

+     *

+     * @param delay the number of milliseconds that have to elapse.

+     * @throws IllegalArgumentException if the given delay is zero or less.

+     * @throws InterruptedException if the current thread is interrupted.

+     */

+    @SuppressWarnings("SleepWhileInLoop")

+    static void tickMilli(long delay) throws InterruptedException {

+        if (delay <= 0L) {

+            throw new IllegalArgumentException(Long.toString(delay));

+        }

+        long then = System.currentTimeMillis();

+        for (int i = 0; i < Short.MAX_VALUE; i++) {

+            long now = System.currentTimeMillis();

+            long delta = (now - then);

+            if (delta >= delay) {

+                return;

+            }

+            Thread.sleep(delay - delta);

+        }

+        throw new AssertionError(then + " " + System.currentTimeMillis());

+    }

+

+    private boolean isFromJavaMail(Class<?> k, boolean include) throws Exception {

+        for (Class<?> t = k; t != null; t = t.getSuperclass()) {

+            final String n = t.getName();

+            if (n.startsWith("javax.mail.")) {

+                return include;

+            }

+

+            //Not included with logging-mailhandler.jar.

+            if (n.startsWith("com.sun.mail.")

+                    && !n.startsWith("com.sun.mail.util.logging.")) {

+                return true;

+            }

+        }

+        return false;

+    }

+

+    private void testNoJavaxAnnotation(AccessibleObject fm) throws Exception {

+        for (Annotation a : fm.getAnnotations()) {

+            testNoJavaxAnnotation(a.annotationType());

+        }

+    }

+

+    private void testNoJavaxAnnotation(Class<?> k) throws Exception {

+        for (; k != null; k = k.getSuperclass()) {

+            assertFalse(k.toString(),

+                    k.getName().startsWith("javax.annotation"));

+        }

+    }

+}

diff --git a/mail/src/test/java/com/sun/mail/util/logging/CollectorFormatterTest.java b/mail/src/test/java/com/sun/mail/util/logging/CollectorFormatterTest.java
new file mode 100644
index 0000000..f608345
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/logging/CollectorFormatterTest.java
@@ -0,0 +1,1089 @@
+/*

+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2013, 2018 Jason Mehrens. All rights reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0, which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * This Source Code may also be made available under the following Secondary

+ * Licenses when the conditions for such availability set forth in the

+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,

+ * version 2 with the GNU Classpath Exception, which is available at

+ * https://www.gnu.org/software/classpath/license.html.

+ *

+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

+ */

+package com.sun.mail.util.logging;

+

+import java.io.ByteArrayInputStream;

+import java.io.ByteArrayOutputStream;

+import java.io.IOException;

+import java.io.InputStream;

+import java.text.DateFormat;

+import java.text.MessageFormat;

+import java.text.NumberFormat;

+import java.text.SimpleDateFormat;

+import java.util.*;

+import java.util.logging.*;

+import java.util.logging.Formatter;

+import org.junit.*;

+import static org.junit.Assert.*;

+

+/**

+ * The collector formatter tests.

+ *

+ * @author Jason Mehrens

+ * @since JavaMail 1.5.2

+ */

+public class CollectorFormatterTest extends AbstractLogging {

+

+    /**

+     * See LogManager.

+     */

+    private static final String LOG_CFG_KEY = "java.util.logging.config.file";

+    /**

+     * Date and time simple format pattern.

+     */

+    private static final String DATE_TIME_FMT = "EEE, MMM dd HH:mm:ss:S ZZZ yyyy";

+

+    /**

+     * The line separator.

+     */

+    private static final String LINE_SEP = System.lineSeparator();

+

+    private static void checkJVMOptions() throws Exception {

+        assertTrue(CollectorFormatterTest.class.desiredAssertionStatus());

+        assertNull(System.getProperty("java.util.logging.manager"));

+        assertNull(System.getProperty("java.util.logging.config.class"));

+        assertNull(System.getProperty(LOG_CFG_KEY));

+        assertEquals(LogManager.class, LogManager.getLogManager().getClass());

+    }

+

+    private static void fullFence() {

+        LogManager.getLogManager().getProperty("");

+    }

+

+    @BeforeClass

+    public static void setUpClass() throws Exception {

+        checkJVMOptions();

+    }

+

+    @AfterClass

+    public static void tearDownClass() throws Exception {

+        checkJVMOptions();

+    }

+

+    @Before

+    public void setUp() {

+        fullFence();

+    }

+

+    @After

+    public void tearDown() {

+        fullFence();

+    }

+

+    @Test

+    public void testDeclaredClasses() throws Exception {

+        Class<?>[] declared = CollectorFormatter.class.getDeclaredClasses();

+        assertEquals(Arrays.toString(declared), 0, declared.length);

+    }

+

+    @Test

+    public void testFormatHead() {

+        String msg = "message";

+        XMLFormatter xml = new XMLFormatter();

+        CollectorFormatter f = new CollectorFormatter("{0}", xml,

+                (Comparator<LogRecord>) null);

+        assertEquals("", f.getHead((Handler) null));

+        f.format(new LogRecord(Level.SEVERE, msg));

+

+        String result = f.getTail((Handler) null);

+        String expect = f.finish(xml.getHead((Handler) null));

+        assertEquals(result, expect);

+        assertEquals("", f.getHead((Handler) null));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatApplyReturnsNull() {

+        CollectorFormatter f = new ApplyReturnsNull();

+        for (int i = 0; i < 10; i++) {

+            String o = f.format(new LogRecord(Level.INFO, ""));

+            assertNotNull(o);

+        }

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatNull() {

+        CollectorFormatter f = new CollectorFormatter("{1}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        f.format((LogRecord) null);

+    }

+

+    private static final class TestFormatterAccept extends LogRecord {

+

+        private static final long serialVersionUID = 1L;

+

+        int inferred;

+        @SuppressWarnings("FieldMayBeFinal")

+        private transient Formatter f;

+

+        TestFormatterAccept(Level level, CollectorFormatter f) {

+            super(level, "");

+            this.f = f;

+        }

+

+        @Override

+        public Throwable getThrown() {

+            if (inferred == 0) {

+                assertEquals(Level.INFO.getLocalizedName().concat("11111"),

+                        f.getTail((Handler) null));

+            }

+            return super.getThrown();

+        }

+

+        @Override

+        public String getSourceMethodName() {

+            ++inferred;

+            return super.getSourceMethodName();

+        }

+    }

+

+    @Test(timeout = 60000)

+    public void testFormatAccept() throws Exception {

+        final CollectorFormatter f = new CollectorFormatter(

+                "{1}{3}{5}{7}{8}{13}",

+                new CompactFormatter("%4$s"),

+                new SeverityComparator());

+        LogRecord first = new LogRecord(Level.INFO, "");

+        first.setThrown(new Throwable());

+        setEpochMilli(first, 1L);

+        f.format(first);

+

+        TestFormatterAccept r = new TestFormatterAccept(Level.FINE, f);

+        setEpochMilli(r, 2L);

+        r.setThrown(new Throwable());

+        f.format(r);

+        assertEquals(1, r.inferred);

+        assertEquals(Level.FINE.getLocalizedName().concat("11222"),

+                f.getTail((Handler) null));

+    }

+

+    private static final class TestFormatAcceptAndUpdate extends LogRecord {

+

+        private static final long serialVersionUID = 1L;

+        int inferred;

+        @SuppressWarnings("FieldMayBeFinal")

+        private transient Formatter f;

+

+        public TestFormatAcceptAndUpdate(Level level, CollectorFormatter f) {

+            super(level, "");

+            this.f = f;

+        }

+

+        @Override

+        public String getSourceMethodName() {

+            if (++inferred == 1) {

+                LogRecord r = new LogRecord(Level.INFO, "");

+                setEpochMilli(r, 1L);

+                f.format(r);

+            }

+            return super.getSourceMethodName();

+        }

+    }

+

+    @Test(timeout = 60000)

+    public void testFormatAcceptAndUpdate() throws Exception {

+        final CollectorFormatter f = new CollectorFormatter(

+                "{1}{3}{5}{7}{8}{13}",

+                new CompactFormatter("%4$s"),

+                new SeverityComparator());

+

+        TestFormatAcceptAndUpdate r

+                = new TestFormatAcceptAndUpdate(Level.SEVERE, f);

+        setEpochMilli(r, 2L);

+        r.setThrown(new Throwable());

+        f.format(r);

+        assertEquals(2, r.inferred);

+        assertEquals(Level.SEVERE.getLocalizedName().concat("21121"),

+                f.getTail((Handler) null));

+    }

+

+    @Test

+    public void testFormatFormat() {

+        String msg = "message";

+        XMLFormatter xml = new XMLFormatter();

+        CollectorFormatter f = new CollectorFormatter("{1}", xml,

+                (Comparator<LogRecord>) null);

+        assertEquals("", f.getTail((Handler) null));

+        LogRecord r;

+        r = new LogRecord(Level.SEVERE, msg);

+        f.format(r);

+

+        String result = f.getTail((Handler) null);

+        String expect = f.finish(xml.format(r));

+        assertEquals(result, expect);

+        assertEquals("", f.getTail((Handler) null));

+    }

+

+    @Test

+    public void testFormatFormatLocale() throws Exception {

+        String msg = "message";

+        XMLFormatter xml = new XMLFormatter();

+        CollectorFormatter f = new CollectorFormatter("{1}", xml,

+                (Comparator<LogRecord>) null);

+

+        assertEquals("", f.getTail((Handler) null));

+        LogRecord r;

+        r = new LogRecord(Level.SEVERE, LOG_CFG_KEY);

+        Properties props = new Properties();

+        props.put(LOG_CFG_KEY, msg);

+

+        r.setResourceBundle(new LocaleResource(props, Locale.US));

+        assertNotNull(r.getResourceBundle().getLocale());

+        f.format(r);

+

+        String result = f.getTail((Handler) null);

+        String expect = f.finish(xml.format(r));

+        assertEquals(result, expect);

+        assertEquals(msg, f.formatMessage(r));

+        assertEquals(msg, xml.formatMessage(r));

+        assertEquals("", f.getTail((Handler) null));

+    }

+

+    @Test

+    public void testFormatNoRecords() {

+        XMLFormatter xml = new XMLFormatter();

+        CollectorFormatter f = new CollectorFormatter((String) null, xml,

+                (Comparator<LogRecord>) null);

+

+        String result = f.getTail((Handler) null);

+        String expect = f.finish(xml.getHead((Handler) null))

+                + f.finish(xml.getTail((Handler) null)) + '\n';

+        assertEquals(result, expect);

+    }

+

+    @Test

+    public void testFormatOneRecord() {

+        XMLFormatter xml = new XMLFormatter();

+        CollectorFormatter f = new CollectorFormatter((String) null, xml,

+                (Comparator<LogRecord>) null);

+        LogRecord record = new LogRecord(Level.SEVERE, "message");

+        assertEquals("", f.format(record));

+        String result = f.getTail((Handler) null);

+        String expect = f.finish(xml.getHead((Handler) null))

+                + f.finish(xml.format(record))

+                + f.finish(xml.getTail((Handler) null)) + '\n';

+        assertEquals(result, expect);

+    }

+

+    @Test

+    public void testFormatTwoRecords() {

+        XMLFormatter xml = new XMLFormatter();

+        CollectorFormatter f = new CollectorFormatter((String) null, xml,

+                (Comparator<LogRecord>) null);

+        LogRecord record = new LogRecord(Level.SEVERE, "first");

+        assertEquals("", f.format(record));

+

+        record = new LogRecord(Level.SEVERE, "second");

+        assertEquals("", f.format(record));

+        String result = f.getTail((Handler) null);

+        String expect = f.finish(xml.getHead((Handler) null))

+                + f.finish(xml.format(record))

+                + f.finish(xml.getTail((Handler) null)) + "... 1 more\n";

+        assertEquals(result, expect);

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFinishNull() {

+        CollectorFormatter f = new CollectorFormatter();

+        String result = f.finish((String) null);

+        fail(result);

+    }

+

+    @Test

+    public void testFinish() {

+        CollectorFormatter f = new CollectorFormatter();

+        String format = LINE_SEP + f.getClass().getName() + LINE_SEP;

+        assertFalse(f.getClass().getName().equals(format));

+        assertEquals(f.getClass().getName(), f.finish(format));

+    }

+

+    @Test

+    public void testFormatTail() {

+        String msg = "message";

+        XMLFormatter xml = new XMLFormatter();

+        CollectorFormatter f = new CollectorFormatter("{2}", xml,

+                (Comparator<LogRecord>) null);

+        f.format(new LogRecord(Level.SEVERE, msg));

+

+        String result = f.getTail((Handler) null);

+        String expect = f.finish(xml.getTail((Handler) null));

+        assertEquals(result, expect);

+        assertEquals(expect, f.getTail((Handler) null));

+    }

+

+    @Test

+    public void testFormatCount() {

+        String msg = "message";

+        CollectorFormatter f = new CollectorFormatter("{3}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        assertEquals("0", f.getTail((Handler) null));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+

+        String result = f.getTail((Handler) null);

+        assertEquals(result, "4");

+        assertEquals("0", f.getTail((Handler) null));

+    }

+

+    @Test

+    public void testFormatRemaing() {

+        String msg = "message";

+        CollectorFormatter f = new CollectorFormatter("{4}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        assertEquals("-1", f.getTail((Handler) null));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+

+        String result = f.getTail((Handler) null);

+        assertEquals(result, "3");

+        assertEquals("-1", f.getTail((Handler) null));

+    }

+

+    @Test

+    public void testFormatThrown() {

+        String msg = "message";

+        CollectorFormatter f = new CollectorFormatter("{5}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        assertEquals("0", f.getTail((Handler) null));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+

+        LogRecord r = new LogRecord(Level.SEVERE, msg);

+        r.setThrown(new Exception());

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        r.setThrown(new Exception());

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        r.setThrown(new Exception());

+        f.format(r);

+

+        String result = f.getTail((Handler) null);

+        assertEquals(result, "3");

+        assertEquals("0", f.getTail((Handler) null));

+    }

+

+    @Test

+    public void testFormatNormal() {

+        String msg = "message";

+        CollectorFormatter f = new CollectorFormatter("{6}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        assertEquals("0", f.getTail((Handler) null));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+

+        LogRecord r = new LogRecord(Level.SEVERE, msg);

+        r.setThrown(new Exception());

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        r.setThrown(new Exception());

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        r.setThrown(new Exception());

+        f.format(r);

+

+        String result = f.getTail((Handler) null);

+        assertEquals(result, "2");

+        assertEquals("0", f.getTail((Handler) null));

+    }

+

+    @Test

+    public void testFormatNextMin() throws Exception {

+        CollectorFormatter minF = new CollectorFormatter("{7}",

+                (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        tickMilli(); //Make sure the max not equal to the start time.

+

+        final String min = minF.getTail((Handler) null);

+        NumberFormat.getIntegerInstance().parse(min);

+        tickMilli();

+

+        //Next min is not old min.

+        String next = minF.getTail((Handler) null);

+        assertFalse(min + ' ' + next, min.equals(next));

+

+        //All mins start at the init time.

+        CollectorFormatter initF = new CollectorFormatter("{10}",

+                (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        next = initF.getTail((Handler) null);

+        assertEquals(min, next);

+    }

+

+    @Test

+    public void testFormatMinDateTime() {

+        String msg = "message";

+        CollectorFormatter f = new CollectorFormatter("{7,date,short} {7,time}",

+                (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        long min = 100L;

+

+        LogRecord r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min + 1000L);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min + 2000L);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min + 3000L);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min + 4000L);

+        f.format(r);

+

+        String result = f.getTail((Handler) null);

+        assertEquals(result, MessageFormat.format("{0,date,short} {0,time}", min));

+    }

+

+    @Test

+    public void testFormatNextMax() throws Exception {

+        CollectorFormatter f = new CollectorFormatter("{8}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        String now = f.getTail((Handler) null);

+        Number num = NumberFormat.getIntegerInstance().parse(now);

+        assertFalse(Long.MIN_VALUE == num.longValue());

+        tickMilli();

+        String next = f.getTail((Handler) null);

+        assertFalse(NumberFormat.getIntegerInstance().parse(now).longValue()

+                == Long.MIN_VALUE);

+        assertFalse(now.equals(next));

+    }

+

+    @Test

+    public void testFormatMaxDateTime() {

+        String msg = "message";

+        CollectorFormatter f = new CollectorFormatter("{8,date,short} {8,time}",

+                (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        long min = 100L;

+        long high = 4000L;

+

+        LogRecord r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min + 1000L);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min + high);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min + 2000L);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, msg);

+        setEpochMilli(r, min + 3000L);

+        f.format(r);

+

+        String result = f.getTail((Handler) null);

+        assertEquals(result, MessageFormat.format("{0,date,short} {0,time}", min + high));

+    }

+

+    @Test

+    public void testFormatWindowToInstant() throws Exception {

+        CollectorFormatter f = new CollectorFormatter("{7}_{8}",

+                (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(r, 100);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(r, 200);

+        f.format(r);

+

+        //Check that the min and max are different.

+        String output = f.getTail((Handler) null);

+        int fence = output.indexOf('_');

+        assertFalse(output.regionMatches(0, output, fence + 1,

+                (output.length() - fence) - 1));

+

+        r = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(r, 400);

+        f.format(r);

+

+        //Previous max is 200 so at this point the min and max better be 400.

+        output = f.getTail((Handler) null);

+        fence = output.indexOf('_');

+        assertTrue(output.regionMatches(0, output, fence + 1,

+                (output.length() - fence) - 1));

+        assertEquals("400_400", output);

+    }

+

+    @Test

+    public void testGetTail() {

+        CollectorFormatter f = new CollectorFormatter(

+                "{0}{1}{2}{3}{4}{5}{6}{7}{8}{9}{10}{11}{12}{13}",

+                (Formatter) null,

+                (Comparator<LogRecord>) null);

+        assertTrue(f.getTail((Handler) null).length() != 0);

+        assertTrue(f.getTail((Handler) null).length() != 0);

+    }

+

+    @Test

+    public void testGetTailExample1() {

+        String p = "{0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n";

+        CollectorFormatter cf = new CollectorFormatter(p);

+        LogRecord r = new LogRecord(Level.WARNING, "warning message");

+        cf.format(r);

+

+        r = new LogRecord(Level.SEVERE, "Encoding failed.");

+        RuntimeException npe = new NullPointerException();

+        StackTraceElement frame = new StackTraceElement("java.lang.String",

+                "getBytes", "String.java", 913);

+        npe.setStackTrace(new StackTraceElement[]{frame});

+        r.setThrown(npe);

+        cf.format(r);

+

+        cf.format(new LogRecord(Level.INFO, "info"));

+        cf.format(new LogRecord(Level.INFO, "info"));

+        String output = cf.getTail((Handler) null);

+        assertNotNull(output);

+    }

+

+    @Test

+    public void testGetTailExample2() {

+        String p = "These {3} messages occurred between\n"

+                + "{7,date,EEE, MMM dd HH:mm:ss:S ZZZ yyyy} and "

+                + "{8,time,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}\n";

+        CollectorFormatter cf = new CollectorFormatter(p);

+        LogRecord min = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(min, 1248203502449L);

+        cf.format(min);

+

+        int count = 290;

+        for (int i = 0; i < count; ++i) {

+            LogRecord mid = new LogRecord(Level.SEVERE, "");

+            setEpochMilli(mid, min.getMillis());

+            cf.format(mid);

+        }

+

+        LogRecord max = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(max, 1258723764000L);

+        cf.format(max);

+        Object[] args = new Object[9];

+        args[3] = count + 2L;

+        args[7] = min.getMillis();

+        args[8] = max.getMillis();

+        assertEquals(MessageFormat.format(p, args), cf.toString());

+        String output = cf.getTail((Handler) null);

+        assertNotNull(output);

+    }

+

+    @Test

+    public void testGetTailExample3a() {

+        String p = "These {3} messages occurred between\n"

+                + "{7,date,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}"

+                + " and {8,time,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}\n";

+        CollectorFormatter cf = new CollectorFormatter(p);

+        LogRecord min = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(min, 1248203502449L);

+        cf.format(min);

+

+        for (int i = 0; i < 71; ++i) {

+            LogRecord mid = new LogRecord(Level.SEVERE, "");

+            setEpochMilli(mid, min.getMillis());

+            cf.format(mid);

+        }

+

+        LogRecord max = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(max, min.getMillis() + 110500);

+        cf.format(max);

+

+        String output = cf.getTail((Handler) null);

+        assertNotNull(output);

+    }

+

+    @Test

+    public void testGetTailExample3b() {

+        String p = "These {3} messages occurred between "

+                + "{9,choice,86400000#{7,date} {7,time} and {8,time}"

+                + "|86400000<{7,date} and {8,date}}\n";

+        CollectorFormatter cf = new CollectorFormatter(p);

+        LogRecord min = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(min, 1248203502449L);

+

+        cf.format(min);

+        for (int i = 0; i < 114; ++i) {

+            LogRecord mid = new LogRecord(Level.SEVERE, "");

+            setEpochMilli(mid, min.getMillis());

+            cf.format(mid);

+        }

+

+        LogRecord max = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(max, min.getMillis() + 2591000000L);

+        cf.format(max);

+

+        String output = cf.getTail((Handler) null);

+        assertNotNull(output);

+    }

+

+    @Test

+    public void testGetTailExample4() throws Exception {

+        String p = "{13} alert reports since {10,date}.\n";

+        CollectorFormatter cf = new CollectorFormatter(p);

+

+        int count = 4320;

+        for (int i = 1; i < count; ++i) {

+            LogRecord mid = new LogRecord(Level.SEVERE, "");

+            cf.format(mid);

+            cf.getTail((Handler) null);

+        }

+

+        String output = cf.getTail((Handler) null);

+        assertNotNull(output);

+        String jd73 = NumberFormat.getIntegerInstance().format(count);

+        assertTrue(output.startsWith(jd73));

+    }

+

+    @Test

+    public void testNewDefaultFormatter() {

+        String msg = "";

+        CollectorFormatter f = new CollectorFormatter();

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        String result = f.getTail((Handler) null);

+        assertTrue(result, result.length() != 0);

+        assertTrue(result, result.contains("..."));

+        assertTrue(result, result.contains("1"));

+        assertTrue(result, result.contains("more"));

+    }

+

+    @Test

+    public void testNewFormatterWithString() {

+        CollectorFormatter f = new CollectorFormatter("{3}");

+        f.format(new LogRecord(Level.SEVERE, ""));

+        String result = f.getTail((Handler) null);

+        assertEquals(result, "1");

+    }

+

+    @Test

+    public void testNewFormatterNullString() {

+        CollectorFormatter f = new CollectorFormatter((String) null);

+        assertEquals(CollectorFormatter.class, f.getClass());

+    }

+

+    @Test

+    public void testNewFormatterNullNonNullNonNull() {

+        CollectorFormatter f = new CollectorFormatter((String) null,

+                new XMLFormatter(), SeverityComparator.getInstance());

+        assertEquals(CollectorFormatter.class, f.getClass());

+    }

+

+    @Test

+    public void testNewFormatterNullNullNull() {

+        CollectorFormatter f = new CollectorFormatter((String) null,

+                (Formatter) null, (Comparator<LogRecord>) null);

+        assertEquals(CollectorFormatter.class, f.getClass());

+    }

+

+    @Test

+    public void testNewFormatterNonNullNullNull() {

+        CollectorFormatter f = new CollectorFormatter("Test {0}",

+                (Formatter) null, (Comparator<LogRecord>) null);

+        assertEquals(CollectorFormatter.class, f.getClass());

+    }

+

+    @Test

+    public void testToString() {

+        String msg = "message";

+        CollectorFormatter f = new CollectorFormatter("{3}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+

+        String result = f.toString();

+        assertEquals(result, f.toString());

+        assertEquals(result, f.getTail((Handler) null));

+        assertFalse(result.equals(f.toString()));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testApplyNullAndNull() {

+        CollectorFormatter f = new CollectorFormatter("{3}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        f.apply((LogRecord) null, (LogRecord) null);

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testApplyNullAndLogRecord() {

+        CollectorFormatter f = new CollectorFormatter("{3}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        f.apply((LogRecord) null, new LogRecord(Level.SEVERE, ""));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testApplyLogRecordAndNull() {

+        CollectorFormatter f = new CollectorFormatter("{3}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        f.apply(new LogRecord(Level.SEVERE, ""), (LogRecord) null);

+    }

+

+    @Test

+    public void testApplyWithoutComparator() {

+        CollectorFormatter f = new CollectorFormatter("{3}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        LogRecord first = new LogRecord(Level.SEVERE, "");

+        LogRecord second = new LogRecord(Level.WARNING, "");

+        assertSame(second, f.apply(first, second));

+    }

+

+    @Test

+    public void testApplyWithComparator() {

+        CollectorFormatter f = new CollectorFormatter("{3}", (Formatter) null,

+                SeverityComparator.getInstance());

+        LogRecord first = new LogRecord(Level.SEVERE, "");

+        LogRecord second = new LogRecord(Level.WARNING, "");

+        assertSame(first, f.apply(first, second));

+    }

+

+    @Test

+    public void testComparator() throws Exception {

+        final String p = CollectorFormatter.class.getName();

+        Properties props = new Properties();

+        props.put(p.concat(".comparator"), SeverityComparator.class.getName());

+        props.put(p.concat(".comparator.reverse"), "false");

+        LogManager manager = LogManager.getLogManager();

+        try {

+            read(manager, props);

+            CollectorFormatter cf = new CollectorFormatter();

+            LogRecord first = new LogRecord(Level.SEVERE, Level.SEVERE.getName());

+            LogRecord second = new LogRecord(Level.WARNING, Level.WARNING.getName());

+            cf.format(second);

+            cf.format(first);

+            String result = cf.getTail((Handler) null);

+            assertTrue(result, result.startsWith(Level.SEVERE.getName()));

+        } finally {

+            manager.reset();

+        }

+    }

+

+    @Test

+    public void testComparatorReverse() throws Exception {

+        final String p = CollectorFormatter.class.getName();

+        Properties props = new Properties();

+        props.put(p.concat(".comparator"), SeverityComparator.class.getName());

+        props.put(p.concat(".comparator.reverse"), "true");

+        LogManager manager = LogManager.getLogManager();

+        try {

+            read(manager, props);

+            CollectorFormatter cf = new CollectorFormatter();

+            LogRecord first = new LogRecord(Level.SEVERE, Level.SEVERE.getName());

+            LogRecord second = new LogRecord(Level.WARNING, Level.WARNING.getName());

+            cf.format(second);

+            cf.format(first);

+            String result = cf.getTail((Handler) null);

+            assertTrue(result, result.startsWith(Level.WARNING.getName()));

+        } finally {

+            manager.reset();

+        }

+    }

+

+    @Test

+    public void testFormat() throws Exception {

+        final String p = CollectorFormatter.class.getName();

+        Properties props = new Properties();

+        final String expect = CollectorFormatterTest.class.getName();

+        props.put(p.concat(".format"), expect);

+        LogManager manager = LogManager.getLogManager();

+        try {

+            read(manager, props);

+            CollectorFormatter cf = new CollectorFormatter();

+            LogRecord first = new LogRecord(Level.SEVERE, Level.SEVERE.getName());

+            assertEquals("", cf.format(first));

+            String result = cf.getTail((Handler) null);

+            assertEquals(expect, result);

+        } finally {

+            manager.reset();

+        }

+    }

+

+    @Test

+    public void testFormatter() throws Exception {

+        final String p = CollectorFormatter.class.getName();

+        Properties props = new Properties();

+        props.put(p.concat(".formatter"), XMLFormatter.class.getName());

+        LogManager manager = LogManager.getLogManager();

+        try {

+            read(manager, props);

+            XMLFormatter xml = new XMLFormatter();

+            CollectorFormatter cf = new CollectorFormatter();

+            LogRecord first = new LogRecord(Level.SEVERE, Level.SEVERE.getName());

+            assertEquals("", cf.format(first));

+            String result = cf.getTail((Handler) null);

+            assertEquals(result, cf.finish(xml.getHead((Handler) null))

+                    + cf.finish(xml.format(first))

+                    + cf.finish(xml.getTail((Handler) null)) + '\n');

+        } finally {

+            manager.reset();

+        }

+    }

+

+    @Test

+    public void testFormatElapsedTime() throws Exception {

+        CollectorFormatter f = new CollectorFormatter("{9}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(r, 25L);

+        f.format(r);

+

+        r = new LogRecord(Level.SEVERE, "");

+        setEpochMilli(r, 100L);

+        f.format(r);

+

+        String init = f.getTail((Handler) null);

+        Number n = NumberFormat.getIntegerInstance().parse(init);

+        assertEquals(75, n.longValue());

+    }

+

+    @Test

+    public void testFormatInitTimeMillis() throws Exception {

+        CollectorFormatter f = new CollectorFormatter("{10}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        String init = f.getTail((Handler) null);

+        NumberFormat.getIntegerInstance().parse(init);

+        tickMilli();

+

+        assertTrue(init.equals(f.getTail((Handler) null)));

+    }

+

+    @Test

+    public void testFormatInitTimeDateTime() throws Exception {

+        CollectorFormatter f = new CollectorFormatter(

+                "{10,date," + DATE_TIME_FMT + "}",

+                (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        String init = f.getTail((Handler) null);

+        DateFormat df = new SimpleDateFormat(DATE_TIME_FMT);

+        Date dt = df.parse(init);

+        tickMilli();

+

+        assertTrue(init.equals(f.getTail((Handler) null)));

+        assertTrue(dt.equals(df.parse(f.getTail((Handler) null))));

+    }

+

+    @Test

+    public void testFormatCurrentTimeMillis() throws Exception {

+        CollectorFormatter f = new CollectorFormatter("{11}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        String now = f.getTail((Handler) null);

+        NumberFormat.getIntegerInstance().parse(now);

+        tickMilli();

+

+        assertFalse(now.equals(f.getTail((Handler) null)));

+    }

+

+    @Test

+    public void testFormatCurrentTimeDateTime() throws Exception {

+        CollectorFormatter f = new CollectorFormatter(

+                "{11,date," + DATE_TIME_FMT + "}",

+                (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        String init = f.getTail((Handler) null);

+        DateFormat df = new SimpleDateFormat(DATE_TIME_FMT);

+        Date dt = df.parse(init);

+        tickMilli();

+

+        assertFalse(init.equals(f.getTail((Handler) null)));

+        assertFalse(dt.equals(df.parse(f.getTail((Handler) null))));

+    }

+

+    @Test

+    public void testFormatUpTime() throws Exception {

+        CollectorFormatter f = new CollectorFormatter("{12}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        String up = f.getTail((Handler) null);

+        NumberFormat.getIntegerInstance().parse(up);

+        tickMilli();

+

+        assertFalse(up.equals(f.getTail((Handler) null)));

+    }

+

+    @Test

+    public void testFormatGeneration() {

+        String msg = "message";

+        CollectorFormatter f = new CollectorFormatter("{13}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+

+        assertEquals("1", f.getTail((Handler) null));

+        assertEquals("1", f.toString());

+        assertEquals("1", f.getTail((Handler) null));

+        assertEquals("1", f.toString());

+

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+

+        assertEquals("1", f.getTail((Handler) null)); //reset

+

+        assertEquals("2", f.getTail((Handler) null));

+        assertEquals("2", f.toString());

+        assertEquals("2", f.getTail((Handler) null));

+        assertEquals("2", f.toString());

+

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+        f.format(new LogRecord(Level.SEVERE, msg));

+

+        assertEquals("2", f.getTail((Handler) null)); //reset

+        assertEquals("3", f.getTail((Handler) null));

+        assertEquals("3", f.toString());

+        assertEquals("3", f.toString());

+        assertEquals("3", f.getTail((Handler) null));

+    }

+

+    @Test

+    public void testFormatNullFormatter() {

+        CollectorFormatter f = new CollectorFormatter("{1}", (Formatter) null,

+                (Comparator<LogRecord>) null);

+        LogRecord r = new LogRecord(Level.SEVERE, "message {0}");

+        r.setParameters(new Object[]{1});

+        f.format(r);

+        String output = f.getTail((Handler) null);

+        assertEquals(f.formatMessage(r), output);

+    }

+

+    @Test

+    public void testFormatIllegalPattern() {

+        CollectorFormatter f = new CollectorFormatter("{9");

+        f.format(new LogRecord(Level.SEVERE, ""));

+        try {

+            f.getTail((Handler) null);

+            fail("Expected format exception.");

+        } catch (IllegalArgumentException expect) {

+        }

+    }

+

+    @Test

+    public void testFormatIllegalTargetPattern() {

+        CollectorFormatter f = new CollectorFormatter("{1}",

+                new CompactFormatter("%1$#tc"), (Comparator<LogRecord>) null);

+        f.format(new LogRecord(Level.SEVERE, ""));

+        try {

+            f.getTail((Handler) null);

+            fail("Expected format exception.");

+        } catch (java.util.IllegalFormatException expect) {

+        }

+    }

+

+    @Test

+    public void testJavaMailLinkage() throws Exception {

+        testJavaMailLinkage(CollectorFormatter.class);

+    }

+

+    @Test

+    public void testLogManagerModifiers() throws Exception {

+        testLogManagerModifiers(CollectorFormatter.class);

+    }

+

+    @Test

+    public void testWebappClassLoaderFieldNames() throws Exception {

+        testWebappClassLoaderFieldNames(CollectorFormatter.class);

+    }

+

+    /**

+     * An example of a broken implementation of apply.

+     */

+    private static class ApplyReturnsNull extends CollectorFormatter {

+

+        /**

+         * The number of records.

+         */

+        private int count;

+

+        /**

+         * Promote access level.

+         */

+        ApplyReturnsNull() {

+        }

+

+        @Override

+        protected LogRecord apply(LogRecord t, LogRecord u) {

+            assertNotNull(t);

+            assertNotNull(u);

+            return (++count & 1) == 1 ? u : null;

+        }

+    }

+

+    /**

+     * A properties resource bundle with locale.

+     */

+    private static class LocaleResource extends PropertyResourceBundle {

+

+        /**

+         * The locale

+         */

+        private final Locale locale;

+

+        /**

+         * Creates the locale resource.

+         *

+         * @param p the properties.

+         * @param l the locale.

+         * @throws IOException if there is a problem.

+         */

+        LocaleResource(Properties p, Locale l) throws IOException {

+            super(toStream(p));

+            locale = l;

+        }

+

+        private static InputStream toStream(Properties p) throws IOException {

+            ByteArrayOutputStream out = new ByteArrayOutputStream();

+            p.store(out, LocaleResource.class.getName());

+            return new ByteArrayInputStream(out.toByteArray());

+        }

+

+        @Override

+        public Locale getLocale() {

+            return locale;

+        }

+    }

+}

diff --git a/mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java b/mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java
new file mode 100644
index 0000000..c79fe7e
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/logging/CompactFormatterTest.java
@@ -0,0 +1,1680 @@
+/*

+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2013, 2018 Jason Mehrens. All rights reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0, which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * This Source Code may also be made available under the following Secondary

+ * Licenses when the conditions for such availability set forth in the

+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,

+ * version 2 with the GNU Classpath Exception, which is available at

+ * https://www.gnu.org/software/classpath/license.html.

+ *

+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

+ */

+package com.sun.mail.util.logging;

+

+import java.io.*;

+import java.lang.reflect.Method;

+import java.net.SocketException;

+import java.util.*;

+import java.util.logging.Level;

+import java.util.logging.LogManager;

+import java.util.logging.LogRecord;

+import javax.mail.MessagingException;

+import javax.mail.internet.MimeUtility;

+import org.junit.*;

+import static org.junit.Assert.*;

+

+/**

+ * Compact formatter tests.

+ *

+ * @author Jason Mehrens

+ * @since JavaMail 1.5.2

+ */

+public class CompactFormatterTest extends AbstractLogging {

+

+    private static final String UNKNOWN_CLASS_NAME

+            = CompactFormatterTest.class.getName().concat("Foo");

+

+    /**

+     * The line separator.

+     */

+    private static final String LINE_SEP = System.lineSeparator();

+

+    /**

+     * The max width.

+     */

+    private static final int MAX_PRE = 160;

+    /**

+     * The default left to right pattern.

+     */

+    private static final String LEFT_TO_RIGHT = "%7$#." + MAX_PRE + "s%n";

+

+    /**

+     * See LogManager.

+     */

+    private static final String LOG_CFG_KEY = "java.util.logging.config.file";

+

+    private static void checkJVMOptions() throws Exception {

+        assertTrue(CollectorFormatterTest.class.desiredAssertionStatus());

+        assertNull(System.getProperty("java.util.logging.manager"));

+        assertNull(System.getProperty("java.util.logging.config.class"));

+        assertNull(System.getProperty(LOG_CFG_KEY));

+        assertEquals(LogManager.class, LogManager.getLogManager().getClass());

+    }

+

+    private static void fullFence() {

+        LogManager.getLogManager().getProperty("");

+    }

+

+    @BeforeClass

+    public static void setUpClass() throws Exception {

+        checkJVMOptions();

+        assertTrue("Recompile tests to include source line numbers.",

+                new Throwable().getStackTrace()[0].getLineNumber() >= 0);

+        try {

+            throw new AssertionError(Class.forName(UNKNOWN_CLASS_NAME));

+        } catch (ClassNotFoundException expect) {

+        }

+    }

+

+    @AfterClass

+    public static void tearDownClass() throws Exception {

+        checkJVMOptions();

+    }

+

+    @Before

+    public void setUp() {

+        fullFence();

+    }

+

+    @After

+    public void tearDown() {

+        fullFence();

+    }

+

+    @Test

+    public void testDeclaredClasses() throws Exception {

+        testLoadDeclaredClasses(CompactFormatter.class);

+    }

+

+    @Test

+    public void testFormat() throws Exception {

+        final String p = CompactFormatter.class.getName();

+        Properties props = new Properties();

+        props.put(p.concat(".format"), "%9$s");

+        LogManager manager = LogManager.getLogManager();

+        try {

+            read(manager, props);

+            CompactFormatter cf = new CompactFormatter();

+            LogRecord first = new LogRecord(Level.SEVERE, Level.INFO.getName());

+            first.setSequenceNumber(Short.MAX_VALUE);

+            String result = cf.format(first);

+            assertEquals(String.valueOf((int) Short.MAX_VALUE), result);

+        } finally {

+            manager.reset();

+        }

+    }

+

+    @Test

+    public void testNewFormatterWithPattern() {

+        CompactFormatter cf = new CompactFormatter("%4$s");

+        String result = cf.format(new LogRecord(Level.SEVERE, ""));

+        assertEquals(Level.SEVERE.getLocalizedName(), result);

+    }

+

+    @Test

+    public void testNewFormatterNullPattern() {

+        CompactFormatter cf = new CompactFormatter((String) null);

+        assertEquals(CompactFormatter.class, cf.getClass());

+    }

+

+    @Test

+    public void testGetHeadAndGetTail() {

+        CompactFormatter cf = new CompactFormatter();

+        assertEquals("", cf.getHead(null));

+        assertEquals("", cf.getTail(null));

+    }

+

+    @Test

+    public void testFormatWithMessage() {

+        LogRecord record = new LogRecord(Level.SEVERE, "message");

+        CompactFormatter cf = new CompactFormatter();

+        String result = cf.format(record);

+        assertTrue(result, result.startsWith(record.getMessage()));

+        assertTrue(result, result.endsWith(LINE_SEP));

+    }

+

+    @Test

+    public void testFormatWithMessagePrecisionOverWidth() {

+        LogRecord record = new LogRecord(Level.SEVERE, "message");

+        record.setThrown(new Throwable("thrown"));

+        CompactFormatter cf = new CompactFormatter("%7$#6.12s");

+        String result = cf.format(record);

+        assertEquals("mes|Throwable", result);

+    }

+

+    @Test

+    public void testFormatWithMessageWidthOverPrecision() {

+        LogRecord record = new LogRecord(Level.SEVERE, "message");

+        record.setThrown(new Throwable("thrown"));

+        CompactFormatter cf = new CompactFormatter("%7$#12.6s");

+        String result = cf.format(record);

+        assertEquals("mes\u0020\u0020\u0020|Thr\u0020\u0020\u0020", result);

+    }

+

+    @Test

+    public void testFormatWithMessageEmpty() {

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        CompactFormatter cf = new CompactFormatter();

+        String result = cf.format(record);

+        assertEquals(result, LINE_SEP);

+    }

+

+    @Test

+    public void testFormatMessageSurrogate() {

+        LogRecord record = new LogRecord(Level.SEVERE,

+                "a\ud801\udc00\ud801\udc00\ud801\udc00\ud801\udc00");

+        record.setThrown(new Throwable("thrown"));

+        CompactFormatter cf = new CompactFormatter("%7$#.6s%n");

+        String result = cf.format(record);

+        assertTrue(result, result.startsWith("a\ud801\udc00"));

+        assertTrue(result, result.endsWith("|Thr" + LINE_SEP));

+    }

+

+    @Test

+    public void testFormatWithMessageAndThrownLeftToRight() {

+        LogRecord record = new LogRecord(Level.SEVERE, "message");

+        record.setThrown(new Throwable("thrown"));

+        CompactFormatter cf = new CompactFormatter();

+        String result = cf.format(record);

+        assertTrue(result, result.startsWith(record.getMessage()));

+        assertTrue(result, result.contains("|"));

+        assertTrue(result, result.contains(Throwable.class.getSimpleName()));

+        assertTrue(result, result.contains(CompactFormatterTest.class.getSimpleName()));

+        assertTrue(result, result.contains("testFormatWithMessageAndThrown"));

+        assertTrue(result, result.contains(String.valueOf(

+                record.getThrown().getStackTrace()[0].getLineNumber())));

+        assertTrue(result, result.endsWith(LINE_SEP));

+    }

+

+    @Test

+    public void testFormatWithThrownLeftToRight() {

+        testFormatWithThrown(LEFT_TO_RIGHT);

+    }

+

+    private void testFormatWithThrown(String fmt) {

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(new Throwable("thrown"));

+        CompactFormatter cf = new CompactFormatter(fmt);

+        String result = cf.format(record);

+        assertFalse(result, result.startsWith("|"));

+        assertTrue(result, result.contains(record.getThrown().getMessage()));

+        assertTrue(result, result.endsWith(LINE_SEP));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatMessageNull() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatMessage((LogRecord) null);

+        fail(cf.toString());

+    }

+

+    @Test

+    public void testFormatMessageLocale() throws Exception {

+        String msg = "message";

+        CompactFormatter cf = new CompactFormatter("%5$s");

+        Properties props = new Properties();

+        props.put(LOG_CFG_KEY, msg);

+

+        LogRecord r = new LogRecord(Level.SEVERE, LOG_CFG_KEY);

+        r.setResourceBundle(new LocaleResource(props, Locale.US));

+        assertNotNull(r.getResourceBundle().getLocale());

+        String result = cf.format(r);

+        assertEquals(msg, result);

+        assertEquals(msg, cf.formatMessage(r));

+    }

+

+    @Test

+    public void testFormatMessage_LogRecordLeftToRight() {

+        testFormatMessage_LogRecord(LEFT_TO_RIGHT);

+    }

+

+    private void testFormatMessage_LogRecord(String fmt) {

+        Exception e = new IOException();

+        assertNull(e.getMessage(), e.getMessage());

+

+        e = new Exception(e.toString(), e);

+        assertNotNull(e.getMessage(), e.getMessage());

+

+        LogRecord record = new LogRecord(Level.SEVERE, e.toString());

+        record.setThrown(e);

+        CompactFormatter cf = new CompactFormatter(fmt);

+        String result = cf.format(record);

+

+        assertTrue(result, result.contains("|"));

+        assertTrue(e.toString(), e.toString().contains(Exception.class.getPackage().getName()));

+        assertTrue(e.toString(), e.toString().contains(IOException.class.getPackage().getName()));

+        int idx;

+        idx = result.indexOf(Exception.class.getSimpleName());

+        assertTrue(result, idx >= 0);

+

+        idx = result.indexOf(IOException.class.getSimpleName(), idx);

+        assertTrue(result, idx >= 0);

+

+        assertTrue(result, result.contains(record.getThrown().getClass().getSimpleName()));

+        assertTrue(result, result.contains(record.getThrown().getCause().getClass().getSimpleName()));

+

+        assertFalse(result, result.contains(Exception.class.getPackage().getName()));

+        assertFalse(result, result.contains(IOException.class.getPackage().getName()));

+

+        assertFalse(result, result.contains(Exception.class.getName()));

+        assertFalse(result, result.contains(IOException.class.getName()));

+

+        assertTrue(result, result.endsWith(LINE_SEP));

+    }

+

+    @Test(timeout = 30000)

+    public void testFormatMessage_LogRecordEvil() {

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(createEvilThrowable());

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatMessage(record);

+    }

+

+    @Test

+    public void testFormatMaxMessageWidthLeftToRight() {

+        testFormatMaxMessageWidth(LEFT_TO_RIGHT, MAX_PRE);

+    }

+

+    private void testFormatMaxMessageWidth(String fmt, int width) {

+        assertTrue(fmt, fmt.contains(Integer.toString(width)));

+        assertTrue(String.valueOf(width), width < Integer.MAX_VALUE / 4);

+

+        CompactFormatter cf = new CompactFormatter(fmt);

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        int padding = LINE_SEP.length();

+        for (int i = 1; i < width; i++) {

+            record.setMessage(rpad("", i, "A"));

+            String result = cf.format(record);

+            assertTrue(result, result.length() == i + padding);

+            assertTrue(result, result.endsWith(LINE_SEP));

+        }

+

+        for (int i = width; i <= (width * 4); i++) {

+            record.setMessage(rpad("", i, "A"));

+            String result = cf.format(record);

+            assertTrue(result, result.length() == width + padding);

+            assertTrue(result, result.endsWith(LINE_SEP));

+        }

+    }

+

+    @Test

+    public void testFormatMaxThrowableWidthLeftToRight() {

+        String fmt = LEFT_TO_RIGHT;

+        int width = MAX_PRE;

+        assertTrue(fmt, fmt.contains(Integer.toString(width)));

+        assertTrue(String.valueOf(width), width < Integer.MAX_VALUE / 4);

+

+        CompactFormatter cf = new CompactFormatter(fmt);

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        int padding = LINE_SEP.length();

+        for (int i = 0; i < width; i++) {

+            record.setThrown(new Throwable(rpad("", i, "A")));

+            String result = cf.format(record);

+            assertTrue(result, result.length() <= width + padding);

+

+            assertTrue(result, result.endsWith(LINE_SEP));

+        }

+

+        for (int i = width; i <= (width * 4); i++) {

+            record.setThrown(new Throwable(rpad("", i, "A")));

+            String result = cf.format(record);

+            assertTrue(result.length() + ", " + (width + padding),

+                    result.length() == width + padding);

+            assertTrue(result, result.endsWith(LINE_SEP));

+        }

+    }

+

+    @Test

+    public void testFormatThrownTrailingDot() {

+        testFormatThrownIllegalClassName("Hello.");

+    }

+

+    @Test

+    public void testFormatThrownClassDotSpace() {

+        String msg = "test";

+        String prefix = IllegalStateException.class.getName() + ". ";

+        Throwable t = new PrefixException(prefix, null, null);

+        assertEquals(prefix, t.toString());

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        String cns = t.getClass().getSimpleName()

+                + ": " + IllegalStateException.class.getSimpleName();

+        assertTrue(result, result.startsWith(cns));

+        assertTrue(result, result.indexOf(cns) == result.lastIndexOf(cns));

+        assertTrue(result, result.contains(msg));

+        assertTrue(result, result.indexOf(msg) == result.lastIndexOf(msg));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatThrownLeadingDot() {

+        testFormatThrownIllegalClassName(".Hello");

+    }

+

+    @Test

+    public void testFormatThrownDotDot() {

+        testFormatThrownIllegalClassName("Hello..World");

+        testFormatThrownIllegalClassName("..HelloWorld");

+        testFormatThrownIllegalClassName("HelloWorld..");

+    }

+

+    @Test

+    public void testFormatThrownColonSpace() {

+        testFormatThrownIllegalClassName("Hello: World");

+    }

+

+    @Test

+    public void testFormatThrownEndSign() {

+        //Some of these are legal but not worth considering legal.

+        testFormatThrownIllegalClassName("HelloWorld$");

+        testFormatThrownIllegalClassName("HelloWorld.$");

+        testFormatThrownIllegalClassName("Hello.World$");

+    }

+

+    @Test

+    public void testFormatThrownStartSign() {

+        testFormatThrownIllegalClassName("$HelloWorld");

+        testFormatThrownIllegalClassName("$.HelloWorld");

+    }

+

+    @Test

+    public void testFormatThrownDotDotSign() {

+        testFormatThrownIllegalClassName("Hello..$World");

+        testFormatThrownIllegalClassName("..$HelloWorld");

+        testFormatThrownIllegalClassName("HelloWorld..$");

+    }

+

+    @Test

+    public void testFormatThrownSignDot() {

+        testFormatThrownIllegalClassName("$.HelloWorld");

+        testFormatThrownIllegalClassName("HelloWorld$.");

+    }

+

+    @Test

+    public void testFormatThrownSignDotDot() {

+        testFormatThrownIllegalClassName("Hello$..World");

+        testFormatThrownIllegalClassName("$..HelloWorld");

+        testFormatThrownIllegalClassName("HelloWorld$..");

+    }

+

+    @Test

+    public void testFormatThrownDotSignDot() {

+        testFormatThrownIllegalClassName("Hello.$.World");

+        testFormatThrownIllegalClassName(".$.HelloWorld");

+        testFormatThrownIllegalClassName("HelloWorld.$.");

+    }

+

+    private void testFormatThrownIllegalClassName(String prefix) {

+        Throwable t = new PrefixException(prefix, null, null);

+        assertEquals(prefix, t.toString());

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        String cn = t.getClass().getSimpleName();

+        assertTrue(result, result.startsWith(cn));

+        assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));

+        assertTrue(result, result.contains(prefix));

+        assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatThrownSimpleClassNameNullMessage() {

+        //javax.management.BadStringOperationException

+        String op = "some op";

+        Throwable t = new PrefixException(PrefixException.class.getSimpleName()

+                + ": " + op, (String) null, null);

+        assertNull(t.getMessage());

+        assertNotNull(t.toString());

+        assertTrue(t.toString().startsWith(t.getClass().getSimpleName()));

+

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        String sn = t.getClass().getSimpleName();

+        assertTrue(result, result.startsWith(sn));

+        assertTrue(result, result.indexOf(sn) == result.lastIndexOf(sn));

+        assertTrue(result, result.contains(op));

+        assertTrue(result, result.indexOf(op) == result.lastIndexOf(op));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatServerSidetMetroException() {

+        //com.sun.xml.ws.developer.ServerSideException

+        String msg = "server error";

+        NullPointerException npe = new NullPointerException(msg);

+        Throwable t = new PrefixException(npe.getClass().getName(), msg, null);

+        assertEquals(msg, npe.getMessage());

+        assertEquals(msg, t.getMessage());

+        assertEquals(npe.toString(), t.toString());

+

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        String cns = t.getClass().getSimpleName()

+                + ": " + npe.getClass().getSimpleName();

+        assertTrue(result, result.startsWith(cns));

+        assertTrue(result, result.indexOf(cns) == result.lastIndexOf(cns));

+        assertTrue(result, result.contains(msg));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatThrownPrefixMessageRetainsFqn() {

+        String msg = "java.io.tmpdir";

+        NullPointerException npe = new NullPointerException(msg);

+        Throwable t = new PrefixException(npe.getClass().getName(), msg, null);

+        assertEquals(msg, npe.getMessage());

+        assertEquals(msg, t.getMessage());

+        assertEquals(npe.toString(), t.toString());

+

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        String cns = t.getClass().getSimpleName()

+                + ": " + npe.getClass().getSimpleName();

+        assertTrue(result, result.startsWith(cns));

+        assertTrue(result, result.indexOf(cns) == result.lastIndexOf(cns));

+        assertTrue(result, result.contains(msg));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatThrownHiddenMessageRetainsFqn() {

+        String msg = "java.io.tmpdir";

+        NullPointerException npe = new NullPointerException(msg);

+        Throwable t = new ToStringException(npe.getClass().getName(), msg);

+        assertEquals(msg, npe.getMessage());

+        assertEquals(msg, t.getMessage());

+

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        String cns = t.getClass().getSimpleName()

+                + ": " + npe.getClass().getSimpleName();

+        assertTrue(result, result.startsWith(cns));

+        assertTrue(result, result.indexOf(cns) == result.lastIndexOf(cns));

+        assertTrue(result, result.contains(msg));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatXMLParseXercesException() {

+        //com.sun.org.apache.xerces.internal.xni.parser.XMLParseException

+        String msg = "XML";

+        String prefix = "1:two:3:four";

+        Throwable t = new PrefixException(prefix, msg, null);

+

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        assertTrue(prefix, t.toString().startsWith(prefix));

+        String cn = t.getClass().getSimpleName();

+        assertTrue(result, result.startsWith(cn));

+        assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));

+        assertTrue(result, result.contains(prefix));

+        assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));

+        assertTrue(result, result.contains(msg));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatGSSException() {

+        //org.ietf.jgss.GSSException

+        String msg = "Invalid name provided";

+        String prefix = PrefixException.class.getSimpleName();

+        Throwable t = new PrefixException(prefix, msg, null);

+        assertTrue(t.toString().startsWith(t.getClass().getSimpleName()));

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        assertTrue(prefix, t.toString().startsWith(prefix));

+        String cn = t.getClass().getSimpleName();

+        assertTrue(result, result.startsWith(cn));

+        assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));

+        assertTrue(result, result.contains(prefix));

+        assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));

+        assertTrue(result, result.contains(msg));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatMismatchedTreeNodeException() {

+        //org.antlr.runtime.MismatchedTreeNodeException

+        String prefix = ToStringException.class.getSimpleName()

+                + '(' + String.class.getName() + "!="

+                + Throwable.class.getName() + ')';

+

+        Throwable t = new ToStringException(prefix, (String) null);

+        assertNull(t.getLocalizedMessage());

+        assertNull(t.getMessage());

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        assertTrue(prefix, t.toString().startsWith(prefix));

+        String cn = t.getClass().getSimpleName();

+        assertTrue(result, result.startsWith(cn));

+        assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));

+        assertTrue(result, result.contains(prefix));

+        assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatInnerException() {

+        String msg = "inner class";

+        String prefix = '(' + String.class.getName() + "!="

+                + Throwable.class.getName() + ')';

+

+        Throwable t = new ToStringException(ToStringException.class.getName()

+                + prefix, msg);

+        assertFalse(t.toString().contains(t.getLocalizedMessage()));

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(t);

+        CompactFormatter cf = new CompactFormatter("%6$s");

+        String result = cf.format(record);

+        StackTraceElement[] ste = t.getStackTrace();

+        String frame = CompactFormatterTest.class.getSimpleName()

+                + '.' + ste[0].getMethodName() + "(:"

+                + ste[0].getLineNumber() + ")";

+

+        assertTrue(prefix, t.toString().contains(prefix));

+        String cn = t.getClass().getSimpleName();

+        assertTrue(result, result.startsWith(cn));

+        assertTrue(result, result.indexOf(cn) == result.lastIndexOf(cn));

+        assertTrue(result, result.contains(prefix));

+        assertTrue(result, result.indexOf(prefix) == result.lastIndexOf(prefix));

+        assertTrue(result, result.contains(msg));

+        assertTrue(result, result.endsWith(frame));

+

+        cf = new CompactFormatter("%11$s %14$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test

+    public void testFormatMessage_Throwable() {

+        Exception e = new IOException(Exception.class.getName());

+        e = new Exception(e.toString(), e);

+        assertNotNull(e.getMessage(), e.getMessage());

+

+        CompactFormatter cf = new CompactFormatter();

+        String result = cf.formatMessage(e);

+        assertEquals(IOException.class.getSimpleName()

+                + ": " + Exception.class.getSimpleName(), result);

+    }

+

+    @Test

+    public void testFormatMessage_ThrowableNull() {

+        CompactFormatter cf = new CompactFormatter();

+        String result = cf.formatMessage((Throwable) null);

+        assertEquals("", result);

+    }

+

+    @Test

+    public void testFormatMessage_ThrowableNullMessage() {

+        CompactFormatter cf = new CompactFormatter();

+        String result = cf.formatMessage(new Throwable());

+        String expect = Throwable.class.getSimpleName();

+        assertEquals(expect, result);

+    }

+

+    @Test(timeout = 30000)

+    public void testFormatMessage_ThrowableEvil() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatMessage(createEvilThrowable());

+    }

+

+    @Test

+    public void testFormatLevel() {

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        String result = cf.formatLevel(record);

+        assertEquals(record.getLevel().getLocalizedName(), result);

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatLevelNull() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatLevel((LogRecord) null);

+        fail(cf.toString());

+    }

+

+    @Test

+    public void testFormatLogger() {

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setSourceMethodName(null);

+        record.setSourceClassName(null);

+        record.setLoggerName(Object.class.getName());

+        String result = cf.formatLoggerName(record);

+        assertEquals(Object.class.getSimpleName(), result);

+    }

+

+    @Test

+    public void testFormatRootLogger() {

+        testFormatLoggerNonClassName("");

+    }

+

+    @Test

+    public void testFormatGlobalLogger() {

+        testFormatLoggerNonClassName("global");

+    }

+

+    @Test

+    public void testFormatLoggerLeadingDot() {

+        testFormatLoggerNonClassName(".Hello");

+    }

+

+    @Test

+    public void testFormatLoggerDotDot() {

+        testFormatLoggerNonClassName("Hello..World");

+    }

+

+    @Test

+    public void testFormatLoggerColonSpace() {

+        testFormatLoggerNonClassName("Hello: World");

+    }

+

+    private void testFormatLoggerNonClassName(String name) {

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setSourceMethodName(null);

+        record.setSourceClassName(null);

+        record.setLoggerName(name);

+        String result = cf.formatLoggerName(record);

+        assertEquals(name, result);

+

+        cf = new CompactFormatter("%3$s");

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatLoggerNull() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatLoggerName((LogRecord) null);

+        fail(cf.toString());

+    }

+

+    @Test

+    public void testFormatMillis() {

+        String p = "%1$tc";

+        CompactFormatter cf = new CompactFormatter(p);

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        assertEquals(String.format(p, r.getMillis()),

+                cf.format(r));

+    }

+

+    @Test

+    public void testFormatMillisLocale() throws Exception {

+        String p = "%1$tc";

+        CompactFormatter cf = new CompactFormatter(p);

+        Properties props = new Properties();

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        r.setResourceBundle(new LocaleResource(props, Locale.ENGLISH));

+        assertEquals(String.format(Locale.ENGLISH, p, r.getMillis()),

+                cf.format(r));

+    }

+

+    @Test

+    public void testFormatMillisByParts() {

+        String p = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp";

+        CompactFormatter cf = new CompactFormatter(p);

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        assertEquals(String.format(p, r.getMillis()),

+                cf.format(r));

+    }

+

+    @Test

+    public void testFormatMillisByPartsLocale() throws Exception {

+        String p = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp";

+        CompactFormatter cf = new CompactFormatter(p);

+        Properties props = new Properties();

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        r.setResourceBundle(new LocaleResource(props, Locale.ENGLISH));

+        assertEquals(String.format(Locale.ENGLISH, p, r.getMillis()),

+                cf.format(r));

+    }

+

+    @Test

+    public void testFormatMillisAsLong() {

+        String p = "%1$tQ";

+        CompactFormatter cf = new CompactFormatter(p);

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        assertEquals(String.format(p, r.getMillis()),

+                cf.format(r));

+    }

+

+    @Test

+    public void testFormatZoneDateTime() throws Exception {

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        Object zdt = LogManagerProperties.getZonedDateTime(r);

+        if (zdt != null) {

+            String p = "%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS.%1$tN %1$Tp";

+            CompactFormatter cf = new CompactFormatter(p);

+            assertEquals(String.format(p, zdt), cf.format(r));

+        } else {

+            try {

+                Method m = LogRecord.class.getMethod("getInstant");

+                fail(m.toString());

+            } catch (final NoSuchMethodException expect) {

+            }

+        }

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatNull() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.format((LogRecord) null);

+    }

+

+    @Test

+    public void testFormatResourceBundleName() {

+        CompactFormatter cf = new CompactFormatter("%15$s");

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        r.setResourceBundleName("name");

+        String output = cf.format(r);

+        assertEquals(r.getResourceBundleName(), output);

+    }

+

+    @Test

+    public void testFormatKey() {

+        CompactFormatter cf = new CompactFormatter("%16$s");

+        LogRecord r = new LogRecord(Level.SEVERE, "message {0}");

+        r.setParameters(new Object[]{2});

+        String output = cf.format(r);

+        assertEquals(r.getMessage(), output);

+        assertFalse(output.equals(cf.formatMessage(r)));

+    }

+

+    @Test

+    public void testFormatSequence() {

+        CompactFormatter cf = new CompactFormatter("%9$d");

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        String output = cf.format(record);

+        String expect = Long.toString(record.getSequenceNumber());

+        assertEquals(expect, output);

+    }

+

+    @Test

+    public void testFormatSourceByLogger() {

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setSourceMethodName(null);

+        record.setSourceClassName(null);

+        record.setLoggerName(Object.class.getName());

+        String result = cf.formatSource(record);

+        assertEquals(Object.class.getSimpleName(), result);

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatSourceNull() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatSource((LogRecord) null);

+        fail(cf.toString());

+    }

+

+    @Test

+    public void testFormatSourceByClass() {

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setSourceMethodName(null);

+        record.setSourceClassName(Object.class.getName());

+        record.setLoggerName("");

+        String result = cf.formatSource(record);

+        assertEquals(Object.class.getSimpleName(), result);

+    }

+

+    @Test

+    public void testFormatSourceByClassAndMethod() {

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setSourceMethodName("method");

+        record.setSourceClassName(Object.class.getName());

+        record.setLoggerName("");

+        String result = cf.formatSource(record);

+        assertFalse(result, record.getSourceClassName().equals(record.getSourceMethodName()));

+        assertTrue(result, result.startsWith(Object.class.getSimpleName()));

+        assertTrue(result, result.endsWith(record.getSourceMethodName()));

+    }

+

+    @Test

+    public void testFormatThrownNullThrown() {

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        String result = cf.formatThrown(record);

+        assertTrue(result, result.startsWith(cf.formatMessage(record.getThrown())));

+        assertTrue(result, result.endsWith(cf.formatBackTrace(record)));

+    }

+

+    @Test(timeout = 30000)

+    public void testFormatThrownEvilThrown() {

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(createEvilThrowable());

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatThrown(record);

+    }

+

+    @Test

+    public void testFormatThrown() {

+        Exception e = new IOException("Fake I/O");

+        e = new Exception(e.toString(), e);

+        assertNotNull(e.getMessage(), e.getMessage());

+

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(e);

+        String result = cf.formatThrown(record);

+        assertTrue(result, result.startsWith(e.getCause().getClass().getSimpleName()));

+        assertTrue(result, result.contains(cf.formatMessage(record.getThrown())));

+        assertTrue(result, result.endsWith(cf.formatBackTrace(record)));

+    }

+

+    @Test

+    public void testFormatThrownLocalized() {

+        //sun.security.provider.PolicyParser$ParsingException

+        CountLocalizedException cle = new CountLocalizedException();

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(cle);

+        String result = cf.formatThrown(record);

+        assertNotNull(result, result);

+        assertTrue(cle.localizedMessage > 0);

+    }

+

+    @Test

+    public void testInheritsFormatMessage() {

+        InheritsFormatMessage cf = new InheritsFormatMessage();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(new Throwable());

+        String result = cf.formatThrown(record);

+        assertNotNull(cf.getClass().getName(), result);

+

+        result = cf.formatError(record);

+        assertNotNull(cf.getClass().getName(), result);

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatThrownNullRecord() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatThrown((LogRecord) null);

+        fail(cf.toString());

+    }

+

+    @Test

+    public void testFormatThreadID() {

+        CompactFormatter cf = new CompactFormatter("%10$d");

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThreadID(10);

+        String output = cf.format(record);

+        String expect = Long.toString(record.getThreadID());

+        assertEquals(expect, output);

+

+        record.setThreadID(-1); //Largest value for the CompactFormatter.

+        output = cf.format(record);

+        expect = Long.toString((1L << 32L) - 1L);

+        assertEquals(expect, output);

+

+        //Test that downcast works right.

+        Number id = cf.formatThreadID(record);

+        assertEquals(record.getThreadID(), id.intValue());

+        assertEquals(expect, Long.toString(id.longValue()));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatThreadIDNull() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatThreadID((LogRecord) null);

+    }

+

+    @Test

+    public void testFormatThreadIDReturnsNull() {

+        CompactFormatter cf = new ThreadIDReturnsNull();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThreadID(10);

+        assertNull(cf.formatThreadID(record));

+        String output = cf.format(record);

+        assertEquals("null", output);

+    }

+

+    @Test

+    public void testFormatError() {

+        CompactFormatter cf = new CompactFormatter("%11$s");

+        LogRecord record = new LogRecord(Level.SEVERE, "message");

+        record.setThrown(new Throwable("error"));

+        String output = cf.format(record);

+        assertTrue(output.startsWith(record.getThrown()

+                .getClass().getSimpleName()));

+        assertTrue(output.endsWith(record.getThrown().getMessage()));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatErrorNull() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatError((LogRecord) null);

+    }

+

+    @Test

+    public void testFormatErrorNullMessage() {

+        CompactFormatter cf = new CompactFormatter("%11$s");

+        LogRecord record = new LogRecord(Level.SEVERE, "message");

+        record.setThrown(new Throwable());

+        String output = cf.format(record);

+        assertNotNull(output);

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatThrownMessageApplyReturnsNull() {

+        CompactFormatter cf = new ApplyReturnsNull();

+        for (int i = 0; i < 10; i++) {

+            String output = cf.formatMessage(new Throwable());

+            assertEquals(Throwable.class.getSimpleName(), output);

+        }

+    }

+

+    @Test

+    public void testFormatMessageError() {

+        CompactFormatter cf = new CompactFormatter("%12$s");

+        LogRecord record = new LogRecord(Level.SEVERE, "message");

+        record.setThrown(new Throwable("error"));

+        String output = cf.format(record);

+        int t = output.indexOf(record.getThrown().getClass().getSimpleName());

+        int f = output.indexOf('|');

+        int m = output.indexOf(record.getThrown().getMessage());

+

+        assertTrue(output, t > -1);

+        assertTrue(output, m > -1);

+        assertTrue(output, f > -1);

+        assertTrue(output, t < m);

+        assertTrue(output, t > f);

+        assertTrue(output, f < m);

+        assertTrue(output, output.startsWith(record.getMessage()));

+    }

+

+    @Test

+    public void testFormatErrorMessage() {

+        CompactFormatter cf = new CompactFormatter("%13$s");

+        LogRecord record = new LogRecord(Level.SEVERE, "message");

+        record.setThrown(new Throwable("error"));

+        String output = cf.format(record);

+        int t = output.indexOf(record.getThrown().getClass().getSimpleName());

+        int f = output.indexOf('|');

+        int m = output.indexOf(record.getThrown().getMessage());

+

+        assertTrue(output, t > -1);

+        assertTrue(output, m > -1);

+        assertTrue(output, f > -1);

+        assertTrue(output, t < m);

+        assertTrue(output, t < f);

+        assertTrue(output, f > m);

+        assertTrue(output, output.endsWith(record.getMessage()));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testErrorApplyReturnsNull() {

+        CompactFormatter cf = new ApplyReturnsNull();

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        for (int i = 0; i < 10; i++) {

+            String output = cf.formatError(r);

+            assertNotNull(output);

+            r.setThrown(new Throwable(Integer.toString(i), r.getThrown()));

+            assertNotNull(cf.format(r));

+        }

+    }

+

+    @Test

+    public void testFormatExample1() {

+        String p = "%7$#.160s%n";

+        LogRecord r = new LogRecord(Level.SEVERE, "Encoding failed.");

+        RuntimeException npe = new NullPointerException();

+        StackTraceElement frame = new StackTraceElement("java.lang.String",

+                "getBytes", "String.java", 913);

+        npe.setStackTrace(new StackTraceElement[]{frame});

+        r.setThrown(npe);

+        CompactFormatter cf = new CompactFormatter(p);

+        String output = cf.format(r);

+        assertNotNull(output);

+    }

+

+    @Test

+    public void testFormatExample2() {

+        String p = "%7$#.20s%n";

+        LogRecord r = new LogRecord(Level.SEVERE, "Encoding failed.");

+        RuntimeException npe = new NullPointerException();

+        StackTraceElement frame = new StackTraceElement("java.lang.String",

+                "getBytes", "String.java", 913);

+        npe.setStackTrace(new StackTraceElement[]{frame});

+        r.setThrown(npe);

+        CompactFormatter cf = new CompactFormatter(p);

+        String output = cf.format(r);

+        assertNotNull(output);

+    }

+

+    @Test

+    public void testFormatExample3() {

+        String p = "%1$tc %2$s%n%4$s: %5$s%6$s%n";

+        LogRecord r = new LogRecord(Level.SEVERE, "Encoding failed.");

+        r.setSourceClassName("MyClass");

+        r.setSourceMethodName("fatal");

+        setEpochMilli(r, 1258723764000L);

+        RuntimeException npe = new NullPointerException();

+        StackTraceElement frame = new StackTraceElement("java.lang.String",

+                "getBytes", "String.java", 913);

+        npe.setStackTrace(new StackTraceElement[]{frame});

+        r.setThrown(npe);

+        CompactFormatter cf = new CompactFormatter(p);

+        String output = cf.format(r);

+        assertNotNull(output);

+    }

+

+    @Test

+    public void testFormatExample4() {

+        String p = "%4$s: %12$#.160s%n";

+        LogRecord r = new LogRecord(Level.SEVERE, "Unable to send notification.");

+        r.setSourceClassName("MyClass");

+        r.setSourceMethodName("fatal");

+        setEpochMilli(r, 1258723764000L);

+

+        Exception t = new SocketException("Permission denied: connect");

+        t = new MessagingException("Couldn't connect to host", t);

+        r.setThrown(t);

+        CompactFormatter cf = new CompactFormatter(p);

+        String output = cf.format(r);

+        assertNotNull(output);

+    }

+

+    @Test

+    public void testFormatExample5() {

+        String p = "[%9$d][%1$tT][%10$d][%2$s] %5$s%n%6$s%n";

+        LogRecord r = new LogRecord(Level.SEVERE, "Unable to send notification.");

+        r.setSequenceNumber(125);

+        r.setThreadID(38);

+        r.setSourceClassName("MyClass");

+        r.setSourceMethodName("fatal");

+        setEpochMilli(r, 1248203502449L);

+

+        Exception t = new SocketException("Permission denied: connect");

+

+        StackTraceElement frame = new StackTraceElement(

+                "com.sun.mail.smtp.SMTPTransport",

+                "openServer", "SMTPTransport.java", 1949);

+        t.setStackTrace(new StackTraceElement[]{frame});

+

+        t = new MessagingException("Couldn't connect to host", t);

+        r.setThrown(t);

+        CompactFormatter cf = new CompactFormatter(p);

+        String output = cf.format(r);

+        assertNotNull(output);

+    }

+

+    @Test

+    public void testFormatIllegalPattern() {

+        CompactFormatter f = new CompactFormatter("%1$#tc");

+        try {

+            f.format(new LogRecord(Level.SEVERE, ""));

+            fail("Expected format exception.");

+        } catch (java.util.IllegalFormatException expect) {

+        }

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatApplyReturnsNull() {

+        CompactFormatter cf = new ApplyReturnsNull();

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        for (int i = 0; i < 10; i++) {

+            String output = cf.format(r);

+            assertNotNull(output);

+            r.setThrown(new Throwable(Integer.toString(i), r.getThrown()));

+            assertNotNull(cf.format(r));

+        }

+    }

+

+    @Test

+    public void testFormatBackTrace() {

+        Exception e = new IOException("Fake I/O");

+        e = new Exception(e.toString(), e);

+        assertNotNull(e.getMessage(), e.getMessage());

+

+        CompactFormatter cf = new CompactFormatter("%14$s");

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(e);

+

+        String result = cf.formatBackTrace(record);

+        assertTrue(result, result.startsWith("CompactFormatterTest"));

+        assertTrue(result, result.contains("testFormatBackTrace"));

+        assertTrue(result, Character.isDigit(result.charAt(result.length() - 2)));

+        assertFalse(result, result.contains(".java"));

+        assertEquals(result, cf.format(record));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testBackTraceApplyReturnsNull() {

+        CompactFormatter cf = new ApplyReturnsNull();

+        LogRecord r = new LogRecord(Level.SEVERE, "");

+        for (int i = 0; i < 10; i++) {

+            String output = cf.formatBackTrace(r);

+            assertNotNull(output);

+            r.setThrown(new Throwable(Integer.toString(i), r.getThrown()));

+            assertNotNull(cf.format(r));

+        }

+    }

+

+    @Test

+    public void testFormatBackTraceUnknown() {

+        Exception e = new IOException("Fake I/O");

+        e.setStackTrace(new StackTraceElement[]{

+            new StackTraceElement(CompactFormatterTest.class.getName(),

+            "testFormatBackTrace", null, -2)});

+        assertNotNull(e.getMessage(), e.getMessage());

+

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(e);

+        String result = cf.formatBackTrace(record);

+        assertTrue(result, result.startsWith("CompactFormatterTest"));

+        assertTrue(result, result.contains("testFormatBackTrace"));

+    }

+

+    @Test

+    public void testFormatBackTracePunt() {

+        final Class<?> k = Collections.class;

+        Exception e = new NullPointerException("Fake NPE");

+        e.setStackTrace(new StackTraceElement[]{

+            new StackTraceElement(k.getName(), "newSetFromMap", null, 3878)});

+        assertNotNull(e.getMessage(), e.getMessage());

+

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(e);

+        String result = cf.formatBackTrace(record);

+        assertTrue(result, result.startsWith(k.getSimpleName()));

+        assertTrue(result, result.contains("newSetFromMap"));

+    }

+

+    @Test

+    public void testFormatBackTraceChainPunt() {

+        final Class<?> k = Collections.class;

+        Throwable e = new NullPointerException("Fake NPE");

+        e.setStackTrace(new StackTraceElement[0]);

+        e = new RuntimeException(e);

+        e.setStackTrace(new StackTraceElement[]{

+            new StackTraceElement(k.getName(), "newSetFromMap", null, 3878)});

+        assertNotNull(e.getMessage(), e.getMessage());

+

+        CompactFormatter cf = new CompactFormatter();

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(e);

+        String result = cf.formatBackTrace(record);

+        assertTrue(result, result.startsWith(k.getSimpleName()));

+        assertTrue(result, result.contains("newSetFromMap"));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testFormatBackTraceNull() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatBackTrace((LogRecord) null);

+        fail(cf.toString());

+    }

+

+    @Test(timeout = 30000)

+    public void testFormatBackTraceEvil() {

+        LogRecord record = new LogRecord(Level.SEVERE, "");

+        record.setThrown(createEvilThrowable());

+        CompactFormatter cf = new CompactFormatter();

+        cf.formatBackTrace(record);

+    }

+

+    @Test

+    public void testApply() {

+        CompactFormatter cf = new CompactFormatter();

+        assertNull(cf.apply((Throwable) null));

+

+        final Throwable t = new Throwable();

+        Throwable e = cf.apply(t);

+        assertSame(t, e);

+    }

+

+    @Test

+    public void testApplyNull() {

+        assertNull(new CompactFormatter().apply(null));

+    }

+

+    @Test(timeout = 30000)

+    public void testApplyEvil() {

+        CompactFormatter cf = new CompactFormatter();

+        assertNotNull(cf.apply(createEvilThrowable()));

+    }

+

+    private Throwable createEvilThrowable() {

+        Throwable third = new Throwable();

+        Throwable second = new Throwable(third);

+        Throwable first = new Throwable(second);

+        return third.initCause(first);  // Pure evil.

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testIgnoreNull() {

+        CompactFormatter cf = new CompactFormatter();

+        cf.ignore((StackTraceElement) null);

+        fail(cf.toString());

+    }

+

+    @Test

+    public void testIgnoreKnownClass() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = cf.getClass().getName();

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "format", f, 20);

+        assertFalse(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreUnknownClass() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = UNKNOWN_CLASS_NAME;

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "format", f, 20);

+        assertFalse(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnorePrivateInnerClass() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = Arrays.asList("foo", "bar", "baz").getClass().getName();

+        assertTrue(n, n.contains("$"));

+

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "size", f, 20);

+        assertFalse(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnorePrivateStaticInnerClass() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = Collections.emptySet().getClass().getName();

+        assertTrue(n, n.contains("$"));

+

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "size", f, 20);

+        assertFalse(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreStaticUtilityClass() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = MimeUtility.class.getName();

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "encodeText", f, 400);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreStaticUtilityClass_Util() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = getClass().getName().concat("MimeUtility");

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "encodeText", f, 400);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreStaticUtilityClass_s() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = getClass().getName().concat("Collections");

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "nCopies", f, 400);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreStaticUtilityClass_es() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = getClass().getName().concat("Properties");

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "get", f, 400);

+        assertFalse(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreStaticUtilityClass_Throwables() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = getClass().getName().concat("Throwables");

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "propagate", f, 400);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreSyntheticMethod() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = UNKNOWN_CLASS_NAME;

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "access$100", f, 10);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreNativeMethod() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = UNKNOWN_CLASS_NAME;

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "foo", f, -2);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreReflectMethod() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = java.lang.reflect.Method.class.getName();

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "invoke", f, 10);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreReflectMethodApiError() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = "java.lang.reflect.".concat(getClass().getName());

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "invoke", f, 10);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreReflectMethodSunError() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = "sun.reflect.".concat(getClass().getName());

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "invoke", f, 10);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreReflectConstructor() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = java.lang.reflect.Constructor.class.getName();

+        String f = n.concat(".java");

+        StackTraceElement s = new StackTraceElement(n, "newInstance", f, 10);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testIgnoreUnknownLine() {

+        CompactFormatter cf = new CompactFormatter();

+        String n = UNKNOWN_CLASS_NAME;

+        StackTraceElement s = new StackTraceElement(n, "foo", null, -1);

+        assertTrue(s.toString(), cf.ignore(s));

+    }

+

+    @Test

+    public void testToAlternate() {

+        CompactFormatter cf = new CompactFormatter();

+        assertEquals("", cf.toAlternate(LINE_SEP));

+    }

+

+    @Test

+    public void testToAlternateNull() {

+        CompactFormatter cf = new CompactFormatter();

+        assertNull(cf.toAlternate((String) null));

+    }

+

+    @Test

+    public void testJavaMailLinkage() throws Exception {

+        testJavaMailLinkage(CompactFormatter.class);

+    }

+

+    @Test

+    public void testLogManagerModifiers() throws Exception {

+        testLogManagerModifiers(CompactFormatter.class);

+    }

+

+    @Test

+    public void testWebappClassLoaderFieldNames() throws Exception {

+        testWebappClassLoaderFieldNames(CompactFormatter.class);

+    }

+

+    private static String rpad(String s, int len, String p) {

+        if (s.length() < len) {

+            StringBuilder sb = new StringBuilder(len);

+            sb.append(s);

+            for (int i = sb.length(); i < len; ++i) {

+                sb.append(p, 0, 1);

+            }

+            return sb.toString();

+        } else {

+            return s;

+        }

+    }

+

+    private final static class CountLocalizedException extends RuntimeException {

+

+        private static final long serialVersionUID = 1L;

+        public int localizedMessage;

+

+        CountLocalizedException() {

+            super();

+        }

+

+        @Override

+        public String getLocalizedMessage() {

+            localizedMessage++;

+            return super.getLocalizedMessage();

+        }

+    }

+

+    private final static class ToStringException extends RuntimeException {

+

+        private static final long serialVersionUID = 1L;

+        private final String toString;

+

+        ToStringException(String toString, String msg) {

+            super(msg);

+            this.toString = toString;

+        }

+

+        @Override

+        public String toString() {

+            return toString;

+        }

+    }

+

+    private final static class PrefixException extends RuntimeException {

+

+        private static final long serialVersionUID = 1L;

+        private final String prefix;

+

+        PrefixException(String prefix, String msg, Throwable cause) {

+            super(msg, cause);

+            this.prefix = prefix;

+        }

+

+        @Override

+        public String toString() {

+            String message = getLocalizedMessage();

+            return (message != null) ? (prefix + ": " + message) : prefix;

+        }

+    }

+

+    /**

+     * An example of a broken implementation of thread ID.

+     */

+    private static class ThreadIDReturnsNull extends CompactFormatter {

+

+        /**

+         * Promote access level.

+         */

+        ThreadIDReturnsNull() {

+            super("%10$d");

+        }

+

+        @Override

+        public Number formatThreadID(LogRecord record) {

+            return null;

+        }

+    }

+

+    private static class InheritsFormatMessage extends CompactFormatter {

+

+        InheritsFormatMessage() {

+        }

+

+        @Override

+        public String formatMessage(Throwable t) {

+            return InheritsFormatMessage.class.getName();

+        }

+    }

+

+    /**

+     * An example of a broken implementation of apply.

+     */

+    private static class ApplyReturnsNull extends CompactFormatter {

+

+        /**

+         * The number of throwables.

+         */

+        private int count;

+

+        /**

+         * Promote access level.

+         */

+        ApplyReturnsNull() {

+            super("%6$s%11$s%14$s%7$#.160s%8$#.160s%12$#.160s%13$#.160s");

+        }

+

+        @Override

+        protected Throwable apply(Throwable t) {

+            return (++count & 1) == 1 ? t : null;

+        }

+    }

+

+    /**

+     * A properties resource bundle with locale.

+     */

+    private static class LocaleResource extends PropertyResourceBundle {

+

+        /**

+         * The locale

+         */

+        private final Locale locale;

+

+        /**

+         * Creates the locale resource.

+         *

+         * @param p the properties.

+         * @param l the locale.

+         * @throws IOException if there is a problem.

+         */

+        LocaleResource(Properties p, Locale l) throws IOException {

+            super(toStream(p));

+            locale = l;

+        }

+

+        private static InputStream toStream(Properties p) throws IOException {

+            ByteArrayOutputStream out = new ByteArrayOutputStream();

+            p.store(out, LocaleResource.class.getName());

+            return new ByteArrayInputStream(out.toByteArray());

+        }

+

+        @Override

+        public Locale getLocale() {

+            return locale;

+        }

+    }

+}

diff --git a/mail/src/test/java/com/sun/mail/util/logging/DurationFilterTest.java b/mail/src/test/java/com/sun/mail/util/logging/DurationFilterTest.java
new file mode 100644
index 0000000..c59682e
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/logging/DurationFilterTest.java
@@ -0,0 +1,747 @@
+/*

+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2015, 2018 Jason Mehrens. All rights reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0, which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * This Source Code may also be made available under the following Secondary

+ * Licenses when the conditions for such availability set forth in the

+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,

+ * version 2 with the GNU Classpath Exception, which is available at

+ * https://www.gnu.org/software/classpath/license.html.

+ *

+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

+ */

+package com.sun.mail.util.logging;

+

+import java.lang.reflect.Field;

+import java.util.Arrays;

+import java.util.Properties;

+import java.util.logging.*;

+import org.junit.*;

+import static org.junit.Assert.*;

+

+/**

+ * Test case for the DurationFilter spec.

+ *

+ * @author Jason Mehrens

+ */

+public class DurationFilterTest extends AbstractLogging {

+

+    public DurationFilterTest() {

+    }

+

+    @BeforeClass

+    public static void setUpClass() {

+        checkJVMOptions();

+    }

+

+    @AfterClass

+    public static void tearDownClass() {

+        checkJVMOptions();

+    }

+

+    private static void checkJVMOptions() {

+        assertTrue(DurationFilterTest.class.desiredAssertionStatus());

+        assertNull(System.getProperty("java.util.logging.manager"));

+        assertNull(System.getProperty("java.util.logging.config.class"));

+        assertNull(System.getProperty("java.util.logging.config.file"));

+        assertEquals(LogManager.class, LogManager.getLogManager().getClass());

+    }

+

+    @Test

+    public void testDeclaredClasses() throws Exception {

+        Class<?>[] declared = DurationFilter.class.getDeclaredClasses();

+        assertEquals(Arrays.toString(declared), 0, declared.length);

+    }

+

+    @Test

+    public void testClone() throws Exception {

+        DurationFilterExt source = new DurationFilterExt();

+        final Filter clone = source.clone();

+        assertNotNull(clone);

+        assertFalse(source == clone);

+        assertTrue(source.equals(clone));

+        assertEquals(source.getClass(), clone.getClass());

+

+        LogRecord r = new LogRecord(Level.INFO, "");

+        assertTrue(source.isLoggable(r));

+        assertFalse(source.equals(clone));

+        assertTrue(((DurationFilterExt) clone).clone().equals(clone));

+    }

+

+    @Test

+    public void testCloneState() throws Exception {

+        long millis = 0;

+        final int records = 10;

+        final int duration = 5 * 60 * 1000;

+        Level lvl = Level.INFO;

+        DurationFilterExt sf = new DurationFilterExt(records, duration);

+        String msg = Long.toString(millis);

+        LogRecord r = new LogRecord(lvl, msg);

+

+        //Allow

+        for (int i = 0; i < records; i++) {

+            setEpochMilli(r, millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+        }

+

+        Filter clone = sf.clone();

+        for (int i = 0; i < records; i++) {

+            setEpochMilli(r, millis);

+            String m = Integer.toString(i);

+            assertFalse(m, sf.isLoggable(r));

+            assertTrue(m, clone.isLoggable(r));

+        }

+

+        assertFalse(sf.isLoggable(r));

+        assertFalse(clone.isLoggable(r));

+    }

+

+    @Test(timeout = 15000)

+    public void testIsLoggableNow() throws Exception {

+        final int records = 10;

+        final int duration = 1000;

+        Level lvl = Level.INFO;

+        DurationFilter sf = new DurationFilter(records, duration);

+        assertTrue(sf.isLoggable());

+

+        LogRecord r = new LogRecord(lvl, "");

+        assertTrue(sf.isLoggable(r));

+        assertTrue(sf.isLoggable());

+

+        //Allow

+        for (int i = 1; i < records; i++) {

+            r = new LogRecord(lvl, "");

+            String msg = Integer.toString(i);

+            assertTrue(msg, sf.isLoggable());

+            assertTrue(msg, sf.isLoggable(r));

+        }

+

+        assertFalse(sf.isLoggable());

+        assertFalse(sf.isLoggable(r));

+

+        tickMilli(duration + 100); //Cool down and allow.

+

+        for (int i = 0; i < records; i++) {

+            r = new LogRecord(lvl, "");

+            String msg = Integer.toString(i);

+            assertTrue(msg, sf.isLoggable());

+            assertTrue(msg, sf.isLoggable(r));

+        }

+

+        assertFalse(sf.isLoggable());

+        assertFalse(sf.isLoggable(r));

+    }

+

+    @Test(timeout = 15000)

+    public void testIsIdleNow() throws Exception {

+        final int records = 10;

+        final int duration = 1000;

+        Level lvl = Level.INFO;

+        DurationFilter sf = new DurationFilter(records, duration);

+        LogRecord r = new LogRecord(lvl, "");

+        assertTrue(sf.isIdle());

+        assertTrue(sf.isLoggable(r));

+        assertFalse(sf.isIdle());

+

+        //Allow

+        for (int i = 1; i < records; i++) {

+            r = new LogRecord(lvl, "");

+            String msg = Integer.toString(i);

+            assertFalse(msg, sf.isIdle());

+            assertTrue(msg, sf.isLoggable(r));

+        }

+

+        assertFalse(sf.isIdle());

+        assertFalse(sf.isLoggable(r));

+

+        tickMilli(duration + 100); //Cool down and allow.

+

+        assertTrue(sf.isIdle());

+        for (int i = 0; i < records; i++) {

+            r = new LogRecord(lvl, "");

+            String msg = Integer.toString(i);

+            assertTrue(msg, sf.isLoggable(r));

+            assertFalse(msg, sf.isIdle());

+        }

+

+        assertFalse(sf.isIdle());

+        assertFalse(sf.isLoggable(r));

+    }

+

+    @Test

+    public void testSaturation() {

+        long millis = 0;

+        final int records = 10;

+        final int duration = 5 * 60 * 1000;

+        Level lvl = Level.INFO;

+        DurationFilter sf = new DurationFilter(records, duration);

+        LogRecord r;

+

+        //Allow

+        for (int i = 0; i < records; i++) {

+            ++millis;

+            r = new LogRecord(lvl, Long.toString(millis));

+            setEpochMilli(r, millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+        }

+

+        //Saturate.

+        for (int i = 0; i < records * 10; i++) {

+            r = new LogRecord(lvl, Long.toString(millis));

+            setEpochMilli(r, millis);

+            assertFalse(Integer.toString(i), sf.isLoggable(r));

+        }

+

+        //Cool down and allow.

+        millis += duration;

+        for (int i = 0; i < records; i++) {

+            ++millis;

+            r = new LogRecord(lvl, Long.toString(millis));

+            setEpochMilli(r, millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+        }

+    }

+

+    @Test

+    public void testSaturateIntergral() {

+        long duration = 15L * 60L * 1000L;

+        for (long i = 0; i <= duration * 2; i++) {

+            testSaturateIntergral(i, duration);

+        }

+    }

+

+    private void testSaturateIntergral(long millis, long duration) {

+        final int records = 10;

+        Level lvl = Level.INFO;

+        DurationFilter sf = new DurationFilter(records, duration);

+        LogRecord r = new LogRecord(lvl, "");

+        sf.isLoggable(r); //Init the duration.

+        millis += (2 * duration) - 1;

+        for (int i = 0; i < records - 2; i++) {

+            setEpochMilli(r, millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+        }

+

+        millis += 100;

+        setEpochMilli(r, millis);

+        assertTrue(sf.isLoggable(r));

+

+        for (int i = 0; i < records - 1; i++) {

+            setEpochMilli(r, millis);

+            assertFalse(Integer.toString(i), sf.isLoggable(r));

+        }

+    }

+

+    @Test

+    public void testSaturatePositiveOutOfOrder() {

+        testSaturateOutOfOrder(Integer.MAX_VALUE);

+    }

+

+    @Test

+    public void testSaturateNegativeOutOfOrder() {

+        testSaturateOutOfOrder(-Integer.MAX_VALUE);

+    }

+

+    @Test

+    public void testSaturateOverFlowOutOfOrder() {

+        testSaturateOutOfOrder(Long.MAX_VALUE);

+    }

+

+    @Test

+    public void testSaturateUnderFlowOutOfOrder() {

+        testSaturateOutOfOrder(-Long.MAX_VALUE);

+    }

+

+    public void testSaturateOutOfOrder(long millis) {

+        final int records = 10;

+        final int duration = 5 * 60 * 1000;

+        Level lvl = Level.INFO;

+        DurationFilter sf = new DurationFilter(records, duration);

+        LogRecord r;

+

+        //Allow

+        for (int i = 0; i < records; i++) {

+            r = new LogRecord(lvl, Long.toString(millis));

+            setEpochMilli(r, millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+            --millis;

+        }

+

+        //Still saturated.

+        millis += duration;

+        final long peak = millis;

+        for (int i = 0; i < records; i++) {

+            r = new LogRecord(lvl, Long.toString(millis));

+            setEpochMilli(r, millis);

+            assertFalse(Integer.toString(i), sf.isLoggable(r));

+        }

+

+        //Cool down and allow.

+        millis = peak + duration;

+        for (int i = 0; i < records; i++) {

+            r = new LogRecord(lvl, Long.toString(millis));

+            setEpochMilli(r, millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+            ++millis;

+        }

+    }

+

+    @Test

+    public void testTimeSpringShort() {

+        testClockAdjustment(5 * 60 * 1000, 1);

+    }

+

+    @Test

+    public void testTimeSpringLong() {

+        testClockAdjustment(24 * 60 * 60 * 1000, 1);

+    }

+

+    @Test

+    public void testTimeFallShort() {

+        testClockAdjustment(5 * 60 * 1000, -1);

+    }

+

+    @Test

+    public void testTimeFallLong() {

+        testClockAdjustment(24 * 60 * 60 * 1000, -1);

+    }

+

+    private void testClockAdjustment(int records, int signum) {

+        assertFalse(0 == signum);

+        assertEquals(Integer.signum(signum), signum);

+        long millis = 0L;

+        DurationFilter sf = new DurationFilter(records, records);

+        LogRecord r = new LogRecord(Level.INFO, "");

+        for (int i = 1; i < (records / 2); i++) {

+            setEpochMilli(r, ++millis);

+            assertTrue(sf.isLoggable(r));

+        }

+

+        millis += signum * (60L * 60L * 1000L);

+        for (int i = (records / 2); i <= records; i++) {

+            setEpochMilli(r, ++millis);

+            assertTrue(sf.isLoggable(r));

+        }

+    }

+

+    @Test

+    public void testPredictedOverflow() {

+        int records = 4;

+        int duration = 4;

+        DurationFilter sf = new DurationFilter(records, duration);

+        for (int i = 0; i < records; i++) {

+            LogRecord r = new LogRecord(Level.INFO, "");

+            setEpochMilli(r, Long.MAX_VALUE);

+            assertTrue(sf.isLoggable(r));

+        }

+

+        LogRecord r = new LogRecord(Level.INFO, "");

+        setEpochMilli(r, Long.MAX_VALUE);

+        assertFalse(sf.isLoggable(r));

+

+        r = new LogRecord(Level.INFO, "");

+        setEpochMilli(r, Long.MAX_VALUE + duration);

+        assertTrue(sf.isLoggable(r));

+    }

+

+    @Test

+    public void testMillisNegativeSaturation() {

+        int records = 4;

+        int duration = 4;

+        DurationFilter sf = new DurationFilter(records, duration);

+        for (int i = 0; i < records; i++) {

+            LogRecord r = new LogRecord(Level.INFO, "");

+            setEpochMilli(r, Long.MIN_VALUE);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+        }

+

+        LogRecord r = new LogRecord(Level.INFO, "");

+        setEpochMilli(r, Long.MIN_VALUE);

+        assertFalse(sf.isLoggable(r));

+

+        r = new LogRecord(Level.INFO, "");

+        setEpochMilli(r, Long.MIN_VALUE + duration);

+        assertTrue(sf.isLoggable(r));

+    }

+

+    @Test

+    public void testExactRate() throws Exception {

+        long millis = System.currentTimeMillis();

+        final int records = 1000;

+        final int duration = 5 * 60 * 1000;

+        Level lvl = Level.INFO;

+        DurationFilter sf = new DurationFilter(records, duration);

+        LogRecord r;

+

+        int period = duration / records;

+        assertEquals(period, (double) duration / (double) records, 0.0);

+        for (int i = 0; i < records * records; i++) {

+            r = new LogRecord(lvl, Long.toString(millis));

+            setEpochMilli(r, millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+            millis += period;

+        }

+    }

+

+    @Test

+    public void testCeilRate() throws Exception {

+        double millis = 0L;

+        final int records = 3;

+        final int duration = 40;

+        Level lvl = Level.INFO;

+        DurationFilter sf = new DurationFilter(records, duration);

+        LogRecord r;

+

+        double period = duration / (double) records;

+        for (int i = 0; i < (duration * records) * 2; i++) {

+            r = new LogRecord(lvl, Double.toString(millis));

+            setEpochMilli(r, (long) millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+            millis = millis + Math.ceil(period);

+        }

+    }

+

+    @Test

+    public void testFloorRate() {

+        double millis = 0.0d;

+        final int records = 30;

+        final int duration = 400;

+        Level lvl = Level.INFO;

+        DurationFilter sf = new DurationFilter(records, duration);

+        LogRecord r;

+        long period = duration / records;

+        for (int i = 0; i < records; i++) {

+            r = new LogRecord(lvl, Long.toString((long) millis));

+            setEpochMilli(r, (long) millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+            millis += period;

+        }

+

+        //Saturated for records + one.

+        for (int i = 0; i <= records; i++) {

+            r = new LogRecord(lvl, Long.toString((long) millis));

+            setEpochMilli(r, (long) millis);

+            assertFalse(Integer.toString(i), sf.isLoggable(r));

+            millis += period;

+        }

+

+        for (int i = 0; i < records; i++) {

+            r = new LogRecord(lvl, Long.toString((long) millis));

+            setEpochMilli(r, (long) millis);

+            assertTrue(Integer.toString(i), sf.isLoggable(r));

+            millis += period;

+        }

+    }

+

+    private void testRate(long millis, long records, long duration) {

+        Level lvl = Level.INFO;

+        DurationFilter sf = new DurationFilter(records, duration);

+        LogRecord r = new LogRecord(lvl, Long.toString(millis));

+

+        for (long i = 0; i < records; i++) {

+            setEpochMilli(r, millis);

+            assertTrue(sf.isLoggable(r));

+        }

+

+        r = new LogRecord(lvl, Long.toString(millis));

+        setEpochMilli(r, millis);

+        assertFalse(sf.isLoggable(r));

+    }

+

+    @Test

+    public void testOneTenthErrorRate() {

+        testRate(0, 10, 1);

+    }

+

+    @Test

+    public void testOneHundredthErrorRate() {

+        testRate(0, 100, 1);

+    }

+

+    @Test

+    public void testOneThousanthErrorRate() {

+        testRate(0, 1000, 1);

+    }

+

+    @Test

+    public void testOneMillionthErrorRate() {

+        testRate(0, 1000000, 1);

+    }

+

+    @Test

+    public void testTwoToThe53rdRate() {

+        testRate(0, 1, 1L << 53L);

+    }

+

+    @Ignore

+    public void testIntegerMaxValueByTenRate() {

+        /**

+         * This can take a few minutes to run.

+         */

+        testRate(0, Integer.MAX_VALUE, 10);

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testIsLoggableNull() {

+        new DurationFilter().isLoggable((LogRecord) null);

+    }

+

+    @Test

+    public void testEquals() {

+        DurationFilter one = new DurationFilter();

+        DurationFilter two = new DurationFilter();

+        assertTrue(one.equals(one));

+        assertTrue(two.equals(two));

+        assertTrue(one.equals(two));

+        assertTrue(two.equals(one));

+

+        LogRecord r = new LogRecord(Level.INFO, "");

+        assertTrue(one.isLoggable(r));

+        assertTrue(one.equals(one));

+        assertTrue(two.equals(two));

+        assertFalse(one.equals(two));

+        assertFalse(two.equals(one));

+        assertFalse(one.equals((Object) null));

+        assertFalse(two.equals((Object) null));

+    }

+

+    @Test

+    public void testHashCode() {

+        DurationFilter one = new DurationFilter(10, 10);

+        DurationFilter two = new DurationFilter(10, 10);

+        DurationFilter three = new DurationFilter(3, 3);

+

+        assertTrue(one.hashCode() == two.hashCode());

+        assertFalse(one.hashCode() == three.hashCode());

+

+        LogRecord r = new LogRecord(Level.INFO, "");

+        assertTrue(one.isLoggable(r));

+        assertTrue(one.hashCode() == two.hashCode());

+        assertFalse(one.hashCode() == three.hashCode());

+    }

+

+    @Test

+    public void testToString() {

+        testToString(new DurationFilter());

+    }

+

+    @Test

+    public void testToStringEx() {

+        testToString(new DurationFilterExt());

+    }

+

+    private void testToString(DurationFilter f) {

+        String s = f.toString();

+        assertTrue(s.startsWith(f.getClass().getName()));

+        assertTrue(s.contains("records="));

+        assertTrue(s.contains("duration="));

+        assertTrue(s.contains("idle="));

+        assertTrue(s.contains("loggable="));

+    }

+

+    @Test

+    public void testJavaMailLinkage() throws Exception {

+        testJavaMailLinkage(DurationFilter.class);

+    }

+

+    @Test

+    public void testLogManagerModifiers() throws Exception {

+        testLogManagerModifiers(DurationFilter.class);

+    }

+

+    @Test

+    public void testWebappClassLoaderFieldNames() throws Exception {

+        testWebappClassLoaderFieldNames(DurationFilter.class);

+    }

+

+    @Test

+    public void testInitRecords() throws Exception {

+        testInitRecords("210", 210);

+    }

+

+    @Test

+    public void testInitRecordsZero() throws Exception {

+        testInitRecords("0", 1000);

+    }

+

+    @Test

+    public void testInitRecordsNegative() throws Exception {

+        testInitRecords("-1", 1000);

+    }

+

+    @Test

+    public void testInitRecordIso8601() throws Exception {

+        if (hasJavaTimeModule()) {

+            testInitRecords("PT30M", 1000);

+        }

+    }

+

+    @Test

+    public void testInitDuration() throws Exception {

+        testInitDuration("1024", 1024);

+    }

+

+    @Test

+    public void testInitDurationZero() throws Exception {

+        testInitDuration("0", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationNegative() throws Exception {

+        testInitDuration("-1", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExp() throws Exception {

+        testInitDuration("15 * 60 * 1000", 15L * 60L * 1000L);

+        testInitDuration("15*60*1000", 15L * 60L * 1000L);

+        testInitDuration("15L * 60L * 1000L", 15L * 60L * 1000L);

+        testInitDuration("15L*60L*1000L", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpLifetime() throws Exception {

+        testInitDuration("125L * 366 * 24L * 60L * 60L * 1000L",

+                125L * 366 * 24L * 60L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpOverflow() throws Exception {

+        testInitDuration(Long.MAX_VALUE + " * "

+                + Long.MAX_VALUE, 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpAlpha() throws Exception {

+        testInitDuration("15LL * 60 * 1000", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpTooManyMult() throws Exception {

+        testInitDuration("15L ** 60 ** 1000", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpTrailing() throws Exception {

+        testInitDuration("15 * 60 * 1000*", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpSpace() throws Exception {

+        testInitDuration("15 * 60 * 1000* ", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpLeading() throws Exception {

+        testInitDuration("*15 * 60 * 1000", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpAdd() throws Exception {

+        testInitDuration("15 + 60 + 1000", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpDivide() throws Exception {

+        testInitDuration("15 / 60 / 1000", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationExpSubstract() throws Exception {

+        testInitDuration("15 - 60 - 1000", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitNegativeDuration() throws Exception {

+        testInitDuration("-1024", 15L * 60L * 1000L);

+    }

+

+    @Test

+    public void testInitDurationIso8601Ms() throws Exception {

+        if (hasJavaTimeModule()) {

+            testInitDuration("PT0.345S", 345);

+        }

+    }

+

+    @Test

+    public void testInitDurationIso8601Sec() throws Exception {

+        if (hasJavaTimeModule()) {

+            testInitDuration("PT20.345S", (20L * 1000L) + 345);

+        }

+    }

+

+    @Test

+    public void testInitDurationIso8601Min() throws Exception {

+        if (hasJavaTimeModule()) {

+            testInitDuration("PT30M", 30L * 60L * 1000L);

+        }

+    }

+

+    @Test

+    public void testInitDurationIso8601Hour() throws Exception {

+        if (hasJavaTimeModule()) {

+            testInitDuration("PT10H", 10L * 60L * 60L * 1000L);

+        }

+    }

+

+    @Test

+    public void testInitDurationIso8601Day() throws Exception {

+        if (hasJavaTimeModule()) {

+            testInitDuration("P2D", 2L * 24L * 60L * 60L * 1000L);

+        }

+    }

+

+    @Test

+    public void testInitDurationIso8601All() throws Exception {

+        if (hasJavaTimeModule()) {

+            testInitDuration("P2DT3H4M20.345S", (2L * 24L * 60L * 60L * 1000L)

+                    + (3L * 60L * 60L * 1000L) + (4L * 60L * 1000L)

+                    + ((20L * 1000L) + 345));

+        }

+    }

+

+    private void testInitDuration(String d, long expect) throws Exception {

+        testInit("duration", d, expect);

+    }

+

+    private void testInitRecords(String r, long expect) throws Exception {

+        testInit("records", r, expect);

+    }

+

+    private void testInit(String field, String value, long expect) throws Exception {

+        String p = DurationFilter.class.getName();

+        Properties props = new Properties();

+        props.put(p + '.' + field, value);

+        LogManager m = LogManager.getLogManager();

+        try {

+            read(m, props);

+            DurationFilter sf = new DurationFilter();

+            Field f = DurationFilter.class.getDeclaredField(field);

+            f.setAccessible(true);

+            assertEquals(expect, f.get(sf));

+        } finally {

+            m.reset();

+        }

+    }

+

+    public static final class DurationFilterExt extends DurationFilter

+            implements Cloneable {

+

+        public DurationFilterExt() {

+            super();

+        }

+

+        public DurationFilterExt(long records, long duration) {

+            super(records, duration);

+        }

+

+        @Override

+        public DurationFilterExt clone() throws CloneNotSupportedException {

+            return (DurationFilterExt) super.clone();

+        }

+    }

+}

diff --git a/mail/src/test/java/com/sun/mail/util/logging/LogManagerPropertiesTest.java b/mail/src/test/java/com/sun/mail/util/logging/LogManagerPropertiesTest.java
new file mode 100644
index 0000000..9072eee
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/logging/LogManagerPropertiesTest.java
@@ -0,0 +1,1380 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2018 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package com.sun.mail.util.logging;
+
+import java.io.*;
+import java.lang.management.CompilationMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.*;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.*;
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import org.junit.*;
+import static org.junit.Assert.*;
+
+/**
+ * Test case for the LogManagerProperties spec.
+ *
+ * @author Jason Mehrens
+ */
+public class LogManagerPropertiesTest extends AbstractLogging {
+
+    /**
+     * Holder used to inject Throwables into other APIs.
+     */
+    private final static ThreadLocal<Throwable> PENDING = new ThreadLocal<>();
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        Assert.assertNull(System.getSecurityManager());
+    }
+
+    private static void fullFence() {
+        LogManager.getLogManager().getProperty("");
+    }
+
+    private static void assumeNoJit() {
+        CompilationMXBean c = ManagementFactory.getCompilationMXBean();
+        if (c != null) { //-Xint
+            Assume.assumeNoException(new IllegalArgumentException(
+                    c.getName() + " must be disabled."));
+        }
+    }
+
+    @Before
+    public void setUp() {
+        fullFence();
+    }
+
+    @After
+    public void tearDown() {
+        fullFence();
+    }
+
+    @Test
+    public void testDeclaredClasses() throws Exception {
+        Class<?>[] declared = LogManagerProperties.class.getDeclaredClasses();
+        assertEquals(Arrays.toString(declared), 0, declared.length);
+    }
+
+    @Test
+    public void testCheckAccessPresent() {
+        LogManager m = LogManager.getLogManager();
+        m.checkAccess();
+        LogManagerProperties.checkLogManagerAccess();
+
+        LogPermSecurityManager sm = new LogPermSecurityManager();
+        sm.secure = false;
+        System.setSecurityManager(sm);
+        try {
+            sm.secure = true;
+            try {
+                m.checkAccess();
+                fail(m.toString());
+            } catch (SecurityException expect) {
+            }
+
+            try {
+                LogManagerProperties.checkLogManagerAccess();
+                fail(LogManagerProperties.class.getName());
+            } catch (SecurityException expect) {
+            }
+        } finally {
+            sm.secure = false;
+            System.setSecurityManager((SecurityManager) null);
+        }
+    }
+
+    @Ignore
+    public void testCheckAccessAbsent() throws Exception {
+        assumeNoJit();
+        final Class<?> k = LogManagerProperties.class;
+        final Field f = k.getDeclaredField("LOG_MANAGER");
+        Field mod = setAccessible(f);
+        try {
+            final Object lm = f.get(null);
+            f.set(null, null);
+            try {
+                fullFence();
+                LogManagerProperties.checkLogManagerAccess();
+
+                LogPermSecurityManager sm = new LogPermSecurityManager();
+                sm.secure = false;
+                System.setSecurityManager(sm);
+                try {
+                    sm.secure = true;
+                    try {
+                        LogManagerProperties.checkLogManagerAccess();
+                        fail(LogManagerProperties.class.getName());
+                    } catch (SecurityException expect) {
+                    }
+                } finally {
+                    sm.secure = false;
+                    System.setSecurityManager((SecurityManager) null);
+                }
+            } finally {
+                f.set(null, lm);
+                fullFence();
+            }
+        } finally {
+            mod.setInt(f, f.getModifiers() | Modifier.FINAL);
+        }
+    }
+
+    @Test
+    public void testFromLogManagerPresent() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String key = prefix.concat(".dummy");
+            String value = "value";
+            String emptyValue = "empty";
+            Properties parent = new Properties();
+            parent.put(key, value);
+            parent.put("", emptyValue);
+
+            read(manager, parent);
+            assertTrue(LogManagerProperties.hasLogManager());
+            assertEquals(value, LogManagerProperties.fromLogManager(key));
+            assertEquals(emptyValue, LogManagerProperties.fromLogManager(""));
+        } finally {
+            manager.reset();
+        }
+
+        try {
+            LogManagerProperties.fromLogManager((String) null);
+            fail("");
+        } catch (NullPointerException expect) {
+        }
+    }
+
+    @Ignore
+    public void testFromLogManagerNull() throws Exception {
+        assumeNoJit();
+        testFromLogManager((Properties) null);
+    }
+
+    @Ignore
+    public void testFromLogManagerAbsent() throws Exception {
+        assumeNoJit();
+        final String cfgKey = "java.util.logging.config.file";
+        final Class<?> k = LogManagerProperties.class;
+        String old = System.getProperty(cfgKey);
+        try {
+            Properties props = new Properties();
+            props.put("", "empty");
+            props.put(k.getName().concat(".dummy"), "value");
+            final File f = File.createTempFile(k.getName(), ".properties");
+            try {
+                try (FileOutputStream out = new FileOutputStream(f)) {
+                    props.store(out, "testFromLogManagerAbsent");
+                }
+                System.setProperty(cfgKey, f.getAbsolutePath());
+                final Method m = k.getDeclaredMethod("readConfiguration");
+                assertTrue(Modifier.isPrivate(m.getModifiers()));
+                m.setAccessible(true);
+                props = (Properties) m.invoke(null);
+                testFromLogManager(props);
+            } finally {
+                assertTrue(f.toString(), f.delete() || !f.exists());
+            }
+        } finally {
+            if (old != null) {
+                System.setProperty(cfgKey, old);
+            } else {
+                System.clearProperty(cfgKey);
+            }
+        }
+    }
+
+    private void testFromLogManager(Properties parent) throws Exception {
+        assertTrue(LogManagerProperties.hasLogManager());
+        final Class<?> k = LogManagerProperties.class;
+        final Field f = k.getDeclaredField("LOG_MANAGER");
+        Field mod = setAccessible(f);
+        try {
+            fullFence();
+            final Object lm = f.get(null);
+            f.set(null, parent);
+            try {
+                fullFence();
+                assertFalse(LogManagerProperties.hasLogManager());
+                if (parent != null) {
+                    assertFalse(parent.isEmpty());
+                    for (Map.Entry<Object, Object> e : parent.entrySet()) {
+                        String key = e.getKey().toString();
+                        String val = LogManagerProperties.fromLogManager(key);
+                        assertEquals(e.getValue(), val);
+                    }
+                } else {
+                    assertNull(LogManagerProperties.fromLogManager(""));
+                    assertNull(LogManagerProperties.fromLogManager("val"));
+                }
+
+                try {
+                    LogManagerProperties.fromLogManager((String) null);
+                    fail("");
+                } catch (NullPointerException expect) {
+                }
+            } finally {
+                f.set(null, lm);
+                fullFence();
+            }
+        } finally {
+            mod.setInt(f, f.getModifiers() | Modifier.FINAL);
+            fullFence();
+        }
+    }
+
+    @Test
+    public void testJavaMailLinkage() throws Exception {
+        testJavaMailLinkage(LogManagerProperties.class);
+    }
+
+    @Test
+    public void testWebappClassLoaderFieldNames() throws Exception {
+        testWebappClassLoaderFieldNames(LogManagerProperties.class);
+    }
+
+    @Test
+    public void testClone() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        Properties parent;
+        LogManagerProperties mp;
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String key = prefix.concat(".dummy");
+            parent = new Properties();
+            parent.put(key, "value");
+
+            read(manager, parent);
+
+            parent = new Properties();
+            mp = new LogManagerProperties(parent, prefix);
+
+            assertFalse(contains(mp, key, null));
+            assertEquals("value", mp.getProperty(key));
+            assertTrue(contains(mp, key, "value")); //ensure copy worked.
+        } finally {
+            manager.reset();
+        }
+
+        Properties clone = (Properties) mp.clone();
+        assertFalse(clone instanceof LogManagerProperties);
+        assertEquals(Properties.class, clone.getClass());
+        assertNotSame(clone, parent);
+        assertNotSame(clone, mp);
+        assertEquals(mp.size(), clone.size());
+        assertTrue(clone.equals(mp)); //don't call mp.equals.
+    }
+
+    @Test
+    public void testIsReflection() throws Exception {
+        assertTrue(LogManagerProperties.isReflectionClass(Constructor.class.getName()));
+        assertTrue(LogManagerProperties.isReflectionClass(Method.class.getName()));
+    }
+
+    @Test
+    public void testIsStaticUtilityClass() throws Exception {
+        boolean nullCheck;
+        try {
+            LogManagerProperties.isStaticUtilityClass((String) null);
+            nullCheck = false;
+        } catch (NullPointerException expect) {
+            nullCheck = true;
+        }
+
+        if (!nullCheck) {
+            fail("Null check");
+        }
+
+        String[] utils = {
+            "java.lang.System",
+            "java.nio.channels.Channels",
+            "java.util.Collections",
+            "javax.mail.internet.MimeUtility",
+            "org.junit.Assert"
+        };
+
+        testIsStaticUtilityClass(utils, true);
+
+        String[] obj = {
+            "java.lang.Exception",
+            "java.lang.Object",
+            "java.lang.Runtime",
+            "java.io.Serializable"
+        };
+        testIsStaticUtilityClass(obj, false);
+
+        String[] enumerations = {
+            "java.util.concurrent.TimeUnit"
+        };
+        testIsStaticUtilityClass(enumerations, false);
+
+        String[] fail = {
+            "badClassName"
+        };
+        for (String name : fail) {
+            boolean pass;
+            try {
+                LogManagerProperties.isStaticUtilityClass(name);
+                pass = false;
+            } catch (ClassNotFoundException expect) {
+                pass = true;
+            }
+
+            if (!pass) {
+                fail(name);
+            }
+        }
+    }
+
+    private void testIsStaticUtilityClass(String[] names, boolean complement) throws Exception {
+        assertFalse(names.length == 0);
+
+        if (complement) {
+            for (String name : names) {
+                assertTrue(name, LogManagerProperties.isStaticUtilityClass(name));
+            }
+        } else {
+            for (String name : names) {
+                assertFalse(name, LogManagerProperties.isStaticUtilityClass(name));
+            }
+        }
+    }
+
+    @Test
+    public void testGetLocalHost() throws Exception {
+        String host = LogManagerPropertiesTest.class.getName();
+        Properties p = new Properties();
+        p.setProperty("mail.smtp.localhost", host);
+        Session s = Session.getInstance(p);
+        Transport t = s.getTransport(InternetAddress.getLocalAddress(s));
+        try {
+            String h = LogManagerProperties.getLocalHost(t);
+            if (h != null || isPrivateSpec(t.getClass())) {
+                Assert.assertEquals(host, h);
+            }
+        } catch (NoSuchMethodException notOfficial) {
+            if (isPrivateSpec(t.getClass())) {
+                fail(t.toString());
+            }
+        }
+    }
+
+    @Test
+    public void testGetLocalHostMissing() throws Exception {
+        Session session = Session.getInstance(new Properties());
+        Service svc = new NoHostService(session);
+        try {
+            LogManagerProperties.getLocalHost(svc);
+            fail("");
+        } catch (NoSuchMethodException expect) {
+        }
+    }
+
+    @Test
+    public void testGetLocalHostSecure() throws Exception {
+        Session session = Session.getInstance(new Properties());
+        Service svc = new NotAllowedService(session);
+        try {
+            LogManagerProperties.getLocalHost(svc);
+        } catch (SecurityException allowed) {
+        } catch (InvocationTargetException expect) {
+            Assert.assertTrue(expect.getCause() instanceof SecurityException);
+        }
+    }
+
+    @Test
+    public void testGetLocalHostNull() throws Exception {
+        boolean fail = true;
+        try {
+            LogManagerProperties.getLocalHost((Service) null);
+        } catch (NullPointerException expected) {
+            fail = false;
+        }
+        Assert.assertFalse(fail);
+    }
+
+    @Test
+    public void testParseDurationMs() throws Exception {
+        try {
+            long ms = LogManagerProperties.parseDurationToMillis("PT0.345S");
+            assertEquals(345L, ms);
+        } catch (ClassNotFoundException | NoClassDefFoundError ignore) {
+            assertFalse(ignore.toString(), hasJavaTimeModule());
+        }
+    }
+
+    @Test
+    public void testParseDurationSec() throws Exception {
+        try {
+            long ms = LogManagerProperties.parseDurationToMillis("PT20.345S");
+            assertEquals((20L * 1000L) + 345L, ms);
+        } catch (ClassNotFoundException | NoClassDefFoundError ignore) {
+            assertFalse(ignore.toString(), hasJavaTimeModule());
+        }
+    }
+
+    @Test
+    public void testParseDurationMin() throws Exception {
+        try {
+            long ms = LogManagerProperties.parseDurationToMillis("PT15M");
+            assertEquals(15L * 60L * 1000L, ms);
+        } catch (ClassNotFoundException | NoClassDefFoundError ignore) {
+            assertFalse(ignore.toString(), hasJavaTimeModule());
+        }
+    }
+
+    @Test
+    public void testParseDurationHour() throws Exception {
+        try {
+            long ms = LogManagerProperties.parseDurationToMillis("PT10H");
+            assertEquals(10L * 60L * 60L * 1000L, ms);
+        } catch (ClassNotFoundException | NoClassDefFoundError ignore) {
+            assertFalse(ignore.toString(), hasJavaTimeModule());
+        }
+    }
+
+    @Test
+    public void testParseDurationDay() throws Exception {
+        try {
+            long ms = LogManagerProperties.parseDurationToMillis("P2D");
+            assertEquals(2L * 24L * 60L * 60L * 1000L, ms);
+        } catch (ClassNotFoundException | NoClassDefFoundError ignore) {
+            assertFalse(ignore.toString(), hasJavaTimeModule());
+        }
+    }
+
+    @Test
+    public void testParseDurationAll() throws Exception {
+        try {
+            long ms = LogManagerProperties
+                    .parseDurationToMillis("P2DT3H4M20.345S");
+            assertEquals((2L * 24L * 60L * 60L * 1000L)
+                    + (3L * 60L * 60L * 1000L) + (4L * 60L * 1000L)
+                    + ((20L * 1000L) + 345), ms);
+        } catch (ClassNotFoundException | NoClassDefFoundError ignore) {
+            assertFalse(ignore.toString(), hasJavaTimeModule());
+        }
+    }
+
+    @Test
+    public void testGetProperty_String() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String key = prefix.concat(".dummy");
+            Properties parent = new Properties();
+            parent.put(key, "value");
+            parent.put("", "empty");
+
+            read(manager, parent);
+
+            parent = new Properties();
+            LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+
+            assertFalse(contains(mp, key, null));
+            assertEquals("value", mp.getProperty(key));
+            assertTrue(contains(mp, key, "value")); //ensure copy worked.
+            parent.put(key, "newValue");
+            assertEquals("newValue", mp.getProperty(key));
+            assertEquals("empty", mp.getProperty(""));
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testGetProperty_String_String() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String key = prefix.concat(".dummy");
+            Properties parent = new Properties();
+            parent.put(key, "value");
+            parent.put("", "empty");
+
+            read(manager, parent);
+
+            parent = new Properties();
+            LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+
+            assertFalse(contains(mp, key, null));
+            assertEquals("value", mp.getProperty(key, null));
+            assertTrue(contains(mp, key, "value")); //ensure copy worked.
+            parent.put(key, "newValue");
+            assertEquals("newValue", mp.getProperty(key, null));
+            assertEquals("default", mp.getProperty("unknown", "default"));
+            assertEquals("empty", mp.getProperty("", null));
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testGet() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String key = prefix.concat(".dummy");
+            Properties parent = new Properties();
+            parent.put(key, "value");
+            parent.put("", "empty");
+
+            read(manager, parent);
+
+            parent = new Properties();
+            LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+
+            assertFalse(contains(mp, key, null));
+            assertEquals("value", mp.get(key));
+            assertTrue(contains(mp, key, "value")); //ensure copy worked.
+            parent.put(key, "newValue");
+            assertEquals("newValue", mp.get(key));
+            assertEquals("empty", mp.get(""));
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testGetObject() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        Properties parent = new Properties();
+        LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+        String key = "key";
+        Object value = new Object();
+        parent.put(key, value);
+
+        assertEquals(parent.get(key), mp.get(key));
+    }
+
+    @Test
+    public void testContainsKey() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String key = prefix.concat(".dummy");
+            Properties parent = new Properties();
+            parent.put(key, "value");
+            parent.put("", "empty");
+
+            read(manager, parent);
+
+            parent = new Properties();
+            LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+
+            assertFalse(contains(mp, key, null));
+            assertTrue(mp.containsKey(key));
+            assertTrue(contains(mp, key, "value")); //ensure copy worked.
+            parent.put(key, "newValue");
+            assertEquals("newValue", mp.get(key));
+            assertTrue(mp.containsKey(""));
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testContainsKeyObject() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        Properties parent = new Properties();
+        LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+        String key = "key";
+        Object value = new Object();
+        parent.put(key, value);
+        assertEquals(parent.containsKey(key), mp.containsKey(key));
+    }
+
+    @Test
+    public void testRemove() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String key = prefix.concat(".dummy");
+            Properties parent = new Properties();
+            parent.put(key, "value");
+            parent.put("", "empty");
+
+            read(manager, parent);
+
+            parent = new Properties();
+            LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+
+            assertFalse(contains(mp, key, null));
+            assertEquals("value", mp.remove(key));
+            assertFalse(contains(mp, key, "value")); //ensure copy worked.
+            parent.put(key, "newValue");
+            assertEquals("newValue", mp.remove(key));
+            assertEquals("empty", mp.remove(""));
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testRemoveObject() {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        Properties parent = new Properties();
+        LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+        String key = "key";
+        Object value = new Object();
+        parent.put(key, value);
+        assertEquals(value, parent.remove(key));
+        assertEquals(parent.containsKey(key), mp.containsKey(key));
+        assertNull(parent.remove(key));
+    }
+
+    @Test
+    public void testPut() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String key = prefix.concat(".dummy");
+            Properties parent = new Properties();
+            parent.put(key, "value");
+            parent.put("", "empty");
+
+            read(manager, parent);
+
+            parent = new Properties();
+            LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+
+            assertFalse(contains(mp, key, null));
+            assertEquals("value", mp.put(key, "newValue"));
+            assertFalse(contains(mp, key, "value")); //ensure copy worked.
+            assertTrue(contains(mp, key, "newValue")); //ensure copy worked.
+            parent.put(key, "defValue");
+            assertEquals("newValue", mp.remove(key));
+            assertEquals("defValue", mp.remove(key));
+            assertEquals("empty", mp.put("", ""));
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testPutObject() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        Properties parent = new Properties();
+        LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+        String key = "key";
+        Object value = TimeUnit.MILLISECONDS;
+        assertNull(mp.put(key, value));
+        Object newValue = TimeUnit.NANOSECONDS;
+        assertEquals(value, mp.put(key, newValue));
+        assertEquals(newValue, mp.get(key));
+    }
+
+    @Test
+    public void testSetProperty() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String key = prefix.concat(".dummy");
+            Properties parent = new Properties();
+            parent.put(key, "value");
+            parent.put("", "empty");
+
+            read(manager, parent);
+
+            parent = new Properties();
+            LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+
+            assertFalse(contains(mp, key, null));
+            assertEquals("value", mp.setProperty(key, "newValue"));
+            assertFalse(contains(mp, key, "value")); //ensure copy worked.
+            assertTrue(contains(mp, key, "newValue")); //ensure copy worked.
+            parent.put(key, "defValue");
+            assertEquals("newValue", mp.remove(key));
+            assertEquals("defValue", mp.remove(key));
+            assertEquals("empty", mp.setProperty("", ""));
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testPropUtil() throws Exception {
+        String prefix = LogManagerPropertiesTest.class.getName();
+        LogManager manager = LogManager.getLogManager();
+        try {
+            String keyShort = "mail.smtp.reportsuccess";
+            String key = prefix + '.' + keyShort;
+            Properties parent = new Properties();
+            parent.put(key, "true");
+
+            read(manager, parent);
+
+            parent = new Properties();
+            LogManagerProperties mp = new LogManagerProperties(parent, prefix);
+            assertFalse(contains(mp, keyShort, null));
+
+            final Session session = Session.getInstance(mp);
+            final Object t = session.getTransport("smtp");
+            if (isPrivateSpec(t.getClass())) {
+                final String clazzName = "com.sun.mail.smtp.SMTPTransport";
+                assertEquals(clazzName, t.getClass().getName());
+            } else {
+                assertNotNull(t);
+                session.getProperty(keyShort); //Force a read through session.
+            }
+            assertTrue(contains(mp, keyShort, "true"));
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testToLanguageTag() throws Exception {
+        assertEquals("en-US", LogManagerProperties.toLanguageTag(Locale.US));
+        assertEquals("en", LogManagerProperties.toLanguageTag(Locale.ENGLISH));
+        assertEquals("", LogManagerProperties.toLanguageTag(new Locale("", "", "")));
+        Locale l = new Locale("en", "US", "slang");
+        assertEquals("en-US-slang", LogManagerProperties.toLanguageTag(l));
+        l = new Locale("en", "", "slang");
+        assertEquals("en--slang", LogManagerProperties.toLanguageTag(l));
+
+        try {
+            LogManagerProperties.toLanguageTag(null);
+            fail("Null was allowed.");
+        } catch (NullPointerException expect) {
+        }
+    }
+
+    @Test
+    public void testNewObjectFrom() throws Exception {
+        try {
+            LogManagerProperties.newObjectFrom((String) null, Object.class);
+            fail("Null name was allowed.");
+        } catch (NullPointerException expect) {
+        }
+
+        try {
+            LogManagerProperties.newObjectFrom(Object.class.getName(),
+                    (Class<Object>) null);
+            fail("Null class was allowed.");
+        } catch (NullPointerException expect) {
+        }
+
+        try {
+            LogManagerProperties.newObjectFrom((String) null,
+                    (Class<Object>) null);
+            fail("Null was allowed.");
+        } catch (NullPointerException expect) {
+        }
+
+        try {
+            LogManagerProperties.newObjectFrom("", Object.class);
+            fail("Empty class was allowed.");
+        } catch (ClassNotFoundException expect) {
+        }
+
+        try {
+            LogManagerProperties.newObjectFrom(Object.class.getName(),
+                    String.class);
+            fail("Wrong type was allowed.");
+        } catch (ClassCastException expect) {
+        }
+
+        Object o = LogManagerProperties.
+                newObjectFrom(String.class.getName(), Object.class);
+        assertEquals(String.class, o.getClass());
+
+        String n = LogManagerProperties.
+                newObjectFrom(String.class.getName(), String.class);
+        assertEquals(String.class, n.getClass());
+    }
+
+    @Test
+    public void testNewAuthenticator() throws Exception {
+        Authenticator a = LogManagerProperties.newObjectFrom(
+                EmptyAuthenticator.class.getName(),
+                Authenticator.class);
+        assertEquals(EmptyAuthenticator.class, a.getClass());
+
+        final Class<?> type = ErrorAuthenticator.class;
+        a = LogManagerProperties.newObjectFrom(
+                type.getName(), Authenticator.class);
+        assertEquals(type, a.getClass());
+
+        setPending(new RuntimeException());
+        try {
+            LogManagerProperties.newObjectFrom(type.getName(),
+                    Authenticator.class);
+            fail("Exception was not thrown.");
+        } catch (InvocationTargetException expect) {
+            assertEquals(RuntimeException.class, expect.getCause().getClass());
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    public void testNewComparator() throws Exception {
+        try {
+            LogManagerProperties.newComparator(null);
+            fail("Null was allowed.");
+        } catch (NullPointerException expect) {
+        }
+
+        try {
+            LogManagerProperties.newComparator("");
+            fail("Empty class was allowed.");
+        } catch (ClassNotFoundException expect) {
+        }
+
+        try {
+            LogManagerProperties.newComparator(Object.class.getName());
+            fail("Wrong type was allowed.");
+        } catch (ClassCastException expect) {
+        }
+
+        final Class<?> type = ErrorComparator.class;
+        final Comparator<? super LogRecord> c
+                = LogManagerProperties.newComparator(type.getName());
+        assertEquals(type, c.getClass());
+
+        setPending(new RuntimeException());
+        try {
+            LogManagerProperties.newComparator(type.getName());
+            fail("Exception was not thrown.");
+        } catch (InvocationTargetException expect) {
+            assertEquals(RuntimeException.class, expect.getCause().getClass());
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    public void testReverseOrder() throws Exception {
+        try {
+            LogManagerProperties.reverseOrder(null);
+            fail("Null was allowed.");
+        } catch (NullPointerException expect) {
+        }
+
+        Comparator<LogRecord> c = new ErrorComparator();
+        Comparator<LogRecord> r = LogManagerProperties.reverseOrder(c);
+        assertTrue(c.getClass() != r.getClass());
+        assertFalse(r instanceof ErrorComparator);
+        assertFalse(r instanceof AscComparator);
+        assertFalse(r instanceof DescComparator);
+
+        c = new AscComparator();
+        r = LogManagerProperties.reverseOrder(c);
+        assertTrue(r instanceof DescComparator);
+
+        c = new AscComparator();
+        r = LogManagerProperties.reverseOrder(c);
+        assertTrue(r instanceof DescComparator);
+    }
+
+    @Test
+    public void testNewErrorManager() throws Exception {
+        try {
+            LogManagerProperties.newErrorManager(null);
+            fail("Null was allowed.");
+        } catch (NullPointerException expect) {
+        }
+
+        try {
+            LogManagerProperties.newErrorManager("");
+            fail("Empty class was allowed.");
+        } catch (ClassNotFoundException expect) {
+        }
+
+        try {
+            LogManagerProperties.newErrorManager(Object.class.getName());
+            fail("Wrong type was allowed.");
+        } catch (ClassCastException expect) {
+        }
+
+        final Class<?> type = ErrorManager.class;
+        ErrorManager f = LogManagerProperties.newErrorManager(type.getName());
+        assertEquals(type, f.getClass());
+
+        setPending(new RuntimeException());
+        try {
+            final String name = ErrorErrorManager.class.getName();
+            LogManagerProperties.newErrorManager(name);
+            fail("Exception was not thrown.");
+        } catch (InvocationTargetException expect) {
+            assertEquals(RuntimeException.class, expect.getCause().getClass());
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    public void testNewFilter() throws Exception {
+        try {
+            LogManagerProperties.newFilter(null);
+            fail("Null was allowed.");
+        } catch (NullPointerException expect) {
+        }
+
+        try {
+            LogManagerProperties.newFilter("");
+            fail("Empty class was allowed.");
+        } catch (ClassNotFoundException expect) {
+        }
+
+        try {
+            LogManagerProperties.newFilter(Object.class.getName());
+            fail("Wrong type was allowed.");
+        } catch (ClassCastException expect) {
+        }
+
+        final Class<?> type = ErrorFilter.class;
+        final Filter f = LogManagerProperties.newFilter(type.getName());
+        assertEquals(type, f.getClass());
+
+        setPending(new RuntimeException());
+        try {
+            LogManagerProperties.newFilter(type.getName());
+            fail("Exception was not thrown.");
+        } catch (InvocationTargetException expect) {
+            assertEquals(RuntimeException.class, expect.getCause().getClass());
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    public void testNewFormatter() throws Exception {
+        try {
+            LogManagerProperties.newFormatter(null);
+            fail("Null was allowed.");
+        } catch (NullPointerException expect) {
+        }
+
+        try {
+            LogManagerProperties.newFormatter("");
+            fail("Empty class was allowed.");
+        } catch (ClassNotFoundException expect) {
+        }
+
+        try {
+            LogManagerProperties.newFormatter(Object.class.getName());
+            fail("Wrong type was allowed.");
+        } catch (ClassCastException expect) {
+        }
+
+        final Class<?> type = SimpleFormatter.class;
+        final Formatter f = LogManagerProperties.newFormatter(type.getName());
+        assertEquals(type, f.getClass());
+
+        setPending(new RuntimeException());
+        try {
+            final String name = ErrorFormatter.class.getName();
+            LogManagerProperties.newFormatter(name);
+            fail("Exception was not thrown.");
+        } catch (InvocationTargetException expect) {
+            assertEquals(RuntimeException.class, expect.getCause().getClass());
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    public void testEscapingAuthenticator() throws Exception {
+        try {
+            Class<?> k = ErrorAuthenticator.class;
+            javax.mail.Authenticator a;
+
+            a = LogManagerProperties.newObjectFrom(k.getName(), Authenticator.class);
+            assertEquals(k, a.getClass());
+
+            setPending(new ThreadDeath());
+            try {
+                a = LogManagerProperties.newObjectFrom(k.getName(), Authenticator.class);
+                fail(String.valueOf(a));
+            } catch (ThreadDeath expect) {
+            }
+
+            setPending(new OutOfMemoryError());
+            try {
+                a = LogManagerProperties.newObjectFrom(k.getName(), Authenticator.class);
+                fail(String.valueOf(a));
+            } catch (OutOfMemoryError expect) {
+            }
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    public void testEscapingComparator() throws Exception {
+        try {
+            Class<?> k = ErrorComparator.class;
+            Comparator<? super LogRecord> c;
+
+            c = LogManagerProperties.newComparator(k.getName());
+            assertEquals(k, c.getClass());
+
+            setPending(new ThreadDeath());
+            try {
+                c = LogManagerProperties.newComparator(k.getName());
+                fail(String.valueOf(c));
+            } catch (ThreadDeath expect) {
+            }
+
+            setPending(new OutOfMemoryError());
+            try {
+                c = LogManagerProperties.newComparator(k.getName());
+                fail(String.valueOf(c));
+            } catch (OutOfMemoryError expect) {
+            }
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    public void testEscapingErrorErrorManager() throws Exception {
+        try {
+            Class<?> k = ErrorErrorManager.class;
+            ErrorManager f;
+
+            f = LogManagerProperties.newErrorManager(k.getName());
+            assertEquals(k, f.getClass());
+
+            setPending(new ThreadDeath());
+            try {
+                f = LogManagerProperties.newErrorManager(k.getName());
+                fail(String.valueOf(f));
+            } catch (ThreadDeath expect) {
+            }
+
+            setPending(new OutOfMemoryError());
+            try {
+                f = LogManagerProperties.newErrorManager(k.getName());
+                fail(String.valueOf(f));
+            } catch (OutOfMemoryError expect) {
+            }
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    public void testEscapingFilter() throws Exception {
+        try {
+            Class<?> k = ErrorFilter.class;
+            Filter f;
+
+            f = LogManagerProperties.newFilter(k.getName());
+            assertEquals(k, f.getClass());
+
+            setPending(new ThreadDeath());
+            try {
+                f = LogManagerProperties.newFilter(k.getName());
+                fail(String.valueOf(f));
+            } catch (ThreadDeath expect) {
+            }
+
+            setPending(new OutOfMemoryError());
+            try {
+                f = LogManagerProperties.newFilter(k.getName());
+                fail(String.valueOf(f));
+            } catch (OutOfMemoryError expect) {
+            }
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    public void testEscapingFormatter() throws Exception {
+        try {
+            Class<?> k = ErrorFormatter.class;
+            Formatter f;
+
+            f = LogManagerProperties.newFormatter(k.getName());
+            assertEquals(k, f.getClass());
+
+            setPending(new ThreadDeath());
+            try {
+                f = LogManagerProperties.newFormatter(k.getName());
+                fail(String.valueOf(f));
+            } catch (ThreadDeath expect) {
+            }
+
+            setPending(new OutOfMemoryError());
+            try {
+                f = LogManagerProperties.newFormatter(k.getName());
+                fail(String.valueOf(f));
+            } catch (OutOfMemoryError expect) {
+            }
+        } finally {
+            setPending(null);
+        }
+    }
+
+    @Test
+    @SuppressWarnings("unchecked")
+    public void testGetZonedDateTime() throws Exception {
+        LogRecord r1 = new LogRecord(Level.SEVERE, "");
+        LogRecord r2 = new LogRecord(Level.SEVERE, "");
+        try {
+            final Class<?> k = Class.forName("java.time.ZonedDateTime");
+            setEpochSecond(r1, 100, 1);
+            setEpochSecond(r2, 100, 1);
+            Comparable<Object> c1 = (Comparable<Object>)
+                LogManagerProperties.getZonedDateTime(r1);
+            Comparable<Object> c2 = (Comparable<Object>)
+                LogManagerProperties.getZonedDateTime(r2);
+
+            assertEquals(k, c1.getClass());
+            assertEquals(k, c2.getClass());
+            assertNotSame(c1, c2);
+            assertEquals(c1.getClass(), c2.getClass());
+            assertEquals(0, c1.compareTo(c2));
+        } catch (final NoSuchMethodException preJdk9) {
+            assertNull(LogManagerProperties.getZonedDateTime(r1));
+            assertNull(LogManagerProperties.getZonedDateTime(r2));
+            assertTrue(hasJavaTimeModule());
+        } catch (final ClassNotFoundException preJdk8) {
+            assertNull(LogManagerProperties.getZonedDateTime(r1));
+            assertNull(LogManagerProperties.getZonedDateTime(r2));
+            assertFalse(hasJavaTimeModule());
+        }
+    }
+
+    @Test(expected=NullPointerException.class)
+    public void testGetZonedDateTimeNull() throws Exception {
+        LogManagerProperties.getZonedDateTime((LogRecord) null);
+    }
+
+    private static void setPending(final Throwable t) {
+        if (t != null) {
+            PENDING.set(t);
+        } else {
+            PENDING.remove();
+        }
+    }
+
+    static void throwPendingIfSet() {
+        Throwable t = PENDING.get();
+        if (t != null) {
+            if (t instanceof Error) {
+                t = t.fillInStackTrace();
+                assert t instanceof Error : t;
+                throw (Error) t;
+            } else if (t instanceof RuntimeException) {
+                t = t.fillInStackTrace();
+                assert t instanceof RuntimeException : t;
+                throw (RuntimeException) t;
+            } else {
+                throw new AssertionError(t);
+            }
+        }
+    }
+
+    private static Field setAccessible(Field f) {
+        f.setAccessible(true);
+        try {
+            assumeNoJit();
+            if (Modifier.isFinal(f.getModifiers())) {
+                Field mod = Field.class.getDeclaredField("modifiers");
+                mod.setAccessible(true);
+                mod.setInt(f, f.getModifiers() & ~Modifier.FINAL);
+                return mod;
+            }
+        } catch (RuntimeException | ReflectiveOperationException re) {
+            Assume.assumeNoException(re);
+        }
+        throw new AssertionError();
+    }
+
+    private boolean contains(Properties props, String key, String value) {
+        if (key == null) {
+            throw new NullPointerException();
+        }
+
+        //walk the entry set so we don't preload a key from the manager.
+        for (Map.Entry<?, ?> e : props.entrySet()) {
+            if (key.equals(e.getKey())) {
+                return value.equals(e.getValue());
+            }
+        }
+        return false;
+    }
+
+    public static final class EmptyAuthenticator extends javax.mail.Authenticator {
+
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            return null;
+        }
+    }
+
+    public static final class ErrorAuthenticator extends javax.mail.Authenticator {
+
+        public ErrorAuthenticator() {
+            throwPendingIfSet();
+        }
+
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            throw new Error("");
+        }
+    }
+
+    public static class ErrorComparator implements Comparator<LogRecord>,
+            Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        public ErrorComparator() {
+            throwPendingIfSet();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord r1, LogRecord r2) {
+            throw new Error("");
+        }
+    }
+
+    public static class AscComparator implements Comparator<LogRecord>,
+            Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord r1, LogRecord r2) {
+            throw new UnsupportedOperationException();
+        }
+
+        @SuppressWarnings("override")
+        public Comparator<LogRecord> reversed() {
+            return new DescComparator();
+        }
+    }
+
+    public static class DescComparator implements Comparator<LogRecord>,
+            Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord r1, LogRecord r2) {
+            throw new UnsupportedOperationException();
+        }
+
+        @SuppressWarnings("override")
+        public Comparator<LogRecord> reversed() {
+            return new AscComparator();
+        }
+    }
+
+    public static class ErrorFilter implements Filter {
+
+        public ErrorFilter() {
+            throwPendingIfSet();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            throw new Error("");
+        }
+    }
+
+    public static class ErrorFormatter extends Formatter {
+
+        public ErrorFormatter() {
+            throwPendingIfSet();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new Error("");
+        }
+    }
+
+    public static class ErrorErrorManager extends ErrorManager {
+
+        public ErrorErrorManager() {
+            throwPendingIfSet();
+        }
+
+        @Override
+        public void error(String msg, Exception ex, int code) {
+            throw new Error("");
+        }
+    }
+
+    private static final class NoHostService extends Service {
+
+        public NoHostService(Session session) {
+            super(session, new URLName("test://somehost"));
+        }
+    }
+
+    private static final class NotAllowedService extends Service {
+
+        public NotAllowedService(Session session) {
+            super(session, new URLName("test://somehost"));
+        }
+
+        public String getLocalHost() {
+            throw new SecurityException();
+        }
+    }
+
+    private static final class LogPermSecurityManager extends SecurityManager {
+
+        volatile boolean secure = false;
+
+        LogPermSecurityManager() {
+        }
+
+        @Override
+        public void checkPermission(java.security.Permission perm) {
+            try { //Call super class always for java.security.debug tracing.
+                super.checkPermission(perm);
+                checkPermission(perm, new SecurityException(perm.toString()));
+            } catch (SecurityException se) {
+                checkPermission(perm, se);
+            }
+        }
+
+        @Override
+        public void checkPermission(java.security.Permission perm, Object context) {
+            try { //Call super class always for java.security.debug tracing.
+                super.checkPermission(perm, context);
+                checkPermission(perm, new SecurityException(perm.toString()));
+            } catch (SecurityException se) {
+                checkPermission(perm, se);
+            }
+        }
+
+        private void checkPermission(java.security.Permission perm, SecurityException se) {
+            if (secure && perm instanceof LoggingPermission) {
+                throw se;
+            }
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java b/mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java
new file mode 100644
index 0000000..a28721d
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/logging/MailHandlerTest.java
@@ -0,0 +1,8520 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2009, 2018 Jason Mehrens. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+package com.sun.mail.util.logging;
+
+import java.io.*;
+import java.lang.management.CompilationMXBean;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.*;
+import java.util.*;
+import java.util.logging.*;
+import java.util.logging.Formatter;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.Authenticator;
+import javax.mail.PasswordAuthentication;
+import javax.mail.internet.*;
+import org.junit.*;
+import static org.junit.Assert.*;
+
+/**
+ * Test case for the MailHandler spec.
+ *
+ * @author Jason Mehrens
+ */
+public class MailHandlerTest extends AbstractLogging {
+
+    /**
+     * See LogManager.
+     */
+    private static final String LOG_CFG_KEY = "java.util.logging.config.file";
+    /**
+     * Holder used to inject Throwables into other APIs.
+     */
+    private static final ThreadLocal<Throwable> PENDING = new ThreadLocal<>();
+    /**
+     * Holder used to inject ClassLoaders into other APIs.
+     */
+    private static final ThreadLocal<ClassLoader> LOADER = new ThreadLocal<>();
+    /**
+     * Stores the value of a port that is not used on the local machine.
+     */
+    private static volatile int OPEN_PORT = Integer.MIN_VALUE;
+    /**
+     * A host name that can not be resolved.
+     */
+    private static final String UNKNOWN_HOST = "bad-host-name";
+    /**
+     * Stores a writable directory that is in the class path and visible to the
+     * context class loader.
+     */
+    private static volatile File anyClassPathDir = null;
+    /**
+     * Used to prevent G.C. of loggers.
+     */
+    private volatile Object hardRef;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        checkJVMOptions();
+        OPEN_PORT = findOpenPort();
+        checkUnknownHost();
+        assertTrue(findClassPathDir().isDirectory());
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        checkJVMOptions();
+        assertTrue(checkUnusedPort(OPEN_PORT));
+        OPEN_PORT = Integer.MIN_VALUE;
+        checkUnknownHost();
+        anyClassPathDir = null;
+    }
+
+    private static void assumeNoJit() {
+        CompilationMXBean c = ManagementFactory.getCompilationMXBean();
+        if (c != null) { //-Xint
+            Assume.assumeNoException(new IllegalArgumentException(
+                    c.getName() + " must be disabled."));
+        }
+    }
+
+    private static void fullFence() {
+        LogManager.getLogManager().getProperty("");
+    }
+
+    @Before
+    public void setUp() {
+        fullFence();
+        assertNull(hardRef);
+    }
+
+    @After
+    public void tearDown() {
+        fullFence();
+        hardRef = null;
+    }
+
+    private static void checkUnknownHost() throws Exception {
+        try {
+            InetAddress.getByName(UNKNOWN_HOST);
+            throw new AssertionError(UNKNOWN_HOST);
+        } catch (UnknownHostException expect) {
+        }
+    }
+
+    private static void checkJVMOptions() throws Exception {
+        assertTrue(MailHandlerTest.class.desiredAssertionStatus());
+        assertNull(System.getProperty("java.util.logging.manager"));
+        assertNull(System.getProperty("java.util.logging.config.class"));
+        assertNull(System.getProperty(LOG_CFG_KEY));
+        assertEquals(LogManager.class, LogManager.getLogManager().getClass());
+        assertTrue(LOW_CAPACITY < NUM_RUNS);
+        //Try to hold MAX_CAPACITY array with log records.
+        assertTrue((60L * 1024L * 1024L) <= Runtime.getRuntime().maxMemory());
+        try {
+            if (InetAddress.getLocalHost().getHostName().length() == 0) {
+                throw new UnknownHostException();
+            }
+        } catch (UnknownHostException UHE) {
+            throw new AssertionError(UHE);
+        }
+    }
+
+    private static Throwable getPending() {
+        return PENDING.get();
+    }
+
+    private static void setPending(final Throwable t) {
+        if (t != null) {
+            PENDING.set(t);
+        } else {
+            PENDING.remove();
+        }
+    }
+
+    private static Field setAccessible(Field f) {
+        f.setAccessible(true);
+        try {
+            assumeNoJit();
+            if (Modifier.isFinal(f.getModifiers())) {
+                Field mod = Field.class.getDeclaredField("modifiers");
+                mod.setAccessible(true);
+                mod.setInt(f, f.getModifiers() & ~Modifier.FINAL);
+                return mod;
+            }
+        } catch (RuntimeException re) {
+            Assume.assumeNoException(re);
+        } catch (Exception e) {
+            Assume.assumeNoException(e);
+        }
+        throw new AssertionError();
+    }
+
+    private static void set(ClassLoader expect) {
+        if (expect == null) {
+            LOADER.remove();
+        } else {
+            LOADER.set(expect);
+        }
+    }
+
+    static void throwPending() {
+        Throwable t = PENDING.get();
+        if (t instanceof Error) {
+            t = t.fillInStackTrace();
+            assert t instanceof Error : t;
+            throw (Error) t;
+        } else if (t instanceof RuntimeException) {
+            t = t.fillInStackTrace();
+            assert t instanceof RuntimeException : t;
+            throw (RuntimeException) t;
+        } else {
+            throw new AssertionError(t);
+        }
+    }
+
+    static boolean isSecurityDebug() {
+        boolean debug;
+        final String value = System.getProperty("java.security.debug");
+        if (value != null) {
+            debug = value.contains("all")
+                    || value.contains("access")
+                    || value.contains("stack");
+        } else {
+            debug = false;
+        }
+        return debug;
+    }
+
+    static void securityDebugPrint(Throwable se) {
+        @SuppressWarnings("UseOfSystemOutOrSystemErr")
+        final PrintStream err = System.err;
+        err.println("Suppressed security exception to allow access:");
+        se.printStackTrace(err);
+    }
+
+    static void checkContextClassLoader(ClassLoader expect) {
+        Object ccl = Thread.currentThread().getContextClassLoader();
+        if (expect != ccl) {
+            AssertionError ae = new AssertionError(expect + " != " + ccl
+                    + ", sm=" + System.getSecurityManager());
+            dump(ae);
+            throw ae;
+        }
+    }
+
+    @Test
+    public void testChildClassLoader() {
+        assertNull(System.getSecurityManager());
+        final Thread thread = Thread.currentThread();
+        final ClassLoader ccl = thread.getContextClassLoader();
+        try {
+            URLClassLoader child = new URLClassLoader(new URL[0], ccl);
+
+            thread.setContextClassLoader(child);
+            testCallingClassLoader((ClassLoaderSecurityManager) null, child);
+
+            thread.setContextClassLoader(child);
+            testCallingClassLoader(new ClassLoaderSecurityManager(), child);
+        } finally {
+            thread.setContextClassLoader(ccl);
+        }
+        assertNull(System.getSecurityManager());
+    }
+
+    private void testCallingClassLoader(
+            ClassLoaderSecurityManager sm, ClassLoader expect) {
+        InternalErrorManager em = new ClassLoaderErrorManager(expect);
+        try {
+            MailHandler instance = new MailHandler(createInitProperties(""));
+            try {
+                if (sm != null) {
+                    System.setSecurityManager(sm);
+                    sm.secure = true;
+                }
+                instance.setErrorManager(em);
+                instance.setLevel(Level.ALL);
+                instance.setPushLevel(Level.SEVERE);
+                instance.setComparator(new ClassLoaderComparator(expect));
+                instance.setFilter(new ClassLoaderFilterFormatter(expect));
+                instance.setPushFilter(new ClassLoaderFilterFormatter(expect));
+                instance.setSubject(new ClassLoaderFilterFormatter(expect));
+                instance.setFormatter(new ClassLoaderFilterFormatter(expect));
+                instance.setAttachmentFormatters(
+                        new ClassLoaderFilterFormatter(expect, "testCCL"));
+                instance.setAttachmentFilters(new ClassLoaderFilterFormatter(expect));
+                instance.setAttachmentNames(new ClassLoaderFilterFormatter(expect));
+                instance.publish(new LogRecord(Level.WARNING, ""));
+                instance.publish(new LogRecord(Level.SEVERE, ""));
+            } finally {
+                instance.close();
+            }
+        } finally {
+            if (sm != null) {
+                sm.secure = false;
+                System.setSecurityManager((SecurityManager) null);
+            }
+        }
+
+        assert em != null;
+        for (Exception exception : em.exceptions) {
+            Throwable t = exception;
+            if (t instanceof MessagingException == false) {
+                dump(t);
+                fail(t.toString());
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testVerifyClassLoader() throws Exception {
+        assertNull(System.getSecurityManager());
+        final Thread thread = Thread.currentThread();
+        final ClassLoader ccl = thread.getContextClassLoader();
+        try {
+            URLClassLoader child = new URLClassLoader(new URL[0], ccl);
+
+            thread.setContextClassLoader(child);
+            testVerify((ClassLoaderSecurityManager) null, child);
+
+            thread.setContextClassLoader(child);
+            testVerify(new ClassLoaderSecurityManager(), child);
+        } finally {
+            thread.setContextClassLoader(ccl);
+        }
+        assertNull(System.getSecurityManager());
+    }
+
+    @Test
+    public void testJavaMailLinkage() throws Exception {
+        /**
+         * The MailHandler has to depend on the official JavaMail spec classes.
+         * The logging-mailhandler.jar needs to be portable to other platforms
+         * so it doesn't depend on reference implementation classes directly.
+         */
+        testJavaMailLinkage(MailHandler.class, false);
+    }
+
+    @Test
+    public void testLogManagerModifiers() throws Exception {
+        testLogManagerModifiers(MailHandler.class);
+    }
+
+    @Test
+    public void testWebappClassLoaderFieldNames() throws Exception {
+        testWebappClassLoaderFieldNames(MailHandler.class);
+    }
+
+    private void testVerify(ClassLoaderSecurityManager sm, ClassLoader expect) throws Exception {
+        final LogManager manager = LogManager.getLogManager();
+        InternalErrorManager em = null;
+        set(expect);
+        try {
+            String p = MailHandler.class.getName();
+            Properties props = createInitProperties(p);
+            props.put(p.concat(".verify"), "local");
+            props.put(p.concat(".comparator"), ClassLoaderComparator.class.getName());
+            props.put(p.concat(".pushFilter"), ClassLoaderFilterFormatter.class.getName());
+            props.put(p.concat(".subject"), ClassLoaderFilterFormatter.class.getName());
+            props.put(p.concat(".errorManager"), ClassLoaderErrorManager.class.getName());
+
+            read(manager, props);
+
+            if (sm != null) {
+                System.setSecurityManager(sm);
+                sm.secure = true;
+            }
+
+            MailHandler instance = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(instance);
+            } finally {
+                instance.close();
+            }
+        } finally {
+            if (sm != null) {
+                sm.secure = false;
+                System.setSecurityManager((SecurityManager) null);
+            }
+            set((ClassLoader) null);
+            manager.reset();
+        }
+
+        assert em != null;
+        for (Exception exception : em.exceptions) {
+            Throwable t = exception;
+            if (t instanceof MessagingException == false) {
+                dump(t);
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testSetMailPropertiesClassLoader() throws Exception {
+        assertNull(System.getSecurityManager());
+        final Thread thread = Thread.currentThread();
+        final ClassLoader ccl = thread.getContextClassLoader();
+        try {
+            URLClassLoader child = new URLClassLoader(new URL[0], ccl);
+
+            thread.setContextClassLoader(child);
+            testSetMailProperties((ClassLoaderSecurityManager) null, child);
+
+            thread.setContextClassLoader(child);
+            testSetMailProperties(new ClassLoaderSecurityManager(), child);
+        } finally {
+            thread.setContextClassLoader(ccl);
+        }
+        assertNull(System.getSecurityManager());
+    }
+
+    private void testSetMailProperties(ClassLoaderSecurityManager sm, ClassLoader expect) throws Exception {
+        InternalErrorManager em = new ClassLoaderErrorManager(expect);
+        try {
+            Properties props = createInitProperties("");
+            props.put("verify", "local");
+
+            if (sm != null) {
+                System.setSecurityManager(sm);
+                sm.secure = true;
+            }
+
+            MailHandler instance = new MailHandler();
+            try {
+                instance.setErrorManager(em);
+                instance.setComparator(new ClassLoaderComparator(expect));
+                instance.setFilter(new ClassLoaderFilterFormatter(expect));
+                instance.setPushFilter(new ClassLoaderFilterFormatter(expect));
+                instance.setSubject(new ClassLoaderFilterFormatter(expect));
+                instance.setFormatter(new ClassLoaderFilterFormatter(expect));
+
+                instance.setMailProperties(props);
+            } finally {
+                instance.close();
+            }
+        } finally {
+            if (sm != null) {
+                sm.secure = false;
+                System.setSecurityManager((SecurityManager) null);
+            }
+        }
+
+        assert em != null;
+        for (Exception exception : em.exceptions) {
+            Throwable t = exception;
+            if (t instanceof MessagingException == false) {
+                dump(t);
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testIsLoggable() {
+        final Level[] lvls = getAllLevels();
+        if (lvls.length > 0) {
+            LogRecord record = new LogRecord(Level.INFO, "");
+            for (Level lvl : lvls) {
+                testLoggable(lvl, null);
+                testLoggable(lvl, record);
+            }
+        } else {
+            fail("No predefined levels.");
+        }
+    }
+
+    private void testLoggable(Level lvl, LogRecord record) {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        instance.setLevel(lvl);
+        MemoryHandler mem = null;
+        boolean result = false;
+        boolean expect = true;
+        try {
+            result = instance.isLoggable(record);
+            mem = new MemoryHandler(new ConsoleHandler(), 100, Level.OFF);
+            mem.setErrorManager(em);
+            mem.setLevel(lvl);
+            expect = mem.isLoggable(record);
+        } catch (RuntimeException mailEx) {
+            try {
+                if (mem != null) {
+                    fail("MemoryHandler threw and exception: " + mailEx);
+                } else {
+                    mem = new MemoryHandler(new ConsoleHandler(), 100, Level.OFF);
+                    mem.setErrorManager(em);
+                    mem.setLevel(lvl);
+                    expect = mem.isLoggable(record);
+                    fail("MailHandler threw and exception: " + mailEx);
+                }
+            } catch (RuntimeException memEx) {
+                assertEquals(memEx.getClass(), mailEx.getClass());
+                result = false;
+                expect = false;
+            }
+        }
+        assertEquals(expect, result);
+
+        instance.setLevel(Level.INFO);
+        instance.setFilter(BooleanFilter.FALSE);
+        instance.setAttachmentFormatters(
+                new Formatter[]{new SimpleFormatter(), new XMLFormatter()});
+        //null filter makes all records INFO and above loggable.
+        instance.setAttachmentFilters(new Filter[]{BooleanFilter.FALSE, null});
+        assertEquals(false, instance.isLoggable(new LogRecord(Level.FINEST, "")));
+        assertEquals(true, instance.isLoggable(new LogRecord(Level.INFO, "")));
+        assertEquals(true, instance.isLoggable(new LogRecord(Level.WARNING, "")));
+        assertEquals(true, instance.isLoggable(new LogRecord(Level.SEVERE, "")));
+
+        assertEquals(em.exceptions.isEmpty(), true);
+    }
+
+    @Test
+    public void testPostConstruct() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.postConstruct();
+
+        assertEquals(true, em.exceptions.isEmpty());
+        instance.close();
+    }
+
+    @Test
+    public void testPreDestroy() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.preDestroy();
+
+        assertEquals(true, em.exceptions.isEmpty());
+        instance.close();
+
+        instance = createHandlerWithRecords();
+        em = internalErrorManagerFrom(instance);
+        instance.preDestroy();
+
+        assertEquals(1, em.exceptions.size());
+        assertEquals(true, em.exceptions.get(0) instanceof MessagingException);
+        instance.close();
+
+        //Test for valid message.
+        instance = createHandlerWithRecords();
+        instance.setErrorManager(new FlushErrorManager(instance));
+        instance.preDestroy();
+        instance.close();
+
+        //Test that preDestroy is normal priority.
+        instance = new MailHandler(2);
+        instance.setMailProperties(createInitProperties(""));
+        instance.setLevel(Level.ALL);
+        instance.setErrorManager(new FlushErrorManager(instance));
+        instance.setPushFilter((Filter) null);
+        instance.setPushLevel(Level.OFF);
+        LogRecord record = new LogRecord(Level.INFO, "");
+        instance.publish(record); //should flush.
+        instance.preDestroy();
+        instance.push(); //Trigger an error if data is present here.
+        instance.close();
+    }
+
+    @Test
+    public void testReflectiveLifeCycleAccess() throws Exception {
+        final Class<?> k = MailHandler.class;
+        final Method construct = k.getDeclaredMethod("postConstruct");
+        final Method destory = k.getDeclaredMethod("preDestroy");
+        testLifeCycleProtypes(construct);
+        testLifeCycleProtypes(destory);
+
+        MailHandler h = new MailHandler();
+        try {
+            h.setMailProperties(createInitProperties(""));
+            InternalErrorManager em = new InternalErrorManager();
+            h.setErrorManager(em);
+
+            construct.invoke(h);
+            destory.invoke(h);
+
+            for (Throwable t : em.exceptions) {
+                dump(t);
+            }
+            assertEquals(true, em.exceptions.isEmpty());
+        } finally {
+            h.close();
+        }
+    }
+
+    private void testLifeCycleProtypes(Method m) throws Exception {
+        final String s = m.toString();
+        assertTrue(s, MailHandler.class.equals(m.getDeclaringClass()));
+        assertTrue(s, Modifier.isPublic(m.getModifiers()));
+        assertFalse(s, Modifier.isStatic(m.getModifiers()));
+        assertTrue(s, Void.TYPE.equals(m.getReturnType()));
+        assertTrue(s, m.getParameterTypes().length == 0);
+        assertTrue(s, m.getExceptionTypes().length == 0);
+    }
+
+    @Test
+    public void testNoDependencyOnJavaxAnnotations() throws Exception {
+        testNoDependencyOnJavaxAnnotations(MailHandler.class);
+    }
+
+    @Test
+    public void testPreDestroyLinkageError() throws Exception {
+        testLinkageErrorWithStack("preDestroy");
+    }
+
+    @Test
+    public void testPreDestroyLinkageErrorEmpty() throws Exception {
+        testLinkageErrorEmptyStack("preDestroy");
+    }
+
+    @Test
+    public void testPublish() {
+        MailHandler instance = createHandlerWithRecords();
+        InternalErrorManager em = internalErrorManagerFrom(instance);
+        assertEquals(em.exceptions.isEmpty(), true);
+        instance.close();
+
+        assertEquals(true, em.exceptions.get(0) instanceof MessagingException);
+        assertEquals(1, em.exceptions.size());
+
+        //Test for valid message.
+        instance = createHandlerWithRecords();
+        instance.setErrorManager(new FlushErrorManager(instance));
+
+        final Level[] lvls = getAllLevels();
+        String SOURCE_CLASS = MailHandlerTest.class.getName();
+        String SOURCE_METHOD = "testPublish";
+        for (Level lvl : lvls) {
+            LogRecord r = new LogRecord(lvl, "");
+            r.setSourceClassName(SOURCE_CLASS);
+            r.setSourceMethodName(SOURCE_METHOD);
+            instance.publish(r);
+        }
+
+        instance.close();
+    }
+
+    @Test
+    public void testPublishLinkageError() throws Exception {
+        testLinkageErrorWithStack("publish");
+    }
+
+    @Test
+    public void testPublishLinkageErrorEmpty() throws Exception {
+        testLinkageErrorEmptyStack("publish");
+    }
+
+    @Test
+    public void testPublishDuringClose() {
+        final Level[] lvls = getAllLevels();
+        for (int levelIndex = 0; levelIndex < lvls.length; levelIndex++) {
+            MailHandler instance = new MailHandler(lvls.length + 2);
+            InternalErrorManager em = new InternalErrorManager();
+            instance.setErrorManager(em);
+            Properties props = createInitProperties("");
+            instance.setMailProperties(props);
+
+            Authenticator auth = new EmptyAuthenticator();
+            Filter filter = BooleanFilter.TRUE;
+            Formatter formatter = new SimpleFormatter();
+            instance.setSubject("publishDuringClose");
+            Formatter subject = instance.getSubject();
+
+            instance.setAuthenticator(auth);
+            instance.setLevel(Level.ALL);
+            instance.setFormatter(formatter);
+            instance.setFilter(filter);
+            instance.setPushLevel(Level.OFF);
+            instance.setPushFilter(filter);
+            instance.setAttachmentFormatters(new Formatter[]{formatter});
+            instance.setAttachmentFilters(new Filter[]{filter});
+            instance.setAttachmentNames(new Formatter[]{subject});
+
+            assertTrue(em.exceptions.isEmpty());
+
+            final String msg = instance.toString();
+            for (int j = 0; j < lvls.length; j++) {
+                Level oldLevel = instance.getLevel();
+                Level lvl = lvls[(levelIndex + j) % lvls.length];
+                CloseLogRecord r = new CloseLogRecord(lvl, msg, instance);
+                assertFalse(r.isClosed());
+                instance.publish(r);
+                if (!oldLevel.equals(Level.OFF)) {
+                    assertEquals(Level.OFF, instance.getLevel());
+                    assertTrue(r.isClosed());
+                }
+            }
+
+            //Close is not allowed to change any settings.
+            assertEquals(Level.OFF, instance.getLevel());
+            assertEquals(props, instance.getMailProperties());
+            assertEquals(auth, instance.getAuthenticator());
+            assertEquals(subject, instance.getSubject());
+            assertEquals(filter, instance.getFilter());
+            assertEquals(formatter, instance.getFormatter());
+            assertEquals(Level.OFF, instance.getPushLevel());
+            assertEquals(filter, instance.getPushFilter());
+            assertEquals(formatter, instance.getAttachmentFormatters()[0]);
+            assertEquals(filter, instance.getAttachmentFilters()[0]);
+            assertEquals(subject, instance.getAttachmentNames()[0]);
+
+            //ensure one transport error.
+            assertEquals(1, em.exceptions.size());
+            assertTrue(em.exceptions.get(0) instanceof MessagingException);
+        }
+    }
+
+    @Test
+    public void testPublishDuringCloseLinkageError() throws Exception {
+        testLinkageErrorWithStack("publishDuringClose");
+    }
+
+    @Test
+    public void testPublishDuringCloseLinkageErrorEmpty() throws Exception {
+        testLinkageErrorEmptyStack("publishDuringClose");
+    }
+
+    private MailHandler createHandlerWithRecords() {
+        final Level[] lvls = getAllLevels();
+
+        MailHandler instance = new MailHandler(lvls.length + 2);
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        Properties props = createInitProperties("");
+        instance.setMailProperties(props);
+        instance.setLevel(Level.ALL);
+        instance.setFilter((Filter) null);
+        instance.setPushLevel(Level.OFF);
+        instance.setPushFilter((Filter) null);
+
+        final String msg = instance.toString();
+        for (Level lvl : lvls) {
+            LogRecord r = new LogRecord(lvl, msg);
+            r.setSourceClassName(MailHandlerTest.class.getName());
+            r.setLoggerName(r.getSourceClassName());
+            r.setSourceMethodName("createHandlerWithRecords");
+            instance.publish(r);
+        }
+        return instance;
+    }
+
+    @Test
+    public void testErrorSubjectFormatter() {
+        MailHandler instance = new MailHandler(2);
+        instance.setLevel(Level.ALL);
+        Properties props = createInitProperties("");
+        instance.setMailProperties(props);
+
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.setSubject(new ErrorFormatter());
+
+        LogRecord record = new LogRecord(Level.INFO, "");
+        instance.publish(record);
+        try {
+            instance.push();
+            fail("Error didn't escape push.");
+        } catch (Error expected) {
+            if (expected.getClass() != Error.class) {
+                throw expected;
+            } else {
+                assertEquals(Level.ALL, instance.getLevel());
+            }
+        }
+
+        instance.publish(record);
+        try {
+            instance.flush();
+            fail("Error didn't escape flush.");
+        } catch (Error expected) {
+            if (expected.getClass() != Error.class) {
+                throw expected;
+            } else {
+                assertEquals(Level.ALL, instance.getLevel());
+            }
+        }
+
+        instance.publish(record);
+        record = new LogRecord(Level.INFO, "");
+        try {
+            instance.publish(record);
+            fail("Error didn't escape publish at full capacity.");
+        } catch (Error expected) {
+            if (expected.getClass() != Error.class) {
+                throw expected;
+            } else {
+                assertEquals(Level.ALL, instance.getLevel());
+            }
+        }
+
+        instance.publish(record);
+        try {
+            instance.close();
+            fail("Error didn't escape close.");
+        } catch (Error expected) {
+            if (expected.getClass() != Error.class) {
+                throw expected;
+            } else {
+                assertEquals(Level.OFF, instance.getLevel());
+            }
+        }
+
+        instance.close();
+        final int size = em.exceptions.size();
+        if (size > 0) {
+            fail(em.exceptions.toString());
+        }
+    }
+
+    @Test
+    public void testErrorComparator() {
+        testErrorComparator(0);
+        testErrorComparator(1);
+        testErrorComparator(2);
+        testErrorComparator(3);
+        testErrorComparator(10);
+        testErrorComparator(999);
+        testErrorComparator(1000);
+        testErrorComparator(1001);
+    }
+
+    private void testErrorComparator(int records) {
+        assertTrue("Invalid argument.", records >= 0);
+        Properties props = createInitProperties("");
+        MailHandler instance = new MailHandler(props);
+        instance.setComparator(new ErrorComparator());
+        instance.setErrorManager(new InternalErrorManager());
+        boolean normal = false;
+        try {
+            try {
+                for (int i = 0; i < records; ++i) {
+                    instance.publish(new LogRecord(Level.SEVERE, ""));
+                }
+            } finally {
+                instance.close();
+            }
+            normal = true;
+        } catch (Error e) {
+            if (records == 0 || e.getClass() != Error.class) {
+                throw e;
+            } else {
+                InternalErrorManager em = internalErrorManagerFrom(instance);
+                for (Throwable t : em.exceptions) {
+                    dump(t);
+                }
+                assertEquals(true, em.exceptions.isEmpty());
+            }
+        }
+
+        if (normal) {
+            assertTrue(records == 0);
+        }
+    }
+
+    @Test
+    public void testThrowFormatters() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        instance.setLevel(Level.ALL);
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.setComparator(new ThrowComparator());
+        instance.setFormatter(new ThrowFormatter());
+        instance.setSubject(new ThrowFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{new ThrowFormatter()});
+        instance.setAttachmentNames(new Formatter[]{new ThrowFormatter()});
+
+        LogRecord record = new LogRecord(Level.INFO, "");
+        instance.publish(record);
+        instance.close();
+
+        final int size = em.exceptions.size();
+        if (size > 0) {
+            for (int i = 0; i < em.exceptions.size() - 1; i++) {
+                assertEquals(true, em.exceptions.get(i) instanceof RuntimeException);
+            }
+            assertEquals(true,
+                    em.exceptions.get(size - 1) instanceof MessagingException);
+            return;
+        }
+        fail("No runtime exceptions reported");
+    }
+
+    @Test
+    public void testErrorFormatters() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        instance.setLevel(Level.ALL);
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.setComparator(new ErrorComparator());
+        instance.setFormatter(new ErrorFormatter());
+        instance.setSubject(new ErrorFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{new ErrorFormatter()});
+        instance.setAttachmentNames(new Formatter[]{new ErrorFormatter()});
+
+        LogRecord record = new LogRecord(Level.INFO, "");
+        instance.publish(record);
+        try {
+            instance.close();
+            fail("Error was swallowed.");
+        } catch (Error expect) {
+            if (expect.getClass() != Error.class) {
+                throw expect;
+            }
+        }
+    }
+
+    @Test
+    public void testErrorFilters() {
+        LogRecord record = new LogRecord(Level.INFO, "");
+        MemoryHandler mh = new MemoryHandler(new ConsoleHandler(), 100, Level.OFF);
+        mh.setFilter(new ErrorFilter());
+        MailHandler instance = null;
+        try {
+            boolean expect = mh.isLoggable(record);
+            instance = new MailHandler(createInitProperties(""));
+            instance.setLevel(Level.ALL);
+            instance.setFilter(new ErrorFilter());
+            boolean result = instance.isLoggable(record);
+            assertEquals(expect, result);
+        } catch (Error expectEx) {
+            if (instance == null) {
+                try {
+                    instance = new MailHandler(createInitProperties(""));
+                    instance.setLevel(Level.ALL);
+                    instance.setFilter(new ErrorFilter());
+                    instance.isLoggable(record);
+                    fail("Doesn't match the memory handler.");
+                } catch (Error resultEx) {
+                    assertEquals(expectEx.getClass(), resultEx.getClass());
+                }
+            } else {
+                fail("Doesn't match the memory handler.");
+            }
+        }
+
+        assert instance != null;
+        instance.setFilter((Filter) null);
+
+        Properties props = new Properties();
+        props.put("mail.smtp.host", UNKNOWN_HOST);
+        instance.setMailProperties(props);
+
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        instance.setAttachmentFormatters(new Formatter[]{new SimpleFormatter()});
+        instance.setAttachmentFilters(new Filter[]{new ErrorFilter()});
+        instance.setAttachmentNames(new String[]{"test.txt"});
+
+        instance.publish(record);
+        try {
+            instance.close();
+            fail("Error was swallowed.");
+        } catch (Error expect) {
+            if (expect.getClass() != Error.class) {
+                throw expect;
+            }
+        }
+
+        assertEquals(true, em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testThrowComparator() {
+        testThrowComparator(0);
+        testThrowComparator(1);
+        testThrowComparator(2);
+        testThrowComparator(3);
+        testThrowComparator(10);
+        testThrowComparator(999);
+        testThrowComparator(1000);
+        testThrowComparator(1001);
+    }
+
+    private void testThrowComparator(int records) {
+        assertTrue(records >= 0);
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        instance.setComparator(new ThrowComparator());
+        instance.setErrorManager(new InternalErrorManager());
+        try {
+            for (int i = 0; i < records; ++i) {
+                instance.publish(new LogRecord(Level.SEVERE, ""));
+            }
+        } finally {
+            instance.close();
+        }
+
+        InternalErrorManager em = internalErrorManagerFrom(instance);
+        boolean seenError = false;
+        for (Throwable t : em.exceptions) {
+            if (isConnectOrTimeout(t)) {
+                continue;
+            } else if (t.getClass() == RuntimeException.class) {
+                seenError = true;
+                continue; //expect.
+            } else {
+                dump(t);
+                fail(t.toString());
+            }
+        }
+
+        if (records == 0) {
+            assertEquals(true, em.exceptions.isEmpty());
+        } else {
+            assertTrue("Exception was not thrown.", seenError);
+            assertEquals(true, !em.exceptions.isEmpty());
+        }
+    }
+
+    @Test
+    public void testThrowFilters() {
+        LogRecord record = new LogRecord(Level.INFO, "");
+        MemoryHandler mh = new MemoryHandler(new ConsoleHandler(), 100, Level.OFF);
+        mh.setFilter(new ThrowFilter());
+        MailHandler instance = null;
+        try {
+            boolean expect = mh.isLoggable(record);
+            instance = new MailHandler(createInitProperties(""));
+            instance.setLevel(Level.ALL);
+            instance.setFilter(new ThrowFilter());
+            boolean result = instance.isLoggable(record);
+            assertEquals(expect, result);
+        } catch (RuntimeException expectEx) {
+            if (instance == null) {
+                try {
+                    instance = new MailHandler(createInitProperties(""));
+                    instance.setLevel(Level.ALL);
+                    instance.setFilter(new ThrowFilter());
+                    instance.isLoggable(record);
+                    fail("Doesn't match the memory handler.");
+                } catch (RuntimeException resultEx) {
+                    assertEquals(expectEx.getClass(), resultEx.getClass());
+                }
+            } else {
+                fail("Doesn't match the memory handler.");
+            }
+        }
+
+        assert instance != null;
+        instance.setFilter((Filter) null);
+
+        Properties props = new Properties();
+        props.put("mail.smtp.host", UNKNOWN_HOST);
+        instance.setMailProperties(props);
+
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        instance.setAttachmentFormatters(new Formatter[]{new SimpleFormatter()});
+        instance.setAttachmentFilters(new Filter[]{new ThrowFilter()});
+        instance.setAttachmentNames(new String[]{"test.txt"});
+
+        instance.publish(record);
+        instance.close();
+
+        assertEquals(true, !em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testEmpty() {
+        MailHandler instance = createHandlerWithRecords();
+        instance.setFormatter(new SimpleFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{
+            new EmptyFormatter(), new SimpleFormatter(), new SimpleFormatter()});
+        testEmpty(instance);
+
+        instance = createHandlerWithRecords();
+        instance.setFormatter(new SimpleFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{
+            new SimpleFormatter(), new EmptyFormatter(), new SimpleFormatter()});
+        testEmpty(instance);
+
+        instance = createHandlerWithRecords();
+        instance.setFormatter(new SimpleFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{
+            new SimpleFormatter(), new SimpleFormatter(), new EmptyFormatter()});
+        testEmpty(instance);
+
+        instance = createHandlerWithRecords();
+        instance.setFormatter(new EmptyFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{
+            new SimpleFormatter(), new SimpleFormatter(), new SimpleFormatter()});
+        testEmpty(instance);
+
+        instance = createHandlerWithRecords();
+        instance.setFormatter(new EmptyFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{
+            new SimpleFormatter(), new EmptyFormatter(), new SimpleFormatter()});
+        testEmpty(instance);
+
+        instance = createHandlerWithRecords();
+        instance.setFormatter(new SimpleFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{new EmptyFormatter()});
+        testEmpty(instance);
+
+        instance = createHandlerWithRecords();
+        instance.setFormatter(new EmptyFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{new SimpleFormatter()});
+        testEmpty(instance);
+
+        instance = createHandlerWithRecords();
+        instance.setFormatter(new EmptyFormatter());
+        instance.setAttachmentFormatters(new Formatter[]{new EmptyFormatter()});
+        testEmpty(instance);
+
+        instance = createHandlerWithRecords();
+        instance.setFormatter(new EmptyFormatter());
+        testEmpty(instance);
+    }
+
+    private void testEmpty(MailHandler instance) {
+        Properties props = instance.getMailProperties();
+        props.setProperty("mail.from", "localhost@localdomain");
+        props.setProperty("mail.to", "localhost@localdomain");
+        instance.setMailProperties(props);
+
+        MessageErrorManager empty = new MessageErrorManager(instance.getMailProperties()) {
+
+            @Override
+            public void error(MimeMessage msg, Throwable t, int code) {
+                ByteArrayOutputStream out = new ByteArrayOutputStream();
+                try {
+                    msg.saveChanges();
+                    msg.writeTo(out);
+                } catch (Throwable ex) {
+                    fail(ex.toString());
+                }
+            }
+        };
+        instance.setErrorManager(empty);
+        instance.close();
+    }
+
+    private void testAttachmentInvariants(boolean error) throws Exception {
+        MailHandler target = new MailHandler(createInitProperties(""));
+        try {
+            InternalErrorManager em = internalErrorManagerFrom(target);
+            if (error) {
+                assertFalse(em.exceptions.isEmpty());
+                boolean unexpected = false;
+                for (Exception e : em.exceptions) {
+                    if (e instanceof IndexOutOfBoundsException == false) {
+                        dump(e);
+                        unexpected = true;
+                    }
+                }
+                assertFalse(unexpected);
+            } else {
+                for (Exception e : em.exceptions) {
+                    dump(e);
+                }
+                assertTrue(em.exceptions.isEmpty());
+            }
+            int len = target.getAttachmentFormatters().length;
+            assertTrue(String.valueOf(len), len > 0);
+            assertEquals(len, target.getAttachmentFilters().length);
+            assertEquals(len, target.getAttachmentNames().length);
+        } finally {
+            target.close();
+        }
+    }
+
+    @Test
+    public void testAlignEmptyFilter() throws Exception {
+        String p = MailHandler.class.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
+        props.put(p.concat(".attachment.names"), "att.txt");
+        final LogManager manager = LogManager.getLogManager();
+        try {
+            read(manager, props);
+            testAttachmentInvariants(false);
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testAlignEmptyNames() throws Exception {
+        String p = MailHandler.class.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
+        props.put(p.concat(".attachment.filters"), ErrorFilter.class.getName());
+        final LogManager manager = LogManager.getLogManager();
+        try {
+            read(manager, props);
+            testAttachmentInvariants(false);
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testAlignEmptyFilterAndNames() throws Exception {
+        String p = MailHandler.class.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
+        final LogManager manager = LogManager.getLogManager();
+        try {
+            read(manager, props);
+            testAttachmentInvariants(false);
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testAlignErrorFilter() throws Exception {
+        String p = MailHandler.class.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".attachment.formatters"),
+                SimpleFormatter.class.getName() + ", " + SimpleFormatter.class.getName());
+        props.put(p.concat(".attachment.filters"), ErrorFilter.class.getName());
+        final LogManager manager = LogManager.getLogManager();
+        try {
+            read(manager, props);
+            testAttachmentInvariants(true);
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testAlignErrorNames() throws Exception {
+        String p = MailHandler.class.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
+        props.put(p.concat(".attachment.names"), "att.txt, extra.txt");
+        final LogManager manager = LogManager.getLogManager();
+        try {
+            read(manager, props);
+            testAttachmentInvariants(true);
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testAlignErrorFilterAndNames() throws Exception {
+        String p = MailHandler.class.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".attachment.formatters"),
+                SimpleFormatter.class.getName() + ", " + SimpleFormatter.class.getName());
+        props.put(p.concat(".attachment.filters"),
+                ErrorFilter.class.getName() + "," + ErrorFilter.class.getName()
+                + "," + ErrorFilter.class.getName());
+        props.put(p.concat(".attachment.names"), "att.txt, next.txt, extra.txt");
+        final LogManager manager = LogManager.getLogManager();
+        try {
+            read(manager, props);
+            testAttachmentInvariants(true);
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testEncoding() throws Exception {
+        final String enc = "iso8859_1";
+        //names are different but equal encodings.
+        final String found = MimeUtility.mimeCharset(enc);
+        Class<?> k = Session.getInstance(new Properties())
+                .getTransport("smtp").getClass();
+        if (isPrivateSpec(k)) {
+            assertFalse(enc + "==" + found, enc.equals(found));
+        }
+
+        LogManager manager = LogManager.getLogManager();
+        final MailHandler instance = new MailHandler(createInitProperties(""));
+        MessageErrorManager em = new MessageErrorManager(instance.getMailProperties()) {
+
+            @Override
+            protected void error(MimeMessage msg, Throwable t, int code) {
+                try {
+                    MimeMultipart multi = (MimeMultipart) msg.getContent();
+                    BodyPart body = multi.getBodyPart(0);
+                    assertEquals(Part.INLINE, body.getDisposition());
+                    ContentType ct = new ContentType(body.getContentType());
+                    assertEquals(MimeUtility.mimeCharset(enc), ct.getParameter("charset"));
+
+                    BodyPart attach = multi.getBodyPart(1);
+                    ct = new ContentType(attach.getContentType());
+                    assertEquals(MimeUtility.mimeCharset(enc), ct.getParameter("charset"));
+                } catch (Throwable E) {
+                    dump(E);
+                    fail(E.toString());
+                }
+            }
+        };
+
+        instance.setErrorManager(em);
+        Properties props = createInitProperties("");
+        props.put("mail.to", "localhost@localdomain");
+        instance.setMailProperties(props);
+        instance.setAttachmentFormatters(new Formatter[]{new XMLFormatter()});
+        instance.setAttachmentNames(new String[]{"all.xml"});
+        String p = instance.getClass().getName();
+
+        assertEquals(manager.getProperty(p.concat(".encoding")), instance.getEncoding());
+        try {
+            instance.setEncoding("unsupported encoding exception");
+            fail("Missing encoding check.");
+        } catch (UnsupportedEncodingException expect) {
+        }
+        assertEquals(manager.getProperty(p.concat(".encoding")), instance.getEncoding());
+
+        assertTrue(em.exceptions.isEmpty());
+
+        instance.setEncoding(enc);
+        assertEquals(enc, instance.getEncoding());
+        instance.setSubject("ORA-17043=Ung\u00FCltige maximale Stream-Gr\u00F6\u00DFe");
+        LogRecord record = new LogRecord(Level.SEVERE, "Zeit\u00FCberschreitung bei Anweisung");
+        instance.publish(record);
+        instance.close();
+    }
+
+    @Test
+    public void testErrorManager() {
+        MailHandler h = new MailHandler();
+        assertNotNull(h.getErrorManager());
+        try {
+            h.setErrorManager((ErrorManager) null);
+        } catch (NullPointerException expect) {
+            assertNotNull(h);
+        }
+        assertNotNull(h.getErrorManager());
+
+        ErrorManager em = new ErrorManager();
+        h.setErrorManager(em);
+        assertSame(em, h.getErrorManager());
+    }
+
+    @Test
+    public void testFormatter() {
+        MailHandler h = new MailHandler();
+        assertNotNull(h.getFormatter());
+        try {
+            h.setFormatter((Formatter) null);
+        } catch (NullPointerException expect) {
+            assertNotNull(h);
+        }
+        assertNotNull(h.getFormatter());
+
+        SimpleFormatter f = new SimpleFormatter();
+        h.setFormatter(f);
+        assertSame(f, h.getFormatter());
+    }
+
+    @Test
+    public void testFilter() {
+        MailHandler h = new MailHandler();
+        assertNull(h.getFilter());
+        h.setFilter((Filter) null);
+        assertNull(h.getFilter());
+
+        BooleanFilter f = new BooleanFilter();
+        h.setFilter(f);
+        assertSame(f, h.getFilter());
+
+        h.setFilter((Filter) null);
+        assertNull(h.getFilter());
+    }
+
+    @Test
+    public void testStatefulFilter() {
+        MailHandler h = new MailHandler();
+        h.setMailProperties(createInitProperties(""));
+        InternalErrorManager em = new FlushErrorManager(h);
+        h.setErrorManager(em);
+        CountingFilter cf = new CountingFilter();
+        h.setFilter(cf);
+        int MAX_RECORDS = 100;
+        for (int i = 0; i < MAX_RECORDS; i++) {
+            LogRecord r = new LogRecord(Level.SEVERE, "");
+            h.publish(r);
+        }
+        h.close();
+        assertEquals(MAX_RECORDS, cf.count);
+        for (Exception exception : em.exceptions) {
+            if (!isConnectOrTimeout(exception)) {
+                dump(exception);
+                fail(String.valueOf(exception));
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testStatefulAttachmentFilter() {
+        MailHandler h = new MailHandler();
+        h.setMailProperties(createInitProperties(""));
+        InternalErrorManager em = new FlushErrorManager(h);
+        h.setErrorManager(em);
+        CountingFilter negativeOne = new CountingFilter(BooleanFilter.FALSE);
+        h.setFilter(negativeOne);
+        h.setAttachmentFormatters(new SimpleFormatter(), new SimpleFormatter(),
+                new SimpleFormatter());
+        CountingFilter one = new CountingFilter(BooleanFilter.FALSE);
+        CountingFilter two = new CountingFilter();
+        h.setAttachmentFilters(BooleanFilter.FALSE, one, two);
+        int MAX_RECORDS = 100;
+        for (int i = 0; i < MAX_RECORDS; i++) {
+            LogRecord r = new LogRecord(Level.SEVERE, "");
+            h.publish(r);
+        }
+        h.close();
+
+        assertEquals(MAX_RECORDS, negativeOne.count);
+        assertEquals(MAX_RECORDS, one.count);
+        assertEquals(MAX_RECORDS, two.count);
+        for (Exception exception : em.exceptions) {
+            if (!isConnectOrTimeout(exception)) {
+                dump(exception);
+                fail(String.valueOf(exception));
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testStatefulInternAttachmentFilter() {
+        testStatefulAttachmentFilter(false);
+    }
+
+    private void testStatefulAttachmentFilter(boolean clear) {
+        MailHandler h = new MailHandler();
+        h.setMailProperties(createInitProperties(""));
+        InternalErrorManager em = new FlushErrorManager(h);
+        h.setErrorManager(em);
+        CountingFilter cf = new CountingFilter(BooleanFilter.TRUE);
+        h.setFilter(cf);
+        h.setAttachmentFormatters(new SimpleFormatter(), new SimpleFormatter());
+        CountingFilter one = new CountingFilter();
+        h.setAttachmentFilters(cf, one);
+        int MAX_RECORDS = 100;
+        for (int i = 0; i < MAX_RECORDS; i++) {
+            LogRecord r = new LogRecord(Level.SEVERE, "");
+            h.publish(r);
+        }
+
+        if (clear) {
+            Filter[] af = h.getAttachmentFilters();
+            h.setAttachmentFormatters();
+            h.setAttachmentFormatters(new SimpleFormatter(),
+                    new SimpleFormatter());
+            h.setAttachmentFilters(af);
+        }
+        h.close();
+
+        assertEquals(MAX_RECORDS, cf.count);
+        assertEquals(MAX_RECORDS, one.count);
+        for (Exception exception : em.exceptions) {
+            if (!isConnectOrTimeout(exception)) {
+                dump(exception);
+                fail(String.valueOf(exception));
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testStatefulAttachmentFilterClearMatches() {
+        testStatefulAttachmentFilter(true);
+    }
+
+    @Test
+    public void testStatefulPushFilter() {
+        MailHandler h = new MailHandler();
+        h.setMailProperties(createInitProperties(""));
+        InternalErrorManager em = new PushErrorManager(h);
+        h.setErrorManager(em);
+        CountingFilter cf = new CountingFilter();
+        h.setFilter(cf);
+        h.setPushLevel(Level.ALL);
+        h.setPushFilter(cf);
+        LogRecord r = new LogRecord(Level.SEVERE, "");
+        h.publish(r);
+        h.close();
+        assertEquals(1, cf.count);
+        for (Exception exception : em.exceptions) {
+            if (!isConnectOrTimeout(exception)) {
+                dump(exception);
+                fail(String.valueOf(exception));
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    private void testStatefulPushAttachmentFilter(boolean clear) {
+        final MailHandler h = new MailHandler();
+        h.setMailProperties(createInitProperties(""));
+        final InternalErrorManager em = new PushErrorManager(h);
+        h.setErrorManager(em);
+        final CountingFilter cf = new CountingFilter(BooleanFilter.FALSE);
+        h.setFilter(cf);
+        h.setPushLevel(Level.ALL);
+        final CountingFilter push = new CountingFilter();
+        h.setPushFilter(push);
+        h.setAttachmentFormatters(new SimpleFormatter(), new SimpleFormatter(), new SimpleFormatter());
+        final CountingFilter one = new CountingFilter(BooleanFilter.FALSE);
+        final CountingFilter two = new CountingFilter(BooleanFilter.FALSE);
+
+        if (clear) {
+            h.setAttachmentFilters(one, two,
+                    new Filter() {
+			@Override
+                        public boolean isLoggable(LogRecord record) {
+                            h.setAttachmentFormatters(new SimpleFormatter(),
+                                    new SimpleFormatter());
+                            h.setAttachmentFilters(one, push);
+                            return push.isLoggable(record);
+                        }
+
+                    });
+        } else {
+            h.setAttachmentFilters(one, two, push);
+        }
+
+        LogRecord r = new LogRecord(Level.SEVERE, "");
+        h.publish(r);
+        h.close();
+
+        if (clear) {
+            assertEquals(2, cf.count);
+            assertEquals(2, one.count);
+            assertEquals(1, two.count);
+            assertEquals(3, push.count);
+        } else {
+            assertEquals(1, cf.count);
+            assertEquals(1, one.count);
+            assertEquals(1, two.count);
+            assertEquals(1, push.count);
+        }
+        for (Exception exception : em.exceptions) {
+            if (!isConnectOrTimeout(exception)) {
+                dump(exception);
+                fail(String.valueOf(exception));
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testStatefulPushAttachmentFilter() {
+        testStatefulPushAttachmentFilter(false);
+    }
+
+    @Test
+    public void testStatefulPushFilterClearMatches() {
+        testStatefulPushAttachmentFilter(true);
+    }
+
+    @Test
+    public void testPushInsidePush() {
+        final Level[] lvls = getAllLevels();
+
+        MailHandler instance = new MailHandler(lvls.length + 2);
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.setMailProperties(createInitProperties(""));
+        instance.setLevel(Level.ALL);
+        instance.setFilter((Filter) null);
+        instance.setPushLevel(Level.OFF);
+        instance.setPushFilter((Filter) null);
+
+        instance.setFormatter(new SimpleFormatter() {
+
+            @Override
+            public String getHead(Handler h) {
+                assert h instanceof MailHandler : h;
+                try {
+                    h.flush();
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getHead(h);
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                assert h instanceof MailHandler : h;
+                final Filter filter = h.getFilter();
+                try {
+                    h.setFilter(filter);
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+
+                final Level lvl = h.getLevel();
+                try {
+                    h.setLevel(lvl);
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+
+                final String enc = h.getEncoding();
+                try {
+                    h.setEncoding(enc);
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+
+                try {
+                    h.setFormatter(new SimpleFormatter());
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+
+                try {
+                    h.close();
+                    assertEquals(h.getLevel(), Level.OFF);
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getTail(h);
+            }
+        });
+
+        Formatter push = new SimpleFormatter() {
+
+            @Override
+            public String getHead(Handler h) {
+                assert h instanceof MailHandler : h;
+                try {
+                    ((MailHandler) h).push();
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getHead(h);
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                assert h instanceof MailHandler : h;
+                try {
+                    ((MailHandler) h).push();
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getTail(h);
+            }
+        };
+
+        Formatter atFor = new SimpleFormatter() {
+
+            @Override
+            public String getHead(Handler h) {
+                assert h instanceof MailHandler : h;
+                MailHandler mh = (MailHandler) h;
+                Formatter[] f = mh.getAttachmentFormatters();
+                try {
+                    mh.setAttachmentFormatters(f);
+                    fail("Mutable formatter.");
+                } catch (IllegalStateException pass) {
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getHead(h);
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                getHead(h);
+                return super.getTail(h);
+            }
+        };
+
+        Formatter atName = new SimpleFormatter() {
+
+            @Override
+            public String getHead(Handler h) {
+                assert h instanceof MailHandler : h;
+                MailHandler mh = (MailHandler) h;
+                Formatter[] f = mh.getAttachmentNames();
+                try {
+                    mh.setAttachmentNames(f);
+                    fail("Mutable formatter.");
+                } catch (IllegalStateException pass) {
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getHead(h);
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                getHead(h);
+                return super.getTail(h);
+            }
+        };
+
+        Formatter atFilter = new SimpleFormatter() {
+
+            @Override
+            public String getHead(Handler h) {
+                assert h instanceof MailHandler;
+                MailHandler mh = (MailHandler) h;
+                Filter[] f = mh.getAttachmentFilters();
+                try {
+                    mh.setAttachmentFilters(f);
+                    fail("Mutable filters.");
+                } catch (IllegalStateException pass) {
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getHead(h);
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                getHead(h);
+                return super.getTail(h);
+            }
+        };
+
+        Formatter nameComp = new Formatter() {
+
+            @Override
+            public String getHead(Handler h) {
+                assert h instanceof MailHandler : h;
+                MailHandler mh = (MailHandler) h;
+                Comparator<? super LogRecord> c = mh.getComparator();
+                try {
+                    mh.setComparator(c);
+                    fail("Mutable comparator.");
+                } catch (IllegalStateException pass) {
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getHead(h);
+            }
+
+            @Override
+            public String format(LogRecord r) {
+                return "";
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                getHead(h);
+                return "name.txt";
+            }
+        };
+
+        Formatter nameMail = new Formatter() {
+
+            @Override
+            public String getHead(Handler h) {
+                assert h instanceof MailHandler : h;
+                MailHandler mh = (MailHandler) h;
+                Properties props = mh.getMailProperties();
+                try {
+                    mh.setMailProperties(props);
+                    fail("Mutable props.");
+                } catch (IllegalStateException pass) {
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getHead(h);
+            }
+
+            @Override
+            public String format(LogRecord r) {
+                return "";
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                getHead(h);
+                return "name.txt";
+            }
+        };
+
+        Formatter nameSub = new Formatter() {
+
+            @Override
+            public String getHead(Handler h) {
+                assert h instanceof MailHandler : h;
+                MailHandler mh = (MailHandler) h;
+                Formatter f = mh.getSubject();
+                try {
+                    mh.setSubject(f);
+                    fail("Mutable subject.");
+                } catch (IllegalStateException pass) {
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getHead(h);
+            }
+
+            @Override
+            public String format(LogRecord r) {
+                return "";
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                getHead(h);
+                return "name.txt";
+            }
+        };
+
+        Formatter nameAuth = new Formatter() {
+
+            @Override
+            public String getHead(Handler h) {
+                assert h instanceof MailHandler : h;
+                MailHandler mh = (MailHandler) h;
+                Authenticator a = mh.getAuthenticator();
+                try {
+                    mh.setAuthenticator(a);
+                    fail("Mutable Authenticator.");
+                } catch (IllegalStateException pass) {
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+                return super.getHead(h);
+            }
+
+            @Override
+            public String format(LogRecord r) {
+                return "";
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                getHead(h);
+                return "name.txt";
+            }
+        };
+
+        instance.setAttachmentFormatters(
+                new Formatter[]{push, atFor, atName, atFilter});
+        instance.setAttachmentNames(
+                new Formatter[]{nameComp, nameMail, nameSub, nameAuth});
+
+        String SOURCE_CLASS = MailHandlerTest.class.getName();
+        String SOURCE_METHOD = "testPushInsidePush";
+        for (Level lvl : lvls) {
+            LogRecord r = new LogRecord(lvl, "");
+            r.setSourceClassName(SOURCE_CLASS);
+            r.setSourceMethodName(SOURCE_METHOD);
+            instance.publish(r);
+        }
+        instance.flush();
+
+        for (Exception exception : em.exceptions) {
+            Throwable t = exception;
+            if ((t instanceof MessagingException == false)
+                    && (t instanceof IllegalStateException == false)) {
+                dump(t);
+                fail(String.valueOf(t));
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testPush() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.push();
+        assertEquals(true, em.exceptions.isEmpty());
+        instance.close();
+
+        instance = createHandlerWithRecords();
+        em = internalErrorManagerFrom(instance);
+        instance.push();
+
+        assertEquals(1, em.exceptions.size());
+        assertEquals(true, em.exceptions.get(0) instanceof MessagingException);
+        instance.close();
+
+        //Test for valid message.
+        instance = createHandlerWithRecords();
+        instance.setErrorManager(new PushErrorManager(instance));
+        instance.push();
+        instance.close();
+
+        instance = new MailHandler(1);
+        instance.setMailProperties(createInitProperties(""));
+        instance.setLevel(Level.ALL);
+        instance.setErrorManager(new PushErrorManager(instance));
+        instance.setPushFilter((Filter) null);
+        instance.setPushLevel(Level.INFO);
+        LogRecord record = new LogRecord(Level.SEVERE, "");
+        instance.publish(record); //should push.
+        instance.close(); //cause a flush if publish didn't push.
+    }
+
+    @Test
+    public void testFlush() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.flush();
+
+        assertEquals(true, em.exceptions.isEmpty());
+        instance.close();
+
+        instance = createHandlerWithRecords();
+        em = internalErrorManagerFrom(instance);
+        instance.flush();
+
+        assertEquals(1, em.exceptions.size());
+        assertEquals(true, em.exceptions.get(0) instanceof MessagingException);
+        instance.close();
+
+        //Test for valid message.
+        instance = createHandlerWithRecords();
+        instance.setErrorManager(new FlushErrorManager(instance));
+        instance.flush();
+        instance.close();
+
+        instance = new MailHandler(1);
+        instance.setMailProperties(createInitProperties(""));
+        instance.setLevel(Level.ALL);
+        instance.setErrorManager(new FlushErrorManager(instance));
+        instance.setPushFilter((Filter) null);
+        instance.setPushLevel(Level.SEVERE);
+        LogRecord record = new LogRecord(Level.INFO, "");
+        instance.publish(record); //should flush.
+        instance.push(); //make FlushErrorManager fail if handler didn't flush.
+        instance.close();
+    }
+
+    @Test
+    public void testFlushLinkageError() throws Exception {
+        testLinkageErrorWithStack("flush");
+    }
+
+    @Test
+    public void testFlushLinkageErrorEmpty() throws Exception {
+        testLinkageErrorEmptyStack("flush");
+    }
+
+    @Test
+    public void testClose() {
+        LogRecord record = new LogRecord(Level.INFO, "");
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        int capacity = instance.getCapacity();
+
+        assertNotNull(instance.getLevel());
+
+        instance.setLevel(Level.ALL);
+        assertEquals(true, instance.isLoggable(record));
+
+        instance.close();
+
+        assertEquals(false, instance.isLoggable(record));
+        assertEquals(Level.OFF, instance.getLevel());
+
+        instance.setLevel(Level.ALL);
+        assertEquals(Level.OFF, instance.getLevel());
+
+        assertEquals(capacity, instance.getCapacity());
+        assertEquals(true, em.exceptions.isEmpty());
+
+        instance = createHandlerWithRecords();
+        em = internalErrorManagerFrom(instance);
+        instance.close();
+
+        assertEquals(1, em.exceptions.size());
+        assertEquals(true, em.exceptions.get(0) instanceof MessagingException);
+
+        //Test for valid message.
+        instance = createHandlerWithRecords();
+        instance.setErrorManager(new FlushErrorManager(instance));
+        instance.close();
+    }
+
+    @Test
+    public void testCloseLinkageError() throws Exception {
+        testLinkageErrorWithStack("close");
+    }
+
+    @Test
+    public void testCloseLinkageErrorEmpty() throws Exception {
+        testLinkageErrorEmptyStack("close");
+    }
+
+    private void testLinkageErrorWithStack(String method) throws IOException {
+        PrintStream ls = new LinkageErrorStream();
+        @SuppressWarnings("UseOfSystemOutOrSystemErr")
+        final PrintStream err = System.err;
+        try {
+            System.setErr(new LinkageErrorStream());
+            boolean linkageErrorEscapes = false;
+            try {
+                ErrorManager em = new ErrorManager();
+                em.error(null, null, ErrorManager.GENERIC_FAILURE);
+            } catch (LinkageError expect) {
+                linkageErrorEscapes = expect.getStackTrace().length != 0;
+            }
+            Assume.assumeTrue(linkageErrorEscapes);
+
+            System.setErr(ls);
+            MailHandler instance = new MailHandler(createInitProperties(""));
+            try {
+                assertEquals(ErrorManager.class,
+                        instance.getErrorManager().getClass());
+                instance.publish(new LogRecord(Level.SEVERE, ""));
+                if ("preDestroy".equals(method)) {
+                    instance.preDestroy();
+                } else if ("publish".equals(method)) {
+                    instance.setPushLevel(Level.ALL);
+                    instance.publish(new LogRecord(Level.SEVERE, ""));
+                } else if ("publishDuringClose".equals(method)) {
+                    CloseLogRecord r
+                            = new CloseLogRecord(Level.SEVERE, "", instance);
+                    instance.publish(r);
+                } else if ("flush".equals(method)) {
+                    instance.flush();
+                } else if ("push".equals(method)) {
+                    instance.push();
+                } else if ("close".equals(method)) {
+                    instance.close();
+                } else {
+                    fail(method);
+                }
+                assertTrue(ls.checkError());
+            } finally {
+                instance.close();
+            }
+            assertTrue(ls.checkError());
+        } finally {
+            System.setErr(err);
+        }
+    }
+
+    private void testLinkageErrorEmptyStack(String method) throws IOException {
+        PrintStream ls = new LinkageErrorStream(new StackTraceElement[0]);
+        @SuppressWarnings("UseOfSystemOutOrSystemErr")
+        final PrintStream err = System.err;
+        final Thread.UncaughtExceptionHandler ueh
+                = Thread.currentThread().getUncaughtExceptionHandler();
+        try {
+            CountingUncaughtExceptionHandler cueh
+                    = new CountingUncaughtExceptionHandler();
+            System.setErr(new LinkageErrorStream(new StackTraceElement[0]));
+            Thread.currentThread().setUncaughtExceptionHandler(cueh);
+            boolean linkageErrorEscapes = false;
+            try {
+                ErrorManager em = new ErrorManager();
+                em.error(null, null, ErrorManager.GENERIC_FAILURE);
+            } catch (LinkageError expect) {
+                linkageErrorEscapes = true;
+            }
+            Assume.assumeTrue(linkageErrorEscapes);
+
+            System.setErr(ls);
+            MailHandler instance = new MailHandler(createInitProperties(""));
+            try {
+                assertEquals(ErrorManager.class,
+                        instance.getErrorManager().getClass());
+                instance.publish(new LogRecord(Level.SEVERE, ""));
+                if ("preDestroy".equals(method)) {
+                    instance.preDestroy();
+                } else if ("publish".equals(method)) {
+                    instance.setPushLevel(Level.ALL);
+                    instance.publish(new LogRecord(Level.SEVERE, ""));
+                } else if ("publishDuringClose".equals(method)) {
+                    CloseLogRecord r
+                            = new CloseLogRecord(Level.SEVERE, "", instance);
+                    instance.publish(r);
+                } else if ("flush".equals(method)) {
+                    instance.flush();
+                } else if ("push".equals(method)) {
+                    instance.push();
+                } else if ("close".equals(method)) {
+                    instance.close();
+                } else {
+                    fail(method);
+                }
+                assertTrue(ls.checkError());
+            } finally {
+                instance.close();
+            }
+            assertTrue(ls.checkError());
+            assertEquals(1, cueh.count);
+        } finally {
+            System.setErr(err);
+            Thread.currentThread().setUncaughtExceptionHandler(ueh);
+        }
+    }
+
+    @Test
+    public void testCloseContextClassLoader() {
+        assertNull(System.getSecurityManager());
+        final Thread thread = Thread.currentThread();
+        final ClassLoader ccl = thread.getContextClassLoader();
+        try {
+            testCloseContextClassLoader0();
+        } finally {
+            thread.setContextClassLoader(ccl);
+        }
+        assertNull(System.getSecurityManager());
+    }
+
+    private void testCloseContextClassLoader0() {
+
+        InternalErrorManager em = new ActivationErrorManager();
+        MailHandler instance = createHandlerWithRecords();
+        try {
+            instance.setErrorManager(em);
+            ClassLoader expect = instance.getClass().getClassLoader();
+            assertNotNull(expect);
+            instance.setAuthenticator(new ClassLoaderAuthenticator(expect));
+
+            /**
+             * java.util.logging.LogManager$Cleaner has a null CCL.
+             */
+            Thread.currentThread().setContextClassLoader(null);
+        } finally {
+            instance.close();
+        }
+
+        for (Exception exception : em.exceptions) {
+            Throwable t = exception;
+            if (!isConnectOrTimeout(t)) {
+                dump(t);
+                fail(t.toString());
+            }
+        }
+    }
+
+    @Test
+    public void testLevel() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        assertNotNull(instance.getLevel());
+        try {
+            instance.setLevel((Level) null);
+            fail("Null level was allowed");
+        } catch (NullPointerException pass) {
+            assertNotNull(instance);
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+        assertNotNull(instance.getLevel());
+
+        final Level[] lvls = getAllLevels();
+        for (Level lvl : lvls) {
+            instance.setLevel(lvl);
+            assertEquals(instance.getLevel(), lvl);
+        }
+
+        instance.setLevel(Level.WARNING);
+        instance.close();
+        assertEquals(Level.OFF, instance.getLevel());
+        for (Level lvl : lvls) {
+            instance.setLevel(lvl);
+            assertEquals(Level.OFF, instance.getLevel());
+        }
+        assertEquals(true, em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testLevelBeforeClose() {
+        MailHandler instance = this.createHandlerWithRecords();
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        final Level expect = Level.WARNING;
+        instance.setLevel(expect);
+
+        instance.setFormatter(new LevelCheckingFormatter(expect));
+        instance.close();
+
+        for (Exception exception : em.exceptions) {
+            Throwable t = exception;
+            if (t instanceof MessagingException) {
+                if (!isConnectOrTimeout(t)) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            } else {
+                dump(t);
+                fail(t.toString());
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testLevelAfterClose() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        instance.setLevel(Level.WARNING);
+        instance.setFormatter(new LevelCheckingFormatter(Level.OFF));
+        instance.publish(new CloseLogRecord(Level.SEVERE, "", instance));
+        assertEquals(Level.OFF, instance.getLevel());
+
+        instance.close();
+        for (Exception exception : em.exceptions) {
+            Throwable t = exception;
+            if (t instanceof MessagingException) {
+                if (!isConnectOrTimeout(t)) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            } else {
+                dump(t);
+                fail(t.toString());
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testLogManagerReset() throws IOException {
+        LogManager manager = LogManager.getLogManager();
+        try {
+            assertEquals(LogManager.class, manager.getClass());
+            MailHandler instance = startLogManagerReset("remote");
+            InternalErrorManager em = internalErrorManagerFrom(instance);
+
+            manager.reset();
+
+            for (Exception exception : em.exceptions) {
+                Throwable t = exception;
+                if (t instanceof MessagingException) {
+                    if (isNoRecipientAddress(t)) {
+                        continue;
+                    }
+                    if (!isConnectOrTimeout(t)) {
+                        dump(t);
+                        fail(t.toString());
+                    }
+                } else {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+
+            instance = startLogManagerReset("local");
+            em = internalErrorManagerFrom(instance);
+
+            for (Exception exception : em.exceptions) {
+                Throwable t = exception;
+                if (t instanceof MessagingException) {
+                    if (isNoRecipientAddress(t)) {
+                        continue;
+                    }
+                    if (!isConnectOrTimeout(t)) {
+                        dump(t);
+                        fail(t.toString());
+                    }
+                } else {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+
+            manager.reset();
+
+            for (Exception exception : em.exceptions) {
+                Throwable t = exception;
+                if (t instanceof MessagingException) {
+                    if (isNoRecipientAddress(t)) {
+                        continue;
+                    }
+                    if (!isConnectOrTimeout(t)) {
+                        dump(t);
+                        fail(t.toString());
+                    }
+                } else {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+
+            String[] noVerify = new String[]{null, "", "null"};
+            for (int v = 0; v < noVerify.length; v++) {
+                instance = startLogManagerReset(noVerify[v]);
+                em = internalErrorManagerFrom(instance);
+
+                for (Exception exception : em.exceptions) {
+                    Throwable t = exception;
+                    dump(t);
+                    fail("Verify index=" + v);
+                }
+
+                manager.reset();
+
+                //No verify results in failed send.
+                for (Exception exception : em.exceptions) {
+                    Throwable t = exception;
+                    if (t instanceof MessagingException) {
+                        if (isNoRecipientAddress(t)) {
+                            continue;
+                        }
+                        if (!isConnectOrTimeout(t)) {
+                            dump(t);
+                            fail("Verify index=" + v);
+                        }
+                    } else {
+                        dump(t);
+                        fail("Verify index=" + v);
+                    }
+                }
+            }
+
+            instance = startLogManagerReset("bad-enum-name");
+            em = internalErrorManagerFrom(instance);
+
+            manager.reset();
+
+            //Allow the LogManagerProperties to copy on a bad enum type.
+            boolean foundIllegalArg = false;
+            for (Exception exception : em.exceptions) {
+                Throwable t = exception;
+                if (t instanceof IllegalArgumentException) {
+                    foundIllegalArg = true;
+                } else if (t instanceof RuntimeException) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+
+            assertTrue(foundIllegalArg);
+            assertFalse(em.exceptions.isEmpty());
+        } finally {
+            hardRef = null;
+            manager.reset();
+        }
+    }
+
+    /**
+     * Setup and load the standard properties.
+     *
+     * @param verify the value of verify enum.
+     * @return a MailHandler
+     * @throws IOException if there is a problem.
+     */
+    private MailHandler startLogManagerReset(String verify) throws IOException {
+        LogManager manager = LogManager.getLogManager();
+        manager.reset();
+
+        final String p = MailHandler.class.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".mail.from"), "localhost@localdomain");
+        props.put(p.concat(".mail.to"), "");
+        props.put(p.concat(".mail.cc"), "");
+        props.put(p.concat(".mail.bcc"), "");
+        props.put(p.concat(".subject"), p.concat(" test"));
+        props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+        if (verify != null) {
+            props.put(p.concat(".verify"), verify);
+        }
+        read(manager, props);
+
+        assertNotNull(manager.getProperty(p.concat(".mail.host")));
+        assertNotNull(manager.getProperty(p.concat(".mail.smtp.host")));
+        assertNotNull(manager.getProperty(p.concat(".mail.smtp.port")));
+        assertNotNull(manager.getProperty(p.concat(".mail.to")));
+        assertNotNull(manager.getProperty(p.concat(".mail.cc")));
+        assertNotNull(manager.getProperty(p.concat(".subject")));
+        assertNotNull(manager.getProperty(p.concat(".mail.from")));
+        assertEquals(verify, manager.getProperty(p.concat(".verify")));
+        assertNotNull(manager.getProperty(p.concat(".mail.smtp.connectiontimeout")));
+        assertNotNull(manager.getProperty(p.concat(".mail.smtp.timeout")));
+
+        MailHandler instance = new MailHandler(10);
+        instance.setLevel(Level.ALL);
+
+        //Don't auto compute a default recipient.
+        Properties bug7092981 = createInitProperties("");
+        bug7092981.setProperty("mail.to", "");
+        bug7092981.setProperty("mail.cc", "");
+        bug7092981.setProperty("mail.bcc", "");
+        instance.setMailProperties(bug7092981);
+
+        assertEquals(InternalErrorManager.class, instance.getErrorManager().getClass());
+
+        final String CLASS_NAME = MailHandlerTest.class.getName();
+        Logger logger = Logger.getLogger(CLASS_NAME);
+        hardRef = logger;
+        logger.setUseParentHandlers(false);
+        logger.addHandler(instance);
+
+        logger.log(Level.SEVERE, "Verify is {0}.", verify);
+        logger.log(Level.SEVERE, "Verify is {0}.", verify);
+        return instance;
+    }
+
+    @Test
+    public void testPushLevel() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        assertNotNull(instance.getPushLevel());
+
+        try {
+            instance.setPushLevel((Level) null);
+            fail("Null level was allowed");
+        } catch (NullPointerException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        final Level[] lvls = getAllLevels();
+        for (Level lvl : lvls) {
+            instance.setPushLevel(lvl);
+            assertEquals(instance.getPushLevel(), lvl);
+        }
+
+        instance.close();
+        for (Level lvl : lvls) {
+            instance.setPushLevel(lvl);
+            assertEquals(instance.getPushLevel(), lvl);
+        }
+        assertEquals(true, em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testPushLinkageError() throws Exception {
+        testLinkageErrorWithStack("push");
+    }
+
+    @Test
+    public void testPushLinkageErrorEmpty() throws Exception {
+        testLinkageErrorEmptyStack("push");
+    }
+
+    @Test
+    public void testPushFilter() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        try {
+            instance.setPushFilter((Filter) null);
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+        assertNull(instance.getPushFilter());
+
+        instance.setPushFilter(BooleanFilter.TRUE);
+        assertEquals(BooleanFilter.TRUE, instance.getPushFilter());
+
+        assertEquals(true, em.exceptions.isEmpty());
+
+        instance = createHandlerWithRecords();
+        instance.setErrorManager(new PushErrorManager(instance));
+        instance.setPushFilter(BooleanFilter.TRUE);
+        instance.setLevel(Level.ALL);
+        instance.setPushLevel(Level.WARNING);
+        instance.publish(new LogRecord(Level.SEVERE, ""));
+        instance.close();
+    }
+
+    @Test
+    public void testContentTypeOf() throws IOException {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.setEncoding((String) null);
+        String head = instance.contentTypeOf(new XMLFormatter().getHead(instance));
+        assertEquals("application/xml", head);
+        instance.setEncoding("US-ASCII");
+
+        head = instance.contentTypeOf(new XMLFormatter().getHead(instance));
+        assertEquals("application/xml", head);
+
+        instance.setEncoding((String) null);
+        head = instance.contentTypeOf(new SimpleFormatter().getHead(instance));
+        assertNull(head);
+
+        instance.setEncoding("US-ASCII");
+        head = instance.contentTypeOf(new SimpleFormatter().getHead(instance));
+        assertNull(head);
+
+        instance.setEncoding((String) null);
+        head = instance.contentTypeOf(new HeadFormatter("<HTML><BODY>").getHead(instance));
+        assertEquals("text/html", head);
+
+        instance.setEncoding((String) null);
+        head = instance.contentTypeOf(new HeadFormatter("<html><body>").getHead(instance));
+        assertEquals("text/html", head);
+
+        instance.setEncoding("US-ASCII");
+        head = instance.contentTypeOf(new HeadFormatter("<HTML><BODY>").getHead(instance));
+        assertEquals("text/html", head);
+
+        instance.setEncoding("US-ASCII");
+        head = instance.contentTypeOf(new HeadFormatter("<HTML><HEAD></HEAD>"
+                + "<BODY></BODY></HTML>").getHead(instance));
+        assertEquals("text/html", head);
+
+        instance.setEncoding((String) null);
+        head = instance.contentTypeOf(new HeadFormatter("Head").getHead(instance));
+        if (head != null) {//null is assumed to be plain text.
+            assertEquals("text/plain", head);
+        }
+
+        instance.setEncoding("US-ASCII");
+        head = instance.contentTypeOf(new HeadFormatter("Head").getHead(instance));
+        if (head != null) { //null is assumed to be plain text.
+            assertEquals("text/plain", head);
+        }
+
+        instance.setEncoding("US-ASCII");
+        head = instance.contentTypeOf(new HeadFormatter("Head.......Neck.......Body").getHead(instance));
+        if (head != null) { //null is assumed to be plain text.
+            assertEquals("text/plain", head);
+        }
+        instance.close();
+
+        for (Exception exception : em.exceptions) {
+            fail(exception.toString());
+        }
+    }
+
+    @Test
+    public void testContentTypeOfFormatter() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        synchronized (instance) {
+            assertNull(instance.contentTypeOf(new SimpleFormatter()));
+            assertNull(instance.contentTypeOf(new SimpleFormatter(){}));
+
+            assertEquals("application/xml", instance.contentTypeOf(new XMLFormatter()));
+            assertEquals("application/xml", instance.contentTypeOf(new XMLFormatter(){}));
+        }
+
+        /**
+         * None of the Formatter methods that can generate content should be
+         * invoked during a verify as that could lead to poor startup times.
+         */
+        class UnsupportedHTML extends Formatter {
+
+            @Override
+            public String getHead(Handler h) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public String format(LogRecord record) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public String getTail(Handler h) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public String toString() {
+                throw new UnsupportedOperationException();
+            }
+        }
+
+        synchronized (instance) {
+            assertEquals("text/html", instance.contentTypeOf(new UnsupportedHTML()));
+            assertEquals("text/html", instance.contentTypeOf(new UnsupportedHTML(){}));
+        }
+
+        instance.close();
+        for (Exception exception : em.exceptions) {
+            fail(exception.toString());
+        }
+    }
+
+    @Test
+    public void testGuessContentTypeReadlimit() throws Exception {
+        class LastMarkInputStream extends ByteArrayInputStream {
+
+            private int lastReadLimit;
+
+            LastMarkInputStream() {
+                super(new byte[1024]);
+            }
+
+            @Override
+            public synchronized void mark(int readlimit) {
+                this.lastReadLimit = readlimit;
+                super.mark(readlimit);
+            }
+
+            public synchronized int getLastReadLimit() {
+                return lastReadLimit;
+            }
+        }
+
+        LastMarkInputStream in = new LastMarkInputStream();
+        URLConnection.guessContentTypeFromStream(in);
+
+        //See MAX_CHARS in MailHandler.contentTypeOf
+        final int lastLimit = in.getLastReadLimit();
+        assertTrue(String.valueOf(lastLimit), 25 >= lastLimit);
+    }
+
+    @Test
+    public void testContentTypeNestedFormatter() throws Exception {
+        String expected = "application/xml; charset=us-ascii";
+        String type = getInlineContentType(new CollectorFormatter("{0}{1}{2}",
+                new XMLFormatter(), new SeverityComparator()));
+        assertEquals(expected, type);
+
+
+        expected = "text/plain; charset=us-ascii";
+        type = getInlineContentType(new CollectorFormatter("{0}{1}{2}",
+                new CompactFormatter(), new SeverityComparator()));
+        assertEquals(expected, type);
+    }
+
+    @Test
+    public void testContentTypeNestedMultiFormatter() throws Exception {
+        String expected = "application/xml; charset=us-ascii";
+        String type = getInlineMultiContentType(new CollectorFormatter("{0}{1}{2}",
+                new XMLFormatter(), new SeverityComparator()));
+        assertEquals(expected, type);
+
+
+        expected = "text/plain; charset=us-ascii";
+        type = getInlineMultiContentType(new CollectorFormatter("{0}{1}{2}",
+                new CompactFormatter(), new SeverityComparator()));
+        assertEquals(expected, type);
+    }
+
+    @Test
+    public void testContentTypeMultiOverride() throws Exception {
+        String expected = "application/xml; charset=us-ascii";
+        String type = getInlineMultiContentType(new XMLFormatter());
+        assertEquals(expected, type);
+
+        MimetypesFileTypeMap m = new MimetypesFileTypeMap();
+        m.addMimeTypes("text/plain txt TXT XMLFormatter");
+        final FileTypeMap old = FileTypeMap.getDefaultFileTypeMap();
+        FileTypeMap.setDefaultFileTypeMap(m);
+        try {
+            type = getInlineMultiContentType(new XMLFormatter());
+            assertEquals("text/plain; charset=us-ascii", type);
+        } finally {
+            FileTypeMap.setDefaultFileTypeMap(old);
+        }
+
+        type = getInlineMultiContentType(new XMLFormatter());
+        assertEquals(expected, type);
+    }
+
+    private String getInlineMultiContentType(Formatter f) throws Exception {
+        final String[] value = new String[1];
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        instance.setAttachmentFormatters(new SimpleFormatter());
+        instance.setAttachmentFilters(BooleanFilter.FALSE);
+        instance.setEncoding("us-ascii");
+
+        MessageErrorManager em = new MessageErrorManager(instance.getMailProperties()) {
+
+            @Override
+            protected void error(MimeMessage msg, Throwable t, int code) {
+                try {
+                    MimeMultipart multi = (MimeMultipart) msg.getContent();
+                    BodyPart body = multi.getBodyPart(0);
+                    assertEquals(Part.INLINE, body.getDisposition());
+                    String desc = msg.getDescription();
+                    assertTrue(desc.contains("Sorted using"));
+                    assertTrue(desc.contains("pushed when"));
+                    value[0] = body.getContentType();
+                } catch (Throwable E) {
+                    dump(E);
+                    fail(E.toString());
+                }
+            }
+        };
+        instance.setErrorManager(em);
+        Properties props = createInitProperties("");
+        props.put("mail.to", "localhost@localdomain");
+        instance.setMailProperties(props);
+        instance.setFormatter(f);
+        instance.publish(new LogRecord(Level.SEVERE, "test"));
+        instance.close();
+
+        return value[0];
+    }
+
+    @Test
+    public void testContentTypeOverride() throws Exception {
+        String expected = "application/xml; charset=us-ascii";
+        String type = getInlineContentType(new XMLFormatter());
+        assertEquals(expected, type);
+
+        MimetypesFileTypeMap m = new MimetypesFileTypeMap();
+        m.addMimeTypes("text/plain txt TXT XMLFormatter");
+        final FileTypeMap old = FileTypeMap.getDefaultFileTypeMap();
+        FileTypeMap.setDefaultFileTypeMap(m);
+        try {
+            type = getInlineContentType(new XMLFormatter());
+            assertEquals("text/plain; charset=us-ascii", type);
+        } finally {
+            FileTypeMap.setDefaultFileTypeMap(old);
+        }
+
+        type = getInlineContentType(new XMLFormatter());
+        assertEquals(expected, type);
+    }
+
+    private ErrorManager getSuperErrorManager(MailHandler h) throws Exception {
+        Method hem = MailHandler.class.getDeclaredMethod("defaultErrorManager");
+        hem.setAccessible(true);
+        return (ErrorManager) hem.invoke(h);
+    }
+
+    private String getInlineContentType(Formatter f) throws Exception {
+        final String[] value = new String[1];
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        instance.setEncoding("us-ascii");
+        MessageErrorManager em = new MessageErrorManager(instance.getMailProperties()) {
+
+            @Override
+            protected void error(MimeMessage msg, Throwable t, int code) {
+                try {
+                    String desc = msg.getDescription();
+                    assertTrue(desc.contains("filtered with"));
+                    assertTrue(desc.contains("named by"));
+                    value[0] = msg.getContentType();
+                } catch (Throwable E) {
+                    dump(E);
+                    fail(E.toString());
+                }
+            }
+        };
+        instance.setErrorManager(em);
+        Properties props = createInitProperties("");
+        props.put("mail.to", "localhost@localdomain");
+        instance.setMailProperties(props);
+        instance.setFormatter(f);
+        instance.publish(new LogRecord(Level.SEVERE, "test"));
+        instance.close();
+
+        return value[0];
+    }
+
+    @Test
+    public void testAcceptLang() throws Exception {
+        class LangManager extends MessageErrorManager {
+
+            LangManager(final Properties props) {
+                super(props);
+            }
+
+            @Override
+            protected void error(MimeMessage msg, Throwable t, int code) {
+                try {
+                    final Locale locale = Locale.getDefault();
+                    String lang = LogManagerProperties.toLanguageTag(locale);
+                    if (lang.length() != 0) {
+                        assertEquals(lang, msg.getHeader("Accept-Language", null));
+                    } else {
+                        assertEquals("", locale.getLanguage());
+                    }
+
+                    MimeMultipart mp = (MimeMultipart) msg.getContent();
+                    assertTrue(mp.getCount() > 0);
+                    for (int i = 0; i < mp.getCount(); i++) {
+                        MimePart part = (MimePart) mp.getBodyPart(i);
+                        if (lang.length() != 0) {
+                            assertEquals(lang, part.getHeader("Accept-Language", null));
+                        } else {
+                            assertEquals("", locale.getLanguage());
+                        }
+                    }
+                } catch (RuntimeException re) {
+                    dump(re);
+                    throw new AssertionError(re);
+                } catch (Exception ex) {
+                    dump(ex);
+                    throw new AssertionError(ex);
+                }
+            }
+        }
+
+        Formatter[] formatters = new Formatter[]{new SimpleFormatter(), new SimpleFormatter()};
+        InternalErrorManager em;
+        MailHandler target;
+        Locale locale = Locale.getDefault();
+        try {
+            target = new MailHandler(createInitProperties(""));
+            try {
+                em = new LangManager(target.getMailProperties());
+                target.setErrorManager(em);
+                target.setAttachmentFormatters(formatters);
+
+                Locale.setDefault(new Locale("", "", ""));
+                target.publish(new LogRecord(Level.SEVERE, ""));
+                target.flush();
+
+                Locale.setDefault(Locale.ENGLISH);
+                target.publish(new LogRecord(Level.SEVERE, ""));
+                target.flush();
+
+                Locale.setDefault(Locale.GERMAN);
+                target.publish(new LogRecord(Level.SEVERE, ""));
+                target.flush();
+
+                Locale.setDefault(Locale.FRANCE);
+                target.publish(new LogRecord(Level.SEVERE, ""));
+                target.flush();
+            } finally {
+                target.close();
+            }
+        } finally {
+            Locale.setDefault(locale);
+        }
+    }
+
+    @Test
+    public void testContentLangBase() throws Exception {
+
+        class Base extends MessageErrorManager {
+
+            private final String bundleName;
+
+            Base(Properties props, final String bundleName) {
+                super(props);
+                this.bundleName = bundleName;
+            }
+
+            @Override
+            protected void error(MimeMessage msg, Throwable t, int code) {
+                try {
+                    assertNotNull(bundleName);
+                    MimeMultipart mp = (MimeMultipart) msg.getContent();
+                    Locale l = Locale.getDefault();
+                    assertEquals(LogManagerProperties.toLanguageTag(l), msg.getHeader("Accept-Language", null));
+                    String lang[] = msg.getContentLanguage();
+                    assertNotNull(lang);
+                    assertEquals(LogManagerProperties.toLanguageTag(l), lang[0]);
+                    assertEquals(1, mp.getCount());
+                    MimePart part;
+
+                    part = (MimePart) mp.getBodyPart(0);
+                    lang = part.getContentLanguage();
+                    assertNotNull(lang);
+                    assertEquals(LogManagerProperties.toLanguageTag(l), lang[0]);
+                    assertEquals(LogManagerProperties.toLanguageTag(l), part.getHeader("Accept-Language", null));
+                } catch (RuntimeException re) {
+                    dump(re);
+                    throw new AssertionError(re);
+                } catch (Exception ex) {
+                    dump(ex);
+                    throw new AssertionError(ex);
+                }
+            }
+        }
+
+        MailHandler target = new MailHandler(createInitProperties(""));
+        target.setAttachmentFormatters(new SimpleFormatter());
+        target.setAttachmentFilters(BooleanFilter.FALSE);
+
+        Properties props = new Properties();
+        props.put("motd", "Hello MailHandler!");
+        final String p = MailHandler.class.getName();
+        final Locale l = Locale.getDefault();
+        final String name = MailHandler.class.getSimpleName().concat("base");
+        final File f = File.createTempFile(name, ".properties", findClassPathDir());
+        Locale.setDefault(Locale.US);
+        try {
+            try (FileOutputStream fos = new FileOutputStream(f)) {
+                props.store(fos, "No Comment");
+            }
+
+            String bundleName = f.getName().substring(0, f.getName().lastIndexOf('.'));
+            target.setErrorManager(new Base(target.getMailProperties(), bundleName));
+            final Logger log = Logger.getLogger(p + '.' + f.getName(), bundleName);
+            hardRef = log;
+            try {
+                assertNotNull(log.getResourceBundle());
+                assertNotNull(log.getResourceBundleName());
+
+                log.addHandler(target);
+                try {
+                    log.setUseParentHandlers(false);
+                    log.log(Level.SEVERE, "motd");
+                } finally {
+                    log.removeHandler(target);
+                }
+            } finally {
+                hardRef = null;
+            }
+
+            target.close();
+
+            InternalErrorManager em = internalErrorManagerFrom(target);
+            for (Exception t : em.exceptions) {
+                if (isConnectOrTimeout(t)) {
+                    continue;
+                }
+                dump(t);
+                fail(t.toString());
+            }
+            assertFalse(em.exceptions.isEmpty());
+        } finally {
+            Locale.setDefault(l);
+            if (!f.delete() && f.exists()) {
+                f.deleteOnExit();
+            }
+        }
+    }
+
+    @Test
+    public void testContentLangInfer() throws Exception {
+
+        class Infer extends MessageErrorManager {
+
+            private final Locale expect;
+
+            Infer(Properties props, final Locale expect) {
+                super(props);
+                this.expect = expect;
+            }
+
+            @Override
+            protected void error(MimeMessage msg, Throwable t, int code) {
+                try {
+                    MimeMultipart mp = (MimeMultipart) msg.getContent();
+                    Locale l = Locale.getDefault();
+                    assertFalse(l.getCountry().equals(expect.getCountry()));
+
+                    assertEquals(LogManagerProperties.toLanguageTag(l), msg.getHeader("Accept-Language", null));
+                    String lang[] = msg.getContentLanguage();
+                    assertEquals(1, lang.length);
+                    assertEquals(LogManagerProperties.toLanguageTag(expect), lang[0]);
+                    assertEquals(1, mp.getCount());
+                    MimePart part;
+
+                    part = (MimePart) mp.getBodyPart(0);
+                    lang = part.getContentLanguage();
+                    assertEquals(1, lang.length);
+                    assertEquals(LogManagerProperties.toLanguageTag(expect), lang[0]);
+                    assertEquals(LogManagerProperties.toLanguageTag(l), part.getHeader("Accept-Language", null));
+                } catch (RuntimeException re) {
+                    dump(re);
+                    throw new AssertionError(re);
+                } catch (Exception ex) {
+                    dump(ex);
+                    throw new AssertionError(ex);
+                }
+            }
+        }
+
+        MailHandler target;
+        Locale cl;
+        String logPrefix;
+        Properties props = new Properties();
+        props.put("motd", "Hello MailHandler!");
+        final String p = MailHandler.class.getName();
+        final Locale l = Locale.getDefault();
+        final String name = MailHandler.class.getSimpleName().concat("infer");
+        final File f = File.createTempFile(name, "_"
+                + Locale.ENGLISH.getLanguage() + ".properties", findClassPathDir());
+        try {
+            try (FileOutputStream fos = new FileOutputStream(f)) {
+                props.store(fos, "No Comment");
+            }
+
+            String bundleName = f.getName().substring(0, f.getName().lastIndexOf('_'));
+            assertTrue(!bundleName.contains(Locale.ENGLISH.getLanguage()));
+
+            cl = Locale.US;
+            target = new MailHandler(createInitProperties(""));
+            target.setAttachmentFormatters(new SimpleFormatter());
+            target.setAttachmentFilters(BooleanFilter.FALSE);
+
+            target.setErrorManager(new Infer(target.getMailProperties(), Locale.ENGLISH));
+            logPrefix = p + '.' + f.getName() + cl;
+            testContentLangInfer(target, logPrefix, bundleName, cl);
+
+            cl = Locale.UK;
+            target = new MailHandler(createInitProperties(""));
+            target.setAttachmentFormatters(new SimpleFormatter());
+            target.setAttachmentFilters(BooleanFilter.FALSE);
+            target.setErrorManager(new Infer(target.getMailProperties(), Locale.ENGLISH));
+            logPrefix = p + '.' + f.getName() + cl;
+            testContentLangInfer(target, logPrefix, bundleName, cl);
+        } finally {
+            Locale.setDefault(l);
+            if (!f.delete() && f.exists()) {
+                f.deleteOnExit();
+            }
+        }
+    }
+
+    private void testContentLangInfer(MailHandler target, String logPrefix, String bundleName, Locale cl) {
+        Locale.setDefault(cl);
+        Logger log = Logger.getLogger(logPrefix + cl, bundleName);
+        hardRef = log;
+        try {
+            assertNotNull(log.getResourceBundle());
+            assertNotNull(log.getResourceBundleName());
+
+            log.addHandler(target);
+            try {
+                log.setUseParentHandlers(false);
+                log.log(Level.SEVERE, "motd");
+            } finally {
+                log.removeHandler(target);
+            }
+        } finally {
+            hardRef = null;
+        }
+
+        target.close();
+
+        InternalErrorManager em = internalErrorManagerFrom(target);
+        for (Exception t : em.exceptions) {
+            if (isConnectOrTimeout(t)) {
+                continue;
+            }
+            dump(t);
+            fail(t.toString());
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testContentLangExact() throws Exception {
+        MailHandler target = new MailHandler(createInitProperties(""));
+        target.setErrorManager(new MessageErrorManager(target.getMailProperties()) {
+
+            @Override
+            protected void error(MimeMessage msg, Throwable t, int code) {
+                try {
+                    MimeMultipart mp = (MimeMultipart) msg.getContent();
+                    Locale l = Locale.getDefault();
+                    assertEquals(LogManagerProperties.toLanguageTag(l), msg.getHeader("Accept-Language", null));
+                    String lang[] = msg.getContentLanguage();
+                    assertEquals(LogManagerProperties.toLanguageTag(Locale.ENGLISH), lang[0]);
+                    assertEquals(LogManagerProperties.toLanguageTag(Locale.GERMAN), lang[1]);
+                    assertEquals(LogManagerProperties.toLanguageTag(Locale.FRANCE), lang[2]);
+                    assertEquals(4, mp.getCount());
+                    MimePart part;
+
+                    part = (MimePart) mp.getBodyPart(0);
+                    assertEquals(LogManagerProperties.toLanguageTag(l), part.getHeader("Accept-Language", null));
+                    assertNull(part.getHeader("Content-Language", ","));
+
+                    part = (MimePart) mp.getBodyPart(1);
+                    assertEquals(LogManagerProperties.toLanguageTag(l), part.getHeader("Accept-Language", null));
+                    assertEquals(LogManagerProperties.toLanguageTag(Locale.ENGLISH), part.getHeader("Content-Language", ","));
+
+                    part = (MimePart) mp.getBodyPart(2);
+                    assertEquals(LogManagerProperties.toLanguageTag(l), part.getHeader("Accept-Language", null));
+                    assertEquals(LogManagerProperties.toLanguageTag(Locale.GERMAN), part.getHeader("Content-Language", ","));
+
+                    part = (MimePart) mp.getBodyPart(3);
+                    assertEquals(LogManagerProperties.toLanguageTag(l), part.getHeader("Accept-Language", null));
+                    assertEquals(LogManagerProperties.toLanguageTag(Locale.FRANCE), part.getHeader("Content-Language", ","));
+                } catch (RuntimeException re) {
+                    dump(re);
+                    throw new AssertionError(re);
+                } catch (Exception ex) {
+                    dump(ex);
+                    throw new AssertionError(ex);
+                }
+            }
+        });
+
+        target.setLevel(Level.ALL);
+        target.setFilter(new LocaleFilter(Locale.JAPANESE, true));
+        target.setPushLevel(Level.OFF);
+        target.setAttachmentFormatters(new Formatter[]{
+            new SimpleFormatter(), new SimpleFormatter(), new SimpleFormatter()});
+        target.setAttachmentFilters(new Filter[]{
+            new LocaleFilter(Locale.ENGLISH, false),
+            new LocaleFilter(Locale.GERMAN, false),
+            new LocaleFilter(Locale.FRANCE, false)}); //just the language.
+
+        assertEquals(3, target.getAttachmentFormatters().length);
+        assertEquals(3, target.getAttachmentFilters().length);
+
+        final List<File> files = new ArrayList<>();
+        final Properties props = new Properties();
+        final Locale current = Locale.getDefault();
+        try {
+            File f;
+            Locale.setDefault(new Locale("", "", ""));
+            f = testContentLangExact(target, props, "_");
+            files.add(f);
+
+            props.put("motd", "Hello MailHandler!");
+            Locale.setDefault(Locale.ENGLISH);
+            f = testContentLangExact(target, props, "_");
+            files.add(f);
+
+            props.put("motd", "Hallo MailHandler!");
+            Locale.setDefault(Locale.GERMAN);
+            f = testContentLangExact(target, props, "_");
+            files.add(f);
+
+            props.put("motd", "Bonjour MailHandler!");
+            Locale.setDefault(Locale.FRANCE); //just the language.
+            f = testContentLangExact(target, props, "_");
+            files.add(f);
+
+            Locale.setDefault(new Locale("", "", ""));
+            f = testContentLangExact(target, props, "_");
+            files.add(f);
+
+            Locale.setDefault(new Locale("", "", ""));
+            f = testContentLangExact(target, props, ".");
+            files.add(f);
+
+            props.put("motd", "Hello MailHandler!");
+            Locale.setDefault(Locale.ENGLISH);
+            f = testContentLangExact(target, props, ".");
+            files.add(f);
+
+            props.put("motd", "Hallo MailHandler!");
+            Locale.setDefault(Locale.GERMAN);
+            f = testContentLangExact(target, props, ".");
+            files.add(f);
+
+            props.put("motd", "Bonjour MailHandler!");
+            Locale.setDefault(Locale.FRANCE); //just the language.
+            f = testContentLangExact(target, props, ".");
+            files.add(f);
+
+            Locale.setDefault(new Locale("", "", ""));
+            f = testContentLangExact(target, props, ".");
+            files.add(f);
+        } finally {
+            Locale.setDefault(current);
+            for (File f : files) {
+                if (!f.delete() && f.exists()) {
+                    f.deleteOnExit();
+                }
+            }
+        }
+
+        target.close();
+
+        InternalErrorManager em = internalErrorManagerFrom(target);
+        for (Exception t : em.exceptions) {
+            if (isConnectOrTimeout(t)) {
+                continue;
+            }
+            dump(t);
+            fail(t.toString());
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    private File testContentLangExact(MailHandler target, Properties props, String exact) throws Exception {
+        final String p = MailHandler.class.getName();
+        final Locale l = Locale.getDefault();
+        boolean fail = true;
+        final String name = MailHandler.class.getSimpleName().concat("motd");
+        assertTrue(name, name.indexOf(exact) < 1);
+
+        String prefix;
+        if (l.getLanguage().length() != 0) {
+            prefix = "_" + l;
+        } else {
+            prefix = "";
+        }
+        final File f = File.createTempFile(name, prefix + ".properties", findClassPathDir());
+        try {
+            try (FileOutputStream fos = new FileOutputStream(f)) {
+                props.store(fos, "No Comment");
+            }
+
+            Logger log;
+            if (l.getLanguage().length() == 0) {
+                log = Logger.getLogger(p + '.' + f.getName());
+                assertNull(log.getResourceBundle());
+            } else {
+                final String loggerName = p + '.' + f.getName() + '.' + l;
+                if (".".equals(exact)) {
+                    log = Logger.getLogger(loggerName,
+                            f.getName().substring(0, f.getName().lastIndexOf(exact)));
+                } else if ("_".equals(exact)) {
+                    log = Logger.getLogger(loggerName,
+                            f.getName().substring(0, f.getName().indexOf(exact)));
+                } else {
+                    throw new IllegalArgumentException(exact);
+                }
+                assertNotNull(log.getResourceBundle());
+                assertNotNull(log.getResourceBundleName());
+            }
+
+            hardRef = log;
+            try {
+                log.setUseParentHandlers(false);
+                try {
+                    log.addHandler(target);
+                    log.log(Level.INFO, "motd");
+                    fail = false;
+                } finally {
+                    log.removeHandler(target);
+                }
+            } finally {
+                hardRef = null;
+            }
+        } finally {
+            if (fail) {
+                if (!f.delete() && f.exists()) {
+                    f.deleteOnExit();
+                }
+            }
+        }
+        return f;
+    }
+
+    /**
+     * Find a writable directory that is in the class path.
+     *
+     * @return a File directory.
+     * @throws IOException if there is a problem.
+     * @throws FileNotFoundException if there are no directories in class path.
+     */
+    @SuppressWarnings("ThrowableInitCause")
+    private static File findClassPathDir() throws IOException {
+        File f = anyClassPathDir;
+        if (f != null) {
+            return f;
+        }
+
+        ClassLoader ccl = Thread.currentThread().getContextClassLoader();
+        if (ccl == null) {
+            ccl = ClassLoader.getSystemClassLoader();
+        }
+
+        if (ccl == null) {
+            throw new IllegalStateException("Missing classloader.");
+        }
+
+        String path = System.getProperty("java.class.path");
+        String[] dirs = path.split(System.getProperty("path.separator"));
+        IOException fail = null;
+        for (String dir : dirs) {
+            f = new File(dir.trim());
+            if (f.isFile()) {
+                f = f.getParentFile();
+                if (f == null) {
+                    continue;
+                }
+            }
+
+            try {
+                if (f.isDirectory()) {
+                    final String name = MailHandlerTest.class.getName();
+                    final File tmp = File.createTempFile(name, ".tmp", f);
+                    final URL url = ccl.getResource(tmp.getName());
+                    if (!tmp.delete() && tmp.exists()) {
+                        IOException ioe = new IOException(tmp.toString());
+                        dump(ioe);
+                        throw ioe;
+                    }
+
+                    if (url == null || !tmp.equals(new File(url.toURI()))) {
+                        throw new FileNotFoundException(tmp + "not visible from " + ccl);
+                    }
+                    anyClassPathDir = f;
+                    return f;
+                } else {
+                    fail = new FileNotFoundException(f.toString());
+                }
+            } catch (final IOException ioe) {
+                fail = ioe;
+            } catch (final URISyntaxException | IllegalArgumentException use) {
+                fail = (IOException) new IOException(use.toString()).initCause(use);
+            }
+        }
+
+        if (fail != null) {
+            throw fail;
+        }
+
+        //modify the classpath to include a writable directory.
+        throw new FileNotFoundException(path);
+    }
+
+    @Test
+    public void testComparator() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        try {
+            instance.setComparator((Comparator<LogRecord>) null);
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+        assertNull(instance.getComparator());
+
+        UselessComparator uselessComparator = new UselessComparator();
+        Comparator<? super LogRecord> result = instance.getComparator();
+        assertEquals(false, uselessComparator.equals(result));
+
+        instance.setComparator(uselessComparator);
+        result = instance.getComparator();
+
+        assertTrue(uselessComparator.equals(result));
+
+        RawTypeComparator raw = new RawTypeComparator();
+        instance.setComparator(raw);
+        assertTrue(raw.equals(instance.getComparator()));
+
+        assertEquals(true, em.exceptions.isEmpty());
+        instance.close();
+    }
+
+    @Test
+    public void testCapacity() {
+        try {
+            MailHandler h = new MailHandler(-1);
+            h.getCapacity();
+            fail("Negative capacity was allowed.");
+        } catch (IllegalArgumentException pass) {
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        try {
+            MailHandler h = new MailHandler(0);
+            h.getCapacity();
+            fail("Zero capacity was allowed.");
+        } catch (IllegalArgumentException pass) {
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        try {
+            MailHandler h = new MailHandler(1);
+            h.getCapacity();
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        final int expResult = 20;
+        MailHandler instance = new MailHandler(20);
+        instance.setMailProperties(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        int result = instance.getCapacity();
+        assertEquals(expResult, result);
+        instance.close();
+
+        result = instance.getCapacity();
+        assertEquals(expResult, result);
+        assertEquals(true, em.exceptions.isEmpty());
+        instance.close();
+
+        String SOURCE_CLASS = MailHandlerTest.class.getName();
+        String SOURCE_METHOD = "testCapacity";
+        for (int i = 0; i <= NUM_RUNS; i++) {
+            instance = new MailHandler(nextCapacity(i));
+            instance.setLevel(Level.ALL);
+            instance.setPushLevel(Level.OFF);
+            em = new InternalErrorManager();
+            instance.setErrorManager(em);
+            CountingFormatter formatter = new CountingFormatter();
+            instance.setFormatter(formatter);
+            Properties props = createInitProperties("");
+            instance.setMailProperties(props);
+            for (int j = 0; j < instance.getCapacity(); j++) {
+                LogRecord r = new LogRecord(Level.INFO, "");
+                r.setSourceClassName(SOURCE_CLASS);
+                r.setSourceMethodName(SOURCE_METHOD);
+                instance.publish(r);
+            }
+            assertEquals(instance.getCapacity(), formatter.format);
+            assertEquals(1, formatter.head);
+            assertEquals(1, formatter.tail);
+            assertEquals(1, em.exceptions.size());
+            assertTrue(em.exceptions.get(0) instanceof MessagingException);
+            instance.close();
+        }
+    }
+    private static final int LOW_CAPACITY = 1000;
+    private static final int MAX_CAPACITY = 1 << 18;
+    private static final int NUM_RUNS = LOW_CAPACITY + 42;
+    private static final Random RANDOM = new Random();
+
+    /**
+     * Test all numbers between 1 and low capacity.
+     *
+     * @param capacity the current capacity.
+     * @return the next random capacity.
+     */
+    private int nextCapacity(int capacity) {
+        if (capacity <= LOW_CAPACITY) {
+            return ++capacity;
+        } else {
+            if (capacity < NUM_RUNS) {
+                int next;
+                do {
+                    next = RANDOM.nextInt(MAX_CAPACITY);
+                } while (next <= LOW_CAPACITY);
+                return next;
+            } else {
+                return MAX_CAPACITY;
+            }
+        }
+    }
+
+    @Test
+    public void testAuthenticator_Authenticator_Arg() {
+        Authenticator auth = new EmptyAuthenticator();
+
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        try {
+            instance.setAuthenticator((Authenticator) null);
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        try {
+            instance.setAuthenticator(instance.getAuthenticator());
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        try {
+            instance.setAuthenticator(auth);
+            assertEquals(auth, instance.getAuthenticator());
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        assertEquals(true, em.exceptions.isEmpty());
+
+        instance = createHandlerWithRecords();
+        instance.setAuthenticator(new ThrowAuthenticator());
+        em = internalErrorManagerFrom(instance);
+        instance.close();
+
+        assertEquals(1, em.exceptions.size());
+        assertEquals(true, em.exceptions.get(0) instanceof MessagingException);
+    }
+
+    @Test
+    public void testAuthenticator_Char_Array_Arg() {
+        PasswordAuthentication pa;
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        //Null literal means actual password value here.
+        instance.setAuthenticator("null".toCharArray());
+        pa = passwordAuthentication(instance.getAuthenticator(), "user");
+        assertEquals("user", pa.getUserName());
+        assertEquals("null", pa.getPassword());
+
+        instance.setAuthenticator("Null".toCharArray());
+        pa = passwordAuthentication(instance.getAuthenticator(), "user");
+        assertEquals("user", pa.getUserName());
+        assertEquals("Null", pa.getPassword());
+
+        instance.setAuthenticator("NULL".toCharArray());
+        pa = passwordAuthentication(instance.getAuthenticator(), "user");
+        assertEquals("user", pa.getUserName());
+        assertEquals("NULL", pa.getPassword());
+
+        try {
+            instance.setAuthenticator((char[]) null);
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        try {
+            instance.setAuthenticator(instance.getAuthenticator());
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        try {
+            instance.setAuthenticator("password".toCharArray());
+            pa = passwordAuthentication(
+                    instance.getAuthenticator(), "user");
+            assertEquals("user", pa.getUserName());
+            assertEquals("password", pa.getPassword());
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        assertEquals(true, em.exceptions.isEmpty());
+
+        instance = createHandlerWithRecords();
+        instance.setAuthenticator("password".toCharArray());
+        em = internalErrorManagerFrom(instance);
+        instance.close();
+
+        assertEquals(1, em.exceptions.size());
+        assertEquals(true, em.exceptions.get(0) instanceof MessagingException);
+    }
+
+    @Test
+    public void testMailProperties() throws Exception {
+        Properties props = new Properties();
+        MailHandler instance = new MailHandler();
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        assertNotNull(instance.getMailProperties());
+        assertEquals(Properties.class, instance.getMailProperties().getClass());
+
+        try {
+            instance.setMailProperties((Properties) null);
+            fail("Null was allowed.");
+        } catch (NullPointerException pass) {
+        } catch (RuntimeException RE) {
+            fail(RE.toString());
+        }
+
+        instance.setMailProperties(props);
+        Properties stored = instance.getMailProperties();
+
+        assertNotNull(stored);
+        assertNotSame(props, stored);
+        assertEquals(props.getClass(), stored.getClass());
+
+        assertEquals(true, em.exceptions.isEmpty());
+        instance.close();
+
+        instance = createHandlerWithRecords();
+        props = instance.getMailProperties();
+        em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        props.setProperty("mail.from", "localhost@localdomain");
+        props.setProperty("mail.to", "localhost@localdomain");
+        instance.setMailProperties(props);
+        instance.flush();
+        for (Exception exception : em.exceptions) {
+            final Throwable t = exception;
+            if (isConnectOrTimeout(t)) {
+                continue;
+            } else {
+                dump(t);
+                fail(t.toString());
+            }
+        }
+        assertFalse(em.exceptions.isEmpty());
+
+        props.setProperty("mail.from", "localhost@localdomain");
+        props.setProperty("mail.to", "::1@@");
+        instance.setMailProperties(props);
+
+        em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        instance.publish(new LogRecord(Level.SEVERE, "test"));
+        instance.close();
+        int failed = 0;
+        for (Exception exception : em.exceptions) {
+            final Throwable t = exception;
+            if (t instanceof AddressException || isConnectOrTimeout(t)) {
+                continue;
+            } else {
+                dump(t);
+                failed++;
+            }
+        }
+        assertEquals(0, failed);
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testEmptyAddressParse() throws Exception {
+        //Assumed to never return null in the MailHandler.
+        InternetAddress[] a = InternetAddress.parse("", false);
+        assertTrue(a.length == 0);
+    }
+
+    @Test
+    public void testDefaultRecipient() throws Exception {
+        Properties props = createInitProperties("");
+        props.remove("mail.from");
+        props.remove("mail.to");
+        props.remove("mail.cc");
+        props.remove("mail.bcc");
+
+        //User didn't specify so auto compute addresses.
+        assertNull(props.getProperty("mail.from"));
+        assertNull(props.get("mail.from"));
+
+        assertNull(props.getProperty("mail.to"));
+        assertNull(props.get("mail.to"));
+        testDefaultRecipient(props);
+
+        //User override of TO and FROM addresses.
+        props.setProperty("mail.from", "");
+        props.setProperty("mail.to", "");
+        testDefaultRecipient(props);
+
+        //Fixed TO and FROM.
+        props.setProperty("mail.from", "localhost@localdomain");
+        props.setProperty("mail.to", "otherhost@localdomain");
+        testDefaultRecipient(props);
+
+        //Compute TO and FROM with a fixed CC and BCC.
+        props.remove("mail.from");
+        props.remove("mail.to");
+        assertNull(props.getProperty("mail.to"));
+        assertNull(props.get("mail.to"));
+        props.setProperty("mail.cc", "localhost@localdomain");
+        props.setProperty("mail.bcc", "otherhost@localdomain");
+        testDefaultRecipient(props);
+    }
+
+    private void testDefaultRecipient(Properties addresses) throws Exception {
+
+        class DefaultRecipient extends MessageErrorManager {
+
+            DefaultRecipient(Properties props) {
+                super(props);
+            }
+
+            private Address[] parseKey(Session s, String key) throws Exception {
+                final String v = s.getProperty(key);
+                if (v == null) {
+                    return null; //value not present for key.
+                } else if (v.length() == 0) {
+                    return new Address[0]; //value empty for key.
+                } else {
+                    return InternetAddress.parse(v, true);
+                }
+            }
+
+            @Override
+            protected void error(MimeMessage msg, Throwable t, int code) {
+                Session s = new MessageContext(msg).getSession();
+                try {
+                    Address[] local = new Address[]{
+                        InternetAddress.getLocalAddress(s)};
+                    Address[] expectFrom = parseKey(s, "mail.from");
+                    Address[] expectTo = parseKey(s, "mail.to");
+                    Address[] expectCc = parseKey(s, "mail.cc");
+                    Address[] expectBcc = parseKey(s, "mail.bcc");
+
+                    checkAddress(expectFrom == null ? local : expectFrom,
+                            msg.getFrom());
+                    checkAddress(expectTo == null ? local : expectTo,
+                            msg.getRecipients(Message.RecipientType.TO));
+
+                    assertArrayEquals(expectCc,
+                            msg.getRecipients(Message.RecipientType.CC));
+                    assertArrayEquals(expectBcc,
+                            msg.getRecipients(Message.RecipientType.BCC));
+
+                    List<Address> all = asList(msg.getAllRecipients());
+                    assertTrue(all.containsAll(asList(expectTo)));
+                    assertTrue(all.containsAll(asList(expectCc)));
+                    assertTrue(all.containsAll(asList(expectBcc)));
+                } catch (Throwable fail) {
+                    dump(fail);
+                    fail(fail.toString());
+                }
+            }
+
+            private void checkAddress(Address[] expect, Address[] found) {
+                if (expect.length == 0) {
+                    assertTrue(found == null || found.length == 0);
+                } else {
+                    assertArrayEquals(expect, found);
+                }
+            }
+
+            private List<Address> asList(Address... a) {
+                return Arrays.asList(a == null ? new Address[0] : a);
+            }
+        }
+        MailHandler instance = createHandlerWithRecords();
+        Properties props = instance.getMailProperties();
+        props.putAll(addresses);
+        InternalErrorManager em = new DefaultRecipient(props);
+        instance.setErrorManager(em);
+
+        assertNotNull(instance.getMailProperties());
+        assertEquals(Properties.class, instance.getMailProperties().getClass());
+
+        instance.setMailProperties(props);
+        instance.close();
+        for (Exception exception : em.exceptions) {
+            final Throwable t = exception;
+            if (isConnectOrTimeout(t) || t instanceof SendFailedException) {
+                continue;
+            } else {
+                dump(t);
+                fail(t.toString());
+            }
+        }
+
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testDefaultUrlName() throws Exception {
+        Properties props = createInitProperties("");
+        props.put("mail.transport.protocol", "smtp");
+        Session s = Session.getInstance(props);
+        Transport t = s.getTransport();
+        if (isPrivateSpec(t.getClass())) {
+            assertEquals(UNKNOWN_HOST, t.getURLName().getHost());
+        }
+    }
+
+    @Test
+    public void testAttachmentFilters() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        Filter[] result = instance.getAttachmentFilters();
+        assertNotNull(result);
+        assertEquals(result.length, instance.getAttachmentFormatters().length);
+
+        assertEquals(false, instance.getAttachmentFilters() == result);
+
+        if (instance.getAttachmentFormatters().length != 0) {
+            instance.setAttachmentFormatters(new Formatter[0]);
+        }
+
+        try {
+            instance.setAttachmentFilters((Filter[]) null);
+            fail("Null allowed.");
+        } catch (NullPointerException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        try {
+            instance.setAttachmentFilters(new Filter[0]);
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        try {
+            assertEquals(0, instance.getAttachmentFormatters().length);
+
+            instance.setAttachmentFilters(new Filter[]{BooleanFilter.TRUE});
+            fail("Filter to formatter mismatch.");
+        } catch (IndexOutOfBoundsException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        instance.setAttachmentFormatters(
+                new Formatter[]{new SimpleFormatter(), new XMLFormatter()});
+
+        try {
+            assertEquals(2, instance.getAttachmentFormatters().length);
+
+            instance.setAttachmentFilters(new Filter[]{BooleanFilter.TRUE});
+            fail("Filter to formatter mismatch.");
+        } catch (IndexOutOfBoundsException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        try {
+            assertEquals(2, instance.getAttachmentFormatters().length);
+            Filter[] filters = new Filter[]{BooleanFilter.TRUE, BooleanFilter.TRUE};
+            assertEquals(instance.getAttachmentFormatters().length, filters.length);
+            instance.setAttachmentFilters(filters);
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        try {
+            assertEquals(2, instance.getAttachmentFormatters().length);
+            Filter[] filters = new Filter[]{null, null};
+            assert filters != null; //Suppress broken NPE hint with assert.
+            assertEquals(instance.getAttachmentFormatters().length, filters.length);
+            instance.setAttachmentFilters(filters);
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        try {
+            assertEquals(2, instance.getAttachmentFormatters().length);
+            instance.setAttachmentFilters(new Filter[0]);
+            fail("Filter to formatter mismatch.");
+        } catch (IndexOutOfBoundsException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        try {
+            assertEquals(instance.getAttachmentFormatters().length, 2);
+            Filter[] filters = new Filter[]{null, null};
+            instance.setAttachmentFilters(filters);
+            filters[0] = BooleanFilter.TRUE;
+            assertEquals(filters[0], filters[0]);
+            assertEquals(filters[0].equals(instance.getAttachmentFilters()[0]), false);
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        assertEquals(instance.getAttachmentFormatters().length, 2);
+        //Force a subclass array.
+        instance.setAttachmentFilters(new ThrowFilter[]{new ThrowFilter(), new ThrowFilter()});
+        assertEquals(Filter[].class, instance.getAttachmentFilters().getClass());
+
+        assertEquals(em.exceptions.isEmpty(), true);
+        instance.close();
+    }
+
+    @Test
+    public void testAttachmentFiltersDefaults() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.setFilter(new ErrorFilter());
+        final Formatter f = new SimpleFormatter();
+        instance.setAttachmentFormatters(f, f, f, f);
+
+        for (Exception exception : em.exceptions) {
+            dump(exception);
+        }
+        assertTrue(em.exceptions.isEmpty());
+
+        assertEquals(ErrorFilter.class, instance.getFilter().getClass());
+        assertEquals(instance.getFilter(), instance.getAttachmentFilters()[0]);
+        assertEquals(instance.getFilter(), instance.getAttachmentFilters()[1]);
+        assertEquals(instance.getFilter(), instance.getAttachmentFilters()[2]);
+        assertEquals(instance.getFilter(), instance.getAttachmentFilters()[3]);
+
+        instance.setAttachmentFilters(null, null, null, null);
+        assertEquals(ErrorFilter.class, instance.getFilter().getClass());
+        assertNull(instance.getAttachmentFilters()[0]);
+        assertNull(instance.getAttachmentFilters()[1]);
+        assertNull(instance.getAttachmentFilters()[2]);
+        assertNull(instance.getAttachmentFilters()[3]);
+    }
+
+    @Test
+    public void testAttachmentFormatters() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        Formatter[] result = instance.getAttachmentFormatters();
+        assertNotNull(result);
+        assertEquals(result == instance.getAttachmentFormatters(), false);
+
+        assertEquals(result.length, instance.getAttachmentFilters().length);
+        assertEquals(result.length, instance.getAttachmentNames().length);
+
+        result = new Formatter[]{new SimpleFormatter(), new XMLFormatter()};
+        instance.setAttachmentFormatters(result);
+
+        assertEquals(result.length, instance.getAttachmentFilters().length);
+        assertEquals(result.length, instance.getAttachmentNames().length);
+
+        result[0] = new XMLFormatter();
+        result[1] = new SimpleFormatter();
+        assertEquals(result[1].getClass(),
+                instance.getAttachmentFormatters()[0].getClass());
+        assertEquals(result[0].getClass(),
+                instance.getAttachmentFormatters()[1].getClass());
+
+        try {
+            instance.setAttachmentFormatters((Formatter[]) null);
+            fail("Null was allowed.");
+        } catch (NullPointerException NPE) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        result[0] = null;
+        try {
+            instance.setAttachmentFormatters(result);
+            fail("Null index was allowed.");
+        } catch (NullPointerException NPE) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        result = new Formatter[0];
+        try {
+            instance.setAttachmentFormatters(result);
+            assertEquals(result.length, instance.getAttachmentFilters().length);
+            assertEquals(result.length, instance.getAttachmentNames().length);
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        instance.setAttachmentFormatters(new ThrowFormatter[]{new ThrowFormatter()});
+        assertEquals(Formatter[].class, instance.getAttachmentFormatters().getClass());
+        assertEquals(Filter[].class, instance.getAttachmentFilters().getClass());
+        assertEquals(Formatter[].class, instance.getAttachmentNames().getClass());
+
+        assertEquals(em.exceptions.isEmpty(), true);
+        instance.close();
+    }
+
+    @Test
+    public void testAttachmentNames_StringArr() {
+        Formatter[] names;
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        names = instance.getAttachmentNames();
+        assertNotNull(names);
+
+        try {
+            instance.setAttachmentNames((String[]) null);
+            fail("Null was allowed.");
+        } catch (RuntimeException re) {
+            assertEquals(NullPointerException.class, re.getClass());
+        }
+
+        if (instance.getAttachmentFormatters().length > 0) {
+            instance.setAttachmentFormatters(new Formatter[0]);
+        }
+
+        try {
+            instance.setAttachmentNames(new String[0]);
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        try {
+            instance.setAttachmentNames(new String[1]);
+            fail("Mismatch with attachment formatters.");
+        } catch (NullPointerException | IndexOutOfBoundsException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        instance.setAttachmentFormatters(
+                new Formatter[]{new SimpleFormatter(), new XMLFormatter()});
+        try {
+            instance.setAttachmentNames(new String[2]);
+            fail("Null index was allowed.");
+        } catch (NullPointerException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        Formatter[] formatters = instance.getAttachmentFormatters();
+        names = instance.getAttachmentNames();
+
+        assertEquals(names[0].toString(), String.valueOf(formatters[0]));
+        assertEquals(names[1].toString(), String.valueOf(formatters[1]));
+
+        String[] stringNames = new String[]{"error.txt", "error.xml"};
+        instance.setAttachmentNames(stringNames);
+        assertEquals(stringNames[0], instance.getAttachmentNames()[0].toString());
+        assertEquals(stringNames[1], instance.getAttachmentNames()[1].toString());
+
+        stringNames[0] = "info.txt";
+        assertEquals(stringNames[0].equals(
+                instance.getAttachmentNames()[0].toString()), false);
+
+        try {
+            instance.setAttachmentNames(new String[0]);
+            fail("Names mismatch formatters.");
+        } catch (IndexOutOfBoundsException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        assertEquals(true, em.exceptions.isEmpty());
+    }
+
+    @Test
+    public void testAttachmentNames_FormatterArr() {
+        Formatter[] formatters;
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        assertNotNull(instance.getAttachmentNames());
+
+        try {
+            instance.setAttachmentNames((Formatter[]) null);
+            fail("Null was allowed.");
+        } catch (NullPointerException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        if (instance.getAttachmentFormatters().length > 0) {
+            instance.setAttachmentFormatters(new Formatter[0]);
+        }
+
+        try {
+            instance.setAttachmentNames(new Formatter[2]);
+            fail("formatter mismatch.");
+        } catch (NullPointerException | IndexOutOfBoundsException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        instance.setAttachmentFormatters(
+                new Formatter[]{new SimpleFormatter(), new XMLFormatter()});
+
+        assertEquals(instance.getAttachmentFormatters().length,
+                instance.getAttachmentNames().length);
+
+        formatters = new Formatter[]{new SimpleFormatter(), new XMLFormatter()};
+        instance.setAttachmentNames(formatters);
+        formatters[0] = new XMLFormatter();
+        assertEquals(formatters[0].equals(instance.getAttachmentNames()[0]), false);
+
+        instance.setAttachmentNames(new ThrowFormatter[]{new ThrowFormatter(), new ThrowFormatter()});
+        assertEquals(Formatter[].class, instance.getAttachmentNames().getClass());
+        assertEquals(em.exceptions.isEmpty(), true);
+        instance.close();
+    }
+
+    @Test
+    public void testSubject_String() {
+        String subject = "Test subject.";
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        assertNotNull(instance.getSubject());
+
+        try {
+            instance.setSubject((String) null);
+            fail("Null subject was allowed.");
+        } catch (NullPointerException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        instance.setSubject(subject);
+        assertEquals(subject, instance.getSubject().toString());
+
+        assertEquals(em.exceptions.isEmpty(), true);
+        instance.close();
+    }
+
+    @Test
+    public void testTailFormatters() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        instance.setSubject(instance.toString());
+        Formatter f1 = instance.getSubject();
+        assertEquals(f1, f1);
+        assertEquals(f1.hashCode(), f1.hashCode());
+        assertEquals(f1.toString(), f1.toString());
+
+        instance.setSubject(instance.toString());
+        Formatter f2 = instance.getSubject();
+        assertEquals(f2, f2);
+        assertEquals(f2.hashCode(), f2.hashCode());
+        assertEquals(f2.toString(), f2.toString());
+
+        assertEquals(f1.getClass(), f2.getClass());
+        assertEquals(f1.toString(), f2.toString());
+
+        Formatter same = new XMLFormatter();
+        instance.setAttachmentFormatters(
+                new Formatter[]{same, same});
+        Formatter[] formatters = instance.getAttachmentNames();
+        f1 = formatters[0];
+        f2 = formatters[1];
+
+        assertEquals(f1, f1);
+        assertEquals(f1.hashCode(), f1.hashCode());
+        assertEquals(f1.toString(), f1.toString());
+
+        assertEquals(f2, f2);
+        assertEquals(f2.hashCode(), f2.hashCode());
+        assertEquals(f2.toString(), f2.toString());
+
+        assertEquals(f1.getClass(), f2.getClass());
+        assertEquals(f1.toString(), f2.toString());
+
+        assertFalse(f1.equals(new SimpleFormatter()));
+        assertFalse(new SimpleFormatter().equals(f1));
+        assertFalse(f2.equals(new SimpleFormatter()));
+        assertFalse(new SimpleFormatter().equals(f2));
+
+        //New in JavaMail 1.4.4.
+        assertEquals(f1, f2);
+        assertEquals(f1.hashCode(), f2.hashCode());
+
+        assertEquals(em.exceptions.isEmpty(), true);
+        instance.close();
+    }
+
+    @Test
+    public void testSubject_Formatter() {
+        Formatter format = new SimpleFormatter();
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        assertNotNull(instance.getSubject());
+        assertEquals(CollectorFormatter.class, instance.getSubject().getClass());
+
+        try {
+            instance.setSubject((Formatter) null);
+            fail("Null subject was allowed.");
+        } catch (NullPointerException pass) {
+        } catch (RuntimeException re) {
+            fail(re.toString());
+        }
+
+        instance.setSubject(format);
+        assertEquals(format, instance.getSubject());
+
+        assertEquals(true, em.exceptions.isEmpty());
+        instance.close();
+    }
+
+    @Test
+    public void testAttachmentFilterSwapBeforePush() {
+        MailHandler instance = new MailHandler(10);
+        instance.setMailProperties(createInitProperties(""));
+        instance.setLevel(Level.ALL);
+        instance.setPushLevel(Level.OFF);
+        instance.setPushFilter((Filter) null);
+        instance.setFilter(BooleanFilter.FALSE);
+        instance.setAttachmentFormatters(new Formatter[]{new XMLFormatter()});
+        instance.setAttachmentFilters(new Filter[]{null});
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        LogRecord record = new LogRecord(Level.SEVERE, "lost record");
+        assertTrue(instance.isLoggable(record));
+
+        instance.publish(record);
+        instance.setAttachmentFilters(new Filter[]{BooleanFilter.FALSE});
+        assertFalse(instance.isLoggable(record));
+        instance.close();
+
+        int seenFormat = 0;
+        for (Exception exception : em.exceptions) {
+            if (exception instanceof MessagingException) {
+                continue;
+            } else if (exception instanceof RuntimeException
+                    && exception.getMessage().contains(instance.getFilter().toString())
+                    && exception.getMessage().contains(Arrays.asList(instance.getAttachmentFilters()).toString())) {
+                seenFormat++;
+                continue; //expected.
+            } else {
+                fail(String.valueOf(exception));
+            }
+        }
+        assertTrue("No format error", seenFormat > 0);
+    }
+
+    @Test
+    public void testFilterSwapBeforePush() {
+        MailHandler instance = new MailHandler(10);
+        instance.setMailProperties(createInitProperties(""));
+        instance.setLevel(Level.ALL);
+        instance.setPushLevel(Level.OFF);
+        instance.setPushFilter((Filter) null);
+        instance.setAttachmentFormatters(new SimpleFormatter());
+        instance.setAttachmentFilters(BooleanFilter.FALSE);
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        LogRecord record = new LogRecord(Level.SEVERE, "lost record");
+        assertTrue(instance.isLoggable(record));
+
+        instance.publish(record);
+        instance.setFilter(BooleanFilter.FALSE);
+        assertFalse(instance.isLoggable(record));
+        instance.close();
+
+        int seenFormat = 0;
+        for (Exception exception : em.exceptions) {
+            if (exception instanceof MessagingException) {
+                continue;
+            } else if (exception instanceof RuntimeException
+                    && exception.getMessage().contains(instance.getFilter().toString())) {
+                seenFormat++;
+                continue; //expected.
+            } else {
+                fail(String.valueOf(exception));
+            }
+        }
+        assertTrue("No format error", seenFormat > 0);
+    }
+
+    @Test
+    public void testFilterFlipFlop() {
+        MailHandler instance = new MailHandlerOverride(10);
+        instance.setMailProperties(createInitProperties(""));
+        instance.setLevel(Level.ALL);
+        instance.setPushLevel(Level.OFF);
+        instance.setPushFilter((Filter) null);
+        FlipFlopFilter badFilter = new FlipFlopFilter();
+        instance.setFilter(badFilter);
+        instance.setAttachmentFormatters(new SimpleFormatter());
+
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+
+        LogRecord record = new LogRecord(Level.SEVERE, "lost record");
+
+        assertSame(badFilter, instance.getFilter());
+        badFilter.value = true;
+        assertSame(badFilter, instance.getFilter());
+
+        assertTrue(instance.isLoggable(record));
+        instance.publish(record);
+        badFilter.value = false;
+
+        assertSame(badFilter, instance.getFilter());
+        assertFalse(instance.isLoggable(record));
+        instance.close();
+        assertSame(badFilter, instance.getFilter());
+
+        int seenFormat = 0;
+        for (Exception exception : em.exceptions) {
+            if (exception instanceof MessagingException) {
+                continue;
+            } else if (exception instanceof RuntimeException
+                    && exception.getMessage().contains(instance.getFilter().toString())) {
+                seenFormat++;
+                continue; //expected.
+            } else {
+                fail(String.valueOf(exception));
+            }
+        }
+        assertTrue("No format error", seenFormat > 0);
+    }
+
+    @Test
+    public void testFilterReentrance() {
+        Logger logger = Logger.getLogger("testFilterReentrance");
+
+        MailHandler instance = new MailHandler(2);
+        instance.setMailProperties(createInitProperties(""));
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.setFilter(new ReentranceFilter());
+
+        logger.setUseParentHandlers(false);
+        logger.setLevel(Level.ALL);
+        logger.addHandler(instance);
+        hardRef = logger;
+        try {
+            assertNotNull(hardRef);
+            logger.logp(Level.SEVERE, MailHandlerTest.class.getName(), "testFilterReentrance", "test");
+
+            int seenIse = 0;
+            for (Exception exception : em.exceptions) {
+                if (exception instanceof MessagingException) {
+                    continue;
+                } else if (exception instanceof IllegalStateException) {
+                    seenIse++;
+                    continue; //expected.
+                } else {
+                    fail(String.valueOf(exception));
+                }
+            }
+
+            assertTrue("No IllegalStateException", seenIse > 0);
+        } finally {
+            logger.removeHandler(instance);
+            logger.setLevel((Level) null);
+            logger.setUseParentHandlers(true);
+            hardRef = null;
+        }
+    }
+
+    @Test
+    public void testPushFilterReentrance() {
+        testPushFilterReentrance(1, 1);
+        testPushFilterReentrance(1, 2);
+        testPushFilterReentrance(1, 1000);
+        testPushFilterReentrance(500, 1000);
+        testPushFilterReentrance(1000, 1000);
+    }
+
+    private void testPushFilterReentrance(int records, int cap) {
+        assert records <= cap : records;
+        Logger logger = Logger.getLogger("testPushFilterReentrance");
+
+        MailHandler instance = new MailHandler(cap);
+
+        Properties props = createInitProperties("");
+        instance.setMailProperties(props);
+
+        InternalErrorManager em = new InternalErrorManager();
+        instance.setErrorManager(em);
+        instance.setPushLevel(Level.ALL);
+        instance.setPushFilter(new ReentranceFilter());
+
+        logger.setUseParentHandlers(false);
+        logger.setLevel(Level.ALL);
+        logger.addHandler(instance);
+        hardRef = logger;
+        try {
+            assertNotNull(hardRef);
+
+            while (records-- > 0) {
+                logger.logp(Level.SEVERE, MailHandlerTest.class.getName(), "testPushFilterReentrance", "test");
+            }
+            instance.close();
+
+            for (Exception exception : em.exceptions) {
+                Throwable t = exception;
+                if ((t instanceof MessagingException == false)
+                        && (t instanceof IllegalStateException == false)) {
+                    dump(t);
+                    fail(String.valueOf(t));
+                }
+            }
+            assertFalse(em.exceptions.isEmpty());
+        } finally {
+            logger.removeHandler(instance);
+            logger.setLevel((Level) null);
+            logger.setUseParentHandlers(true);
+            hardRef = null;
+        }
+    }
+
+    @Test
+    public void testMailDebugLowCap() throws Exception {
+        MailHandler instance = new MailHandler(1);
+        try {
+            testMailDebug(instance, 2);
+        } finally {
+            instance.close();
+        }
+    }
+
+    @Test
+    public void testMailDebugPushLevel() throws Exception {
+        MailHandler instance = new MailHandler(1000);
+        try {
+            instance.setPushLevel(Level.ALL);
+            testMailDebug(instance, 2);
+        } finally {
+            instance.close();
+        }
+    }
+
+    @Test
+    public void testMailDebugPush() throws Exception {
+        MailHandler instance = new MailHandler(4);
+        try {
+            testMailDebug(instance, -3);
+        } finally {
+            instance.close();
+        }
+    }
+
+    @Test
+    public void testMailDebugFlush() throws Exception {
+        MailHandler instance = new MailHandler(3);
+        try {
+            testMailDebug(instance, -2);
+        } finally {
+            instance.close();
+        }
+    }
+
+    @Test
+    public void testMailDebugClose() throws Exception {
+        MailHandler instance = new MailHandler(1000);
+        try {
+            testMailDebug(instance, -1);
+        } finally {
+            instance.close();
+        }
+    }
+
+    @Test
+    public void testMailDebugErrorManager() throws Exception {
+        MailHandler instance = new MailHandler(1);
+        try {
+            instance.setErrorManager(new MailDebugErrorManager());
+            testMailDebug(instance, 2);
+        } finally {
+            instance.close();
+        }
+    }
+
+    private void testMailDebug(MailHandler instance, int records) throws Exception {
+        final Properties props = createInitProperties("");
+        props.put("mail.debug", "true");
+        props.put("verify", "local");
+
+        instance.setLevel(Level.ALL);
+        testMailDebugQuietLog(instance, props, records);
+    }
+
+    private void testMailDebugQuietLog(MailHandler instance, Properties props, int records) throws Exception {
+        final Logger root = Logger.getLogger("");
+        hardRef = root;
+        try {
+            final Handler[] handlers = root.getHandlers();
+            for (Handler h : handlers) {
+                root.removeHandler(h);
+            }
+            try {
+                root.setLevel(Level.ALL);
+                root.addHandler(instance);
+                try {
+                    testMailDebugQuietStreams(instance, props, records);
+                } finally {
+                    root.setLevel(null);
+                    root.removeHandler(instance);
+                }
+            } finally {
+                for (Handler h : handlers) {
+                    root.addHandler(h);
+                }
+            }
+        } finally {
+            hardRef = null;
+        }
+    }
+
+    @SuppressWarnings("UseOfSystemOutOrSystemErr")
+    private void testMailDebugQuietStreams(MailHandler instance, Properties props, int records) throws Exception {
+        final PrintStream out = System.out;
+        try {
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            System.setOut(new PrintStream(baos, true, "ISO-8859-1"));
+            final PrintStream err = System.err;
+            try {
+                System.setErr(new PrintStream(baos, true, "ISO-8859-1"));
+                instance.setMailProperties(props);
+
+                if (records > 0) {
+                    for (int i = 0; i < records; i++) {
+                        baos.reset();
+                        instance.publish(new LogRecord(Level.SEVERE, ""));
+                    }
+                } else {
+                    records = -records;
+                    for (int i = 0; i < records; i++) {
+                        baos.reset();
+                        instance.publish(new LogRecord(Level.SEVERE, ""));
+                    }
+
+                    if (records == 1) {
+                        instance.close();
+                    } else if (records == 2) {
+                        instance.flush();
+                    } else if (records == 3) {
+                        instance.push();
+                    }
+                }
+            } finally {
+                System.setErr(err);
+            }
+        } finally {
+            System.setOut(out);
+        }
+    }
+
+    @Test
+    public void testReportError() {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+        instance.setErrorManager(new ErrorManager() {
+
+            @Override
+            public void error(String msg, Exception ex, int code) {
+                assertNull(msg);
+            }
+        });
+
+        instance.reportError(null, null, ErrorManager.GENERIC_FAILURE);
+
+        instance.setErrorManager(new ErrorManager() {
+
+            @Override
+            public void error(String msg, Exception ex, int code) {
+                assertEquals(msg.indexOf(Level.SEVERE.getName()), 0);
+            }
+        });
+
+        instance.reportError("simple message.", null, ErrorManager.GENERIC_FAILURE);
+        instance.close();
+
+        //Test for valid message.
+        instance = createHandlerWithRecords();
+        instance.setErrorManager(new MessageErrorManager(instance.getMailProperties()) {
+
+            @Override
+            protected void error(MimeMessage message, Throwable t, int code) {
+                try {
+                    assertTrue(message.getHeader("X-Mailer")[0].startsWith(MailHandler.class.getName()));
+                    assertTrue(null != message.getSentDate());
+                    message.saveChanges();
+                } catch (MessagingException ME) {
+                    fail(ME.toString());
+                }
+            }
+        });
+        instance.close();
+    }
+
+    @Test
+    public void testReportErrorUtf8Addresses() throws Exception {
+        final String test = "test\u03b1";
+        final String saddr = test + '@' + UNKNOWN_HOST;
+        final String sender = test + saddr;
+        Properties props = createInitProperties("");
+        props.put("mail.to", saddr);
+        props.put("mail.cc", saddr);
+        props.put("mail.bcc", saddr);
+        props.put("mail.from", saddr);
+        props.put("mail.sender", sender);
+        props.put("mail.mime.allowutf8",  "true");
+        MailHandler instance = new MailHandler(props);
+        instance.setEncoding("UTF-8");
+        instance.setFormatter(new SimpleFormatter());
+        instance.setErrorManager(new MessageErrorManager(instance.getMailProperties()) {
+
+            @Override
+            protected void error(MimeMessage message, Throwable t, int code) {
+                try {
+                    assertEquals(saddr, toString(message.getFrom()[0]));
+                    assertEquals(saddr, toString(message.getRecipients(Message.RecipientType.TO)[0]));
+                    assertEquals(saddr, toString(message.getRecipients(Message.RecipientType.CC)[0]));
+                    assertEquals(saddr, toString(message.getRecipients(Message.RecipientType.BCC)[0]));
+
+                    assertEquals(sender, toString(message.getSender()));
+                    assertTrue(String.valueOf(message.getContent()).contains(sender));
+
+                    message.saveChanges();
+                } catch (MessagingException | IOException E) {
+                    fail(E.toString());
+                }
+            }
+
+            private String toString(Address o) {
+                return ((InternetAddress)o).toUnicodeString();
+            }
+        });
+        instance.publish(new LogRecord(Level.SEVERE, sender));
+        instance.close();
+    }
+
+    @Test
+    public void testReportErrorSuper() throws Exception {
+        assertNull(System.getSecurityManager());
+        Field mhem = MailHandler.class.getDeclaredField("errorManager");
+        mhem.setAccessible(true);
+
+        InternalErrorManager superEm = new InternalErrorManager();
+        InternalErrorManager em = new InternalErrorManager();
+        MailHandler h = new MailHandler();
+        try {
+            Exception tester = new Exception();
+            synchronized (h) {
+                assertSame(h.getErrorManager(), getSuperErrorManager(h));
+
+                h.setErrorManager(superEm);
+                assertSame(superEm, getSuperErrorManager(h));
+                assertSame(superEm, mhem.get(h));
+
+                mhem.set(h, em);
+                assertSame(em, h.getErrorManager());
+                assertSame(superEm, getSuperErrorManager(h));
+                assertNotSame(h.getErrorManager(), getSuperErrorManager(h));
+                h.reportError("", tester, ErrorManager.GENERIC_FAILURE);
+            }
+
+            assertEquals(1, em.exceptions.size());
+            assertSame(tester, em.exceptions.get(0));
+            assertTrue(superEm.exceptions.toString(),
+                    superEm.exceptions.isEmpty());
+        } finally {
+            h.close();
+        }
+    }
+
+    @Test
+    public void testGaeReportErrorSuper() throws Exception {
+        Field mhem = MailHandler.class.getDeclaredField("errorManager");
+        mhem.setAccessible(true);
+
+        InternalErrorManager em = new InternalErrorManager();
+        GaeSecurityManager sm = new GaeSecurityManager();
+        System.setSecurityManager(sm);
+        sm.secure = true;
+        try {
+            MailHandler h = new MailHandler();
+            try {
+                Exception tester = new Exception();
+                synchronized (h) {
+                    sm.secure = false;
+                    final Object def = getSuperErrorManager(h);
+                    assertSame(def, getSuperErrorManager(h));
+                    sm.secure = true;
+
+                    assertEquals(h.getErrorManager().getClass(), def.getClass());
+                    assertNotSame(h.getErrorManager(), def);
+
+                    h.setErrorManager(em);
+                    sm.secure = false;
+                    final Object sem = getSuperErrorManager(h);
+                    sm.secure = true;
+                    assertSame(def, sem);
+                    assertNotSame(h.getErrorManager(), def);
+                    assertNotSame(h.getErrorManager(), sem);
+                    assertSame(h.getErrorManager(), em);
+
+                    h.reportError("", tester, ErrorManager.GENERIC_FAILURE);
+                }
+
+                assertEquals(1, em.exceptions.size());
+                assertSame(tester, em.exceptions.get(0));
+            } finally {
+                h.close();
+            }
+        } finally {
+            sm.secure = false;
+            System.setSecurityManager((SecurityManager) null);
+        }
+    }
+
+    @Test
+    public void testReportErrorLinkageWithStack() throws Exception {
+        testReportErrorLinkageWithStack(new LinkageErrorStream());
+    }
+
+    @Test
+    public void testReportErrorRuntimeWithStack() throws Exception {
+        testReportErrorLinkageWithStack(new RuntimeErrorStream());
+    }
+
+    private void testReportErrorLinkageWithStack(PrintStream ps) throws Exception {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+
+        @SuppressWarnings("UseOfSystemOutOrSystemErr")
+        final PrintStream err = System.err;
+        System.setErr(ps);
+        try {
+            try {
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.CLOSE_FAILURE);
+
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.FLUSH_FAILURE);
+
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.GENERIC_FAILURE);
+
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.OPEN_FAILURE);
+
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.WRITE_FAILURE);
+            } finally {
+                System.setErr(err);
+            }
+        } catch (PrintThrowsRuntimeException unexpected) {
+            unexpected.dump();
+            fail(unexpected.getMessage());
+        }
+    }
+
+    @Test
+    public void testReportErrorLinkageEmptyStack() throws Throwable {
+        testReportErrorLinkageEmptyStack(
+                new LinkageErrorStream(new StackTraceElement[0]));
+    }
+
+    @Test
+    public void testReportErrorLinkageShortStack() throws Throwable {
+        testReportErrorLinkageEmptyStack(
+                new LinkageErrorStream(new StackTraceElement[]{
+                    new StackTraceElement("", "", "", -1)}));
+    }
+
+    @Test
+    public void testReportErrorRuntimeEmptyStack() throws Throwable {
+        testReportErrorLinkageEmptyStack(
+                new RuntimeErrorStream(new StackTraceElement[0]));
+    }
+
+    @Test
+    public void testReportErrorRuntimeShortStack() throws Throwable {
+        testReportErrorLinkageEmptyStack(
+                new RuntimeErrorStream(new StackTraceElement[]{
+                    new StackTraceElement("", "", "", -1)}));
+    }
+
+    private void testReportErrorLinkageEmptyStack(PrintStream ps) throws Throwable {
+        MailHandler instance = new MailHandler(createInitProperties(""));
+
+        @SuppressWarnings("UseOfSystemOutOrSystemErr")
+        final PrintStream err = System.err;
+        System.setErr(ps);
+        try {
+            try {
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.FLUSH_FAILURE);
+
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.CLOSE_FAILURE);
+
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.GENERIC_FAILURE);
+
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.OPEN_FAILURE);
+
+                instance.setErrorManager(new ErrorManager());
+                instance.reportError(null, null, ErrorManager.WRITE_FAILURE);
+            } finally {
+                System.setErr(err);
+            }
+        } catch (PrintThrowsRuntimeException unexpected) {
+            unexpected.dump();
+            fail(unexpected.getMessage());
+        }
+    }
+
+    @Ignore
+    public void testGaeForbiddenHeaders() throws Exception {
+        assumeNoJit();
+        assertNull(System.getSecurityManager());
+        assertTrue(LogManagerProperties.hasLogManager());
+        final Class<?> k = LogManagerProperties.class;
+        final Field f = k.getDeclaredField("LOG_MANAGER");
+        final Field mod = setAccessible(f);
+        try {
+            final Object lm = f.get(null);
+            f.set(null, new Properties());
+            try {
+                fullFence();
+                assertFalse(LogManagerProperties.hasLogManager());
+                MailHandler instance = createHandlerWithRecords();
+                instance.setErrorManager(new GaeErrorManager(instance));
+                instance.close();
+            } finally {
+                f.set(null, lm);
+                fullFence();
+            }
+        } finally {
+            mod.setInt(f, f.getModifiers() | Modifier.FINAL);
+        }
+        assertTrue(LogManagerProperties.hasLogManager());
+    }
+
+    @Ignore
+    public void testGaeSecurityManager() throws Exception {
+        assumeNoJit();
+        InternalErrorManager em;
+        MailHandler h = null;
+        final GaeSecurityManager manager = new GaeSecurityManager();
+        System.setSecurityManager(manager);
+        try {
+            manager.secure = false;
+            h = new MailHandler(createInitProperties(""));
+            em = new InternalErrorManager();
+            h.setErrorManager(em);
+            manager.secure = true;
+            assertEquals(manager, System.getSecurityManager());
+
+            //GAE allows access to loggers.
+            Logger global = Logger.getLogger("global");
+            hardRef = global;
+            global.addHandler(h);
+            global.removeHandler(h);
+            global.removeHandler((Handler) null);
+            hardRef = null;
+
+            h.postConstruct();
+            h.setAttachmentFormatters(new Formatter[]{new ThrowFormatter()});
+            assertEquals(1, h.getAttachmentFormatters().length);
+
+            h.setAttachmentFilters(new Filter[]{new ThrowFilter()});
+            assertEquals(1, h.getAttachmentFormatters().length);
+
+            assertEquals(1, h.getAttachmentFormatters().length);
+            h.setAttachmentNames(new String[]{"error.txt"});
+
+            assertEquals(1, h.getAttachmentFormatters().length);
+            h.setAttachmentNames(new Formatter[]{new ThrowFormatter()});
+
+            h.setAuthenticator((Authenticator) null);
+            h.setComparator((Comparator<? super LogRecord>) null);
+
+            h.setLevel(Level.ALL);
+            h.setFilter(BooleanFilter.FALSE);
+            h.setFilter((Filter) null);
+            h.setFormatter(new EmptyFormatter());
+
+            assertNotNull(h.getErrorManager());
+            h.setErrorManager(new ErrorManager());
+
+            h.setEncoding((String) null);
+
+            h.flush();
+            h.push();
+
+            h.setMailProperties(new Properties());
+
+            h.setPushFilter((Filter) null);
+            h.setPushLevel(Level.OFF);
+
+            h.setSubject(new ThrowFormatter());
+            h.setSubject("test");
+
+            h.getAuthenticator();
+            h.getMailProperties();
+
+            h.preDestroy();
+            h.close();
+
+            //check for internal exceptions caused by security manager.
+            for (Exception e : em.exceptions) {
+                dump(e);
+            }
+            assertTrue(em.exceptions.isEmpty());
+
+            hardRef = h = new MailHandler();
+            h.close();
+
+            hardRef = h = new MailHandler(100);
+            assertEquals(100, h.getCapacity());
+            h.close();
+
+            Properties props = new Properties();
+            props.put("test", "test");
+            hardRef = h = new MailHandler(props);
+            assertEquals(props, h.getMailProperties());
+        } finally {
+            hardRef = null;
+            manager.secure = false;
+            System.setSecurityManager((SecurityManager) null);
+            if (h != null) {
+                h.close();
+            }
+        }
+    }
+
+    /**
+     * Test logging permissions of the MailHandler. Must run by itself or run in
+     * isolated VM. Use system property java.security.debug=all to troubleshoot
+     * failures.
+     */
+    @Test
+    public void testSecurityManager() {
+        InternalErrorManager em;
+        MailHandler h = null;
+        final ThrowSecurityManager manager = new ThrowSecurityManager();
+        System.setSecurityManager(manager);
+        try {
+            manager.secure = false;
+            h = new MailHandler(createInitProperties(""));
+            em = new InternalErrorManager();
+            h.setErrorManager(em);
+            manager.secure = true;
+            assertEquals(manager, System.getSecurityManager());
+
+            try {
+                assertEquals(0, h.getAttachmentFormatters().length);
+                h.setAttachmentNames(new String[]{"error.txt"});
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                assertEquals(0, h.getAttachmentFormatters().length);
+                h.setAttachmentNames(new Formatter[]{new ThrowFormatter()});
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                assertEquals(0, h.getAttachmentFormatters().length);
+                h.setAttachmentFilters(new Filter[]{new ThrowFilter()});
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setAttachmentFormatters(new Formatter[]{new ThrowFormatter()});
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            manager.secure = false;
+            try {
+                h.setAttachmentFormatters(new Formatter[]{new ThrowFormatter()});
+            } catch (SecurityException fail) {
+                fail("Unexpected secure check.");
+            } catch (Exception fail) {
+                fail(fail.toString());
+            } finally {
+                manager.secure = true;
+            }
+
+            try {
+                assertEquals(1, h.getAttachmentFormatters().length);
+                h.setAttachmentFilters(new Filter[]{new ThrowFilter()});
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                assertEquals(1, h.getAttachmentFormatters().length);
+                h.setAttachmentFilters((Filter[]) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                assertEquals(1, h.getAttachmentFormatters().length);
+                h.setAttachmentNames(new String[]{"error.txt"});
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                assertEquals(1, h.getAttachmentFormatters().length);
+                h.setAttachmentNames((String[]) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                assertEquals(1, h.getAttachmentFormatters().length);
+                h.setAttachmentNames((Formatter[]) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                assertEquals(1, h.getAttachmentFormatters().length);
+                h.setAttachmentNames(new Formatter[]{new ThrowFormatter()});
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            manager.secure = false;
+            try {
+                assertEquals(1, h.getAttachmentFormatters().length);
+                h.setAttachmentFormatters(new Formatter[0]);
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            } finally {
+                manager.secure = true;
+            }
+
+            try {
+                assertEquals(0, h.getAttachmentFormatters().length);
+                assertEquals(0, h.getAttachmentFilters().length);
+                assertEquals(0, h.getAttachmentNames().length);
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setAuthenticator((Authenticator) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setComparator((Comparator<? super LogRecord>) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getComparator();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setLevel(Level.ALL);
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setLevel((Level) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getLevel();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setFilter(BooleanFilter.FALSE);
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setFilter((Filter) null);
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getFilter();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setFormatter(new EmptyFormatter());
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setFormatter((Formatter) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getFormatter();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                assertNotNull(h.getErrorManager());
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setErrorManager(new ErrorManager());
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setErrorManager((ErrorManager) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setEncoding((String) null);
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getEncoding();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.flush();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.push();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setMailProperties(new Properties());
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setMailProperties((Properties) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setPushFilter((Filter) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getPushFilter();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setPushLevel(Level.OFF);
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setPushLevel((Level) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getPushLevel();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setSubject((Formatter) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setSubject((String) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setSubject(new ThrowFormatter());
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.setSubject("test");
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getSubject();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                assertTrue(h.getCapacity() > 0);
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getAuthenticator();
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.getMailProperties();
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.close();
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                h.publish(new LogRecord(Level.SEVERE, ""));
+                h.flush();
+            } catch (SecurityException fail) {
+                fail(fail.toString());
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            //check for internal exceptions caused by security manager.
+            next:
+            for (Exception e : em.exceptions) {
+                for (Throwable t = e; t != null; t = t.getCause()) {
+                    if (t instanceof SecurityException) {
+                        continue next; //expected
+                    } else if (t instanceof RuntimeException) {
+                        throw (RuntimeException) t; //fail
+                    }
+                }
+            }
+            em.exceptions.clear();
+
+            try {
+                hardRef = new MailHandler();
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                hardRef = new MailHandler(100);
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                hardRef = new MailHandler(new Properties());
+                fail("Missing secure check.");
+            } catch (SecurityException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                hardRef = new MailHandler(-100);
+                fail("Missing secure check.");
+            } catch (SecurityException | IllegalArgumentException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+
+            try {
+                hardRef = new MailHandler((Properties) null);
+                fail("Missing secure check.");
+            } catch (SecurityException | NullPointerException pass) {
+            } catch (Exception fail) {
+                fail(fail.toString());
+            }
+        } finally {
+            hardRef = null;
+            manager.secure = false;
+            System.setSecurityManager((SecurityManager) null);
+            if (h != null) {
+                h.close();
+            }
+        }
+    }
+
+    @Test
+    public void testVerifyErrorManager() throws Exception {
+        LogManager manager = LogManager.getLogManager();
+        try {
+            manager.reset();
+
+            final String p = MailHandler.class.getName();
+            Properties props = createInitProperties(p);
+            props.put(p.concat(".encoding"), "us-ascii");
+            props.put(p.concat(".mail.to"), "foo@bar.com");
+            props.put(p.concat(".mail.cc"), "fizz@buzz.com");
+            props.put(p.concat(".mail.bcc"), "baz@bar.com");
+            props.put(p.concat(".subject"), p.concat(" test"));
+            props.put(p.concat(".mail.from"), "localhost@localdomain");
+            props.put(p.concat(".mail.sender"), "mail@handler");
+            props.put(p.concat(".errorManager"), VerifyErrorManager.class.getName());
+            props.put(p.concat(".verify"), "remote");
+
+            read(manager, props);
+
+            MailHandler instance = new MailHandler();
+            InternalErrorManager em = internalErrorManagerFrom(instance);
+
+            //ensure VerifyErrorManager was installed.
+            assertEquals(VerifyErrorManager.class, em.getClass());
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (!isConnectOrTimeout(t)) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+
+            assertFalse(em.exceptions.isEmpty());
+            instance.close();
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testVerifyNoContent() throws Exception {
+        Properties props = createInitProperties("");
+        Session session = Session.getInstance(props);
+        MimeMessage msg = new MimeMessage(session);
+        Address[] from = InternetAddress.parse("me@localhost", false);
+        msg.addFrom(from);
+        msg.setRecipients(Message.RecipientType.TO, from);
+        ByteArrayOutputStream out = new ByteArrayOutputStream(384);
+        msg.setHeader("Content-Transfer-Encoding", "base64");
+        msg.saveChanges();
+        try {
+            msg.writeTo(out);
+            fail("Verify type 'remote' may send a message with no content.");
+        } catch (MessagingException | IOException expect) {
+            msg.setContent("", "text/plain");
+            msg.saveChanges();
+            msg.writeTo(out);
+        } finally {
+            out.close();
+        }
+    }
+
+    @Test
+    public void testIsMissingContent() throws Exception {
+        Properties props = createInitProperties("");
+
+        MailHandler target = new MailHandler(props);
+        Session session = Session.getInstance(props);
+        MimeMessage msg = new MimeMessage(session);
+        Address[] from = InternetAddress.parse("me@localhost", false);
+        msg.addFrom(from);
+        msg.setRecipients(Message.RecipientType.TO, from);
+        msg.setHeader("Content-Transfer-Encoding", "base64");
+        msg.saveChanges();
+        try {
+            msg.writeTo(new ByteArrayOutputStream(384));
+            fail("Verify type 'remote' may hide remote exceptions.");
+        } catch (RuntimeException re) {
+            throw re; //Avoid catch all.
+        } catch (Exception expect) {
+            assertNotNull(expect.getMessage());
+            assertTrue(expect.getMessage().length() != 0);
+            assertTrue(target.isMissingContent(msg, expect));
+            assertTrue(target.isMissingContent(msg, new Exception(expect)));
+            assertTrue(target.isMissingContent(msg, new MessagingException("", expect)));
+            assertFalse(target.isMissingContent(msg, new Exception()));
+            assertFalse(target.isMissingContent(msg, new RuntimeException()));
+        }
+    }
+
+    @Test
+    public void testIntern() throws Exception {
+        assertNull(System.getSecurityManager());
+        final String p = MailHandler.class.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".errorManager"),
+                InternFilterErrorManager.class.getName());
+        props.put(p.concat(".comparator"),
+                InternFilterFormatterComparator.class.getName());
+        props.put(p.concat(".filter"), InternFilter.class.getName());
+        props.put(p.concat(".pushFilter"),
+                InternFilterErrorManager.class.getName());
+
+        props.put(p.concat(".attachment.formatters"),
+                SimpleFormatter.class.getName() + ", "
+                + InternFilterFormatter.class.getName() + ", "
+                + InternFormatter.class.getName() + ", "
+                + XMLFormatter.class.getName() + ", "
+                + InternFormatter.class.getName() + ", "
+                + SimpleFormatter.class.getName() + ", "
+                + SimpleFormatter.class.getName());
+
+        props.put(p.concat(".attachment.filters"),
+                null + ", "
+                + InternFilterFormatter.class.getName() + ", "
+                + InternFilterFormatter.class.getName() + ", "
+                + InternFilter.class.getName() + ", "
+                + InternFilter.class.getName() + ", "
+                + InternBadSubFilter.class.getName() + ", "
+                + InternBadFilter.class.getName());
+
+        final String txt = "Intern test";
+        props.put(p.concat(".attachment.names"),
+                txt + ", "
+                + InternFilterFormatter.class.getName() + ", "
+                + InternFilterFormatter.class.getName() + ", "
+                + txt + ", "
+                + InternFormatter.class.getName() + ", "
+                + InternFilterFormatterComparator.class.getName() + ", "
+                + InternFilterFormatterComparator.class.getName());
+        props.put(p.concat(".subject"), txt);
+
+        MailHandler instance = testIntern(p, props);
+        instance.close();
+
+        Formatter[] formatter = instance.getAttachmentFormatters();
+        Filter[] filter = instance.getAttachmentFilters();
+        Formatter[] names = instance.getAttachmentNames();
+
+        assertSame(instance.getErrorManager(), instance.getPushFilter());
+        assertNull(filter[0]);
+        assertSame(filter[1], filter[2]);
+        assertSame(filter[1], formatter[1]);
+        assertSame(filter[2], formatter[1]);
+        assertSame(names[1], filter[1]);
+        assertSame(names[1], formatter[1]);
+        assertSame(names[2], filter[2]);
+        assertSame(names[2], formatter[1]);
+        assertSame(names[1], filter[2]);
+        assertNotNull(instance.getSubject());
+        assertSame(instance.getSubject(), names[0]);
+        assertSame(instance.getSubject(), names[3]);
+        assertSame(names[0], names[3]);
+        assertNotNull(instance.getFilter());
+        assertSame(instance.getFilter(), filter[3]);
+        assertSame(instance.getFilter(), filter[4]);
+        assertSame(filter[3], filter[4]);
+        assertNotSame(filter[5], filter[6]); //Bad equals method.
+        assertNotNull(instance.getComparator());  //Comparator is not interned.
+        assertNotSame(instance.getComparator(), names[5]);
+        assertNotSame(instance.getComparator(), names[6]);
+        assertSame(names[5], names[6]);
+
+        InternalErrorManager em = internalErrorManagerFrom(instance);
+        for (Exception exception : em.exceptions) {
+            final Throwable t = exception;
+            if (t instanceof IllegalArgumentException
+                    && String.valueOf(t.getMessage()).contains("equal")) {
+                continue;
+            }
+            dump(t);
+        }
+        assertFalse(em.exceptions.isEmpty());
+    }
+
+    private MailHandler testIntern(String p, Properties props) throws Exception {
+        props.put(p.concat(".mail.to"), "badAddress");
+        props.put(p.concat(".mail.cc"), "badAddress");
+        props.put(p.concat(".mail.from"), "badAddress");
+
+        final LogManager manager = LogManager.getLogManager();
+        read(manager, props);
+        try {
+            return new MailHandler();
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testComparatorReverse() throws Exception {
+        final String p = MailHandler.class.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".comparator"), SequenceComparator.class.getName());
+        props.put(p.concat(".comparator.reverse"), "false");
+        props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+
+        LogRecord low = new LogRecord(Level.INFO, "");
+        LogRecord high = new LogRecord(Level.INFO, "");
+        final LogManager manager = LogManager.getLogManager();
+        try {
+            read(manager, props);
+            MailHandler instance = new MailHandler();
+            instance.close();
+            InternalErrorManager em = internalErrorManagerFrom(instance);
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                dump(t);
+                fail(t.toString());
+            }
+            assertTrue(em.exceptions.isEmpty());
+
+            assertEquals(SequenceComparator.class,
+                    instance.getComparator().getClass());
+
+            assertTrue(instance.getComparator().compare(low, high) < 0);
+            assertTrue(instance.getComparator().compare(high, low) > 0);
+
+            props.put(p.concat(".comparator.reverse"), "true");
+            read(manager, props);
+            instance = new MailHandler();
+            instance.close();
+            em = internalErrorManagerFrom(instance);
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                dump(t);
+                fail(t.toString());
+            }
+            assertTrue(em.exceptions.isEmpty());
+
+            Comparator<? super LogRecord> c = instance.getComparator();
+            assertTrue(SequenceComparator.class != c.getClass());
+            assertFalse(instance.getComparator().compare(low, high) < 0);
+            assertFalse(instance.getComparator().compare(high, low) > 0);
+
+            props.put(p.concat(".comparator"),
+                    SequenceComparatorWithReverse.class.getName());
+            read(manager, props);
+            instance = new MailHandler();
+            instance.close();
+            em = internalErrorManagerFrom(instance);
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                dump(t);
+                fail(t.toString());
+            }
+            assertTrue(em.exceptions.isEmpty());
+
+            c = instance.getComparator();
+            assertTrue(SequenceDescComparator.class == c.getClass());
+            assertFalse(instance.getComparator().compare(low, high) < 0);
+            assertFalse(instance.getComparator().compare(high, low) > 0);
+
+            props.put(p.concat(".comparator"), "");
+            read(manager, props);
+            instance = new MailHandler();
+            instance.close();
+            em = internalErrorManagerFrom(instance);
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (t instanceof IllegalArgumentException) {
+                    continue;
+                }
+                dump(t);
+                fail(t.toString());
+            }
+
+            assertFalse(IllegalArgumentException.class.getName(),
+                    em.exceptions.isEmpty());
+
+            props.put(p.concat(".comparator"), "null");
+            read(manager, props);
+            instance = new MailHandler();
+            instance.close();
+            em = internalErrorManagerFrom(instance);
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (t instanceof IllegalArgumentException) {
+                    continue;
+                }
+                dump(t);
+                fail(t.toString());
+            }
+
+            assertFalse(IllegalArgumentException.class.getName(),
+                    em.exceptions.isEmpty());
+
+            props.remove(p.concat(".comparator"));
+            read(manager, props);
+            instance = new MailHandler();
+            instance.close();
+            em = internalErrorManagerFrom(instance);
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (t instanceof IllegalArgumentException) {
+                    continue;
+                }
+                dump(t);
+                fail(t.toString());
+            }
+
+            assertFalse(IllegalArgumentException.class.getName(),
+                    em.exceptions.isEmpty());
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testVerifyLogManager() throws Exception {
+        LogManager manager = LogManager.getLogManager();
+        try {
+            manager.reset();
+
+            final String p = MailHandler.class.getName();
+            Properties props = createInitProperties(p);
+            props.put(p.concat(".subject"), p.concat(" test"));
+            props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+            props.put(p.concat(".verify"), "limited");
+
+            read(manager, props);
+
+            MailHandler instance = new MailHandler();
+            InternalErrorManager em = internalErrorManagerFrom(instance);
+
+            assertEquals(InternalErrorManager.class, em.getClass());
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (t instanceof AddressException == false) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+            assertFalse(em.exceptions.isEmpty());
+
+            instance.close();
+
+            props.put(p.concat(".verify"), "local");
+
+            read(manager, props);
+
+            instance = new MailHandler();
+            em = internalErrorManagerFrom(instance);
+
+            assertEquals(InternalErrorManager.class, em.getClass());
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (t instanceof AddressException == false) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+            assertFalse(em.exceptions.isEmpty());
+
+            instance.close();
+
+            props.put(p.concat(".verify"), "resolve");
+
+            read(manager, props);
+
+            instance = new MailHandler();
+            em = internalErrorManagerFrom(instance);
+
+            assertEquals(InternalErrorManager.class, em.getClass());
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (isConnectOrTimeout(t)) {
+                    continue;
+                }
+                if (t instanceof AddressException == false) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+            assertFalse(em.exceptions.isEmpty());
+
+            instance.close();
+
+            props.put(p.concat(".verify"), "remote");
+            read(manager, props);
+
+            instance = new MailHandler();
+            em = internalErrorManagerFrom(instance);
+
+            assertEquals(InternalErrorManager.class, em.getClass());
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (t instanceof AddressException) {
+                    continue;
+                } else if (isConnectOrTimeout(t)) {
+                    continue;
+                } else {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+            assertFalse(em.exceptions.isEmpty());
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testVerifyProperties() throws Exception {
+        Properties props = createInitProperties("");
+        props.put("subject", "test");
+        props.put("verify", "limited");
+
+        InternalErrorManager em = new InternalErrorManager();
+        MailHandler instance = new MailHandler();
+        try {
+            instance.setErrorManager(em);
+            instance.setMailProperties(props);
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (t instanceof AddressException == false) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+        } finally {
+            instance.close();
+        }
+
+        props.put("verify", "local");
+        instance = new MailHandler();
+        try {
+            em = new InternalErrorManager();
+            instance.setErrorManager(em);
+            instance.setMailProperties(props);
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (t instanceof AddressException == false) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+        } finally {
+            instance.close();
+        }
+
+        props.put("verify", "resolve");
+        instance = new MailHandler();
+        try {
+            em = new InternalErrorManager();
+            instance.setErrorManager(em);
+            instance.setMailProperties(props);
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (isConnectOrTimeout(t)) {
+                    continue;
+                }
+                if (t instanceof AddressException == false) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+        } finally {
+            instance.close();
+        }
+
+        props.put("verify", "login");
+        instance = new MailHandler();
+        try {
+            em = new InternalErrorManager();
+            instance.setErrorManager(em);
+            instance.setMailProperties(props);
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (isConnectOrTimeout(t)) {
+                    continue;
+                }
+                if (t instanceof AddressException == false) {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+        } finally {
+            instance.close();
+        }
+
+        props.put("verify", "remote");
+        instance = new MailHandler();
+        try {
+            em = new InternalErrorManager();
+            instance.setErrorManager(em);
+            instance.setMailProperties(props);
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                if (t instanceof AddressException) {
+                    continue;
+                } else if (isConnectOrTimeout(t)) {
+                    continue;
+                } else {
+                    dump(t);
+                    fail(t.toString());
+                }
+            }
+        } finally {
+            instance.close();
+        }
+    }
+
+    @Test
+    public void testVerifyPropertiesConstructor() throws Exception {
+        LogManager manager = LogManager.getLogManager();
+        try {
+            manager.reset();
+
+            final String p = MailHandler.class.getName();
+            Properties props = createInitProperties(p);
+            props.put(p.concat(".subject"), p.concat(" test"));
+            props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+            props.put(p.concat(".formatter"), XMLFormatter.class.getName());
+            read(manager, props);
+
+            props = createInitProperties("");
+            props.put("subject", "test");
+            props.put("verify", "limited");
+
+            MailHandler instance = new MailHandler(props);
+            try {
+                InternalErrorManager em = internalErrorManagerFrom(instance);
+
+                for (Exception exception : em.exceptions) {
+                    final Throwable t = exception;
+                    if (t instanceof AddressException == false) {
+                        dump(t);
+                        fail(t.toString());
+                    }
+                }
+                assertFalse(em.exceptions.isEmpty());
+            } finally {
+                instance.close();
+            }
+
+            props = createInitProperties("");
+            props.put("mail.to", "badAddress");
+            props.put("mail.cc", "badAddress");
+            props.put("subject", "test");
+            props.put("mail.from", "badAddress");
+            props.put("verify", "local");
+
+            instance = new MailHandler(props);
+            try {
+                InternalErrorManager em = internalErrorManagerFrom(instance);
+
+                for (Exception exception : em.exceptions) {
+                    final Throwable t = exception;
+                    if (t instanceof AddressException == false) {
+                        dump(t);
+                        fail(t.toString());
+                    }
+                }
+                assertFalse(em.exceptions.isEmpty());
+            } finally {
+                instance.close();
+            }
+
+            props.put("verify", "resolve");
+
+            instance = new MailHandler(props);
+            try {
+                InternalErrorManager em = internalErrorManagerFrom(instance);
+
+                for (Exception exception : em.exceptions) {
+                    final Throwable t = exception;
+                    if (isConnectOrTimeout(t)) {
+                        continue;
+                    }
+                    if (t instanceof AddressException == false) {
+                        dump(t);
+                        fail(t.toString());
+                    }
+                }
+                assertFalse(em.exceptions.isEmpty());
+            } finally {
+                instance.close();
+            }
+
+            props.put("verify", "remote");
+            instance = new MailHandler(props);
+            try {
+                InternalErrorManager em = internalErrorManagerFrom(instance);
+
+                for (Exception exception : em.exceptions) {
+                    final Throwable t = exception;
+                    if (t instanceof AddressException) {
+                        continue;
+                    } else if (isConnectOrTimeout(t)) {
+                        continue;
+                    } else {
+                        dump(t);
+                        fail(t.toString());
+                    }
+                }
+                assertFalse(em.exceptions.isEmpty());
+            } finally {
+                instance.close();
+            }
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testNoVerifyReplacedProperties() throws Exception {
+        LogManager manager = LogManager.getLogManager();
+        try {
+            manager.reset();
+
+            final String p = MailHandler.class.getName();
+            Properties props = createInitProperties(p);
+            props.put(p.concat(".subject"), p.concat(" test"));
+            props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+            props.put(p.concat(".verify"), "remote");
+
+            read(manager, props);
+
+            //Use empty properties to prove fallback to LogManager.
+            MailHandler instance = new MailHandler(new Properties());
+            InternalErrorManager em = internalErrorManagerFrom(instance);
+            assertEquals(InternalErrorManager.class, em.getClass());
+            instance.close();
+
+            for (Exception exception : em.exceptions) {
+                final Throwable t = exception;
+                dump(t);
+                fail(t.toString());
+            }
+            assertTrue(em.exceptions.isEmpty());
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testInitSubject() throws Exception {
+        InternalErrorManager em;
+        MailHandler target;
+        final String p = MailHandler.class.getName();
+        final LogManager manager = LogManager.getLogManager();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+
+        //test class cast.
+        props.put(p.concat(".subject"), Properties.class.getName());
+
+        read(manager, props);
+
+        try {
+            target = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(target);
+                for (Exception exception : em.exceptions) {
+                    dump(exception);
+                }
+                assertTrue(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+
+            //test linkage error.
+            props.put(p.concat(".subject"), ThrowFormatter.class.getName().toUpperCase(Locale.US));
+            read(manager, props);
+
+            target = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(target);
+                for (Exception exception : em.exceptions) {
+                    dump(exception);
+                }
+                assertTrue(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+
+            //test mixed linkage error.
+            props.put(p.concat(".subject"), Properties.class.getName().toUpperCase(Locale.US));
+            read(manager, props);
+
+            target = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(target);
+                for (Exception exception : em.exceptions) {
+                    dump(exception);
+                }
+                assertTrue(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testInitAuthenticator() throws Exception {
+        //Liternal null and null reference mean no Authenticator
+        //when dealing with a properties file.
+        testInitAuthenticator("user", null);
+        testInitAuthenticator("user", "null");
+        testInitAuthenticator("user", "NULL");
+        testInitAuthenticator("user", "Null");
+        testInitAuthenticator("user", "");
+        testInitAuthenticator("user", "somepassword");
+    }
+
+    private void testInitAuthenticator(String user, String pass) throws Exception {
+        InternalErrorManager em;
+        MailHandler target;
+        final String p = MailHandler.class.getName();
+        final LogManager manager = LogManager.getLogManager();
+        final Properties props = createInitProperties(p);
+        props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+        if (pass != null) {
+            props.put(p.concat(".authenticator"), pass);
+        }
+        props.put(p.concat(".mail.transport.protocol"), "smtp");
+
+        read(manager, props);
+        try {
+            target = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(target);
+                for (Exception exception : em.exceptions) {
+                    dump(exception);
+                }
+                assertTrue(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+
+            if (pass != null && !"null".equalsIgnoreCase(pass)) {
+                assertNotNull(target.getAuthenticator());
+                PasswordAuthentication initPa = passwordAuthentication(
+                        target.getAuthenticator(), user);
+                assertEquals(user, initPa.getUserName());
+                assertEquals(pass, initPa.getPassword());
+
+                target.setAuthenticator(pass.toCharArray());
+                PasswordAuthentication setPa = passwordAuthentication(
+                        target.getAuthenticator(), user);
+                assertEquals(setPa.getUserName(), initPa.getUserName());
+                assertEquals(setPa.getPassword(), initPa.getPassword());
+            } else {
+                assertNull(target.getAuthenticator());
+                target.setAuthenticator((char[]) null);
+                assertNull(target.getAuthenticator());
+            }
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testInitAttachmentFilters() throws Exception {
+        InternalErrorManager em;
+        MailHandler target;
+        final String p = MailHandler.class.getName();
+        final LogManager manager = LogManager.getLogManager();
+        final Properties props = createInitProperties(p);
+        props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+        props.put(p.concat(".filter"), ErrorFilter.class.getName());
+        props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
+        props.put(p.concat(".attachment.names"), Properties.class.getName());
+        assertNull(props.getProperty(p.concat(".attachment.filters")));
+
+        read(manager, props);
+        try {
+            target = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(target);
+                for (Exception exception : em.exceptions) {
+                    dump(exception);
+                }
+                assertTrue(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+        } finally {
+            manager.reset();
+        }
+
+        assertEquals(ErrorFilter.class, target.getFilter().getClass());
+        assertEquals(target.getFilter(), target.getAttachmentFilters()[0]);
+
+        props.put(p.concat(".attachment.formatters"),
+                SimpleFormatter.class.getName() + ", "
+                + SimpleFormatter.class.getName() + ", "
+                + SimpleFormatter.class.getName() + ", "
+                + SimpleFormatter.class.getName());
+        props.put(p.concat(".attachment.names"), "a.txt, b.txt, c.txt, d.txt");
+        props.put(p.concat(".attachment.filters"), "null, "
+                + ThrowFilter.class.getName());
+
+        read(manager, props);
+        try {
+            target = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(target);
+                for (Exception exception : em.exceptions) {
+                    final Throwable t = exception;
+                    if (t instanceof IndexOutOfBoundsException) {
+                        continue;
+                    }
+                    dump(t);
+                }
+                assertFalse(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+        } finally {
+            manager.reset();
+        }
+
+        assertEquals(ErrorFilter.class, target.getFilter().getClass());
+        assertNull(target.getAttachmentFilters()[0]);
+        assertEquals(ThrowFilter.class,
+                target.getAttachmentFilters()[1].getClass());
+        assertEquals(target.getFilter(), target.getAttachmentFilters()[2]);
+        assertEquals(target.getFilter(), target.getAttachmentFilters()[3]);
+    }
+
+    @Test
+    public void testInitAttachmentNames() throws Exception {
+        InternalErrorManager em;
+        MailHandler target;
+        final String p = MailHandler.class.getName();
+        final LogManager manager = LogManager.getLogManager();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+
+        //test class cast.
+        props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
+        props.put(p.concat(".attachment.names"), Properties.class.getName());
+
+        read(manager, props);
+
+        try {
+            target = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(target);
+                for (Exception exception : em.exceptions) {
+                    dump(exception);
+                }
+                assertTrue(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+
+            //test linkage error.
+            props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
+            props.put(p.concat(".attachment.names"), SimpleFormatter.class.getName().toUpperCase(Locale.US));
+
+            read(manager, props);
+
+            target = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(target);
+                for (Exception exception : em.exceptions) {
+                    dump(exception);
+                }
+                assertTrue(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+
+            //test mixed linkage error.
+            props.put(p.concat(".attachment.formatters"), SimpleFormatter.class.getName());
+            props.put(p.concat(".attachment.names"), Properties.class.getName().toUpperCase(Locale.US));
+            read(manager, props);
+
+            target = new MailHandler();
+            try {
+                em = internalErrorManagerFrom(target);
+                for (Exception exception : em.exceptions) {
+                    dump(exception);
+                }
+                assertTrue(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testInitErrorManagerException() throws Exception {
+        final String encoding = System.getProperty("file.encoding", "8859_1");
+        final String p = MailHandler.class.getName();
+        final Properties props = createInitProperties(p);
+        String key;
+
+        setPending(new RuntimeException());
+        try {
+            key = p.concat(".errorManager");
+            props.put(key, InitErrorManager.class.getName());
+
+            final LogManager manager = LogManager.getLogManager();
+            try {
+                read(manager, props);
+                ByteArrayOutputStream oldErrors = new ByteArrayOutputStream();
+                PrintStream newErr = new PrintStream(oldErrors, false, encoding);
+                @SuppressWarnings("UseOfSystemOutOrSystemErr")
+                final PrintStream err = System.err;
+                System.setErr(newErr);
+                try {
+                    final MailHandler target = new MailHandler();
+                    try {
+                        System.setErr(err);
+                        target.setErrorManager(new ErrorManager());
+                    } finally {
+                        target.close();
+                    }
+                } finally {
+                    System.setErr(err);
+                }
+
+                //java.util.logging.ErrorManager: 4
+                //java.lang.reflect.InvocationTargetException
+                // at...
+                //Caused by: java.lang.RuntimeException
+                final String data = oldErrors.toString(encoding);
+                assertTrue(data, data.contains(ErrorManager.class.getName()));
+                int ite, re;
+                ite = data.indexOf(InvocationTargetException.class.getName());
+                re = data.indexOf(RuntimeException.class.getName(), ite);
+                assertTrue(data, ite < re);
+            } finally {
+                manager.reset();
+            }
+        } finally {
+            setPending((Throwable) null);
+        }
+    }
+
+    @Test
+    public void testInitErrorManagerError() throws Exception {
+        final String encoding = System.getProperty("file.encoding", "8859_1");
+        final String p = MailHandler.class.getName();
+        final Properties props = createInitProperties(p);
+        String key;
+
+        setPending(new Error());
+        try {
+            key = p.concat(".errorManager");
+            props.put(key, InitErrorManager.class.getName());
+
+            final LogManager manager = LogManager.getLogManager();
+            try {
+                read(manager, props);
+                ByteArrayOutputStream oldErrors = new ByteArrayOutputStream();
+                PrintStream newErr = new PrintStream(oldErrors, false, encoding);
+                @SuppressWarnings("UseOfSystemOutOrSystemErr")
+                final PrintStream err = System.err;
+                System.setErr(newErr);
+                try {
+                    final MailHandler target = new MailHandler();
+                    try {
+                        System.setErr(err);
+                        target.setErrorManager(new ErrorManager());
+                    } finally {
+                        target.close();
+                    }
+                } finally {
+                    System.setErr(err);
+                }
+
+                //java.util.logging.ErrorManager: 4
+                //java.lang.reflect.InvocationTargetException
+                // at...
+                //Caused by: java.lang.Error
+                final String data = oldErrors.toString(encoding);
+                assertTrue(data, data.contains(ErrorManager.class.getName()));
+                int ite, re;
+                ite = data.indexOf(InvocationTargetException.class.getName());
+                re = data.indexOf(Error.class.getName(), ite);
+                assertTrue(data, ite < re);
+            } finally {
+                manager.reset();
+            }
+        } finally {
+            setPending((Throwable) null);
+        }
+    }
+
+    @Test
+    public void testInitError() throws Exception {
+        final String p = MailHandler.class.getName();
+        final Properties props = createInitProperties(p);
+        String filter;
+        String name;
+        String key;
+
+        setPending(new Error());
+        try {
+            key = p.concat(".authenticator");
+            props.put(key, InitAuthenticator.class.getName());
+            testInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".comparator");
+            props.put(key, InitComparator.class.getName());
+            testInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".filter");
+            props.put(key, InitFilter.class.getName());
+            testInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".formatter");
+            props.put(key, InitFormatter.class.getName());
+            testInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".subject");
+            props.put(key, InitFormatter.class.getName());
+            testInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, InitFormatter.class.getName());
+            testInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, ThrowFormatter.class.getName());
+            filter = p.concat(".attachment.filters");
+            props.put(filter, InitFilter.class.getName());
+            name = p.concat(".attachment.names");
+            props.put(name, "test.txt");
+            testInitError(props);
+            assertNotNull(props.remove(key));
+            assertNotNull(props.remove(name));
+            assertNotNull(props.remove(filter));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, ThrowFormatter.class.getName());
+            name = p.concat(".attachment.names");
+            props.put(name, InitFormatter.class.getName());
+            testInitError(props);
+            assertNotNull(props.remove(key));
+            assertNotNull(props.remove(name));
+        } finally {
+            setPending((Throwable) null);
+        }
+    }
+
+    private void testInitError(Properties props) throws Exception {
+        testInitException(props);
+    }
+
+    @Test
+    public void testInitException() throws Exception {
+        final String p = MailHandler.class.getName();
+        final Properties props = createInitProperties(p);
+        String filter;
+        String name;
+        String key;
+
+        setPending(new RuntimeException());
+        try {
+            key = p.concat(".authenticator");
+            props.put(key, InitAuthenticator.class.getName());
+            testInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".comparator");
+            props.put(key, InitComparator.class.getName());
+            testInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".filter");
+            props.put(key, InitFilter.class.getName());
+            testInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".formatter");
+            props.put(key, InitFormatter.class.getName());
+            testInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".subject");
+            props.put(key, InitFormatter.class.getName());
+            testInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, InitFormatter.class.getName());
+            testInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, ThrowFormatter.class.getName());
+            filter = p.concat(".attachment.filters");
+            props.put(filter, InitFilter.class.getName());
+            name = p.concat(".attachment.names");
+            props.put(name, "test.txt");
+            testInitException(props);
+            assertNotNull(props.remove(key));
+            assertNotNull(props.remove(name));
+            assertNotNull(props.remove(filter));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, ThrowFormatter.class.getName());
+            name = p.concat(".attachment.names");
+            props.put(name, InitFormatter.class.getName());
+            testInitException(props);
+            assertNotNull(props.remove(key));
+            assertNotNull(props.remove(name));
+        } finally {
+            setPending((Throwable) null);
+        }
+    }
+
+    private void testInitException(Properties props) throws Exception {
+        final LogManager manager = LogManager.getLogManager();
+        try {
+            read(manager, props);
+            final MailHandler target = new MailHandler();
+            try {
+                InternalErrorManager em = internalErrorManagerFrom(target);
+                next:
+                for (Exception t : em.exceptions) {
+                    for (Throwable cause = t; cause != null; cause = cause.getCause()) {
+                        if (cause == getPending()) {
+                            continue next;
+                        }
+                    }
+                    dump(t);
+                    fail(t.toString());
+                }
+                assertFalse(em.exceptions.isEmpty());
+            } finally {
+                target.close();
+            }
+        } finally {
+            manager.reset();
+        }
+    }
+
+    @Test
+    public void testStaticInitErrorManagerException() throws Exception {
+        final String encoding = System.getProperty("file.encoding", "8859_1");
+        final String test = MailHandlerTest.class.getName();
+        final String p = MailHandler.class.getName();
+        final Properties props = createInitProperties(p);
+        String key;
+
+        setPending(new RuntimeException());
+        try {
+            key = p.concat(".errorManager");
+            props.put(key, test.concat("$StaticInitReErrorManager"));
+
+            final LogManager manager = LogManager.getLogManager();
+            try {
+                read(manager, props);
+                ByteArrayOutputStream oldErrors = new ByteArrayOutputStream();
+                PrintStream newErr = new PrintStream(oldErrors, false, encoding);
+                @SuppressWarnings("UseOfSystemOutOrSystemErr")
+                final PrintStream err = System.err;
+                System.setErr(newErr);
+                try {
+                    final MailHandler target = new MailHandler();
+                    try {
+                        System.setErr(err);
+                        target.setErrorManager(new ErrorManager());
+                    } finally {
+                        target.close();
+                    }
+                } finally {
+                    System.setErr(err);
+                }
+
+                //java.util.logging.ErrorManager: 4
+                //java.lang.reflect.InvocationTargetException
+                // at ....
+                //Caused by: java.lang.ExceptionInInitializerError
+                // at...
+                //Caused by: java.lang.RuntimeException
+                final String data = oldErrors.toString(encoding);
+                assertTrue(data, data.contains(ErrorManager.class.getName()));
+                int ite, eiie, re;
+                ite = data.indexOf(InvocationTargetException.class.getName());
+                eiie = data.indexOf(ExceptionInInitializerError.class.getName(), ite);
+                if (eiie < 0) {
+                    re = data.indexOf(RuntimeException.class.getName(), ite);
+                    assertTrue(data, ite < re);
+                } else {
+                    re = data.indexOf(RuntimeException.class.getName(), eiie);
+                    assertTrue(data, ite < eiie);
+                    assertTrue(data, eiie < re);
+                }
+            } finally {
+                manager.reset();
+            }
+            assertNotNull(props.remove(key));
+        } finally {
+            setPending((Throwable) null);
+        }
+    }
+
+    @Test
+    public void testStaticInitException() throws Exception {
+        final String test = MailHandlerTest.class.getName();
+        final String p = MailHandler.class.getName();
+        final Properties props = createInitProperties(p);
+        String filter;
+        String name;
+        String key;
+
+        setPending(new RuntimeException());
+        try {
+            key = p.concat(".authenticator");
+            props.put(key, test.concat("$StaticInitReAuthenticator"));
+            testStaticInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".comparator");
+            props.put(key, test.concat("$StaticInitReComparator"));
+            testStaticInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".filter");
+            props.put(key, test.concat("$StaticInitReFilter"));
+            testStaticInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".formatter");
+            props.put(key, test.concat("$StaticInitReFormatter"));
+            testStaticInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".subject");
+            props.put(key, test.concat("$StaticInitReSubjectFormatter"));
+            testStaticInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, test.concat("$StaticInitReAttachFormatter"));
+            testStaticInitException(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, ThrowFormatter.class.getName());
+            filter = p.concat(".attachment.filters");
+            props.put(filter, test.concat("$StaticInitReAttachFilter"));
+            name = p.concat(".attachment.names");
+            props.put(name, "test.txt");
+            testStaticInitException(props);
+            assertNotNull(props.remove(key));
+            assertNotNull(props.remove(name));
+            assertNotNull(props.remove(filter));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, ThrowFormatter.class.getName());
+            name = p.concat(".attachment.names");
+            props.put(name, test.concat("$StaticInitReNameFormatter"));
+            testStaticInitException(props);
+            assertNotNull(props.remove(key));
+            assertNotNull(props.remove(name));
+        } finally {
+            setPending((Throwable) null);
+        }
+    }
+
+    private void testStaticInitException(Properties props) throws Exception {
+        testInitException(props);
+    }
+
+    @Test
+    public void testStaticInitError() throws Exception {
+        final String test = MailHandlerTest.class.getName();
+        final String p = MailHandler.class.getName();
+        final Properties props = createInitProperties(p);
+        String filter;
+        String name;
+        String key;
+
+        setPending(new Error());
+        try {
+            key = p.concat(".authenticator");
+            props.put(key, test.concat("$StaticInitErAuthenticator"));
+            testStaticInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".comparator");
+            props.put(key, test.concat("$StaticInitErComparator"));
+            testStaticInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".errorManager");
+            props.put(key, test.concat("$StaticInitErErrorManager"));
+            testStaticInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".filter");
+            props.put(key, test.concat("$StaticInitErFilter"));
+            testStaticInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".formatter");
+            props.put(key, test.concat("$StaticInitErFormatter"));
+            testStaticInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".subject");
+            props.put(key, test.concat("$StaticInitErSubjectFormatter"));
+            testStaticInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, test.concat("$StaticInitErAttachFormatter"));
+            testStaticInitError(props);
+            assertNotNull(props.remove(key));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, ThrowFormatter.class.getName());
+            filter = p.concat(".attachment.filters");
+            props.put(filter, test.concat("$StaticInitErAttachFilter"));
+            name = p.concat(".attachment.names");
+            props.put(name, "test.txt");
+            testStaticInitError(props);
+            assertNotNull(props.remove(key));
+            assertNotNull(props.remove(name));
+            assertNotNull(props.remove(filter));
+
+            key = p.concat(".attachment.formatters");
+            props.put(key, ThrowFormatter.class.getName());
+            name = p.concat(".attachment.names");
+            props.put(name, test.concat("$StaticInitErNameFormatter"));
+            testStaticInitError(props);
+            assertNotNull(props.remove(key));
+            assertNotNull(props.remove(name));
+        } finally {
+            setPending((Throwable) null);
+        }
+    }
+
+    @SuppressWarnings("ThrowableInitCause")
+    private void testStaticInitError(Properties props) throws Exception {
+        final LogManager manager = LogManager.getLogManager();
+        try {
+            read(manager, props);
+            MailHandler target = null;
+            try {
+                target = new MailHandler();
+                AssertionError AE = new AssertionError(props.toString());
+                AE.initCause(getPending());
+                throw AE;
+            } catch (AssertionError e) {
+                throw e; //avoid catch all.
+            } catch (Error expect) {
+                assertEquals(Error.class, expect.getClass());
+            } finally {
+                if (target != null) {
+                    target.close();
+                }
+            }
+        } finally {
+            manager.reset();
+        }
+    }
+
+    static Properties createInitProperties(String p) {
+        final Properties props = new Properties();
+        if (p.length() != 0) {
+            p = p.concat(".");
+        }
+        props.put(p.concat("mail.host"), UNKNOWN_HOST);
+        props.put(p.concat("mail.smtp.host"), UNKNOWN_HOST);
+        props.put(p.concat("mail.smtp.port"), Integer.toString(OPEN_PORT));
+        props.put(p.concat("mail.to"), "");
+        props.put(p.concat("mail.cc"), "badAddress");
+        props.put(p.concat("mail.from"), "");
+        props.put(p.concat("mail.smtp.connectiontimeout"), "1");
+        props.put(p.concat("mail.smtp.timeout"), "1");
+        props.put(p.concat("errorManager"), InternalErrorManager.class.getName());
+        return props;
+    }
+
+    @Test
+    public void testInitFromLogManager() throws Exception {
+        final LogManager manager = LogManager.getLogManager();
+        synchronized (manager) {
+            try {
+                initGoodTest(MailHandler.class, new Class<?>[0], new Object[0]);
+                initBadTest(MailHandler.class, new Class<?>[0], new Object[0]);
+                initGoodTest(MailHandler.class,
+                        new Class<?>[]{Integer.TYPE}, new Object[]{10});
+                initBadTest(MailHandler.class,
+                        new Class<?>[]{Integer.TYPE}, new Object[]{100});
+                initGoodTest(MailHandler.class,
+                        new Class<?>[]{Properties.class},
+                        new Object[]{new Properties()});
+                initBadTest(MailHandler.class,
+                        new Class<?>[]{Properties.class},
+                        new Object[]{new Properties()});
+
+                //Test subclass properties.
+                initGoodTest(MailHandlerExt.class,
+                        new Class<?>[0], new Object[0]);
+                initBadTest(MailHandlerExt.class,
+                        new Class<?>[0], new Object[0]);
+
+                initGoodTest(MailHandlerExt.class,
+                        new Class<?>[]{Integer.TYPE}, new Object[]{10});
+                initBadTest(MailHandlerExt.class,
+                        new Class<?>[]{Integer.TYPE}, new Object[]{100});
+
+                initGoodTest(MailHandlerExt.class,
+                        new Class<?>[]{Properties.class},
+                        new Object[]{new Properties()});
+                initBadTest(MailHandlerExt.class,
+                        new Class<?>[]{Properties.class},
+                        new Object[]{new Properties()});
+            } finally {
+                manager.reset();
+            }
+        }
+    }
+
+    private static String freeTextSubject() {
+        String name = "Mail Handler test subject";
+        try {
+            Class.forName(name); //ensure this can't be loaded.
+            fail("Invalid subject: " + name);
+        } catch (AssertionError fail) {
+            throw fail;
+        } catch (Throwable expected) {
+        }
+        return name;
+    }
+
+    private void initGoodTest(Class<? extends MailHandler> type,
+            Class<?>[] types, Object[] params) throws Exception {
+
+        final String p = type.getName();
+        Properties props = createInitProperties(p);
+        props.put(p.concat(".errorManager"), InternalErrorManager.class.getName());
+        props.put(p.concat(".capacity"), "10");
+        props.put(p.concat(".level"), "ALL");
+        props.put(p.concat(".formatter"), XMLFormatter.class.getName());
+        props.put(p.concat(".filter"), ThrowFilter.class.getName());
+        props.put(p.concat(".authenticator"), EmptyAuthenticator.class.getName());
+        props.put(p.concat(".pushLevel"), "WARNING");
+        props.put(p.concat(".pushFilter"), ThrowFilter.class.getName());
+        props.put(p.concat(".comparator"), ThrowComparator.class.getName());
+        props.put(p.concat(".encoding"), "UTF-8");
+        props.put(p.concat(".subject"), EmptyFormatter.class.getName());
+
+        props.put(p.concat(".attachment.filters"),
+                "null, " + ThrowFilter.class.getName() + ", "
+                + ThrowFilter.class.getName());
+
+        props.put(p.concat(".attachment.formatters"),
+                SimpleFormatter.class.getName() + ", "
+                + XMLFormatter.class.getName() + ", "
+                + SimpleFormatter.class.getName());
+
+        props.put(p.concat(".attachment.names"), "msg.txt, "
+                + SimpleFormatter.class.getName() + ", error.txt");
+
+        read(LogManager.getLogManager(), props);
+
+        MailHandler h = type.getConstructor(types).newInstance(params);
+        assertEquals(10, h.getCapacity());
+        assertEquals(Level.ALL, h.getLevel());
+        assertEquals(ThrowFilter.class, h.getFilter().getClass());
+        assertEquals(XMLFormatter.class, h.getFormatter().getClass());
+        assertEquals(Level.WARNING, h.getPushLevel());
+        assertEquals(ThrowFilter.class, h.getPushFilter().getClass());
+        assertEquals(ThrowComparator.class, h.getComparator().getClass());
+        assertEquals("UTF-8", h.getEncoding());
+        assertEquals(EmptyFormatter.class, h.getSubject().getClass());
+        assertEquals(EmptyAuthenticator.class, h.getAuthenticator().getClass());
+        assertEquals(3, h.getAttachmentFormatters().length);
+        assertTrue(null != h.getAttachmentFormatters()[0]);
+        assertTrue(null != h.getAttachmentFormatters()[1]);
+        assertTrue(null != h.getAttachmentFormatters()[2]);
+        assertEquals(3, h.getAttachmentFilters().length);
+        assertEquals(null, h.getAttachmentFilters()[0]);
+        assertEquals(ThrowFilter.class, h.getAttachmentFilters()[1].getClass());
+        assertEquals(ThrowFilter.class, h.getAttachmentFilters()[2].getClass());
+        assertEquals(3, h.getAttachmentNames().length);
+        assertTrue(null != h.getAttachmentNames()[0]);
+        assertTrue(null != h.getAttachmentNames()[1]);
+        assertTrue(null != h.getAttachmentNames()[2]);
+
+        InternalErrorManager em = internalErrorManagerFrom(h);
+        for (Exception exception : em.exceptions) {
+            fail(String.valueOf(exception));
+        }
+        assertTrue(em.exceptions.isEmpty());
+
+        h.setComparator(null);
+        h.close();
+        assertEquals(em.exceptions.isEmpty(), true);
+
+        props.put(p.concat(".subject"), freeTextSubject());
+
+        read(LogManager.getLogManager(), props);
+
+        h = type.getConstructor(types).newInstance(params);
+        em = internalErrorManagerFrom(h);
+        assertTrue(em.exceptions.isEmpty());
+        assertEquals(freeTextSubject(), h.getSubject().toString());
+
+        props.remove(p.concat(".attachment.filters"));
+        read(LogManager.getLogManager(), props);
+
+        h = type.getConstructor(types).newInstance(params);
+        em = internalErrorManagerFrom(h);
+        assertTrue(em.exceptions.isEmpty());
+        assertEquals(3, h.getAttachmentFormatters().length);
+        h.close();
+
+        props.remove(p.concat(".attachment.names"));
+        read(LogManager.getLogManager(), props);
+
+        h = type.getConstructor(types).newInstance(params);
+        em = internalErrorManagerFrom(h);
+        assertTrue(em.exceptions.isEmpty());
+        assertEquals(h.getAttachmentFormatters().length, 3);
+        h.close();
+    }
+
+    private PasswordAuthentication passwordAuthentication(javax.mail.Authenticator auth, String user) {
+        final Session s = Session.getInstance(new Properties(), auth);
+        return s.requestPasswordAuthentication(null, 25, "SMTP", "", user);
+    }
+
+    @SuppressWarnings("UseOfSystemOutOrSystemErr")
+    private void initBadTest(Class<? extends MailHandler> type,
+            Class<?>[] types, Object[] params) throws Exception {
+        final String encoding = System.getProperty("file.encoding", "8859_1");
+        final PrintStream err = System.err;
+        ByteArrayOutputStream oldErrors = new ByteArrayOutputStream();
+
+        final String p = type.getName();
+        Properties props = createInitProperties(p);
+
+        props.put(p.concat(".errorManager"), "InvalidErrorManager");
+        props.put(p.concat(".capacity"), "-10");
+        props.put(p.concat(".level"), "BAD");
+        props.put(p.concat(".formatter"), "InvalidFormatter");
+        props.put(p.concat(".filter"), "InvalidFilter");
+        props.put(p.concat(".authenticator"), "password");
+        props.put(p.concat(".pushLevel"), "PUSHBAD");
+        props.put(p.concat(".pushFilter"), "InvalidPushFilter");
+        props.put(p.concat(".comparator"), "InvalidComparator");
+        props.put(p.concat(".encoding"), "MailHandler-ENC");
+        props.put(p.concat(".subject"), ThrowFilter.class.getName());
+        props.put(p.concat(".attachment.filters"), "null, "
+                + "InvalidAttachFilter1, " + ThrowFilter.class.getName());
+
+        props.put(p.concat(".attachment.formatters"),
+                "InvalidAttachFormatter0, "
+                + ThrowComparator.class.getName() + ", "
+                + XMLFormatter.class.getName());
+
+        props.put(p.concat(".attachment.names"), "msg.txt, "
+                + ThrowComparator.class.getName() + ", "
+                + XMLFormatter.class.getName());
+
+        MailHandler h = null;
+        oldErrors.reset();
+        System.setErr(new PrintStream(oldErrors, false, encoding));
+        try {
+            /**
+             * Bad level value for property:
+             * com.sun.mail.util.logging.MailHandler.level The
+             * LogManager.setLevelsOnExistingLoggers triggers an error. This
+             * code swallows that error message.
+             */
+            read(LogManager.getLogManager(), props);
+            System.err.print(""); //flushBuffer.
+            System.err.flush();
+            String result = oldErrors.toString(encoding).trim();
+            oldErrors.reset();
+            if (result.length() > 0) {
+                final String expect = "Bad level value for property: " + p + ".level";
+                //if (result.length() > expect.length()) {
+                //    result = result.substring(0, expect.length());
+                //}
+                assertEquals(expect, result);
+            }
+
+            /**
+             * The default error manager writes to System.err. Since this test
+             * is trying to install an invalid ErrorManager we can only capture
+             * the error by capturing System.err.
+             */
+            h = type.getConstructor(types).newInstance(params);
+            System.err.flush();
+            result = oldErrors.toString(encoding).trim();
+            int index = result.indexOf(ErrorManager.class.getName() + ": "
+                    + ErrorManager.OPEN_FAILURE + ": " + Level.SEVERE.getName()
+                    + ": InvalidErrorManager");
+            assertTrue(index > -1);
+            assertTrue(result.indexOf(
+                    "java.lang.ClassNotFoundException: InvalidErrorManager") > index);
+            oldErrors.reset();
+        } finally {
+            System.setErr(err);
+        }
+
+        assert h != null;
+        assertEquals(ErrorManager.class, h.getErrorManager().getClass());
+        assertTrue(h.getCapacity() != 10);
+        assertTrue(h.getCapacity() != -10);
+        assertEquals(Level.WARNING, h.getLevel());
+        assertEquals(null, h.getFilter());
+        assertEquals(SimpleFormatter.class, h.getFormatter().getClass());
+        assertEquals(Level.OFF, h.getPushLevel());
+        assertEquals(null, h.getPushFilter());
+        assertNull(h.getComparator());
+        assertEquals(null, h.getEncoding());
+        assertEquals(ThrowFilter.class.getName(), h.getSubject().toString());
+        PasswordAuthentication pa = passwordAuthentication(h.getAuthenticator(), "user");
+        assertEquals("user", pa.getUserName());
+        assertEquals("password", pa.getPassword());
+        assertEquals(3, h.getAttachmentFormatters().length);
+        assertTrue(null != h.getAttachmentFormatters()[0]);
+        assertTrue(null != h.getAttachmentFormatters()[1]);
+        assertTrue(null != h.getAttachmentFormatters()[2]);
+        assertEquals(3, h.getAttachmentFilters().length);
+        assertTrue(null == h.getAttachmentFilters()[0]);
+        assertTrue(null == h.getAttachmentFilters()[1]);
+        assertTrue(null != h.getAttachmentFilters()[2]);
+        assertEquals(ThrowFilter.class, h.getAttachmentFilters()[2].getClass());
+        assertEquals(3, h.getAttachmentNames().length);
+        assertTrue(null != h.getAttachmentNames()[0]);
+        assertTrue(null != h.getAttachmentNames()[1]);
+        assertTrue(null != h.getAttachmentNames()[2]);
+        assertEquals(XMLFormatter.class, h.getAttachmentNames()[2].getClass());
+        h.close();
+    }
+
+    private static boolean isConnectOrTimeout(Throwable t) {
+        if (t instanceof MessagingException) {
+            Throwable cause = t.getCause();
+            if (cause == null) { //GNU JavaMail doesn't support 1.4 chaining.
+               cause = ((MessagingException) t).getNextException();
+            }
+            return isConnectOrTimeout(cause);
+        } else if (isInstanceof(t, "com.sun.mail.util.SocketConnectException")) {
+            return isConnectOrTimeout(t.getCause());
+        } else {
+            return t instanceof java.net.ConnectException
+                    || t instanceof java.net.UnknownHostException
+                    || t instanceof java.net.SocketTimeoutException;
+        }
+    }
+
+    private static boolean isInstanceof(Object o, String s) {
+        if (s == null) {
+           throw new NullPointerException();
+        }
+
+        if (o != null) {
+            for (Class<?> k = o.getClass(); k != null; k = k.getSuperclass()) {
+                if (s.equals(k.getClass().getName())) {
+                   return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static boolean isNoRecipientAddress(Throwable t) {
+        if (t instanceof MessagingException) {
+            return String.valueOf(t).contains("No recipient addresses");
+        }
+        return false;
+    }
+
+    private static InternalErrorManager internalErrorManagerFrom(Handler h) {
+        final ErrorManager em = h.getErrorManager();
+        if (em instanceof InternalErrorManager) {
+            return (InternalErrorManager) em;
+        }
+        throw new ClassCastException(String.valueOf(em));
+    }
+
+    /**
+     * http://www.iana.org/assignments/port-numbers
+     *
+     * @return a open dynamic port.
+     */
+    private static int findOpenPort() {
+        final int MAX_PORT = 65535;
+        for (int i = 49152; i <= MAX_PORT; ++i) {
+            if (checkUnusedPort(i)) {
+                return i;
+            }
+        }
+
+        try {
+            close(new Socket("localhost", MAX_PORT));
+            return MAX_PORT;
+        } catch (Throwable t) { //Config error or fix isConnectOrTimeout method.
+            throw new Error("Can't find open port.", t);
+        }
+    }
+
+    private static boolean checkUnusedPort(int port) {
+        try {
+            close(new Socket("localhost", port));
+        } catch (UnknownHostException UHE) {
+            throw new AssertionError(UHE);
+        } catch (IOException IOE) {
+            return isConnectOrTimeout(IOE);
+        }
+        return false; //listening.
+    }
+
+    private static void close(Socket s) {
+        try {
+            s.close();
+        } catch (IOException ignore) {
+        }
+    }
+
+    private static abstract class MessageErrorManager extends InternalErrorManager {
+
+        private final Properties props;
+
+        protected MessageErrorManager(final Properties props) {
+            if (props == null) {
+                throw new NullPointerException();
+            }
+            this.props = props;
+        }
+
+        @Override
+        public final void error(String msg, Exception ex, int code) {
+            super.error(msg, ex, code);
+            if (msg != null && msg.length() > 0
+                    && !msg.startsWith(Level.SEVERE.getName())) {
+                MimeMessage message;
+                try { //Headers can be UTF-8 or US-ASCII.
+                    byte[] b = msg.getBytes("UTF-8");
+                    assertTrue(b.length > 0);
+
+                    ByteArrayInputStream in = new ByteArrayInputStream(b);
+                    Session session = Session.getInstance(props);
+                    message = new MimeMessage(session, in);
+                    error(message, ex, code);
+                } catch (Error e) {
+                    throw e;
+                } catch (Throwable T) {
+                    fail(T.toString());
+                }
+            } else {
+                new ErrorManager().error(msg, ex, code);
+                fail("Message.writeTo failed.");
+            }
+        }
+
+        protected abstract void error(MimeMessage msg, Throwable t, int code);
+    }
+
+    public static class PushErrorManager extends MessageErrorManager {
+
+        public PushErrorManager(MailHandler h) {
+            super(h.getMailProperties());
+        }
+
+        protected PushErrorManager(Properties p) {
+            super(p);
+        }
+
+        @Override
+        protected void error(MimeMessage message, Throwable t, int code) {
+            try {
+                assertNotNull(message.getSentDate());
+                assertNotNull(message.getDescription());
+                assertNotNull(message.getHeader("X-Priority"));
+                assertEquals("2", message.getHeader("X-Priority")[0]);
+                assertNotNull(message.getHeader("Importance"));
+                assertEquals("High", message.getHeader("Importance")[0]);
+                assertNotNull(message.getHeader("Priority"));
+                assertEquals("urgent", message.getHeader("Priority")[0]);
+                assertEquals("auto-generated", message.getHeader("auto-submitted")[0]);
+                message.saveChanges();
+            } catch (RuntimeException | MessagingException RE) {
+                dump(RE);
+                fail(RE.toString());
+            }
+        }
+    }
+
+    public static final class VerifyErrorManager extends PushErrorManager {
+
+        public VerifyErrorManager() {
+            super(new Properties());
+        }
+
+        @Override
+        protected void error(MimeMessage message, Throwable t, int code) {
+            super.error(message, t, code);
+            try {
+                final Locale locale = Locale.getDefault();
+                String lang = LogManagerProperties.toLanguageTag(locale);
+                if (lang.length() != 0) {
+                    assertEquals(lang, message.getHeader("Accept-Language", null));
+                } else {
+                    assertEquals("", locale.getLanguage());
+                }
+
+                Address[] a = message.getRecipients(Message.RecipientType.TO);
+                assertEquals(InternetAddress.parse("foo@bar.com")[0], a[0]);
+                assertEquals(1, a.length);
+
+                a = message.getRecipients(Message.RecipientType.CC);
+                assertEquals(InternetAddress.parse("fizz@buzz.com")[0], a[0]);
+                assertEquals(1, a.length);
+
+                a = message.getRecipients(Message.RecipientType.BCC);
+                assertEquals(InternetAddress.parse("baz@bar.com")[0], a[0]);
+                assertEquals(1, a.length);
+
+                a = message.getFrom();
+                assertEquals(InternetAddress.parse("localhost@localdomain")[0], a[0]);
+                assertEquals(1, a.length);
+
+                a = new Address[]{message.getSender()};
+                assertEquals(InternetAddress.parse("mail@handler")[0], a[0]);
+
+                assertEquals(MailHandler.class.getName() + " test", message.getSubject());
+
+                assertNotNull(message.getHeader("Incomplete-Copy", null));
+
+                assertTrue(message.getContentType(), message.isMimeType("multipart/mixed"));
+                Multipart multipart = (Multipart) message.getContent();
+                MimePart body = (MimePart) multipart.getBodyPart(0);
+                ContentType type = new ContentType(body.getContentType());
+                assertEquals("text/plain", type.getBaseType());
+                assertEquals("us-ascii", type.getParameter("charset").toLowerCase(Locale.US));
+                assertEquals("auto-generated", message.getHeader("auto-submitted")[0]);
+
+                if (lang.length() != 0) {
+                    assertEquals(lang, body.getHeader("Accept-Language", null));
+                } else {
+                    assertEquals("", locale.getLanguage());
+                }
+            } catch (MessagingException | IOException me) {
+                dump(me);
+                fail(me.toString());
+            }
+        }
+    }
+
+    public static final class FlushErrorManager extends MessageErrorManager {
+
+        public FlushErrorManager(MailHandler h) {
+            super(h.getMailProperties());
+        }
+
+        @Override
+        protected void error(MimeMessage message, Throwable t, int code) {
+            try {
+                assertTrue(null != message.getSentDate());
+                assertNotNull(message.getDescription());
+                assertNull(message.getHeader("X-Priority"));
+                assertNull(message.getHeader("Importance"));
+                assertNull(message.getHeader("Priority"));
+                assertEquals("auto-generated", message.getHeader("auto-submitted")[0]);
+                message.saveChanges();
+            } catch (RuntimeException | MessagingException RE) {
+                dump(RE);
+                fail(RE.toString());
+            }
+        }
+    }
+
+    public static class ThrowFilter implements Filter {
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            throw new RuntimeException(record.toString());
+        }
+    }
+
+    public static final class ThrowComparator
+            implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = 8493707928829966353L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            throw new RuntimeException();
+        }
+    }
+
+    public static final class ThrowFormatter extends Formatter {
+
+        @Override
+        public String format(LogRecord record) {
+            throw new RuntimeException("format");
+        }
+
+        @Override
+        public String getHead(Handler h) {
+            throw new RuntimeException("head");
+        }
+
+        @Override
+        public String getTail(Handler h) {
+            throw new RuntimeException("tail");
+        }
+    }
+
+    public static class UselessComparator
+            implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = 7973575043680596722L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            return o1.toString().compareTo(o2.toString());
+        }
+    };
+
+    public static class SequenceComparator
+            implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            long s1 = o1.getSequenceNumber();
+            long s2 = o2.getSequenceNumber();
+            return s1 < s2 ? -1 : s1 > s2 ? 1 : 0;
+        }
+    }
+
+    public static class SequenceDescComparator
+            implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            long s1 = o1.getSequenceNumber();
+            long s2 = o2.getSequenceNumber();
+            return s1 < s2 ? 1 : s1 > s2 ? -1 : 0;
+        }
+    }
+
+    public static final class SequenceComparatorWithReverse
+            implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            long s1 = o1.getSequenceNumber();
+            long s2 = o2.getSequenceNumber();
+            return s1 < s2 ? -1 : s1 > s2 ? 1 : 0;
+        }
+
+        @SuppressWarnings("override")
+        public Comparator<LogRecord> reversed() {
+            return new SequenceDescComparator();
+        }
+    }
+
+    public static class RawTypeComparator
+            implements Comparator<Object>, Serializable {
+
+        private static final long serialVersionUID = -6539179106541617400L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(Object o1, Object o2) {
+            long s1 = LogRecord.class.cast(o1).getSequenceNumber();
+            long s2 = LogRecord.class.cast(o2).getSequenceNumber();
+            return s1 < s2 ? -1 : s1 > s2 ? 1 : 0;
+        }
+    }
+
+    public static final class ActivationErrorManager extends InternalErrorManager {
+
+        @Override
+        public void error(String msg, Exception ex, int code) {
+            if (isDataTypeError(msg)) {
+                Exception e = new UnsupportedDataTypeException(msg);
+                super.error(msg, e, code);
+            } else {
+                if (!isConnectOrTimeout(ex)) {
+                    super.error(msg, ex, code);
+                } else {
+                    for (Throwable t = ex; t != null; t = t.getCause()) {
+                        if (isDataTypeError(t.getMessage())
+                                || isDataTypeError(t.getClass().getName())) {
+                            super.error(msg, ex, code);
+                        }
+                    }
+                }
+            }
+        }
+
+        private boolean isDataTypeError(String m) {
+            if (m != null) {
+                return m.contains("javax.activation.UnsupportedDataTypeException");
+            }
+            return false;
+        }
+    }
+
+    public static final class BooleanFilter implements Filter {
+
+        static final BooleanFilter TRUE = new BooleanFilter(true);
+        static final BooleanFilter FALSE = new BooleanFilter(false);
+        private final boolean value;
+
+        public BooleanFilter() {
+            this(false);
+        }
+
+        private BooleanFilter(boolean v) {
+            this.value = v;
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord r) {
+            return value;
+        }
+    }
+
+    public static final class CountingFilter implements Filter {
+
+        private final Filter result;
+        int count;
+
+        public CountingFilter() {
+            this.result = BooleanFilter.TRUE;
+        }
+
+        public CountingFilter(Filter f) {
+            this.result = f;
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord r) {
+            ++count;
+            return result.isLoggable(r);
+        }
+    }
+
+    public static final class CountingFormatter extends Formatter {
+
+        int head;
+        int tail;
+        int format;
+
+        @Override
+        public String getHead(Handler h) {
+            ++head;
+            return "";
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            ++format;
+            return String.valueOf(record.getMessage());
+        }
+
+        @Override
+        public String getTail(Handler h) {
+            ++tail;
+            return "";
+        }
+    }
+
+    public static final class HeadFormatter extends Formatter {
+
+        private final String name;
+
+        public HeadFormatter() {
+            this((String) null);
+        }
+
+        public HeadFormatter(final String name) {
+            this.name = name;
+        }
+
+        @Override
+        public String getHead(Handler h) {
+            return name;
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            return "";
+        }
+    }
+
+    public static class InternalErrorManager extends ErrorManager {
+
+        protected final List<Exception> exceptions = new ArrayList<>();
+
+        @Override
+        public void error(String msg, Exception ex, int code) {
+            exceptions.add(ex);
+        }
+    }
+
+    public static final class ThrowAuthenticator extends javax.mail.Authenticator {
+
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            throw new RuntimeException();
+        }
+    }
+
+    public static final class EmptyAuthenticator extends javax.mail.Authenticator {
+
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            return new PasswordAuthentication("", "");
+        }
+    }
+
+    public static final class EmptyFormatter extends Formatter {
+
+        @Override
+        public String format(LogRecord r) {
+            return "";
+        }
+    }
+
+    public static final class ThrowSecurityManager extends SecurityManager {
+
+        boolean secure = false;
+        private final boolean debug;
+
+        public ThrowSecurityManager() {
+            debug = isSecurityDebug();
+        }
+
+        @Override
+        public void checkPermission(java.security.Permission perm) {
+            try { //Call super class always for java.security.debug tracing.
+                super.checkPermission(perm);
+                checkPermission(perm, new SecurityException(perm.toString()));
+            } catch (SecurityException se) {
+                checkPermission(perm, se);
+            }
+        }
+
+        @Override
+        public void checkPermission(java.security.Permission perm, Object context) {
+            try { //Call super class always for java.security.debug tracing.
+                super.checkPermission(perm, context);
+                checkPermission(perm, new SecurityException(perm.toString()));
+            } catch (SecurityException se) {
+                checkPermission(perm, se);
+            }
+        }
+
+        private void checkPermission(java.security.Permission perm, SecurityException se) {
+            if (secure && perm instanceof LoggingPermission) {
+                throw se;
+            } else {
+                if (debug) {
+                    securityDebugPrint(se);
+                }
+            }
+        }
+    }
+
+    public static final class GaeErrorManager extends MessageErrorManager {
+
+        public GaeErrorManager(MailHandler h) {
+            super(h.getMailProperties());
+        }
+
+        @Override
+        protected void error(MimeMessage message, Throwable t, int code) {
+            try {
+                assertFalse(LogManagerProperties.hasLogManager());
+                String[] a = message.getHeader("auto-submitted");
+                assertTrue(Arrays.toString(a), a == null || a.length == 0);
+                message.saveChanges();
+            } catch (RuntimeException RE) {
+                dump(RE);
+                fail(RE.toString());
+            } catch (Exception ME) {
+                dump(ME);
+                fail(ME.toString());
+            }
+        }
+    }
+
+    public static final class GaeSecurityManager extends SecurityManager {
+
+        boolean secure = false;
+        private final boolean debug;
+
+        public GaeSecurityManager() {
+            debug = isSecurityDebug();
+        }
+
+        @Override
+        public void checkPermission(java.security.Permission perm) {
+            try { //Call super class always for java.security.debug tracing.
+                super.checkPermission(perm);
+                checkPermission(perm, new SecurityException(perm.toString()));
+            } catch (SecurityException se) {
+                checkPermission(perm, se);
+            }
+        }
+
+        @Override
+        public void checkPermission(java.security.Permission perm, Object context) {
+            try { //Call super class always for java.security.debug tracing.
+                super.checkPermission(perm, context);
+                checkPermission(perm, new SecurityException(perm.toString()));
+            } catch (SecurityException se) {
+                checkPermission(perm, se);
+            }
+        }
+
+        private void checkPermission(java.security.Permission perm, SecurityException se) {
+            if (secure && perm instanceof LoggingPermission) {
+                final StackTraceElement[] stack = se.getStackTrace();
+                if (stack.length == 0) {
+                    Assume.assumeNoException(se);
+                }
+                for (StackTraceElement e : stack) {
+                    if (Handler.class.getName().equals(e.getClassName())) {
+                        throw se;
+                    }
+                }
+            }
+            if (debug) {
+                securityDebugPrint(se);
+            }
+        }
+    }
+
+    public static class ErrorFormatter extends Formatter {
+
+        @Override
+        public String format(LogRecord record) {
+            throw new Error("format");
+        }
+
+        @Override
+        public String getHead(Handler h) {
+            throw new Error("head");
+        }
+
+        @Override
+        public String getTail(Handler h) {
+            throw new Error("tail");
+        }
+    }
+
+    public static class ErrorComparator implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord r1, LogRecord r2) {
+            throw new Error("");
+        }
+    }
+
+    public static class ReentranceFilter implements Filter {
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            if (!getClass().getName().equals(record.getSourceClassName())) {
+                final Logger logger = Logger.getLogger(record.getLoggerName());
+                logger.logp(Level.SEVERE, getClass().getName(), "isLoggable", toString());
+            }
+            return true;
+        }
+    }
+
+    public static class ErrorFilter implements Filter {
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            throw new Error("");
+        }
+    }
+
+    public static class FlipFlopFilter implements Filter {
+
+        volatile boolean value;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            return value;
+        }
+    }
+
+    public static final class InitAuthenticator extends javax.mail.Authenticator {
+
+        public InitAuthenticator() {
+            throwPending();
+        }
+
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class InitFilter implements Filter {
+
+        public InitFilter() {
+            throwPending();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class InitFormatter extends Formatter {
+
+        public InitFormatter() {
+            throwPending();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class InitComparator
+            implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        public InitComparator() {
+            throwPending();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class InitErrorManager extends ErrorManager {
+
+        public InitErrorManager() {
+            throwPending();
+        }
+    }
+
+    public final static class InternFilterFormatterComparator extends Formatter
+            implements Comparator<LogRecord>, Filter,
+            Serializable {
+
+        private static final long serialVersionUID = -7282673499043066003L;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            throw new UnsupportedOperationException();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord lr) {
+            return true;
+        }
+
+        @Override
+        public String format(LogRecord lr) {
+            return "";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o == null ? false : getClass().equals(o.getClass());
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * getClass().hashCode();
+        }
+    }
+
+    public static class InternBadFilter implements Filter {
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            return true;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o instanceof InternBadFilter; //Not safe.
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * InternBadFilter.class.hashCode(); //Not safe.
+        }
+    }
+
+    public final static class InternBadSubFilter extends InternBadFilter {
+
+        @Override
+        public boolean isLoggable(LogRecord record) {
+            return false;
+        }
+    }
+
+    public final static class InternFilter implements Filter {
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord lr) {
+            return true;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o == null ? false : getClass().equals(o.getClass());
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * getClass().hashCode();
+        }
+    }
+
+    public final static class InternFilterErrorManager
+            extends InternalErrorManager implements Filter {
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord lr) {
+            return true;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o == null ? false : getClass().equals(o.getClass());
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * getClass().hashCode();
+        }
+    }
+
+    public final static class InternFilterFormatter
+            extends Formatter implements Filter {
+
+        @Override
+        public String format(LogRecord lr) {
+            return "";
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord lr) {
+            return true;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o == null ? false : getClass().equals(o.getClass());
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * getClass().hashCode();
+        }
+    }
+
+    public final static class InternFormatter extends Formatter {
+
+        @Override
+        public boolean equals(Object o) {
+            return o == null ? false : getClass().equals(o.getClass());
+        }
+
+        @Override
+        public int hashCode() {
+            return 31 * getClass().hashCode();
+        }
+
+        @Override
+        public String format(LogRecord lr) {
+            return "";
+        }
+    }
+
+    public static final class StaticInitReAuthenticator extends javax.mail.Authenticator {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitReFilter implements Filter {
+
+        static {
+            throwPending();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitReAttachFilter implements Filter {
+
+        static {
+            throwPending();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitReFormatter extends Formatter {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitReSubjectFormatter extends Formatter {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitReAttachFormatter extends Formatter {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitReNameFormatter extends Formatter {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitReComparator
+            implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        static {
+            throwPending();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitReErrorManager extends ErrorManager {
+
+        static {
+            throwPending();
+        }
+    }
+
+    public static final class StaticInitErAuthenticator extends javax.mail.Authenticator {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        protected PasswordAuthentication getPasswordAuthentication() {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitErFilter implements Filter {
+
+        static {
+            throwPending();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitErAttachFilter implements Filter {
+
+        static {
+            throwPending();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitErFormatter extends Formatter {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitErSubjectFormatter extends Formatter {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitErAttachFormatter extends Formatter {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitErNameFormatter extends Formatter {
+
+        static {
+            throwPending();
+        }
+
+        @Override
+        public String format(LogRecord record) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitErComparator
+            implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        static {
+            throwPending();
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            throw new NoSuchMethodError();
+        }
+    }
+
+    public final static class StaticInitErErrorManager extends ErrorManager {
+
+        static {
+            throwPending();
+        }
+    }
+
+    private final static class LevelCheckingFormatter extends Formatter {
+
+        private final Level expect;
+
+        public LevelCheckingFormatter(final Level expect) {
+            this.expect = expect;
+        }
+
+        @Override
+        public String format(LogRecord lr) {
+            return "";
+        }
+
+        @Override
+        public String getHead(Handler h) {
+            assertEquals(expect, h.getLevel());
+            return "";
+        }
+
+        @Override
+        public String getTail(Handler h) {
+            return getHead(h);
+        }
+    }
+
+    private static class PrintThrowsRuntimeException extends RuntimeException {
+
+        private static final long serialVersionUID = 1L;
+
+        PrintThrowsRuntimeException(Throwable t) {
+            super(t);
+        }
+
+        @Override
+        public void printStackTrace(PrintWriter s) {
+            throw this;
+        }
+
+        @Override
+        public void printStackTrace(PrintStream s) {
+            throw this;
+        }
+
+        @Override
+        public void printStackTrace() {
+            throw this;
+        }
+
+        @SuppressWarnings("CallToPrintStackTrace")
+        public void dump() {
+            Throwable t = new Throwable(getClass().getName(), getCause());
+            t.setStackTrace(getStackTrace());
+            t.printStackTrace();
+        }
+    }
+
+    private static class CountingUncaughtExceptionHandler
+                                implements Thread.UncaughtExceptionHandler {
+
+        int count;
+
+        @SuppressWarnings("override") //JDK-6954234
+        public void uncaughtException(Thread t, Throwable e) {
+            count++;
+        }
+    }
+
+    private static class LinkageErrorStream extends PrintStream {
+
+        private final StackTraceElement[] stack;
+
+        LinkageErrorStream() throws IOException {
+            this((StackTraceElement[]) null);
+        }
+
+        LinkageErrorStream(final StackTraceElement[] stack) throws IOException {
+            super(new ByteArrayOutputStream(), false, "UTF-8");
+            if (stack != null) {
+                this.stack = stack.clone();
+            } else {
+                this.stack = null;
+            }
+        }
+
+        @Override
+        public void println(String x) {
+            setError();
+            LinkageError le = new LinkageError(x);
+            if (stack != null) {
+                le.setStackTrace(stack);
+            }
+            throw le;
+        }
+    }
+
+    private static class RuntimeErrorStream extends PrintStream {
+
+        private final StackTraceElement[] stack;
+
+        RuntimeErrorStream() throws IOException {
+            this((StackTraceElement[]) null);
+        }
+
+        RuntimeErrorStream(final StackTraceElement[] stack) throws IOException {
+            super(new ByteArrayOutputStream(), false, "UTF-8");
+            if (stack != null) {
+                this.stack = stack.clone();
+            } else {
+                this.stack = null;
+            }
+        }
+
+        @Override
+        public void println(String x) {
+            setError();
+            PrintThrowsRuntimeException re
+                    = new PrintThrowsRuntimeException(new Throwable());
+            if (stack != null) {
+                re.setStackTrace(stack);
+            }
+            throw re;
+        }
+    }
+
+    private final static class LocaleFilter implements Filter {
+
+        private final Locale locale;
+        private final boolean allow;
+
+        LocaleFilter(final Locale l, final boolean allow) {
+            if (l == null) {
+                throw new NullPointerException();
+            }
+            this.locale = l;
+            this.allow = allow;
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            final ResourceBundle rb = record.getResourceBundle();
+            return rb == null ? allow : locale.equals(rb.getLocale());
+        }
+    }
+
+    public static class MailDebugErrorManager extends ErrorManager {
+
+        @Override
+        public void error(String msg, Exception ex, int code) {
+            try {
+                Session session = Session.getInstance(createInitProperties(""));
+                session.setDebug(true);
+                Message m = new MimeMessage(session);
+                m.setFrom();
+                m.setRecipient(Message.RecipientType.TO, m.getFrom()[0]);
+                m.setText(MailDebugErrorManager.class.getName());
+                Transport.send(m);
+            } catch (Exception e) {
+                super.error(msg, e, code);
+            }
+        }
+    }
+
+    public final static class MailHandlerExt extends MailHandler {
+
+        public MailHandlerExt() {
+            super();
+        }
+
+        public MailHandlerExt(Properties props) {
+            super(props);
+        }
+
+        public MailHandlerExt(int capacity) {
+            super(capacity);
+        }
+    }
+
+    public final static class MailHandlerOverride extends MailHandler {
+
+        public MailHandlerOverride() {
+            super();
+        }
+
+        public MailHandlerOverride(Properties props) {
+            super(props);
+        }
+
+        public MailHandlerOverride(int capacity) {
+            super(capacity);
+        }
+
+        @Override
+        public boolean isLoggable(LogRecord record) {
+            int levelValue = getLevel().intValue();
+            if (record.getLevel().intValue() < levelValue
+                    || levelValue == Level.OFF.intValue()) {
+                return false;
+            }
+
+            Filter body = getFilter();
+            if (body == null || body.isLoggable(record)) {
+                return true;
+            }
+
+            final Filter[] filters = this.getAttachmentFilters();
+            for (int i = 0; i < filters.length; ++i) {
+                final Filter f = filters[i];
+                if (f == null || f.isLoggable(record)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    private final static class ClassLoaderSecurityManager extends SecurityManager {
+
+        volatile boolean secure = false;
+        private final boolean debug;
+
+        public ClassLoaderSecurityManager() {
+            debug = isSecurityDebug();
+        }
+
+        @Override
+        public void checkPermission(java.security.Permission perm) {
+            try { //Call super class always for java.security.debug tracing.
+                super.checkPermission(perm);
+                checkPermission(perm, new SecurityException(perm.toString()));
+            } catch (SecurityException se) {
+                checkPermission(perm, se);
+            }
+        }
+
+        @Override
+        public void checkPermission(java.security.Permission perm, Object context) {
+            try { //Call super class always for java.security.debug tracing.
+                super.checkPermission(perm, context);
+                checkPermission(perm, new SecurityException(perm.toString()));
+            } catch (SecurityException se) {
+                checkPermission(perm, se);
+            }
+        }
+
+        @Override
+        public void checkRead(String file, Object context) {
+        }
+
+        @Override
+        public void checkRead(String file) {
+        }
+
+        private void checkPermission(java.security.Permission perm, SecurityException se) {
+            //Check for set and get context class loader.
+            String name = perm.getName();
+            if (secure && name.contains("ContextClassLoader")) {
+                throw se;
+            } else {
+                if (debug) {
+                    securityDebugPrint(se);
+                }
+            }
+        }
+    }
+
+    public final static class ClassLoaderErrorManager extends InternalErrorManager {
+
+        private final ClassLoader expect;
+
+        public ClassLoaderErrorManager() {
+            this(LOADER.get());
+        }
+
+        public ClassLoaderErrorManager(final ClassLoader expect) {
+            this.expect = expect;
+        }
+
+        @Override
+        public void error(String msg, Exception ex, int code) {
+            checkContextClassLoader(expect);
+            super.error(msg, ex, code);
+        }
+
+        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+        @Override
+        public boolean equals(Object o) {
+            checkContextClassLoader(expect);
+            return super.equals(o);
+        }
+
+        @Override
+        public int hashCode() {
+            checkContextClassLoader(expect);
+            return super.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            checkContextClassLoader(expect);
+            return super.toString();
+        }
+    }
+
+    public static final class ClassLoaderAuthenticator
+            extends javax.mail.Authenticator {
+
+        private final ClassLoader expect;
+
+        public ClassLoaderAuthenticator() {
+            this(LOADER.get());
+        }
+
+        ClassLoaderAuthenticator(ClassLoader loader) {
+            this.expect = loader;
+        }
+
+        @SuppressWarnings("override")
+        protected PasswordAuthentication getPasswordAuthentication() {
+            checkContextClassLoader(expect);
+            for (StackTraceElement se : new Throwable().getStackTrace()) {
+                if ("javax.mail.Transport".equals(se.getClassName())
+                        && "send".equals(se.getMethodName())) {
+                    return null;
+                }
+            }
+            throw new AssertionError("Not calling Transport.send");
+        }
+    }
+
+    public final static class ClassLoaderComparator
+            implements Comparator<LogRecord>, Serializable {
+
+        private static final long serialVersionUID = -1L;
+
+        private final ClassLoader expect;
+
+        public ClassLoaderComparator() {
+            this(LOADER.get());
+        }
+
+        ClassLoaderComparator(final ClassLoader expect) {
+            this.expect = expect;
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public int compare(LogRecord o1, LogRecord o2) {
+            checkContextClassLoader(expect);
+            return new SequenceComparator().compare(o1, o2);
+        }
+
+        @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass", "override"})
+        public boolean equals(Object o) {
+            checkContextClassLoader(expect);
+            return super.equals(o);
+        }
+
+        @SuppressWarnings("override")
+        public int hashCode() {
+            checkContextClassLoader(expect);
+            return super.hashCode();
+        }
+
+        @SuppressWarnings("override")
+        public String toString() {
+            checkContextClassLoader(expect);
+            return super.toString();
+        }
+    }
+
+    public final static class ClassLoaderFilterFormatter
+            extends Formatter implements Filter {
+
+        private final ClassLoader expect;
+        private final String format;
+
+        public ClassLoaderFilterFormatter() {
+            this(LOADER.get());
+        }
+
+        ClassLoaderFilterFormatter(final ClassLoader expect) {
+            this.expect = expect;
+            this.format = "";
+        }
+
+        ClassLoaderFilterFormatter(final ClassLoader expect, String format) {
+            this.expect = expect;
+            this.format = format;
+        }
+
+        @Override
+        public String getHead(Handler h) {
+            checkContextClassLoader(expect);
+            return "";
+        }
+
+        @Override
+        public String getTail(Handler h) {
+            checkContextClassLoader(expect);
+            return "";
+        }
+
+        @Override
+        public String format(final LogRecord lr) {
+            checkContextClassLoader(expect);
+            return format;
+        }
+
+        @SuppressWarnings("override") //JDK-6954234
+        public boolean isLoggable(LogRecord record) {
+            checkContextClassLoader(expect);
+            return true;
+        }
+
+        @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
+        @Override
+        public boolean equals(Object o) {
+            checkContextClassLoader(expect);
+            return super.equals(o);
+        }
+
+        @Override
+        public int hashCode() {
+            checkContextClassLoader(expect);
+            return super.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            checkContextClassLoader(expect);
+            return super.toString();
+        }
+    }
+
+    private final static class CloseLogRecord extends LogRecord {
+
+        private static final long serialVersionUID = 1L;
+        private transient volatile Handler target;
+
+        CloseLogRecord(Level level, String msg, final Handler target) {
+            super(level, msg);
+            this.target = target;
+        }
+
+        @Override
+        public String getSourceMethodName() {
+            close();
+            return super.getSourceMethodName();
+        }
+
+        @Override
+        public String getSourceClassName() {
+            close();
+            return super.getSourceClassName();
+        }
+
+        public boolean isClosed() {
+            return this.target == null;
+        }
+
+        private void close() {
+            final Handler h = this.target;
+            if (h != null) {
+                h.close();
+                this.target = null;
+            }
+        }
+
+        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+            in.defaultReadObject();
+            this.target = null;
+        }
+    }
+}
diff --git a/mail/src/test/java/com/sun/mail/util/logging/SeverityComparatorTest.java b/mail/src/test/java/com/sun/mail/util/logging/SeverityComparatorTest.java
new file mode 100644
index 0000000..2911986
--- /dev/null
+++ b/mail/src/test/java/com/sun/mail/util/logging/SeverityComparatorTest.java
@@ -0,0 +1,1373 @@
+/*

+ * Copyright (c) 2013, 2018 Oracle and/or its affiliates. All rights reserved.

+ * Copyright (c) 2013, 2018 Jason Mehrens. All rights reserved.

+ *

+ * This program and the accompanying materials are made available under the

+ * terms of the Eclipse Public License v. 2.0, which is available at

+ * http://www.eclipse.org/legal/epl-2.0.

+ *

+ * This Source Code may also be made available under the following Secondary

+ * Licenses when the conditions for such availability set forth in the

+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,

+ * version 2 with the GNU Classpath Exception, which is available at

+ * https://www.gnu.org/software/classpath/license.html.

+ *

+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0

+ */

+package com.sun.mail.util.logging;

+

+import java.io.*;

+import java.lang.reflect.InvocationTargetException;

+import java.lang.reflect.UndeclaredThrowableException;

+import java.nio.channels.ClosedByInterruptException;

+import java.nio.channels.FileLockInterruptionException;

+import java.util.*;

+import java.util.concurrent.ExecutionException;

+import java.util.logging.Level;

+import java.util.logging.LogRecord;

+import static org.junit.Assert.*;

+import org.junit.Test;

+

+/**

+ *

+ * @author Jason Mehrens

+ * @since JavaMail 1.5.2

+ */

+public class SeverityComparatorTest extends AbstractLogging {

+

+    @Test

+    public void testDeclaredClasses() throws Exception {

+        Class<?>[] declared = SeverityComparator.class.getDeclaredClasses();

+        assertEquals(Arrays.toString(declared), 0, declared.length);

+    }

+

+    @Test

+    public void testApplyNull() {

+        SeverityComparator a = new SeverityComparator();

+        assertNull(a.apply(null));

+    }

+

+    @Test

+    public void testApplyErrorByNormal() {

+        SeverityComparator a = new SeverityComparator();

+        Throwable next = this.headIeChain(new AssertionError());

+        next = new Error(next);

+        next = new NoClassDefFoundError().initCause(next);

+        Throwable reduced = a.apply(next);

+        assertEquals(Error.class, reduced.getClass());

+    }

+

+    @Test(timeout = 30000)

+    public void testApplyEvil() {

+        SeverityComparator a = new SeverityComparator();

+        final int len = 7;

+        final Class<? extends Throwable> type = Exception.class;

+        Throwable l = a.apply(createEvilThrowable(type, len));

+        assertEquals(type, l.getClass());

+    }

+

+    @Test

+    public void testApplyProxyNormal() {

+        //UTE->IE->AE = IE

+        SeverityComparator a = new SeverityComparator();

+        Throwable next = this.headIeChain(new AssertionError());

+        next = new UndeclaredThrowableException(next);

+        Throwable reduced = a.apply(next);

+        assertEquals(InterruptedException.class, reduced.getClass());

+    }

+

+    @Test

+    public void testApplyReflectNormal() {

+        //ITE->IE->AE = IE

+        SeverityComparator a = new SeverityComparator();

+        Throwable next = this.headIeChain(new AssertionError());

+        next = new InvocationTargetException(next);

+        Throwable reduced = a.apply(next);

+        assertEquals(InterruptedException.class, reduced.getClass());

+    }

+

+    @Test

+    public void testApplyNormalByNormal() {

+        //EE->IIOE->E->IE->AE = IE

+        SeverityComparator a = new SeverityComparator();

+        Throwable next = this.headIeChain(new AssertionError());

+        next = new Error(next);

+        next = this.headIioeChain(next);

+        next = new ExecutionException(next);

+        Throwable reduced = a.apply(next);

+        assertEquals(InterruptedException.class, reduced.getClass());

+    }

+

+    @Test

+    public void testApplyFindsRootCause() {

+        /**

+         * Lots of frameworks wrap exceptions with runtime exceptions. The root

+         * cause is always more important.

+         */

+        SeverityComparator a = new SeverityComparator();

+        final Throwable root = new Exception();

+        Throwable next = root;

+        for (int i = 0; i < 7; i++) {

+            next = new RuntimeException(next);

+        }

+        Throwable reduced = a.apply(next);

+        assertEquals(root, reduced);

+    }

+

+    @Test

+    public void testCompareThrownDoesNotApply() {

+        SeverityComparator a = new SeverityComparator();

+        Throwable tc1 = new Error(new Exception());

+        Throwable tc2 = new Exception(new Exception());

+        assertTrue(a.compareThrowable(tc1, tc2) > 0);

+        assertTrue(a.compareThrowable(tc2, tc1) < 0);

+

+        tc1 = new RuntimeException(tc1);

+        assertTrue(a.compareThrowable(tc1, tc2) > 0);

+        assertTrue(a.compareThrowable(tc2, tc1) < 0);

+

+    }

+

+    @Test

+    public void testCompareThrownNull() {

+        SeverityComparator a = new SeverityComparator();

+        assertEquals(0, a.compareThrowable((Throwable) null, (Throwable) null));

+        assertTrue(a.compareThrowable(new Throwable(), (Throwable) null) > 0);

+        assertTrue(a.compareThrowable((Throwable) null, new Throwable()) < 0);

+    }

+

+    @Test

+    public void testApplyThenCompareNull() {

+        SeverityComparator a = new SeverityComparator();

+        assertEquals(0, a.applyThenCompare((Throwable) null, (Throwable) null));

+    }

+

+    @Test(timeout = 30000)

+    public void testApplyThenCompareThrownEvilError() {

+        testApplyThenCompareThrownEvil(Error.class);

+    }

+

+    @Test(timeout = 30000)

+    public void testApplyThenCompareThrownEvilRuntimeException() {

+        testApplyThenCompareThrownEvil(RuntimeException.class);

+    }

+

+    @Test(timeout = 30000)

+    public void testApplyThenCompareThrownEvilException() {

+        testApplyThenCompareThrownEvil(Exception.class);

+    }

+

+    private void testApplyThenCompareThrownEvil(Class<? extends Throwable> t) {

+        SeverityComparator a = new SeverityComparator();

+        final int len = 7;

+        Throwable l = createEvilThrowable(t, len);

+        Throwable r = createEvilThrowable(t, len);

+        assertEquals(0, a.applyThenCompare(l, r));

+    }

+

+    @Test(timeout = 30000)

+    public void testCompareThrownEvilError() {

+        testCompareThrownEvil(Error.class);

+    }

+

+    @Test(timeout = 30000)

+    public void testCompareThrownEvilRuntimeException() {

+        testCompareThrownEvil(RuntimeException.class);

+    }

+

+    @Test(timeout = 30000)

+    public void testCompareThrownEvilException() {

+        testCompareThrownEvil(Exception.class);

+    }

+

+    private void testCompareThrownEvil(Class<? extends Throwable> t) {

+        SeverityComparator a = new SeverityComparator();

+        final int len = 7;

+        Throwable l = createEvilThrowable(t, len);

+        Throwable r = createEvilThrowable(t, len);

+        assertEquals(0, a.compareThrowable(l, r));

+    }

+

+    private Throwable createEvilThrowable(Class<? extends Throwable> t, int len) {

+        Throwable tail = create(t, null);

+        Throwable head = tail;

+        for (int i = 1; i < len; i++) {

+            head = create(t, head);

+        }

+        tail.initCause(head); //Pure evil.

+        return head;

+    }

+

+    @Test

+    public void testGetInstance() {

+        SeverityComparator a = new SeverityComparator();

+        assertEquals(a, SeverityComparator.getInstance());

+        assertEquals(a.getClass(), SeverityComparator.getInstance().getClass());

+        assertEquals(a.hashCode(), SeverityComparator.getInstance().hashCode());

+    }

+

+    @Test

+    public void testIsNormalError() {

+        testIsNotNormal(Error.class, headIeChain(null));

+    }

+

+    @Test

+    public void testIsNormalRuntimeException() {

+        testIsNotNormal(RuntimeException.class, headIeChain(null));

+    }

+

+    @Test

+    public void testIsNormalException() {

+        testIsNotNormal(Exception.class, headIeChain(null));

+    }

+

+    private void testIsNotNormal(Class<? extends Throwable> t, Throwable root) {

+        SeverityComparator a = new SeverityComparator();

+        assertFalse(a.isNormal(create(t, null)));

+

+        Throwable next = root;

+        for (int i = 0; i < 5; i++) {

+            next = create(t, next);

+            assertFalse(a.isNormal(next));

+        }

+    }

+

+    @Test

+    public void testIsNormal() {

+        SeverityComparator a = new SeverityComparator();

+        assertTrue(a.isNormal(this.headIeChain(null)));

+        assertTrue(a.isNormal(this.headIioeChain(null)));

+        assertTrue(a.isNormal(this.headCbieChain(null)));

+        assertTrue(a.isNormal(this.headFlieChain(null)));

+        assertTrue(a.isNormal(this.headSubIeChain(null)));

+        assertTrue(a.isNormal(this.headSubIioeChain(null)));

+        assertTrue(a.isNormal(this.headSubTdChain(null)));

+        assertTrue(a.isNormal(this.headSubTdChain(null)));

+        assertTrue(a.isNormal(this.headWidChain(null)));

+        assertTrue(a.isNormal(this.headWitChain(null)));

+    }

+

+    @Test

+    public void testIsNormalEvil() {

+        SeverityComparator a = new SeverityComparator();

+        assertFalse(a.isNormal(createEvilThrowable(Exception.class, 7)));

+    }

+

+    @Test

+    public void testIsNormalDoesNotUseApply() {

+        SeverityComparator a = new SeverityComparator();

+        Throwable next = this.headIeChain(new AssertionError());

+        next = new Error(next);

+        next = this.headIioeChain(next);

+        next = new ExecutionException(next);

+        assertFalse(a.isNormal(next));

+    }

+

+    @Test

+    public void testIsNormalNull() {

+        SeverityComparator a = new SeverityComparator();

+        assertFalse(a.isNormal((Throwable) null));

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testCompareNullAndNull() {

+        SeverityComparator a = new SeverityComparator();

+        a.compare((LogRecord) null, (LogRecord) null);

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testCompareNotNullAndNull() {

+        SeverityComparator a = new SeverityComparator();

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        a.compare(r1, (LogRecord) null);

+    }

+

+    @Test(expected = NullPointerException.class)

+    public void testCompareNullAndNotNull() {

+        SeverityComparator a = new SeverityComparator();

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        a.compare((LogRecord) null, r1);

+    }

+

+    @Test

+    public void testLeadingErrorAndInterrupted() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        swapSeq(r1, r2);

+        assertEquals(r1.getLevel(), r2.getLevel());

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+

+        r1.setThrown(headIeChain(new Error()));

+        r2.setThrown(new Error(headIeChain(null)));

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headIeChain(new RuntimeException()));

+        r2.setThrown(new Error(headIeChain(null)));

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headIeChain(new Exception()));

+        r2.setThrown(new Error(headIeChain(null)));

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByLevel() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.WARNING, Level.WARNING.toString());

+        assertTrue(r1.getSequenceNumber() < r2.getSequenceNumber());

+

+        //Level test with null thrown.

+        assertRecordLessThan(r1, r2);

+

+        //Ensure Error doesn't change primary order.

+        r1.setThrown(new Error());

+        r2.setThrown(null);

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(Error.class, r1.getThrown().getClass());

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(Error.class, r1.getThrown().getClass());

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(Error.class, r1.getThrown().getClass());

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(Error.class, r1.getThrown().getClass());

+        r2.setThrown(new InterruptedException());

+        assertRecordLessThan(r1, r2);

+

+        //Ensure RuntimeException doesn't change primary order.

+        r1.setThrown(new RuntimeException());

+        r2.setThrown(null);

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(RuntimeException.class, r1.getThrown().getClass());

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(RuntimeException.class, r1.getThrown().getClass());

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(RuntimeException.class, r1.getThrown().getClass());

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(RuntimeException.class, r1.getThrown().getClass());

+        r2.setThrown(new InterruptedException());

+        assertRecordLessThan(r1, r2);

+

+        //Ensure Exception doesn't change primary order.

+        r1.setThrown(new Exception());

+        r2.setThrown(null);

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(Exception.class, r1.getThrown().getClass());

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(Exception.class, r1.getThrown().getClass());

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(Exception.class, r1.getThrown().getClass());

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+

+        assertEquals(Exception.class, r1.getThrown().getClass());

+        r2.setThrown(new InterruptedException());

+        assertRecordLessThan(r1, r2);

+

+        //Ensure thrown and sequence doesn't change primary order.

+        swapSeq(r1, r2);

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(null);

+        r2.setThrown(null);

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByNullAndFrameworkInterrupted() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        assertEquals(r1.getLevel(), r2.getLevel());

+

+        //Ensure Interrupted is less than null.

+        r1.setThrown(this.headWidChain(null));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(this.headWidChain(new Error()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(this.headWidChain(new RuntimeException()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(this.headWidChain(new Exception()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByNullAndInterrupted() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        assertEquals(r1.getLevel(), r2.getLevel());

+

+        //Ensure Interrupted is less than null.

+        r1.setThrown(headIeChain(null));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headIeChain(new Error()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headIeChain(new RuntimeException()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headIeChain(new Exception()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByNullAndInterruptedSub() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        assertEquals(r1.getLevel(), r2.getLevel());

+        //Ensure subclass of IE is less than null.

+        r1.setThrown(headSubIeChain(null));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headSubIeChain(new Error()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headSubIeChain(new RuntimeException()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headSubIeChain(new Exception()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByNullAndInterruptedIo() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        assertEquals(r1.getLevel(), r2.getLevel());

+

+        //Esure subclass of IOE is less than null.

+        r1.setThrown(headIioeChain(null));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headIioeChain(new Error()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headIioeChain(new RuntimeException()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headIioeChain(new Exception()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByNullAndInterruptedIoSub() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        assertEquals(r1.getLevel(), r2.getLevel());

+

+        //Esure subclass of IOE is less than null.

+        r1.setThrown(headSubIioeChain(null));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headSubIioeChain(new Error()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headSubIioeChain(new RuntimeException()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headSubIioeChain(new Exception()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByNullAndThreadDeath() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        assertEquals(r1.getLevel(), r2.getLevel());

+

+        //Ensure ThreadDeath is less than null.

+        r1.setThrown(headTdChain(null));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headTdChain(new Error()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headTdChain(new RuntimeException()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headTdChain(new Exception()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByNullAndThreadDeathSub() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        assertEquals(r1.getLevel(), r2.getLevel());

+

+        //Ensure subclass ThreadDeath is less than null.

+        r1.setThrown(headSubTdChain(null));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headSubTdChain(new Error()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headSubTdChain(new RuntimeException()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(headSubTdChain(new Exception()));

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByNullAndThrown() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        r1.setSequenceNumber(r2.getSequenceNumber());

+        setEpochMilli(r2, System.currentTimeMillis()); //Truncate nanos.

+        setEpochMilli(r1, r2.getMillis());

+

+        assertEquals(r1.getLevel(), r2.getLevel());

+        assertEquals(r1.getSequenceNumber(), r2.getSequenceNumber());

+        assertEquals(r1.getMillis(), r2.getMillis());

+

+        //Ensure null is less than Error.

+        assertNull(r1.getThrown());

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        //Ensure null is less than RuntimeException.

+        assertNull(r1.getThrown());

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        //Ensure null is less than Exception.

+        assertNull(r1.getThrown());

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByNullAndNullSeq() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+

+        assertTrue(r1.getSequenceNumber() < r2.getSequenceNumber());

+        assertNull(r1.getThrown());

+        assertNull(r2.getThrown());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByThrownAndThrownSeq() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+

+        assertTrue(r1.getSequenceNumber() < r2.getSequenceNumber());

+        assertTrue(r1.getSequenceNumber() < r2.getSequenceNumber());

+        r1.setThrown(new Error());

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() < r2.getSequenceNumber());

+        r1.setThrown(new RuntimeException());

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() < r2.getSequenceNumber());

+        r1.setThrown(new Exception());

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByThrownLenSeq() {

+        final int MAX_RUNS = 10;

+        final int MAX_LEN = 20;

+

+        for (int r = MAX_RUNS; r > 0; r--) {

+            for (int i = 0; i < MAX_LEN; i++) {

+                testByThrownLenSeq(Error.class, i, r);

+            }

+

+            for (int i = 0; i < MAX_LEN; i++) {

+                testByThrownLenSeq(RuntimeException.class, i, r);

+            }

+

+            for (int i = 0; i < MAX_LEN; i++) {

+                testByThrownLenSeq(Exception.class, i, r);

+            }

+        }

+    }

+

+    @Test

+    public void testByNormalLenSeq() {

+        final int MAX_RUNS = 10;

+        final int MAX_LEN = 20;

+

+        for (int r = MAX_RUNS; r > 0; r--) {

+            //Null is higher than normal so we have to start at one.

+            for (int i = 1; i <= MAX_LEN; i++) {

+                testByThrownLenSeq(InterruptedException.class, i, r);

+            }

+

+            for (int i = 1; i <= MAX_LEN; i++) {

+                testByThrownLenSeq(InterruptedIOException.class, i, r);

+            }

+

+            for (int i = 1; i <= MAX_LEN; i++) {

+                testByThrownLenSeq(ThreadDeath.class, i, r);

+            }

+        }

+    }

+

+    private void testByThrownLenSeq(

+            Class<? extends Throwable> t, int c1, int c2) {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+

+        assertTrue(r1.getSequenceNumber() < r2.getSequenceNumber());

+

+        setThrown(r1, t, c1);

+        setThrown(r2, t, c2);

+        assertRecordLessThan(r1, r2);

+    }

+

+    private Throwable create(Class<? extends Throwable> type, Throwable cause) {

+        try {

+            if (cause == null) {

+                return type.getConstructor().newInstance();

+            }

+

+            try {

+                return type.getConstructor(Throwable.class).newInstance(cause);

+            } catch (NoSuchMethodException tryInitCause) {

+                Throwable next = type.getConstructor().newInstance();

+                return next.initCause(cause);

+            }

+        } catch (InstantiationException ex) {

+            throw new AssertionError(ex);

+        } catch (IllegalAccessException ex) {

+            throw new AssertionError(ex);

+        } catch (IllegalArgumentException ex) {

+            throw new AssertionError(ex);

+        } catch (InvocationTargetException ex) {

+            throw new AssertionError(ex);

+        } catch (NoSuchMethodException ex) {

+            throw new AssertionError(ex);

+        }

+    }

+

+    private void setThrown(LogRecord r, Class<? extends Throwable> t, int d) {

+        for (int i = 0; i < d; i++) {

+            r.setThrown(create(t, r.getThrown()));

+        }

+    }

+

+    @Test

+    public void testByThrownAndInterrupted() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        swapSeq(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headIeChain(null));

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headIeChain(null));

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headIeChain(null));

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headIeChain(new Error()));

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headIeChain(new RuntimeException()));

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headIeChain(new Exception()));

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByThrownAndInterruptedSub() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        swapSeq(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubIeChain(null));

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubIeChain(null));

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubIeChain(null));

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubIeChain(new Error()));

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubIeChain(new RuntimeException()));

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubIeChain(new Exception()));

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByThrownAndInterruptedIoSub() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        swapSeq(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubIioeChain(null));

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubIioeChain(null));

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubIioeChain(null));

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByThrownAndThreadDeath() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        swapSeq(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headTdChain(null));

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headTdChain(null));

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headTdChain(null));

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headTdChain(new Error()));

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headTdChain(new RuntimeException()));

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headTdChain(new Exception()));

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testByThrownAndThreadDeathSub() {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        swapSeq(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubTdChain(null));

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubTdChain(null));

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubTdChain(null));

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubTdChain(new Error()));

+        r2.setThrown(new Error());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubTdChain(new RuntimeException()));

+        r2.setThrown(new RuntimeException());

+        assertRecordLessThan(r1, r2);

+

+        assertTrue(r1.getSequenceNumber() > r2.getSequenceNumber());

+        r1.setThrown(headSubTdChain(new Exception()));

+        r2.setThrown(new Exception());

+        assertRecordLessThan(r1, r2);

+    }

+

+    @Test

+    public void testOfClassLoadReadFailed() {

+        /**

+         * When exceptions triggers errors that is more important than the root

+         * cause.

+         */

+        Throwable clrf = new NoClassDefFoundError().initCause(

+                new ClassNotFoundException("", new IOException()));

+

+        Throwable readFailed = new IOException();

+

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+

+        r1.setThrown(readFailed);

+        r2.setThrown(clrf);

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(clrf);

+        r2.setThrown(readFailed);

+        assertRecordLessThan(r2, r1);

+    }

+

+    @Test

+    public void testOfClassLoadInterrupted() {

+        /**

+         * When exceptions triggers errors that is more important than the root

+         * cause.

+         */

+        Throwable cli = new NoClassDefFoundError().initCause(

+                new ClassNotFoundException("", headIioeChain(null)));

+

+        Throwable iioe = headIioeChain(null);

+

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+

+        r1.setThrown(iioe);

+        r2.setThrown(cli);

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(cli);

+        r2.setThrown(iioe);

+        assertRecordLessThan(r2, r1);

+    }

+

+    @Test

+    public void testOfNpeVsIoe() {

+        Throwable readFailed = new IOException();

+        Throwable npe = new IOException().initCause(new NullPointerException());

+

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+

+        r1.setThrown(readFailed);

+        r2.setThrown(npe);

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(npe);

+        r2.setThrown(readFailed);

+        assertRecordLessThan(r2, r1);

+    }

+

+    @Test

+    public void testArraySort() {

+        testArraySort(new SeverityComparator());

+    }

+

+    @Test

+    public void testReverseArraySort() {

+        testArraySort(Collections.reverseOrder(new SeverityComparator()));

+    }

+

+    private void testArraySort(Comparator<LogRecord> sc) {

+        LogRecord[] r = createRecords();

+        LogRecord[] copy = r.clone();

+        Arrays.sort(copy, sc);

+        assertTrue(r.length > 1);

+

+        //Ensure createRecords is not already sorted.

+        int orderChanged = 0;

+        for (int i = 0; i < r.length; i++) {

+            if (sc.compare(r[i], copy[i]) != 0) {

+                ++orderChanged;

+            }

+        }

+        assertTrue(String.valueOf(orderChanged), orderChanged > r.length / 2);

+

+        //Ensure sorted.

+        for (int i = 0; i < r.length - 1; i++) {

+            if (sc.compare(copy[i], copy[i + 1]) > 0) {

+                fail(toString(copy[i], copy[i + 1]));

+            }

+        }

+    }

+

+    @Test

+    public void testListSort() {

+        testListSort(new SeverityComparator());

+    }

+

+    @Test

+    public void testReverseListSort() {

+        testListSort(Collections.reverseOrder(new SeverityComparator()));

+    }

+

+    private void testListSort(Comparator<LogRecord> sc) {

+        List<LogRecord> a = Arrays.asList(createRecords());

+        Collections.sort(a, sc);

+

+        List<LogRecord> b = serialClone(a);

+        Collections.sort(b, sc);

+

+        assertEquals(a.size(), b.size());

+        for (int i = 0; i < a.size(); i++) {

+            LogRecord r1 = a.get(i);

+            LogRecord r2 = b.get(i);

+            if (sc.compare(r1, r2) != 0) {

+                throw new AssertionError(toString(i, r1, r2));

+            }

+        }

+    }

+

+    @Test

+    public void testTreeMap() {

+        testMap(new TreeMap<LogRecord, Boolean>(new SeverityComparator()));

+    }

+

+    @Test

+    public void testReverseTreeMap() {

+        testMap(new TreeMap<LogRecord, Boolean>(Collections

+                .reverseOrder(new SeverityComparator())));

+    }

+

+    private void testMap(SortedMap<LogRecord, Boolean> m) {

+        assertTrue(m.isEmpty());

+

+        for (LogRecord r : createRecords()) {

+            assertNull(m.put(r, Boolean.TRUE));

+        }

+

+        SortedMap<LogRecord, Boolean> copy = serialClone(m);

+        Iterator<LogRecord> keys = copy.keySet().iterator();

+        assertTrue(keys.hasNext());

+        int i = 0;

+        for (LogRecord r1 : m.keySet()) {

+            LogRecord r2 = keys.next();

+            if (m.comparator().compare(r1, r2) != 0) {

+                throw new AssertionError(toString(i, r1, r2));

+            }

+            ++i;

+        }

+

+        //Don't use containsAll.

+        assertFalse(m.isEmpty());

+        for (LogRecord r : m.keySet()) {

+            if (!copy.containsKey(r)) {

+                throw new AssertionError(toString(r));

+            }

+        }

+

+        //Don't use containsAll.

+        assertFalse(copy.isEmpty());

+        for (LogRecord r : copy.keySet()) {

+            if (!m.containsKey(r)) {

+                throw new AssertionError(toString(r));

+            }

+        }

+    }

+

+    @Test

+    public void testPriorityQueue() {

+        SeverityComparator sc = new SeverityComparator();

+        LogRecord[] rs = createRecords();

+        PriorityQueue<LogRecord> q1

+                = new PriorityQueue<>(rs.length, sc);

+        Collections.addAll(q1, rs);

+        PriorityQueue<LogRecord> q2 = serialClone(q1);

+

+        assertFalse(q1.isEmpty());

+        assertFalse(q2.isEmpty());

+

+        for (int i = 0; i < rs.length; i++) {

+            LogRecord r1 = q1.poll();

+            LogRecord r2 = q2.poll();

+            if (sc.compare(r1, r2) != 0) {

+                throw new AssertionError(toString(i, r1, r2));

+            }

+        }

+

+        assertTrue(q1.isEmpty());

+        assertTrue(q2.isEmpty());

+    }

+

+    @Test

+    @SuppressWarnings("ObjectEqualsNull")

+    public void testEquals() {

+        final SeverityComparator a = new SeverityComparator();

+        final SeverityComparator b = new SeverityComparator();

+        assertNotSame(a, b);

+

+        //NPE checks.

+        assertNotNull(a);

+        assertFalse(a.equals(null));

+

+        assertNotNull(b);

+        assertFalse(b.equals(null));

+

+        //Reflexive test.

+        assertTrue(a.equals(a));

+        assertTrue(b.equals(b));

+

+        //Transitive test.

+        assertTrue(a.equals(b));

+        assertTrue(b.equals(a));

+    }

+

+    @Test

+    public void testHashCode() {

+        final SeverityComparator a = new SeverityComparator();

+        final SeverityComparator b = new SeverityComparator();

+        assertNotSame(a, b);

+

+        assertTrue(a.equals(b));

+        assertTrue(b.equals(a));

+

+        assertEquals(a.hashCode(), b.hashCode());

+    }

+

+    @Test

+    public void testToString() {

+        final SeverityComparator a = new SeverityComparator();

+        assertNotNull(a.toString());

+        assertEquals(a.toString(), a.toString());

+    }

+

+    @Test

+    public void testJavaMailLinkage() throws Exception {

+        testJavaMailLinkage(SeverityComparator.class);

+    }

+

+    @Test

+    public void testLogManagerModifiers() throws Exception {

+        testLogManagerModifiers(SeverityComparator.class);

+    }

+

+    @Test

+    public void testWebappClassLoaderFieldNames() throws Exception {

+        testWebappClassLoaderFieldNames(SeverityComparator.class);

+    }

+

+    @Test

+    public void testSerializable() throws Exception {

+        final SeverityComparator a = new SeverityComparator();

+        final SeverityComparator b = serialClone(a);

+

+        assertTrue(a.equals(b));

+        assertTrue(b.equals(a));

+

+        assertEquals(a.hashCode(), b.hashCode());

+    }

+

+    @SuppressWarnings("unchecked")

+    private <T> T serialClone(T t) {

+        try {

+            final ByteArrayOutputStream os = new ByteArrayOutputStream();

+            final ObjectOutputStream out = new ObjectOutputStream(os);

+            try {

+                out.writeObject(t);

+                out.flush();

+            } finally {

+                out.close();

+            }

+

+            ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());

+            final ObjectInputStream in = new ObjectInputStream(is);

+            try {

+                return (T) in.readObject();

+            } finally {

+                in.close();

+            }

+        } catch (ClassNotFoundException CNFE) {

+            throw new AssertionError(CNFE);

+        } catch (IOException ioe) {

+            throw new AssertionError(ioe);

+        }

+    }

+

+    @Test

+    public void testByMillis() {

+        testByMillis(new Error());

+        testByMillis(new RuntimeException());

+        testByMillis(new Exception());

+    }

+

+    private void testByMillis(Throwable t) {

+        LogRecord r1 = new LogRecord(Level.INFO, Level.INFO.toString());

+        LogRecord r2 = new LogRecord(Level.INFO, Level.INFO.toString());

+        r2.setSequenceNumber(r1.getSequenceNumber());

+        setEpochMilli(r1, 10);

+        setEpochMilli(r2, 20);

+

+        assertEquals(r1.getLevel(), r2.getLevel());

+        assertEquals(r1.getThrown(), r2.getThrown());

+        assertEquals(r1.getSequenceNumber(), r2.getSequenceNumber());

+        assertEquals(r1.getThreadID(), r2.getThreadID());

+        assertEquals(r1.getClass(), r2.getClass());

+        assertTrue(r1.getMillis() < r2.getMillis());

+        assertRecordLessThan(r1, r2);

+

+        r1.setThrown(t);

+        r2.setThrown(r1.getThrown());

+        assertEquals(r1.getLevel(), r2.getLevel());

+        assertEquals(r1.getThrown(), r2.getThrown());

+        assertEquals(r1.getSequenceNumber(), r2.getSequenceNumber());

+        assertEquals(r1.getThreadID(), r2.getThreadID());

+        assertEquals(r1.getClass(), r2.getClass());

+        assertTrue(r1.getMillis() < r2.getMillis());

+        assertRecordLessThan(r1, r2);

+    }

+

+    private static void assertRecordLessThan(LogRecord r1, LogRecord r2) {

+        final SeverityComparator a = new SeverityComparator();

+        if (a.compare(r1, r2) >= 0) {

+            throw new AssertionError(toString(r1, r2));

+        }

+

+        if (a.compare(r2, r1) <= 0) {

+            throw new AssertionError(toString(r1, r2));

+        }

+    }

+

+    private static String toString(int i, LogRecord r1, LogRecord r2) {

+        return "Index: " + i + " " + toString(r1, r2);

+    }

+

+    private static String toString(LogRecord r1, LogRecord r2) {

+        return toString(r1) + '|' + toString(r2);

+    }

+

+    private static String toString(LogRecord r) {

+        StringBuilder b = new StringBuilder();

+        b.append(r.getLevel()).append(", ");

+        b.append(toString(r.getThrown()));

+        b.append(' ').append(r.getSequenceNumber());

+        return b.toString();

+    }

+

+    private static String toString(Throwable chain) {

+        StringBuilder b = new StringBuilder();

+        if (chain != null) {

+            String sep = "";

+            for (Throwable t = chain; t != null; t = t.getCause()) {

+                b.append(sep).append(t.getClass().getSimpleName());

+                sep = ", ";

+            }

+        } else {

+            b.append("null");

+        }

+        return b.toString();

+    }

+

+    private static void swapSeq(LogRecord r1, LogRecord r2) {

+        final long seq = r1.getSequenceNumber();

+        r1.setSequenceNumber(r2.getSequenceNumber());

+        r2.setSequenceNumber(seq);

+        assertFalse(r1.getSequenceNumber() == r2.getSequenceNumber());

+    }

+

+    private LogRecord[] createRecords() {

+        LogRecord[] r = new LogRecord[8];

+        r[0] = new LogRecord(Level.INFO, "Started {0}, {1}");

+        r[0].setParameters(new Object[]{"10", new Object()});

+        r[1] = new LogRecord(Level.CONFIG, "99%");

+        r[2] = new LogRecord(Level.SEVERE, "1st failure.");

+        r[2].setThrown(new ClassNotFoundException());

+        r[3] = new LogRecord(Level.SEVERE, "2nd failure.");

+        r[3].setThrown(new ExceptionInInitializerError(new NullPointerException()));

+        r[4] = new LogRecord(Level.SEVERE, "3rd failure.");

+        r[4].setThrown(new NoClassDefFoundError());

+        r[5] = new LogRecord(Level.WARNING, "Restart required.");

+        r[6] = new LogRecord(Level.INFO, "Restarting....");

+        r[7] = new LogRecord(Level.FINE, "Good bye.");

+        return r;

+    }

+

+    private Throwable headIeChain(Throwable last) {

+        return new InterruptedException(String.valueOf(last)).initCause(last);

+    }

+

+    private Throwable headIioeChain(Throwable last) {

+        return new InterruptedIOException(String.valueOf(last)).initCause(last);

+    }

+

+    private Throwable headCbieChain(Throwable last) {

+        return new ClosedByInterruptException().initCause(last);

+    }

+

+    private Throwable headFlieChain(Throwable last) {

+        return new FileLockInterruptionException().initCause(last);

+    }

+

+    private Throwable headSubIeChain(Throwable last) {

+        return new SubOfIe().initCause(last);

+    }

+

+    private Throwable headSubIioeChain(Throwable last) {

+        return new SubOfIioe().initCause(last);

+    }

+

+    private Throwable headTdChain(Throwable last) {

+        return new ThreadDeath().initCause(last);

+    }

+

+    private Throwable headSubTdChain(Throwable last) {

+        return new SubOfTd().initCause(last);

+    }

+

+    private Throwable headWidChain(Throwable last) {

+        return new WorkInterruptedException().initCause(last);

+    }

+

+    private Throwable headWitChain(Throwable last) {

+        return new WorkInterruptionException().initCause(last);

+    }

+

+    /**

+     * Lots of framework designers dislike checked exceptions.

+     */

+    private static class WorkInterruptionException extends RuntimeException {

+

+        private static final long serialVersionUID = 1L;

+

+        /**

+         * Promote access.

+         */

+        WorkInterruptionException() {

+        }

+    }

+

+    /**

+     * Lots of framework designers dislike checked exceptions.

+     */

+    private static class WorkInterruptedException extends RuntimeException {

+

+        private static final long serialVersionUID = 1L;

+

+        /**

+         * Promote access.

+         */

+        WorkInterruptedException() {

+        }

+    }

+

+    /**

+     * Sub class of interrupted.

+     */

+    private static class SubOfIe extends InterruptedException {

+

+        private static final long serialVersionUID = 1L;

+

+        /**

+         * Promote access.

+         */

+        SubOfIe() {

+        }

+    }

+

+    /**

+     * Sub class of interrupted IO.

+     */

+    private static class SubOfIioe extends InterruptedIOException {

+

+        private static final long serialVersionUID = 1L;

+

+        /**

+         * Promote access.

+         */

+        SubOfIioe() {

+        }

+    }

+

+    /**

+     * Sub class of thread death.

+     */

+    private static class SubOfTd extends ThreadDeath {

+

+        private static final long serialVersionUID = 1L;

+

+        /**

+         * Promote access.

+         */

+        SubOfTd() {

+        }

+    }

+}

diff --git a/mail/src/test/java/javax/mail/URLNameTest.java b/mail/src/test/java/javax/mail/URLNameTest.java
new file mode 100644
index 0000000..26ef800
--- /dev/null
+++ b/mail/src/test/java/javax/mail/URLNameTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail;
+
+import java.net.URL;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the URLName class.
+ *
+ * XXX - for now, just some simple regression tests for reported bugs.
+ */
+public class URLNameTest {
+ 
+    @Test
+    public void testReflexiveEquality() throws Exception {
+	URLName u = new URLName("test");
+	assertEquals(u, u);	// bug 6365
+	u = new URLName("imap://test.com/INBOX");
+	assertEquals(u, u);
+    }
+
+    /**
+     * Test that the getFile method returns the file part *without*
+     * the separator character.  This behavior is different than the
+     * URL or URI classes but needs to be preserved for compatibility.
+     */
+    @Test
+    public void testFile() throws Exception {
+	URLName u = new URLName("http://host/file");
+	assertEquals("file", u.getFile());
+	u = new URLName("http://host:123/file");
+	assertEquals("file", u.getFile());
+	u = new URLName("http://host/");
+	assertEquals("", u.getFile());
+	u = new URLName("http://host");
+	assertEquals(null, u.getFile());
+	u = new URLName("http://host:123");
+	assertEquals(null, u.getFile());
+    }
+
+    /**
+     * Test that the getURL method returns a URL with the same value
+     * as the URLName.
+     */
+    @Test
+    public void testURL() throws Exception {
+	// Note: must use a protocol supported by the URL class
+	URLName u = new URLName("http://host/file");
+	assertEquals("file", u.getFile());
+	URL url = u.getURL();
+	assertEquals(u.toString(), url.toString());
+	u = new URLName("http://host:123/file");
+	url = u.getURL();
+	assertEquals(u.toString(), url.toString());
+	u = new URLName("http://host:123/");
+	url = u.getURL();
+	assertEquals(u.toString(), url.toString());
+	u = new URLName("http://host:123");
+	url = u.getURL();
+	assertEquals(u.toString(), url.toString());
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/AddAddressHeaderTest.java b/mail/src/test/java/javax/mail/internet/AddAddressHeaderTest.java
new file mode 100644
index 0000000..b1d337c
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/AddAddressHeaderTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.MessagingException;
+import javax.mail.Message;
+import javax.mail.internet.MimeMessage;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that "add" methods for address headers result in only a single
+ * address header, per RFC 2822.
+ */
+public class AddAddressHeaderTest {
+ 
+    private static Session s = Session.getInstance(new Properties());
+    private static InternetAddress[] setList = new InternetAddress[1];
+    private static InternetAddress[] addList = new InternetAddress[1];
+
+    static {
+	try {
+	    setList[0] = new InternetAddress("me@example.com");
+	    addList[0] = new InternetAddress("you@example.com");
+	} catch (MessagingException ex) {
+	}
+    }
+
+    @Test
+    public void testFrom() throws Exception {
+        MimeMessage m = new MimeMessage(s);
+	m.setFrom(setList[0]);
+	m.addFrom(addList);
+	m.saveChanges();
+	String[] h = m.getHeader("From");
+	assertEquals(1, h.length);
+    }
+
+    @Test
+    public void testTo() throws Exception {
+	testRecipients(Message.RecipientType.TO);
+    }
+
+    @Test
+    public void testCc() throws Exception {
+	testRecipients(Message.RecipientType.CC);
+    }
+
+    @Test
+    public void testBcc() throws Exception {
+	testRecipients(Message.RecipientType.BCC);
+    }
+
+    private void testRecipients(Message.RecipientType type) throws Exception {
+        MimeMessage m = new MimeMessage(s);
+	m.setRecipients(type, setList);
+	m.addRecipients(type, addList);
+	m.saveChanges();
+	// XXX - depends on RecipientType.toString
+	String[] h = m.getHeader(type.toString());
+	assertEquals(1, h.length);
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/AddFromTest.java b/mail/src/test/java/javax/mail/internet/AddFromTest.java
new file mode 100644
index 0000000..a2a48ee
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/AddFromTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.internet.AddressException;
+import javax.mail.internet.MimeMessage;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the MimeMultipart.addFrom method, for bug 5057742.
+ */
+public class AddFromTest {
+ 
+    private static final Session s = Session.getInstance(new Properties());
+    private static final String ADDR = "a@example.com";
+    private static final InternetAddress iaddr;
+    private static final InternetAddress[] addresses;
+    static {
+	InternetAddress ia = null;
+	try {
+	    ia = new InternetAddress(ADDR);
+	} catch (AddressException ex) {
+	    // can't happen
+	} finally {
+	    iaddr = ia;
+	}
+	addresses = new InternetAddress[] { iaddr };
+    }
+
+    @Test
+    public void testNoFrom() throws Exception {
+        MimeMessage m = new MimeMessage(s);
+	m.addFrom(addresses);
+	assertEquals("Number of From headers", 1, m.getHeader("From").length);
+	assertEquals("From header", ADDR, m.getHeader("From", ","));
+    }
+
+    @Test
+    public void testOneFrom() throws Exception {
+        MimeMessage m = new MimeMessage(s);
+	m.setFrom(iaddr);
+	m.addFrom(addresses);
+	assertEquals("Number of From headers", 1, m.getHeader("From").length);
+	assertEquals("From header", ADDR + ", " + ADDR,
+	    m.getHeader("From", ","));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/AllowEncodedMessages.java b/mail/src/test/java/javax/mail/internet/AllowEncodedMessages.java
new file mode 100644
index 0000000..7b9f90a
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/AllowEncodedMessages.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import com.sun.mail.test.AsciiStringInputStream;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.MessagingException;
+import javax.mail.BodyPart;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test "mail.mime.allowencodedmessages" System property.
+ */
+public class AllowEncodedMessages {
+ 
+    private static Session s = Session.getInstance(new Properties());
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("AllowEncodedMessages");
+	System.setProperty("mail.mime.allowencodedmessages", "true");
+    }
+
+    @Test
+    public void testEncodedMessages() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	BodyPart bp = mp.getBodyPart(0);
+	assertEquals("message/rfc822", bp.getContentType());
+
+	MimeMessage m2 = (MimeMessage)bp.getContent();
+	assertEquals("text/plain", m2.getContentType());
+	assertEquals("test message\r\n", m2.getContent());
+    }
+
+    @AfterClass
+    public static void after() {
+	// should be unnecessary
+	System.clearProperty("mail.mime.allowencodedmessages");
+    }
+
+    private static MimeMessage createMessage() throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Subject: Example\n" +
+	    "Content-Type: multipart/mixed; boundary=\"-\"\n" +
+	    "\n" +
+	    "---\n" +
+	    "Content-Type: message/rfc822\n" +
+	    "Content-Transfer-Encoding: base64\n" +
+	    "\n" +
+	    "TWltZS1WZXJzaW9uOiAxLjANClN1YmplY3Q6IH" +
+	    "Rlc3QNCkNvbnRlbnQtVHlwZTogdGV4dC9wbGFp\n" +
+	    "bg0KDQp0ZXN0IG1lc3NhZ2UNCg==\n" +
+	    "\n" +
+	    "-----\n";
+
+	return new MimeMessage(s, new AsciiStringInputStream(content));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/AppleFileNames.java b/mail/src/test/java/javax/mail/internet/AppleFileNames.java
new file mode 100644
index 0000000..5d6d477
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/AppleFileNames.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that the "mail.mime.applefilenames" System property
+ * causes the filename to be returned properly.
+ */
+public class AppleFileNames {
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("AppleFileNames");
+	System.setProperty("mail.mime.applefilenames", "true");
+    }
+
+    @Test
+    public void testProp() throws Exception {
+	ParameterList pl = new ParameterList("; filename=a b.txt");
+	assertEquals(pl.get("filename"), "a b.txt");
+    }
+
+    @AfterClass
+    public static void after() {
+	// should be unnecessary
+	System.clearProperty("mail.mime.applefilenames");
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/ContentDispositionNoStrict.java b/mail/src/test/java/javax/mail/internet/ContentDispositionNoStrict.java
new file mode 100644
index 0000000..c920d3c
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ContentDispositionNoStrict.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the property that contols ContentDisposition non-strict mode
+ */
+public class ContentDispositionNoStrict {
+
+    @BeforeClass
+    public static void before() {
+        System.setProperty("mail.mime.contentdisposition.strict", "false");
+    }
+
+    @Test
+    public void testDecode() throws Exception {
+        try {
+            ContentDisposition cd = new ContentDisposition("\"/non/standard/stuff/here.csv\"");
+            assertNull("Content disposition must parse to null in non-strict mode", cd.getDisposition());
+        } catch (ParseException px) {
+            fail("Exception must not be thrown in non-strict mode");
+        }
+    }
+
+    @Test
+    public void testDecodeWithParams() throws Exception {
+        try {
+            ContentDisposition cd = new ContentDisposition(" ; size=12345");
+            assertNull("Content disposition must parse to null in non-strict mode", cd.getDisposition());
+        } catch (ParseException px) {
+            fail("Exception must not be thrown in non-strict mode");
+        }
+    }
+
+    @AfterClass
+    public static void after() {
+        System.clearProperty("mail.mime.contentdisposition.strict");
+    }
+}
+
diff --git a/mail/src/test/java/javax/mail/internet/ContentDispositionStrict.java b/mail/src/test/java/javax/mail/internet/ContentDispositionStrict.java
new file mode 100644
index 0000000..a41efe5
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ContentDispositionStrict.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+/**
+ * Test the property that contols ContentDisposition non-strict mode
+ */
+public class ContentDispositionStrict {
+
+    @BeforeClass
+    public static void before() {
+        System.setProperty("mail.mime.contentdisposition.strict", "true");
+    }
+
+    @Test
+    public void testDecode() throws Exception {
+        try {
+            ContentDisposition cd = new ContentDisposition("\"/non/standard/stuff/here.csv\"");
+            fail("ParseException should be thrown on malformed content disposition");
+        } catch (ParseException expected) {
+            // the exception is the expected outcome in this test
+        }
+    }
+
+    @AfterClass
+    public static void after() {
+        System.clearProperty("mail.mime.contentdisposition.strict");
+    }
+}
+
diff --git a/mail/src/test/java/javax/mail/internet/ContentDispositionTestSuite.java b/mail/src/test/java/javax/mail/internet/ContentDispositionTestSuite.java
new file mode 100644
index 0000000..93ccf5b
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ContentDispositionTestSuite.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite.SuiteClasses;
+
+import com.sun.mail.test.ClassLoaderSuite;
+import com.sun.mail.test.ClassLoaderSuite.TestClass;
+
+/**
+ * Suite of ParameterList tests that need to be run in a separate class loader.
+ */
+@RunWith(ClassLoaderSuite.class)
+@TestClass(ContentDisposition.class)
+@SuiteClasses( {
+    ContentDispositionNoStrict.class,
+    ContentDispositionStrict.class
+})
+public class ContentDispositionTestSuite {
+}
diff --git a/mail/src/test/java/javax/mail/internet/ContentTypeTest.java b/mail/src/test/java/javax/mail/internet/ContentTypeTest.java
new file mode 100644
index 0000000..34e8735
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ContentTypeTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * Test the ContentType class.
+ *
+ * XXX - for now, just some simple regression tests for reported bugs.
+ */
+public class ContentTypeTest {
+ 
+    @Test
+    public void testMatch() throws Exception {
+	ContentType empty = new ContentType();
+	assertFalse(empty.match("text/plain"));
+	ContentType plain = new ContentType("text/plain");
+	assertTrue(plain.match("text/plain"));
+	assertFalse(empty.match(plain));
+	assertFalse(plain.match(empty));
+	assertTrue(plain.match("text/*"));
+	ContentType text = new ContentType("text/*");
+	assertTrue(text.match(plain));
+	assertTrue(plain.match(text));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/DecodeParameters.java b/mail/src/test/java/javax/mail/internet/DecodeParameters.java
new file mode 100644
index 0000000..374d587
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/DecodeParameters.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that the "mail.mime.decodeparameters" System property
+ * causes the parameters to be properly decoded.
+ */
+public class DecodeParameters extends ParameterListDecode {
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("DecodeParameters");
+	System.setProperty("mail.mime.decodeparameters", "true");
+    }
+
+    @Test
+    public void testDecode() throws Exception {
+	testDecode("paramdata");
+    }
+
+    @AfterClass
+    public static void after() {
+	// should be unnecessary
+	System.clearProperty("mail.mime.decodeparameters");
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/EncodeFileName.java b/mail/src/test/java/javax/mail/internet/EncodeFileName.java
new file mode 100644
index 0000000..f331ba0
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/EncodeFileName.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+
+import org.junit.*;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test "mail.mime.encodefilename" System property set.
+ */
+public class EncodeFileName extends NoEncodeFileName {
+ 
+    // depends on exactly how MimeUtility.encodeText splits long words
+    private static String expected1 =
+	"=?utf-8?B?w4DDgcOFw4bDgMOBw4XDhsOHw4jDicOKw4vDjMONw47Dj8OQw4DDgcOF?=";
+    private static String expected2 =
+	"=?utf-8?B?w4bDh8OIw4nDisOLw4zDjcOOw4/DkMORw5LDk8OUw5XDlsOYw5nDmsObw5w=?=";
+    private static String expected3 =
+	"=?utf-8?B?w53DnsOfw6DDocOiw6PDpMOlw6bDp8Oow6nDqsOrw6zDrcOuw6/DsMOx?=";
+    private static String expected4 =
+	"=?utf-8?B?w7LDs8O0w7XDtsO4w7nDusO7w7zDvcO+w7/DgMOBw4XDhsOHLmRvYw==?=";
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("EncodeFileName");
+	System.setProperty("mail.mime.charset", "utf-8");
+	System.setProperty("mail.mime.encodefilename", "true");
+	// assume mail.mime.encodeparamters defaults to true
+	System.clearProperty("mail.mime.encodeparamters");
+    }
+
+    @Test
+    @Override
+    public void test() throws Exception {
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setText("test");
+	mbp.setFileName(fileName);
+	mbp.updateHeaders();
+	String h = mbp.getHeader("Content-Type", "");
+	assertTrue(h.contains("name="));
+	assertTrue(h.contains(expected1));
+	assertTrue(h.contains(expected2));
+	assertTrue(h.contains(expected3));
+	assertTrue(h.contains(expected4));
+	h = mbp.getHeader("Content-Disposition", "");
+	assertTrue(h.contains("filename="));
+	assertTrue(h.contains(expected1));
+	assertTrue(h.contains(expected2));
+	assertTrue(h.contains(expected3));
+	assertTrue(h.contains(expected4));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/EncodeFileNameNoEncodeParameters.java b/mail/src/test/java/javax/mail/internet/EncodeFileNameNoEncodeParameters.java
new file mode 100644
index 0000000..1db08af
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/EncodeFileNameNoEncodeParameters.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+
+/**
+ * Test "mail.mime.encodefilename" System property set to "true"
+ * and "mail.mime.encodeparameters" set to "false".
+ */
+public class EncodeFileNameNoEncodeParameters extends EncodeFileName {
+ 
+    @BeforeClass
+    public static void before() {
+	System.out.println("EncodeFileNameNoEncodeParameters");
+	System.setProperty("mail.mime.charset", "utf-8");
+	System.setProperty("mail.mime.encodefilename", "true");
+	System.setProperty("mail.mime.encodeparamters", "false");
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/FoldTest.java b/mail/src/test/java/javax/mail/internet/FoldTest.java
new file mode 100644
index 0000000..39f49d8
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/FoldTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.*;
+import java.util.*;
+import javax.mail.internet.MimeUtility;
+
+import org.junit.Test;
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test header folding.
+ *
+ * @author Bill Shannon
+ */
+
+@RunWith(Parameterized.class)
+public class FoldTest {
+    private String direction;
+    private String orig;
+    private String expect;
+
+    private static List<Object[]> testData;
+
+    public FoldTest(String direction, String orig, String expect) {
+	this.direction = direction;
+	this.orig = orig;
+	this.expect = expect;
+    }
+
+    @Parameters
+    public static Collection<Object[]> data() throws IOException {
+	testData = new ArrayList<>();
+	parse(new BufferedReader(new InputStreamReader(
+	    FoldTest.class.getResourceAsStream("folddata"))));
+	return testData;
+    }
+
+    /**
+     * Read the data from the test file.  Format is multiple of any of
+     * the following:
+     *
+     * FOLD\nString$\nEXPECT\nString$\n
+     * UNFOLD\nString$\nEXPECT\nString$\n
+     * BOTH\nString$\n
+     */
+    private static void parse(BufferedReader in) throws IOException {
+	String line;
+	while ((line = in.readLine()) != null) {
+	    if (line.startsWith("#") || line.length() == 0)
+		continue;
+	    String orig = readString(in);
+	    if (line.equals("BOTH")) {
+		testData.add(new Object[] { line, orig, null });
+	    } else {
+		String e = in.readLine();
+		if (!e.equals("EXPECT"))
+		    throw new IOException("TEST DATA FORMAT ERROR");
+		String expect = readString(in);
+		testData.add(new Object[] { line, orig, expect });
+	    }
+	}
+    }
+
+    /**
+     * Read a string that ends with '$', preserving all characters,
+     * especially including CR and LF.
+     */
+    private static String readString(BufferedReader in) throws IOException {
+	StringBuffer sb = new StringBuffer();
+	int c;
+	while ((c = in.read()) != '$')
+	    sb.append((char)c);
+	in.readLine();	// throw away rest of line
+	return sb.toString();
+    }
+
+    @Test
+    public void testFold() {
+	if (direction.equals("BOTH")) {
+	    String fs = MimeUtility.fold(0, orig);
+	    String us = MimeUtility.unfold(fs);
+	    Assert.assertEquals(orig, us);
+	} else if (direction.equals("FOLD")) {
+	    Assert.assertEquals("Fold", expect, MimeUtility.fold(0, orig));
+	} else if (direction.equals("UNFOLD")) {
+	    Assert.assertEquals("Unfold", expect, MimeUtility.unfold(orig));
+	} else {
+	    Assert.fail("Unknown direction: " + direction);
+	}
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/GetLocalAddressTest.java b/mail/src/test/java/javax/mail/internet/GetLocalAddressTest.java
new file mode 100644
index 0000000..a4432e4
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/GetLocalAddressTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.MessagingException;
+import javax.mail.internet.InternetAddress;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the InternetAddress.getLocalAddress() method.
+ */
+public class GetLocalAddressTest {
+ 
+    private static String localhost;
+    static {
+	try {
+	    localhost = InetAddress.getLocalHost().getCanonicalHostName();
+	} catch (UnknownHostException ex) {
+	    localhost = "localhost";
+	}
+    }
+
+    @Test
+    public void testUserName() throws Exception {
+	System.setProperty("user.name", "Joe");
+	InternetAddress ia = InternetAddress.getLocalAddress(null);
+	assertEquals("Joe@" + localhost, ia.getAddress());
+    }
+
+    @Test
+    public void testUserNameSession() throws Exception {
+	System.setProperty("user.name", "Joe");
+	Session s = Session.getInstance(new Properties());
+	InternetAddress ia = InternetAddress.getLocalAddress(s);
+	assertEquals("Joe@" + localhost, ia.getAddress());
+    }
+
+    @Test
+    public void testMailFrom() throws Exception {
+	System.setProperty("user.name", "Joe");
+	Properties p = new Properties();
+	p.setProperty("mail.from", "Bob@home");
+	Session s = Session.getInstance(p);
+	InternetAddress ia = InternetAddress.getLocalAddress(s);
+	assertEquals("Bob@home", ia.getAddress());
+    }
+
+    @Test
+    public void testMailFromAddress() throws Exception {
+	System.setProperty("user.name", "Joe");
+	Properties p = new Properties();
+	p.setProperty("mail.from", "Bob <Bob@home>");
+	Session s = Session.getInstance(p);
+	InternetAddress ia = InternetAddress.getLocalAddress(s);
+	assertEquals("Bob@home", ia.getAddress());
+    }
+
+    @Test
+    public void testMailUser() throws Exception {
+	System.setProperty("user.name", "Joe");
+	Properties p = new Properties();
+	p.setProperty("mail.user", "Bob");
+	Session s = Session.getInstance(p);
+	InternetAddress ia = InternetAddress.getLocalAddress(s);
+	assertEquals("Bob@" + localhost, ia.getAddress());
+    }
+
+    @Test
+    public void testMailUserSpace() throws Exception {
+	System.setProperty("user.name", "Joe");
+	Properties p = new Properties();
+	p.setProperty("mail.user", "NETWORK SERVICE");
+	Session s = Session.getInstance(p);
+	InternetAddress ia = InternetAddress.getLocalAddress(s);
+	assertEquals("\"NETWORK SERVICE\"@" + localhost, ia.getAddress());
+    }
+
+    @Test
+    public void testMailHost() throws Exception {
+	System.setProperty("user.name", "Joe");
+	Properties p = new Properties();
+	p.setProperty("mail.user", "Bob");
+	p.setProperty("mail.host", "home");
+	Session s = Session.getInstance(p);
+	InternetAddress ia = InternetAddress.getLocalAddress(s);
+	assertEquals("Bob@home", ia.getAddress());
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/HeaderTokenizerTest.java b/mail/src/test/java/javax/mail/internet/HeaderTokenizerTest.java
new file mode 100644
index 0000000..12c1086
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/HeaderTokenizerTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.*;
+import java.util.*;
+import javax.mail.*;
+import javax.mail.internet.HeaderTokenizer;
+import javax.mail.internet.ParseException;
+
+import org.junit.Test;
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test MIME HeaderTokenizer.
+ *
+ * @author Bill Shannon
+ */
+
+@RunWith(Parameterized.class)
+public class HeaderTokenizerTest {
+    private String header;
+    private String value;
+    private String[] expect;
+
+    static boolean gen_test_input = false;	// output good for input to -p
+    static boolean parse_mail = false;		// parse input in mail format
+    static boolean return_comments = false;	// return comments as tokens
+    static boolean mime = false;		// use MIME specials
+    static int errors = 0;			// number of errors detected
+
+    static boolean junit;
+    static List<Object[]> testData;
+
+    public HeaderTokenizerTest(String heder, String value, String[] expect) {
+	this.header = header;
+	this.value = value;
+	this.expect = expect;
+    }
+
+    @Parameters
+    public static Collection<Object[]> data() throws IOException {
+	junit = true;
+	testData = new ArrayList<>();
+	parse(new BufferedReader(new InputStreamReader(
+	    InternetAddressTest.class.getResourceAsStream("tokenlist"))));
+	return testData;
+    }
+
+    @Test
+    public void test() {
+	test(header, value, expect);
+    }
+
+    public static void main(String argv[]) throws Exception {
+	int optind;
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-")) {
+		// ignore
+	    } else if (argv[optind].equals("-g")) {
+		gen_test_input = true;
+	    } else if (argv[optind].equals("-p")) {
+		parse_mail = true;
+	    } else if (argv[optind].equals("-c")) {
+		return_comments = true;
+	    } else if (argv[optind].equals("-m")) {
+		mime = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+		    "Usage: tokenizertest [-g] [-p] [-c] [-m] [-] [header ...]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	/*
+	 * If there's any args left on the command line,
+	 * concatenate them into a string and test that.
+	 */
+	if (optind < argv.length) {
+	    StringBuffer sb = new StringBuffer();
+	    for (int i = optind; i < argv.length; i++) {
+		sb.append(argv[i]);
+		sb.append(" ");
+	    }
+	    test("To", sb.toString(), null);
+	} else {
+	    // read from stdin
+	    BufferedReader in =
+		new BufferedReader(new InputStreamReader(System.in));
+	    String s;
+
+	    if (parse_mail)
+		parse(in);
+	    else {
+		while ((s = in.readLine()) != null)
+		    test("To", s, null);
+	    }
+	}
+	System.exit(errors);
+
+    }
+
+    /*
+     * Parse the input in "mail" format, extracting the From, To, and Cc
+     * headers and testing them.  The parse is rather crude, but sufficient
+     * to test against most existing UNIX mailboxes.
+     */
+    public static void parse(BufferedReader in) throws IOException {
+	String header = "";
+
+	for (;;) {
+	    String s = in.readLine();
+	    if (s != null && s.length() > 0) {
+		char c = s.charAt(0);
+		if (c == ' ' || c == '\t') {
+		    // a continuation line, add it to the current header
+		    header += '\n' + s;
+		    continue;
+		}
+	    }
+	    // "s" is the next header, "header" is the last complete header
+	    if (header.startsWith("From: ") ||
+		    header.startsWith("To: ") ||
+		    header.startsWith("Cc: ")) {
+		int i;
+		String[] expect = null;
+		if (s != null && s.startsWith("Expect: ")) {
+		    try {
+			int nexpect = Integer.parseInt(s.substring(8));
+			expect = new String[nexpect];
+			for (i = 0; i < nexpect; i++)
+			    expect[i] = in.readLine().trim();
+		    } catch (NumberFormatException e) {
+			try {
+			    if (s.substring(8, 17).equals("Exception")) {
+				expect = new String[1];
+				expect[0] = "Exception";
+			    }
+			} catch (StringIndexOutOfBoundsException se) {
+			    // ignore it
+			}
+		    }
+		}
+		i = header.indexOf(':');
+		try {
+		    if (junit)
+			testData.add(new Object[] {
+			    header.substring(0, i),
+			    header.substring(i + 2),
+			    expect });
+		    else
+			test(header.substring(0, i), header.substring(i + 2),
+			    expect);
+		} catch (StringIndexOutOfBoundsException e) {
+		    // ignore
+		}
+	    }
+	    if (s == null)
+		return;		// EOF
+	    if (s.length() == 0) {
+		while ((s = in.readLine()) != null) {
+		    if (s.startsWith("From "))
+			break;
+		}
+		if (s == null)
+		    return;
+	    }
+	    header = s;
+	}
+    }
+
+    /**
+     * Test the header's value to see if we can tokenize it as expected.
+     */
+    public static void test(String header, String value, String expect[]) {
+	PrintStream out = System.out;
+	if (gen_test_input)
+	    out.println(header + ": " + value);
+	else if (!junit)
+	    out.println("Test: " + value);
+
+	try {
+	    HeaderTokenizer ht = new HeaderTokenizer(value,
+			mime ? HeaderTokenizer.MIME : HeaderTokenizer.RFC822,
+			!return_comments);
+	    HeaderTokenizer.Token tok;
+	    Vector<HeaderTokenizer.Token> toklist
+		    = new Vector<>();
+	    while ((tok = ht.next()).getType() != HeaderTokenizer.Token.EOF)
+		toklist.addElement(tok);
+	    if (gen_test_input)
+		out.println("Expect: " + toklist.size());
+	    else {
+		if (junit) {
+		    Assert.assertEquals("Number of tokens",
+			expect.length, toklist.size());
+		} else {
+		    out.println("Got " + toklist.size() + " tokens:");
+		    if (expect != null && toklist.size() != expect.length) {
+			out.println("Expected " + expect.length + " tokens");
+			errors++;
+		    }
+		}
+	    }
+	    for (int i = 0; i < toklist.size(); i++) {
+		tok = toklist.elementAt(i);
+		if (gen_test_input)
+		    out.println("\t" + type(tok.getType()) +
+						"\t" + tok.getValue());
+		else {
+		    if (!junit)
+			out.println("\t[" + (i+1) + "] " + type(tok.getType()) +
+						"\t" + tok.getValue());
+		    if (expect != null && i < expect.length) {
+			HeaderTokenizer.Token t = makeToken(expect[i]);
+			if (junit) {
+			    Assert.assertEquals("Token type",
+				t.getType(), tok.getType());
+			    Assert.assertEquals("Token value",
+				t.getValue(), tok.getValue());
+			} else {
+			    if (t.getType() != tok.getType() ||
+				!t.getValue().equals(tok.getValue())) {
+				out.println("\tExpected:\t" +
+				    type(t.getType()) + "\t" + t.getValue());
+				errors++;
+			    }
+			}
+		    }
+		}
+	    }
+	} catch (ParseException e) {
+	    if (gen_test_input)
+		out.println("Expect: Exception " + e);
+	    else {
+		if (junit) {
+		    Assert.assertTrue("Expected exception",
+			expect.length == 1 && expect[0].equals("Exception"));
+		} else {
+		    out.println("Got Exception: " + e);
+		    if (expect != null &&
+		       (expect.length != 1 || !expect[0].equals("Exception"))) {
+			out.println("Expected " + expect.length + " tokens");
+			for (int i = 0; i < expect.length; i++)
+			    out.println("\tExpected:\t" + expect[i]);
+			errors++;
+		    }
+		}
+	    }
+	}
+    }
+
+    private static String type(int t) {
+	if (t == HeaderTokenizer.Token.ATOM)
+	    return "ATOM";
+	else if (t == HeaderTokenizer.Token.QUOTEDSTRING)
+	    return "QUOTEDSTRING";
+	else if (t == HeaderTokenizer.Token.COMMENT)
+	    return "COMMENT";
+	else if (t == HeaderTokenizer.Token.EOF)
+	    return "EOF";
+	else if (t < 0)
+	    return "UNKNOWN";
+	else
+	    return "SPECIAL";
+    }
+
+    private static int type(String s) {
+	if (s.equals("ATOM"))
+	    return HeaderTokenizer.Token.ATOM;
+	else if (s.equals("QUOTEDSTRING"))
+	    return HeaderTokenizer.Token.QUOTEDSTRING;
+	else if (s.equals("COMMENT"))
+	    return HeaderTokenizer.Token.COMMENT;
+	else if (s.equals("EOF"))
+	    return HeaderTokenizer.Token.EOF;
+	else // if (s.equals("SPECIAL"))
+	    return 0;
+    }
+
+    private static HeaderTokenizer.Token makeToken(String line) {
+	int i = line.indexOf('\t');
+	int t = type(line.substring(0, i));
+	String value = line.substring(i + 1);
+	if (t == 0)
+	    return new HeaderTokenizer.Token(value.charAt(0), value);
+	else
+	    return new HeaderTokenizer.Token(t, value);
+    }
+
+    private static final String n(String s) {
+	return s == null ? "<null>" : s;
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/InternetAddressExtraTest.java b/mail/src/test/java/javax/mail/internet/InternetAddressExtraTest.java
new file mode 100644
index 0000000..0f2fa73
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/InternetAddressExtraTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.AddressException;
+
+import org.junit.Test;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Test Internet address parsing that can't be tested using paramterized test.
+ *
+ * @author Bill Shannon
+ */
+
+public class InternetAddressExtraTest {
+    @Test
+    public void testNewlineInDomainLiteral() throws Exception {
+	InternetAddress ia = new InternetAddress("test@[\r\nfoo]", "test");
+	try {
+	    ia.validate();
+	    fail("validation succeeded");
+	} catch (AddressException ex) {
+	    // success!
+	}
+    }
+
+    @Test
+    public void testNewlineInLocal() throws Exception {
+	InternetAddress ia =
+	    new InternetAddress("\"test\r\nfoo\"@example.com", "test");
+	try {
+	    ia.validate();
+	    fail("validation succeeded");
+	} catch (AddressException ex) {
+	    // success!
+	}
+    }
+
+    @Test
+    public void testNewlineInLocalWithWhitespace() throws Exception {
+	InternetAddress ia =
+	    new InternetAddress("\"test\r\n foo\"@example.com", "test");
+	ia.validate();
+	// success!
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/InternetAddressFoldTest.java b/mail/src/test/java/javax/mail/internet/InternetAddressFoldTest.java
new file mode 100644
index 0000000..4270822
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/InternetAddressFoldTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.*;
+import java.util.*;
+import javax.mail.internet.InternetAddress;
+
+import org.junit.Test;
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test InternetAddress folding.
+ *
+ * @author Bill Shannon
+ */
+
+@RunWith(Parameterized.class)
+public class InternetAddressFoldTest {
+    private InternetAddress[] orig;
+    private String expect;
+
+    private static List<Object[]> testData;
+
+    public InternetAddressFoldTest(InternetAddress[] orig, String expect) {
+	this.orig = orig;
+	this.expect = expect;
+    }
+
+    @Parameters
+    public static Collection<Object[]> data() throws Exception {
+	testData = new ArrayList<>();
+	parse(new BufferedReader(new InputStreamReader(
+	    InternetAddressFoldTest.class.getResourceAsStream("addrfolddata"))));
+	return testData;
+    }
+
+    /**
+     * Read the data from the test file.  Format is:
+     *
+     * FOLD N
+     * address1$
+     * ...
+     * addressN$
+     * EXPECT
+     * address1, ..., addressN$
+     */
+    private static void parse(BufferedReader in) throws Exception {
+	String line;
+	while ((line = in.readLine()) != null) {
+	    if (line.startsWith("#") || line.length() == 0)
+		continue;
+	    if (!line.startsWith("FOLD"))
+		throw new IOException("TEST DATA FORMAT ERROR, MISSING FOLD");
+	    int count = Integer.parseInt(line.substring(5));
+	    InternetAddress[] orig = new InternetAddress[count];
+	    for (int i = 0; i < count; i++)
+		orig[i] = new InternetAddress(readString(in));
+	    String e = in.readLine();
+	    if (!e.equals("EXPECT"))
+		throw new IOException("TEST DATA FORMAT ERROR, MISSING EXPECT");
+	    String expect = readString(in);
+	    testData.add(new Object[] { orig, expect });
+	}
+    }
+
+    /**
+     * Read a string that ends with '$', preserving all characters,
+     * especially including CR and LF.
+     */
+    private static String readString(BufferedReader in) throws IOException {
+	StringBuffer sb = new StringBuffer();
+	int c;
+	while ((c = in.read()) != '$')
+	    sb.append((char)c);
+	in.readLine();	// throw away rest of line
+	return sb.toString();
+    }
+
+    @Test
+    public void testFold() {
+	Assert.assertEquals("Fold", expect, InternetAddress.toString(orig, 0));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/InternetAddressTest.java b/mail/src/test/java/javax/mail/internet/InternetAddressTest.java
new file mode 100644
index 0000000..7c5f753
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/InternetAddressTest.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.*;
+import java.util.*;
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.AddressException;
+
+import org.junit.Test;
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test Internet address parsing.
+ *
+ * @author Bill Shannon
+ */
+
+@RunWith(Parameterized.class)
+public class InternetAddressTest {
+    private String headerName;
+    private String headerValue;
+    private String[] expected;
+    private boolean doStrict;
+    private boolean doParseHeader;
+
+    static boolean strict = false;		// enforce strict RFC822 syntax
+    static boolean gen_test_input = false;	// output good for input to -p
+    static boolean parse_mail = false;		// parse input in mail format
+    static boolean parse_header = false;	// use parseHeader method?
+    static boolean verbose;			// print progress?
+    static int errors = 0;			// number of errors detected
+
+    static boolean junit;
+    static List<Object[]> testData;
+
+    public InternetAddressTest(String headerName, String headerValue,
+	    String[] expected, boolean doStrict, boolean doParseHeader) {
+	this.headerName = headerName;
+	this.headerValue = headerValue;
+	this.expected = expected;
+	this.doStrict = doStrict;
+	this.doParseHeader = doParseHeader;
+    }
+
+    @Parameters
+    public static Collection<Object[]> data() throws IOException {
+	junit = true;
+	testData = new ArrayList<>();
+	parse(new BufferedReader(new InputStreamReader(
+	    InternetAddressTest.class.getResourceAsStream("addrlist"))));
+	return testData;
+    }
+
+    public static void main(String argv[]) throws Exception {
+	verbose = true;		// default for standalone
+	int optind;
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-")) {
+		// ignore
+	    } else if (argv[optind].equals("-g")) {
+		gen_test_input = true;
+	    } else if (argv[optind].equals("-h")) {
+		parse_header = true;
+	    } else if (argv[optind].equals("-p")) {
+		parse_mail = true;
+	    } else if (argv[optind].equals("-s")) {
+		strict = true;
+	    } else if (argv[optind].equals("-q")) {
+		verbose = false;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+		"Usage: addrtest [-g] [-h] [-p] [-s] [-q] [-] [address ...]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	/*
+	 * If there's any args left on the command line,
+	 * concatenate them into a string and test that.
+	 */
+	if (optind < argv.length) {
+	    StringBuffer sb = new StringBuffer();
+	    for (int i = optind; i < argv.length; i++) {
+		sb.append(argv[i]);
+		sb.append(" ");
+	    }
+	    test("To", sb.toString(), null, strict, parse_header);
+	} else {
+	    // read from stdin
+	    BufferedReader in =
+		new BufferedReader(new InputStreamReader(System.in));
+	    String s;
+
+	    if (parse_mail)
+		parse(in);
+	    else {
+		while ((s = in.readLine()) != null)
+		    test("To", s, null, strict, parse_header);
+	    }
+	}
+	System.exit(errors);
+
+    }
+
+    /*
+     * Parse the input in "mail" format, extracting the From, To, and Cc
+     * headers and testing them.  The parse is rather crude, but sufficient
+     * to test against most existing UNIX mailboxes.
+     */
+    public static void parse(BufferedReader in) throws IOException {
+	String header = "";
+	boolean doStrict = strict;
+	boolean doParseHeader = parse_header;
+
+	for (;;) {
+	    String s = in.readLine();
+	    if (s != null && s.length() > 0) {
+		char c = s.charAt(0);
+		if (c == ' ' || c == '\t') {
+		    // a continuation line, add it to the current header
+		    header += '\n' + s;
+		    continue;
+		}
+	    }
+	    // "s" is the next header, "header" is the last complete header
+	    if (header.startsWith("Strict: ")) {
+		doStrict = Boolean.parseBoolean(value(header));
+	    } else if (header.startsWith("Header: ")) {
+		doParseHeader = Boolean.parseBoolean(value(header));
+	    } else if (header.startsWith("From: ") ||
+		    header.startsWith("To: ") ||
+		    header.startsWith("Cc: ")) {
+		int i;
+		String[] expect = null;
+		if (s != null && s.startsWith("Expect: ")) {
+		    try {
+			int nexpect = Integer.parseInt(s.substring(8));
+			expect = new String[nexpect];
+			for (i = 0; i < nexpect; i++)
+			    expect[i] = readLine(in).trim();
+		    } catch (NumberFormatException e) {
+			try {
+			    if (s.substring(8, 17).equals("Exception")) {
+				expect = new String[1];
+				expect[0] = "Exception";
+			    }
+			} catch (StringIndexOutOfBoundsException se) {
+			    // ignore it
+			}
+		    }
+		}
+		i = header.indexOf(':');
+		try {
+		    if (junit)
+			testData.add(new Object[] {
+			    header.substring(0, i), header.substring(i + 2),
+			    expect, doStrict, doParseHeader });
+		    else
+			test(header.substring(0, i), header.substring(i + 2),
+			    expect, doStrict, doParseHeader);
+		} catch (StringIndexOutOfBoundsException e) {
+		    e.printStackTrace(System.out);
+		}
+	    }
+	    if (s == null)
+		return;		// EOF
+	    if (s.length() == 0) {
+		while ((s = in.readLine()) != null) {
+		    if (s.startsWith("From "))
+			break;
+		}
+		if (s == null)
+		    return;
+	    }
+	    header = s;
+	}
+    }
+
+    private static String value(String header) {
+	return header.substring(header.indexOf(':') + 1).trim();
+    }
+
+    /**
+     * Read an "expected" line, handling continuations
+     * (backslash at end of line).  If line ends with
+     * two backslashes, it's not a continuation, just a
+     * line that ends with a single backslash.
+     */
+    private static String readLine(BufferedReader in) throws IOException {
+	String line = in.readLine();
+	if (!line.endsWith("\\"))
+	    return line;
+	if (line.endsWith("\\\\"))
+	    return line.substring(0, line.length() - 1);
+	StringBuilder sb = new StringBuilder(line);
+	sb.setCharAt(sb.length() - 1, '\n');
+	for (;;) {
+	    line = in.readLine();
+	    sb.append(line);
+	    if (!line.endsWith("\\"))
+		break;
+	    if (line.endsWith("\\\\")) {
+		sb.setLength(sb.length() - 1);
+		break;
+	    }
+	    sb.setCharAt(sb.length() - 1, '\n');
+	}
+	return sb.toString();
+    }
+
+    @Test
+    public void testAddress() {
+	test(headerName, headerValue, expected, doStrict, doParseHeader);
+    }
+
+    /**
+     * Test the header's value to see if we can parse it as expected.
+     */
+    public static void test(String header, String value, String expect[],
+		boolean doStrict, boolean doParseHeader) {
+	PrintStream out = System.out;
+	if (gen_test_input)
+	    pr(header + ": " + value);
+	else
+	    pr("Test: " + value);
+
+	try {
+	    InternetAddress[] al;
+	    if (doParseHeader)
+		al = InternetAddress.parseHeader(value, doStrict);
+	    else
+		al = InternetAddress.parse(value, doStrict);
+	    if (gen_test_input)
+		pr("Expect: " + al.length);
+	    else {
+		pr("Got " + al.length + " addresses:");
+		if (expect != null && al.length != expect.length) {
+		    pr("Expected " + expect.length + " addresses");
+		    if (junit)
+			Assert.assertEquals("For " + value +
+			    " number of addresses",
+			    al.length, expect.length);
+		    errors++;
+		}
+	    }
+	    for (int i = 0; i < al.length; i++) {
+		if (gen_test_input)
+		    pr("\t" + al[i].getAddress());	// XXX - escape newlines
+		else {
+		    pr("\t[" + (i+1) + "] " + al[i].getAddress() +
+			"\t\tPersonal: " + n(al[i].getPersonal()));
+		    if (expect != null && i < expect.length &&
+				!expect[i].equals(al[i].getAddress())) {
+			pr("\tExpected:\t" + expect[i]);
+			if (junit)
+			    Assert.assertEquals("For " + value +
+				" address[" + i + "]",
+				expect[i], al[i].getAddress());
+			errors++;
+		    }
+		}
+	    }
+
+	    if (al.length == 0)
+		return;
+
+	    /*
+	     * Some of the really bad addresses fail the toString
+	     * tests, but we don't want them to cause build failures.
+	     */
+	    if (junit)
+		return;
+
+	    /*
+	     * As a sanity test, convert the address array to a string and
+	     * then parse it again, to see if we get the same thing back.
+	     */
+	    try {
+		InternetAddress[] al2;
+		String ta = InternetAddress.toString(al);
+		if (doParseHeader)
+		    al2 = InternetAddress.parseHeader(ta, doStrict);
+		else
+		    al2 = InternetAddress.parse(ta, doStrict);
+		if (al.length != al2.length) {
+		    pr("toString FAILED!!!");
+		    pr("Expected length " + al.length +
+				", got " + al2.length);
+		    if (junit)
+			Assert.assertEquals("For " + value +
+			    " toString number of addresses",
+			    al.length, al2.length);
+		    errors++;
+		} else {
+		    for (int i = 0; i < al.length; i++) {
+			if (!al[i].getAddress().equals(al2[i].getAddress())) {
+			    pr("toString FAILED!!!");
+			    pr("Expected address " +
+					al[i].getAddress() +
+					", got " + al2[i].getAddress());
+			    if (junit)
+				Assert.assertEquals("For " + value +
+				    " toString " + ta + " address[" + i + "]",
+				    al[i].getAddress(), al2[i].getAddress());
+			    errors++;
+			}
+			String p1 = al[i].getPersonal();
+			String p2 = al2[i].getPersonal();
+			if (!(p1 == p2 || (p1 != null && p1.equals(p2)))) {
+			    pr("toString FAILED!!!");
+			    pr("Expected personal " + n(p1) +
+					", got " + n(p2));
+			    if (junit)
+				Assert.assertEquals("For " + value +
+				    " toString " + ta + " personal[" + i + "]",
+				    p1, p2);
+			    errors++;
+			}
+		    }
+		}
+	    } catch (AddressException e2) {
+		pr("toString FAILED!!!");
+		pr("Got Exception: " + e2);
+		if (junit)
+		    Assert.fail("For " + value +
+				" toString got Exception: " + e2);
+		errors++;
+	    }
+	} catch (AddressException e) {
+	    if (gen_test_input)
+		pr("Expect: Exception " + e);
+	    else {
+		pr("Got Exception: " + e);
+		if (expect != null &&
+		   (expect.length != 1 || !expect[0].equals("Exception"))) {
+		    pr("Expected " + expect.length + " addresses");
+		    for (int i = 0; i < expect.length; i++)
+			pr("\tExpected:\t" + expect[i]);
+		    if (junit)
+			Assert.fail("For " + value + " expected " +
+			    expect.length + "addresses, got Exception: " + e);
+		    errors++;
+		}
+	    }
+	}
+    }
+
+    private static final void pr(String s) {
+	if (verbose)
+	    System.out.println(s);
+    }
+
+    private static final String n(String s) {
+	return s == null ? "<null>" : s;
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/InternetHeadersTest.java b/mail/src/test/java/javax/mail/internet/InternetHeadersTest.java
new file mode 100644
index 0000000..a96688c
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/InternetHeadersTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import com.sun.mail.test.AsciiStringInputStream;
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.Enumeration;
+
+import javax.mail.*;
+
+import org.junit.*;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the InternetHeaders class.
+ */
+public class InternetHeadersTest {
+ 
+    private static final String initialWhitespaceHeader =
+						" \r\nSubject: test\r\n\r\n";
+    private static final String initialContinuationHeader =
+						" Subject: test\r\n\r\n";
+
+    /**
+     * Test that a continuation line is handled properly.
+     */
+    @Test
+    public void testContinuationLine() throws Exception {
+	String header = "Subject: a\r\n b\r\n\r\n";
+	InternetHeaders ih = new InternetHeaders(
+		new AsciiStringInputStream(header));
+	assertEquals(1, ih.getHeader("Subject").length);
+	assertEquals("a\r\n b", ih.getHeader("Subject")[0]);
+    }
+
+    /**
+     * Test that a whitespace line at the beginning is ignored.
+     */
+    @Test
+    public void testInitialWhitespaceLineConstructor() throws Exception {
+	InternetHeaders ih = new InternetHeaders(
+		new AsciiStringInputStream(initialWhitespaceHeader));
+	testInitialWhitespaceLine(ih);
+    }
+
+    /**
+     * Test that a whitespace line at the beginning is ignored.
+     */
+    @Test
+    public void testInitialWhitespaceLineLoad() throws Exception {
+	InternetHeaders ih = new InternetHeaders();
+	ih.load(new AsciiStringInputStream(initialWhitespaceHeader));
+	testInitialWhitespaceLine(ih);
+    }
+
+    private void testInitialWhitespaceLine(InternetHeaders ih)
+				throws Exception {
+	assertEquals(1, ih.getHeader("Subject").length);
+	assertEquals("test", ih.getHeader("Subject")[0]);
+	Enumeration<Header> e = ih.getAllHeaders();
+	while (e.hasMoreElements()) {
+	    Header h = e.nextElement();
+	    assertEquals("Subject", h.getName());
+	    assertEquals("test", h.getValue());
+	}
+    }
+
+    /**
+     * Test that a continuation line at the beginning is handled.
+     */
+    @Test
+    public void testInitialContinuationLineConstructor() throws Exception {
+	InternetHeaders ih = new InternetHeaders(
+		new AsciiStringInputStream(initialContinuationHeader));
+	testInitialContinuationLine(ih);
+    }
+
+    /**
+     * Test that a continuation line at the beginning is handled.
+     */
+    @Test
+    public void testInitialContinuationLineLoad() throws Exception {
+	InternetHeaders ih = new InternetHeaders();
+	ih.load(new AsciiStringInputStream(initialContinuationHeader));
+	testInitialContinuationLine(ih);
+    }
+
+    private void testInitialContinuationLine(InternetHeaders ih)
+				throws Exception {
+	assertEquals(1, ih.getHeader("Subject").length);
+	assertEquals("test", ih.getHeader("Subject")[0]);
+	Enumeration<Header> e = ih.getAllHeaders();
+	while (e.hasMoreElements()) {
+	    Header h = e.nextElement();
+	    assertEquals("Subject", h.getName());
+	    assertEquals("test", h.getValue());
+	}
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/MailDateFormatTest.java b/mail/src/test/java/javax/mail/internet/MailDateFormatTest.java
new file mode 100644
index 0000000..c421609
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/MailDateFormatTest.java
@@ -0,0 +1,516 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.text.DateFormat;
+import java.text.DateFormatSymbols;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import org.junit.Test;
+
+/**
+ * Test MailDateFormat: formatting and parsing of dates as specified by
+ * <a href="http://www.ietf.org/rfc/rfc2822.txt">RFC 2822</a>.
+ *
+ * @author	Anthony Vanelverdinghe
+ */
+public class MailDateFormatTest {
+
+    /*
+     * Serialization
+     */
+    @Test
+    public void mustRetainBinaryCompatibility()
+            throws IOException, ClassNotFoundException, ParseException {
+        verifySerialization("MailDateFormat_old.ser");
+        verifySerialization("MailDateFormat_new.ser");
+    }
+
+    private void verifySerialization(String resourceName)
+            throws IOException, ClassNotFoundException {
+        MailDateFormat fmt = readSerializedFormat(resourceName);
+
+        assertThat(fmt.getTimeZone().getID(), is("America/Los_Angeles"));
+
+        Date date = mustPass(fmt, "Thu, 1 Jan 2015 00:00:00 +0000");
+        assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000 (UTC)");
+
+        assertThat(fmt.format(date),
+                is("Wed, 31 Dec 2014 16:00:00 -0800 (PST)"));
+    }
+
+    private MailDateFormat readSerializedFormat(String resourceName)
+            throws ClassNotFoundException, IOException {
+        InputStream resource = null;
+        ObjectInputStream in = null;
+        try {
+            resource = this.getClass().getResourceAsStream(resourceName);
+            in = new ObjectInputStream(resource);
+            return (MailDateFormat) in.readObject();
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    /*
+     * Formatting + parsing
+     */
+    @Test
+    public void mustSucceedRoundTrip() throws ParseException {
+        Date date = new Date(1341100798000L); // milliseconds must be 0
+        SimpleDateFormat fmt = getDefault();
+        assertThat(fmt.parse(fmt.format(date)), is(date));
+    }
+
+    /*
+     * Formatting
+     */
+    @Test()
+    public void formatMustThrowNpeForNullArgs() {
+        for (int mask = 0; mask < 7; mask++) {
+            try {
+                Date date = (mask & 1) == 1
+                        ? new Date() : null;
+                StringBuffer toAppendTo = (mask & 2) == 2
+                        ? new StringBuffer() : null;
+                FieldPosition pos = (mask & 4) == 4
+                        ? new FieldPosition(0) : null;
+                getDefault().format(date, toAppendTo, pos);
+                fail("No NullPointerException thrown for mask = " + mask);
+            } catch (NullPointerException e) {
+            }
+        }
+    }
+
+    @Test
+    public void mustFormatRfc2822WithOptionalCfws() {
+        Date date = mustPass(getDefault(), "1 Jan 2015 00:00 +0000");
+        assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000 (UTC)");
+    }
+
+    @Test
+    public void mustUseTimeZoneForFormatting() {
+        String input = "1 Jan 2015 00:00 +0100";
+        SimpleDateFormat fmt = getDefault();
+        Date date = mustPass(fmt, input);
+        fmt.setTimeZone(TimeZone.getTimeZone("Etc/GMT+8"));
+        assertThat(fmt.format(date),
+                is("Wed, 31 Dec 2014 15:00:00 -0800 (GMT-08:00)"));
+    }
+
+    /*
+     * Parsing
+     */
+    @Test
+    public void parseMustThrowNpeForNullArgs() {
+        for (int mask = 0; mask < 3; mask++) {
+            try {
+                String source = (mask & 1) == 1
+                        ? "1 Jan 2015 00:00 +0100" : null;
+                ParsePosition pos = (mask & 2) == 2
+                        ? new ParsePosition(0) : null;
+                getDefault().parse(source, pos);
+                fail("No NullPointerException thrown for mask = " + mask);
+            } catch (NullPointerException e) {
+            }
+        }
+    }
+
+    @Test
+    public void mustReturnOnInvalidParsePosition() {
+        assertNull(getDefault().parse("", new ParsePosition(-1)));
+        assertNull(getDefault().parse("", new ParsePosition(0)));
+        assertNull(getDefault().parse("", new ParsePosition(1)));
+    }
+
+    @Test
+    public void mustParseRfc2822() {
+        mustPass(getDefault(), "Thu, 1 Jan 2015 00:00:00 +0000");
+        mustFail(getStrict(), "foo", 0);
+        mustFail(getLenient(), "foo", 3);
+    }
+
+    @Test
+    public void mustRetainTimeZoneForBackwardCompatibility() {
+        // though java.text.DateFormat::parse(String, ParsePosition) specifies:
+        // "This parsing operation uses the calendar to produce a Date. As a
+        // result, the calendar's date-time fields and the TimeZone value
+        // may have been overwritten, depending on subclass implementations.
+        // Any TimeZone value that has previously been set by a call to
+        // setTimeZone may need to be restored for further operations."
+        // we must retain the TimeZone value for backward compatibility reasons
+        SimpleDateFormat fmt = getDefault();
+        fmt.setTimeZone(TimeZone.getTimeZone("Europe/Brussels"));
+        mustPass(fmt, "1 Jan 2015 00:00 +0000");
+        assertThat(fmt.getTimeZone(),
+                is(TimeZone.getTimeZone("Europe/Brussels")));
+    }
+
+    @Test
+    public void mustUseLeniencyForParsing() {
+        String lenientInput = "1-Jan-2015 00:00 +0000";
+        String strictInput = "1 Jan 2015 00:00 +0000";
+
+        SimpleDateFormat fmt = getStrict();
+        mustFail(fmt, lenientInput, 1);
+        mustPass(fmt, strictInput);
+
+        fmt.setLenient(true);
+        mustPass(fmt, lenientInput);
+        mustPass(fmt, strictInput);
+
+        fmt.setLenient(false);
+        mustFail(fmt, lenientInput, 1);
+        mustPass(fmt, strictInput);
+    }
+
+    @Test
+    public void mustReportCorrectErrorIndex() {
+        mustFail(getDefault(), "1 Juk 2015 00:00 +0000", 2);
+        mustFail(getStrict(), "1\r\nJan 2015 00:00 +0000", 1);
+        mustFail(getStrict(), "1 Jan 201 00:00 +0000", 6);
+    }
+
+    @Test
+    public void lenientMustBeBackwardCompatible() {
+        mustPass(getLenient(), "Thu, 1 Jan 2015 12:00 +0000");
+        mustPass(getLenient(), "32 dEC 2015 12:00 +0099");
+        mustPass(getLenient(), "fooFri,999-Jan-99999999 99:99:99+2399");
+        mustPass(getLenient(), "fooFri,999-Jan-99999999 99:99:99+2399");
+        mustPass(getLenient(), "fooFri ,999Jan99999999 99:99:99+2399");
+        mustPass(getLenient(), "foo31-mAY-1\r3:3:3+");
+    }
+
+    @Test
+    public void strictMustRejectInvalidBegin() {
+        mustFail(getStrict(), "foo1 Jan 2015 00:00 +0000", 0);
+    }
+
+    @Test
+    public void strictMustRejectSemanticallyInvalidDate() {
+        mustFail(getStrict(), "Fri, 1 Jan 2015 00:00 +0000", 27);
+    }
+
+    /*
+     * Parsing - range constraints
+     */
+    @Test
+    public void mustRejectFieldsWithTooManyDigits() {
+        mustFail(getDefault(), "1234 Jan 2015 00:00 +0000", 0);
+        mustFail(getDefault(), "1 Jan 123456789 00:00 +0000", 6);
+        mustFail(getDefault(), "1 Jan 2015 123:00 +0000", 11);
+        mustFail(getDefault(), "1 Jan 2015 00:123 +0000", 14);
+        mustFail(getDefault(), "1 Jan 2015 00:00:123 +0000", 17);
+    }
+
+    @Test
+    public void mustCountLeadingZeroesForTooManyDigits() {
+        mustFail(getDefault(), "0000000000001 Jan 2015 00:00 +0000", 0);
+    }
+
+    /*
+     * Parsing - overflow
+     */
+    @Test
+    public void mustFailFastIfDayContainsTooManyDigits() {
+        mustFail(getLenient(),
+		"100000000000000000000000000000001 Jan 2015 00:00 +0000", 0);
+    }
+
+    @Test
+    public void mustFailFastIfYearContainsTooManyDigits() {
+        mustFail(getLenient(),
+		"1 Jan 100000000000000000000000000000002015 00:00 +0000", 6);
+    }
+
+    @Test
+    public void mustFailIfYearIsTooBigForGregorianCalendar() {
+        int tooBig = 999999999;
+        assertTrue(tooBig > new GregorianCalendar().getMaximum(Calendar.YEAR));
+        mustFail(getLenient(), "1 Jan " + tooBig + " 00:00 +0000", 6);
+    }
+
+    @Test
+    public void mustFailOnIntegerOverflow() {
+        mustFail(getLenient(), "456378941961 Jan 2015 00:00 +0000", 0);
+    }
+
+    /*
+     * Parsing - FWS (folding white space)
+     */
+    @Test
+    public void mustFailOrSkipCfws() {
+        String input = "(3 Jan 2015 00:00 +0000) 1 Jan 2015 00:00 +0000";
+        try {
+            // NOTE this test fails with getLenient(),
+            // since lenient parsing must remain backward compatible
+            Date date = getStrict().parse(input);
+            assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000 (UTC)");
+        } catch (ParseException ignored) {
+            assertTrue("Not supporting CFWS is allowed", true);
+        }
+    }
+
+    @Test
+    public void lenientMustAcceptEmptyFwsIffUnambiguous() {
+        mustPass(getLenient(), "Thu,1Jan2015 12:00+0000");
+        mustFail(getLenient(), "1 Jan 1015123456:00 +0000", 6);
+    }
+
+    @Test
+    public void lenientMustRejectInconsistentMonthFws() {
+        mustFail(getLenient(), "1-Jan 2015 00:00 +0000", 5);
+    }
+
+    @Test
+    public void strictMustRejectInvalidFws() {
+        mustFail(getStrict(), "\r\n\r\n 1 Jan 2015 00:00 +0000", 0);
+        mustFail(getStrict(), "\r\n1 Jan 2015 00:00 +0000", 0);
+        mustFail(getStrict(), "\n 1 Jan 2015 00:00 +0000", 0);
+        mustFail(getStrict(), " \n 1 Jan 2015 00:00 +0000", 1);
+        mustFail(getStrict(), " \r\n1 Jan 2015 00:00 +0000", 0);
+    }
+
+    /*
+     * Parsing - day-name and month-name
+     */
+    @Test
+    public void mustAcceptValidDayMonthNames() {
+        SimpleDateFormat fmt = getDefault();
+        for (String dayMonth : new String[]{
+            "Mon, 5 Jan", "Tue, 3 Feb", "Wed, 4 Mar", "Thu, 2 Apr",
+            "Fri, 1 May", "Sat, 6 Jun", "Sun, 5 Jul", "Sat, 1 Aug",
+            "Tue, 1 Sep", "Thu, 1 Oct", "Sun, 1 Nov", "Tue, 1 Dec"
+        }) {
+            mustPass(fmt, dayMonth + " 2015 00:00:00 +0000");
+        }
+    }
+
+    @Test
+    public void strictMustMatchDayNameCaseSensitive() {
+        mustFail(getStrict(), "tHU, 1 Jan 2015 00:00 +0000", 0);
+    }
+
+    @Test
+    public void strictMustMatchMonthNameCaseSensitive() {
+        mustFail(getStrict(), "1 jAN 2015 00:00 +0000", 2);
+    }
+
+    /*
+     * Parsing - year
+     */
+    @Test
+    public void lenientMustAcceptSingleDigitYears() {
+        mustPass(getLenient(), "1 Jan 1 00:00 +0000");
+    }
+
+    @Test
+    public void lenientMustAcceptYearsBetween1000and1899() {
+        mustPass(getLenient(), "1 Jan 999 00:00 +0000");
+        mustPass(getLenient(), "1 Jan 1000 00:00 +0000");
+        mustPass(getLenient(), "1 Jan 1899 00:00 +0000");
+        mustPass(getStrict(), "1 Jan 1900 00:00 +0000");
+    }
+
+    @Test
+    public void lenientMustAdd1900To3DigitYear() {
+        Date date = mustPass(getLenient(), "1 Jul 900 00:00 +0000");
+        assertThatDate(date, "Sat, 1 Jul 2800 00:00:00 +0000 (UTC)");
+    }
+
+    @Test
+    public void strictMustRejectAllYearsLt1900() {
+        mustFail(getStrict(), "1 Jan 000001 00:00 +0000", 6);
+        mustFail(getStrict(), "1 Jan 999 00:00 +0000", 6);
+        mustFail(getStrict(), "1 Jan 1000 00:00 +0000", 6);
+        mustFail(getStrict(), "1 Jan 1899 00:00 +0000", 6);
+        mustPass(getStrict(), "1 Jan 1900 00:00 +0000");
+    }
+
+    /*
+     * Parsing - second
+     */
+    @Test
+    public void mustParseLeapSecondAsJsr310() {
+        // JSR-310 replaces 60 with 59
+        Date date = mustPass(getDefault(), "30 Jun 2012 23:59:60 +0000");
+        // Date.from(ISO_INSTANT.parse("2012-06-30T23:59:60Z", Instant::from))
+        assertThat(date, is(new Date(1341100799000L)));
+    }
+
+    /*
+     * Parsing - zone
+     */
+    @Test
+    public void mustParseNegativeZeroesZoneAsUtc() {
+        Date date = mustPass(getDefault(), "1 Jan 2015 00:00 -0000");
+        assertThatDate(date, "Thu, 1 Jan 2015 00:00:00 +0000 (UTC)");
+    }
+
+    @Test
+    public void mustCorrectlyParseInputWithTrailingDigits() {
+        Date date = mustPass(getStrict(), "1 Jan 2015 00:00 -001530");
+        assertThatDate(date, "Thu, 1 Jan 2015 00:15:00 +0000 (UTC)");
+    }
+
+    @Test
+    public void mustAcceptZoneWithExtremeOffset() {
+        Date date = mustPass(getStrict(), "1 Jan 2015 00:00 +9959");
+        Date equivalentWithoutOffset = mustPass(getStrict(),
+                "27 Dec 2014 20:01 +0000");
+        assertThat(date, is(equivalentWithoutOffset));
+
+        date = mustPass(getStrict(), "1 Jan 2015 00:00 -9959");
+        equivalentWithoutOffset = mustPass(getStrict(),
+                "5 Jan 2015 03:59 +0000");
+        assertThat(date, is(equivalentWithoutOffset));
+    }
+
+    @Test
+    public void lenientMustAcceptNonMilitaryZoneNames() {
+        SimpleDateFormat fmt = getLenient();
+        for (String zoneName : new String[]{"GMT", "UT",
+            "EDT", "EST", "CDT", "CST", "MDT", "MST", "PDT", "PST"}) {
+            mustPass(fmt, "1 Jan 2015 00:00 " + zoneName);
+        }
+    }
+
+    @Test
+    public void strictMustMatchZone() {
+        mustFail(getStrict(), "1 Jan 2015 00:00", 16);
+    }
+
+    @Test
+    public void strictMustRejectZoneOffsetMinutesGreaterThan60() {
+        mustFail(getStrict(), "1 Jan 2015 00:00 +0060", 17);
+    }
+
+    /*
+     * Unsupported methods. When possible, the test also demonstrates 
+     * why invoking the method must be prohibited.
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void mustProhibitSetCalendar() {
+        getDefault().setCalendar(Calendar.getInstance());
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void mustProhibitSetNumberFormat() {
+        getDefault().setNumberFormat(NumberFormat.getInstance());
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void mustProhibitApplyLocalizedPattern() {
+        SimpleDateFormat fmt = getStrict();
+        fmt.applyLocalizedPattern("yyyy");
+        Date date = mustPass(fmt, "1 Jan 2015 00:00:00 +0000");
+        assertThat(fmt.format(date), is("2015"));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void mustProhibitApplyPattern() {
+        SimpleDateFormat fmt = getStrict();
+        fmt.applyPattern("yyyy");
+        Date date = mustPass(fmt, "1 Jan 2015 00:00:00 +0000");
+        assertThat(fmt.format(date), is("2015"));
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void mustProhibitGet2DigitYearStart() {
+        getDefault().get2DigitYearStart();
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void mustProhibitSet2DigitYearStart() {
+        getDefault().set2DigitYearStart(new Date());
+    }
+
+    @Test(expected = UnsupportedOperationException.class)
+    public void mustProhibitSetDateFormatSymbols() {
+        SimpleDateFormat fmt = getStrict();
+        fmt.setDateFormatSymbols(new DateFormatSymbols(Locale.FRENCH));
+        Date date = mustPass(fmt, "1 Jan 2015 00:00:00 +0000");
+        assertThatDate(date, "jeu., 1 janv. 2015 00:00:00 +0000 (UTC)");
+    }
+
+    /*
+     * Helper methods
+     */
+    private static SimpleDateFormat getDefault() {
+        return new MailDateFormat();
+    }
+
+    private static SimpleDateFormat getLenient() {
+        SimpleDateFormat fmt = getDefault();
+        fmt.setLenient(true);
+        return fmt;
+    }
+
+    private static SimpleDateFormat getStrict() {
+        SimpleDateFormat fmt = getDefault();
+        fmt.setLenient(false);
+        return fmt;
+    }
+
+    private void assertThatDate(Date date, String formattedDate) {
+        SimpleDateFormat fmt = getDefault();
+        fmt.setTimeZone(TimeZone.getTimeZone("UTC"));
+        assertThat(fmt.format(date), is(formattedDate));
+    }
+
+    private Date mustPass(DateFormat fmt, String input) {
+        try {
+            Date date = fmt.parse(input);
+            assertNotNull(date);
+            return date;
+        } catch (ParseException e) {
+            fail(String.format("'%s' is a valid date in %s mode", input,
+                    fmt.isLenient() ? "lenient" : "strict"));
+            throw new AssertionError();
+        }
+    }
+
+    private void mustFail(DateFormat fmt, String input, int errorOffset) {
+        try {
+            Date result = fmt.parse(input);
+            fail(String.format("'%s' is not a valid date in %s mode", input,
+                    fmt.isLenient() ? "lenient" : "strict"));
+        } catch (ParseException e) {
+            assertThat(e.getErrorOffset(), is(errorOffset));
+        }
+    }
+
+}
diff --git a/mail/src/test/java/javax/mail/internet/MimeBodyPartTest.java b/mail/src/test/java/javax/mail/internet/MimeBodyPartTest.java
new file mode 100644
index 0000000..8c7875a
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/MimeBodyPartTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2011, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+import javax.activation.DataHandler;
+
+import javax.mail.*;
+
+import com.sun.mail.test.AsciiStringInputStream;
+
+import org.junit.*;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+
+/**
+ * Test the MimeBodyPart class.
+ */
+public class MimeBodyPartTest {
+ 
+    private static String[] languages = new String[] {
+	    "language1", "language2", "language3", "language4", "language5",
+	    "language6", "language7", "language8", "language9", "language10",
+	    "language11", "language12", "language13", "language14", "language15"
+	};
+
+    /**
+     * Test that the Content-Language header is properly folded
+     * if there are a lot of languages.
+     */
+    @Test
+    public void testContentLanguageFold() throws Exception {
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setContentLanguage(languages);
+	String header = mbp.getHeader("Content-Language", ",");
+	assertTrue(header.indexOf("\r\n") > 0);
+
+	String[] langs = mbp.getContentLanguage();
+	assertArrayEquals(languages, langs);
+    }
+
+    /**
+     * Test that copying a DataHandler from one message to another
+     * has the desired effect.
+     */
+    @Test
+    public void testCopyDataHandler() throws Exception {
+	Session s = Session.getInstance(new Properties());
+	// create a message and extract the DataHandler for a part
+	MimeMessage orig = createMessage(s);
+	MimeMultipart omp = (MimeMultipart)orig.getContent();
+	MimeBodyPart obp = (MimeBodyPart)omp.getBodyPart(0);
+	DataHandler dh = obp.getDataHandler();
+	// create a new message and use the DataHandler
+	MimeMessage msg = new MimeMessage(s);
+	MimeMultipart mp = new MimeMultipart();
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setDataHandler(dh);
+	mp.addBodyPart(mbp);
+	msg.setContent(mp);
+	// depend on copy constructor streaming the data
+	msg = new MimeMessage(msg);
+	mp = (MimeMultipart)msg.getContent();
+	mbp = (MimeBodyPart)mp.getBodyPart(0);
+	assertEquals("text/x-test", mbp.getContentType());
+	assertEquals("quoted-printable", mbp.getEncoding());
+	assertEquals("test part", getString(mbp.getInputStream()));
+    }
+
+    /**
+     * Test that copying a DataHandler from one message to another
+     * by setting the "dh" field in a subclass has the desired effect.
+     */
+    @Test
+    public void testSetDataHandler() throws Exception {
+	Session s = Session.getInstance(new Properties());
+	// create a message and extract the DataHandler for a part
+	MimeMessage orig = createMessage(s);
+	MimeMultipart omp = (MimeMultipart)orig.getContent();
+	MimeBodyPart obp = (MimeBodyPart)omp.getBodyPart(0);
+	final DataHandler odh = obp.getDataHandler();
+	// create a new message and use the DataHandler
+	MimeMessage msg = new MimeMessage(s);
+	MimeMultipart mp = new MimeMultipart();
+	MimeBodyPart mbp = new MimeBodyPart() {
+		{ dh = odh; }
+	    };
+	mp.addBodyPart(mbp);
+	msg.setContent(mp);
+	// depend on copy constructor streaming the data
+	msg = new MimeMessage(msg);
+	mp = (MimeMultipart)msg.getContent();
+	mbp = (MimeBodyPart)mp.getBodyPart(0);
+	assertEquals("text/x-test", mbp.getContentType());
+	assertEquals("quoted-printable", mbp.getEncoding());
+	assertEquals("test part", getString(mbp.getInputStream()));
+    }
+
+    /**
+     * Test that a MimeBodyPart created from a stream with unencoded data
+     * will have the data be encoded when the data is copied to another
+     * MimeBodyPart by copying the DataHandler.
+     */
+    @Test
+    public void testEncodingCopiedDataHandler() throws Exception {
+	String part = 
+	    "Content-Type: application/x-test\n" +
+	    "\n" +
+	    "\u0001\u0002\u0003" +
+	    "\n";
+	MimeBodyPart mbp = new MimeBodyPart(new AsciiStringInputStream(part));
+	MimeBodyPart mbp2 = new MimeBodyPart() {
+	    @Override
+	    public void setDataHandler(DataHandler dh)
+						throws MessagingException {
+		super.setDataHandler(dh);
+		updateHeaders();
+	    }
+	};
+	mbp2.setDataHandler(mbp.getDataHandler());
+	assertEquals("base64", mbp2.getEncoding());
+	// ensure the data is correct by reading the first byte
+	InputStream in = mbp2.getInputStream();
+	assertEquals(1, in.read());
+	in.close();
+    }
+
+    /**
+     * Test that isMimeType does something reasonable even if the
+     * Content-Type header can't be parsed because of a bad parameter.
+     */
+    @Test
+    public void testIsMimeTypeBadParameter() throws Exception {
+	String part = 
+	    "Content-Type: application/x-test; type=a/b\n" +
+	    "\n" +
+	    "\n";
+	MimeBodyPart mbp = new MimeBodyPart(new AsciiStringInputStream(part));
+	assertTrue("complete MIME type", mbp.isMimeType("application/x-test"));
+	assertTrue("pattern MIME type", mbp.isMimeType("application/*"));
+	assertFalse("wrong MIME type", mbp.isMimeType("application/test"));
+    }
+
+    /**
+     * Test that a Content-Transfer-Encoding header with no value doesn't
+     * cause an IOException.
+     */
+    @Test
+    public void testEmptyContentTransferEncoding() throws Exception {
+	String part = 
+	    "Content-Type: text/plain; charset=\"us-ascii\"\n" +
+	    "Content-Transfer-Encoding: \n" +
+	    "\n" +
+	    "test" +
+	    "\n";
+	MimeBodyPart mbp = new MimeBodyPart(new AsciiStringInputStream(part));
+	assertEquals("empty C-T-E value", null, mbp.getEncoding());
+	assertEquals("empty C-T-E data", "test\n", mbp.getContent());
+    }
+
+
+    private static MimeMessage createMessage(Session s)
+				throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Subject: Example\n" +
+	    "Content-Type: multipart/mixed; boundary=\"-\"\n" +
+	    "\n" +
+	    "preamble\n" +
+	    "---\n" +
+	    "Content-Type: text/x-test\n" +
+	    "Content-Transfer-Encoding: quoted-printable\n" +
+	    "\n" +
+	    "test part\n" +
+	    "\n" +
+	    "-----\n";
+
+	return new MimeMessage(s, new AsciiStringInputStream(content));
+    }
+
+    private static String getString(InputStream is) throws IOException {
+	BufferedReader r = new BufferedReader(new InputStreamReader(is));
+	return r.readLine();
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/MimeBodyPartTestSuite.java b/mail/src/test/java/javax/mail/internet/MimeBodyPartTestSuite.java
new file mode 100644
index 0000000..4e5e871
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/MimeBodyPartTestSuite.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite.SuiteClasses;
+
+import com.sun.mail.test.ClassLoaderSuite;
+import com.sun.mail.test.ClassLoaderSuite.TestClass;
+
+/**
+ * Suite of MimeBodyPart tests that need to be run in a separate class loader.
+ */
+@RunWith(ClassLoaderSuite.class)
+@TestClass(MimeBodyPart.class)
+@SuiteClasses( {
+    NoEncodeFileName.class,
+    EncodeFileName.class,
+    EncodeFileNameNoEncodeParameters.class
+})
+public class MimeBodyPartTestSuite {
+}
diff --git a/mail/src/test/java/javax/mail/internet/MimeMessageTest.java b/mail/src/test/java/javax/mail/internet/MimeMessageTest.java
new file mode 100644
index 0000000..c23f2bc
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/MimeMessageTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import com.sun.mail.test.AsciiStringInputStream;
+import java.io.*;
+import java.nio.charset.Charset;
+import java.util.Properties;
+import java.util.Enumeration;
+
+import javax.activation.DataHandler;
+
+import javax.mail.*;
+import static javax.mail.Message.RecipientType.*;
+import static javax.mail.internet.MimeMessage.RecipientType.*;
+
+import org.junit.*;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test MimeMessage methods.
+ *
+ * XXX - just a beginning...
+ *
+ * @author Bill Shannon
+ */
+public class MimeMessageTest {
+
+    private static final Session s = Session.getInstance(new Properties());
+
+    /**
+     * Test that setRecipients with a null string address removes the header.
+     * (Bug 7021190)
+     */
+    @Test
+    public void testSetRecipientsStringNull() throws Exception {
+	String addr = "joe@example.com";
+	MimeMessage m = new MimeMessage(s);
+	m.setRecipients(TO, addr);
+	assertEquals("To: is set", addr, m.getRecipients(TO)[0].toString());
+	m.setRecipients(TO, (String)null);
+	assertArrayEquals("To: is removed", null, m.getRecipients(TO));
+    }
+
+    /**
+     * Test that setRecipient with a null string address removes the header.
+     * (Bug 7536)
+     */
+    @Test
+    public void testSetRecipientStringNull() throws Exception {
+	String addr = "joe@example.com";
+	MimeMessage m = new MimeMessage(s);
+	m.setRecipient(TO, new InternetAddress(addr));
+	assertEquals("To: is set", addr, m.getRecipients(TO)[0].toString());
+	m.setRecipient(TO, (Address)null);
+	assertArrayEquals("To: is removed", null, m.getRecipients(TO));
+    }
+
+    /**
+     * Test that setFrom with an address containing a newline is folded
+     * properly.
+     * (Bug 7529)
+     */
+    @Test
+    public void testSetFromFold() throws Exception {
+	InternetAddress addr = new InternetAddress("joe@bad.com", "Joe\r\nBad");
+	MimeMessage m = new MimeMessage(s);
+	m.setFrom(addr);
+	assertEquals("Joe\r\n Bad <joe@bad.com>", m.getHeader("From", null));
+    }
+
+    /**
+     * Test that setSender with an address containing a newline is folded
+     * properly.
+     * (Bug 7529)
+     */
+    @Test
+    public void testSetSenderFold() throws Exception {
+	InternetAddress addr = new InternetAddress("joe@bad.com", "Joe\r\nBad");
+	MimeMessage m = new MimeMessage(s);
+	m.setSender(addr);
+	assertEquals("Joe\r\n Bad <joe@bad.com>", m.getHeader("Sender", null));
+    }
+
+    /**
+     * Test that setRecipient with a newsgroup address containing a newline is
+     * handled properly.
+     * (Bug 7529)
+     */
+    @Test
+    public void testSetNewsgroupWhitespace() throws Exception {
+	NewsAddress addr = new NewsAddress("alt.\r\nbad");
+	MimeMessage m = new MimeMessage(s);
+	m.setRecipient(NEWSGROUPS, addr);
+	assertEquals("alt.bad", m.getHeader("Newsgroups", null));
+    }
+
+    /**
+     * Test that setRecipients with many newsgroup addresses is folded properly.
+     * (Bug 7529)
+     */
+    @Test
+    public void testSetNewsgroupFold() throws Exception {
+	NewsAddress[] longng = NewsAddress.parse(
+	    "alt.loooooooooooooooooooooooooooooooooooooooooooooooooong," +
+	    "alt.verylongggggggggggggggggggggggggggggggggggggggggggggg");
+	MimeMessage m = new MimeMessage(s);
+	m.setRecipients(NEWSGROUPS, longng);
+	assertTrue(m.getHeader("Newsgroups", null).indexOf("\r\n\t") > 0);
+    }
+
+    /**
+     * Test that newsgroups can be set and read back (even if folded).
+     */
+    @Test
+    public void testSetGetNewsgroups() throws Exception {
+	NewsAddress[] longng = NewsAddress.parse(
+	    "alt.loooooooooooooooooooooooooooooooooooooooooooooooooong," +
+	    "alt.verylongggggggggggggggggggggggggggggggggggggggggggggg");
+	MimeMessage m = new MimeMessage(s);
+	m.setRecipients(NEWSGROUPS, longng);
+	assertArrayEquals(longng, m.getRecipients(NEWSGROUPS));
+    }
+
+    /**
+     * Test that copying a DataHandler from one message to another
+     * has the desired effect.
+     */
+    @Test
+    public void testCopyDataHandler() throws Exception {
+	Session s = Session.getInstance(new Properties());
+	// create a message and extract the DataHandler
+	MimeMessage orig = createMessage(s);
+	DataHandler dh = orig.getDataHandler();
+	// create a new message and use the DataHandler
+	MimeMessage msg = new MimeMessage(s);
+	msg.setDataHandler(dh);
+	// depend on copy constructor streaming the data
+	msg = new MimeMessage(msg);
+	assertEquals("text/x-test", msg.getContentType());
+	assertEquals("quoted-printable", msg.getEncoding());
+	assertEquals("test message", getString(msg.getInputStream()));
+    }
+
+    /**
+     * Test that copying a DataHandler from one message to another
+     * by setting the "dh" field in a subclass has the desired effect.
+     */
+    @Test
+    public void testSetDataHandler() throws Exception {
+	Session s = Session.getInstance(new Properties());
+	// create a message and extract the DataHandler for a part
+	MimeMessage orig = createMessage(s);
+	final DataHandler odh = orig.getDataHandler();
+	// create a new message and use the DataHandler
+	MimeMessage msg = new MimeMessage(s) {
+		{ dh = odh; }
+	    };
+	// depend on copy constructor streaming the data
+	msg = new MimeMessage(msg);
+	assertEquals("text/x-test", msg.getContentType());
+	assertEquals("quoted-printable", msg.getEncoding());
+	assertEquals("test message", getString(msg.getInputStream()));
+    }
+
+    /**
+     * Test that address headers account for the header length when folding.
+     */
+    @Test
+    public void testAddressHeaderFolding() throws Exception {
+	Session s = Session.getInstance(new Properties());
+	MimeMessage msg = new MimeMessage(s);
+	InternetAddress[] addrs = InternetAddress.parse(
+	"long-address1@example.com, long-address2@example.com, joe@foobar.com");
+	msg.setReplyTo(addrs);	// use Reply-To because it's a long header name
+	Enumeration<String> e 
+		= msg.getMatchingHeaderLines(new String[] { "Reply-To" });
+	String line = e.nextElement();
+	int npos = line.indexOf("\r");
+	// was the line folded where we expected?
+	assertTrue("Header folded",
+	    npos > 9 && npos <= 77 && npos < line.length());
+    }
+
+    private static MimeMessage createMessage(Session s)
+				throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Subject: Example\n" +
+	    "Content-Type: text/x-test\n" +
+	    "Content-Transfer-Encoding: quoted-printable\n" +
+	    "\n" +
+	    "test message\n";
+
+	return new MimeMessage(s, new AsciiStringInputStream(content));
+    }
+
+    private static String getString(InputStream is) throws IOException {
+	BufferedReader r = new BufferedReader(new InputStreamReader(is));
+	return r.readLine();
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/MimeMessageTestSuite.java b/mail/src/test/java/javax/mail/internet/MimeMessageTestSuite.java
new file mode 100644
index 0000000..578918e
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/MimeMessageTestSuite.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite.SuiteClasses;
+
+import com.sun.mail.test.ClassLoaderSuite;
+import com.sun.mail.test.ClassLoaderSuite.TestClass;
+
+/**
+ * Suite of MimeMessage tests that need to be run in a separate class loader.
+ */
+@RunWith(ClassLoaderSuite.class)
+@TestClass(MimeMessage.class)
+@SuiteClasses( {
+    AllowEncodedMessages.class
+})
+public class MimeMessageTestSuite {
+}
diff --git a/mail/src/test/java/javax/mail/internet/MimeMultipartParseTest.java b/mail/src/test/java/javax/mail/internet/MimeMultipartParseTest.java
new file mode 100644
index 0000000..e8c21a9
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/MimeMultipartParseTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.util.*;
+import java.io.*;
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+import javax.mail.util.*;
+import javax.activation.*;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/*
+ * Test multipart parsing.
+ *
+ * @author Bill Shannon
+ */
+
+public class MimeMultipartParseTest {
+    private static Session session =
+	Session.getInstance(new Properties(), null);
+
+    private static final int maxsize = 10000;
+    private static final String data =
+	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+    @Test
+    public void testParse() throws Exception {
+	test(false);
+    }
+
+    @Test
+    public void testParseShared() throws Exception {
+	test(true);
+    }
+
+    /*
+     * Test a few potential boundary cases, then test a range.
+     * This is a compromise to make the run time of this test reasonable,
+     * although it still takes about 30 seconds, which is on the long side
+     * for a unit test.
+     */
+    public void test(boolean shared) throws Exception {
+	testMessage(1, shared);
+	testMessage(2, shared);
+	testMessage(62, shared);
+	testMessage(63, shared);
+	testMessage(64, shared);
+	testMessage(65, shared);
+	testMessage(1023, shared);
+	testMessage(1024, shared);
+	testMessage(1025, shared);
+	for (int size = 8100; size <= maxsize; size++)
+	    testMessage(size, shared);
+    }
+
+    public void testMessage(int size, boolean shared) throws Exception {
+	//System.out.println("SIZE: " + size);
+	/*
+	 * Construct a multipart message with a part of the
+	 * given size.
+	 */
+	MimeMessage msg = new MimeMessage(session);
+	msg.setFrom(new InternetAddress("me@example.com"));
+	msg.setSubject("test multipart parsing");
+	msg.setSentDate(new Date(0));
+	MimeBodyPart mbp1 = new MimeBodyPart();
+	mbp1.setText("main text\n");
+	MimeBodyPart mbp3 = new MimeBodyPart();
+	mbp3.setText("end text\n");
+	MimeBodyPart mbp2 = new MimeBodyPart();
+	byte[] part = new byte[size];
+	for (int i = 0; i < size; i++) {
+	    int j = i % 64;
+	    if (j == 62)
+		part[i] = (byte)'\r';
+	    else if (j == 63)
+		part[i] = (byte)'\n';
+	    else
+		part[i] = (byte)data.charAt((j + i / 64) % 62);
+	}
+	mbp2.setDataHandler(new DataHandler(
+	    new ByteArrayDataSource(part, "text/plain")));
+
+	MimeMultipart mp = new MimeMultipart();
+	mp.addBodyPart(mbp1);
+	mp.addBodyPart(mbp2);
+	mp.addBodyPart(mbp3);
+	msg.setContent(mp);
+	msg.saveChanges();
+
+	/*
+	 * Write the message out to a byte array.
+	 */
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	msg.writeTo(bos);
+	bos.close();
+	byte[] buf = bos.toByteArray();
+
+	/*
+	 * Construct a new message to parse the bytes.
+	 */
+	msg = new MimeMessage(session, shared ?
+	    new SharedByteArrayInputStream(buf) :
+	    new ByteArrayInputStream(buf));
+
+	// verify that the part content is correct
+	mp = (MimeMultipart)msg.getContent();
+	mbp2 = (MimeBodyPart)mp.getBodyPart(1);
+	InputStream is = mbp2.getInputStream();
+	int k = 0;
+	int c;
+	while ((c = is.read()) >= 0) {
+	    int j = k % 64;
+	    byte e;
+	    if (j == 62)
+		e = (byte)'\r';
+	    else if (j == 63)
+		e = (byte)'\n';
+	    else
+		e = (byte)data.charAt((j + k / 64) % 62);
+	    Assert.assertEquals("Size " + size + " at byte " + k, e, c);
+	    k++;
+	}
+	Assert.assertEquals("Expected size", size, k);
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/MimeMultipartPreambleTest.java b/mail/src/test/java/javax/mail/internet/MimeMultipartPreambleTest.java
new file mode 100644
index 0000000..7528444
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/MimeMultipartPreambleTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.ByteArrayInputStream;
+import java.util.Properties;
+
+import javax.mail.BodyPart;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the MIME multipart messages are parsed correctly and the
+ * correct preamble is returned no matter what line terminators
+ * are used.  This test found some bugs in the way LineInputStream
+ * handled different line terminators.
+ *
+ * @author	trejkaz@kenai.com
+ */
+public class MimeMultipartPreambleTest {
+    @SuppressWarnings({"SingleCharacterStringConcatenation"})
+    private String THREE_PART_MAIL =
+        "From: user1@example.com\n" +
+        "To: user2@example.com\n" +
+        "Subject: Receipts\n" +
+        "Date: Wed, 14 Jul 2010 19:25:30 +1000\n" +
+        "MIME-Version: 1.0\n" +
+        "Content-Type: multipart/mixed;boundary=\"----=_NextPart_000_001C_01CB238A.52E35400\"\n" +
+        "\n" +
+        "This is a multi-part message in MIME format.\n" +
+        "\n" +
+        "------=_NextPart_000_001C_01CB238A.52E35400\n" +
+        "Content-Type: text/plain;charset=\"us-ascii\"\n" +
+        "Content-Transfer-Encoding: 7bit\n" +
+        "\n" +
+        "Hi.\n" +
+        "\n" +
+        "\n" +
+        "------=_NextPart_000_001C_01CB238A.52E35400\n" +
+        "Content-Type: application/pdf;name=\"Receipt 1.pdf\"\n" +
+        "Content-Transfer-Encoding: base64\n" +
+        "Content-Disposition: attachment;filename=\"Receipt 1.pdf\"\n" +
+        "\n" +
+        "JVBERi0xLjQKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFIvRmlsdGVyIC9GbGF0ZURlY29k\n" +
+        "\n" +
+        "------=_NextPart_000_001C_01CB238A.52E35400\n" +
+        "Content-Type: application/pdf;name=\"Receipt 2.pdf\"\n" +
+        "Content-Transfer-Encoding: base64\n" +
+        "Content-Disposition: attachment;filename=\"Receipt 2.pdf\"\n" +
+        "\n" +
+        "JVBERi0xLjQKJcfsj6IKNSAwIG9iago8PC9MZW5ndGggNiAwIFIvRmlsdGVyIC9GbGF0ZURlY29k\n" +
+        "\n" +
+        "------=_NextPart_000_001C_01CB238A.52E35400--\n" +
+        "\n" +
+        "\n";
+
+    @Test
+    public void testUnixLines() throws Exception {
+        doThreePartMailTest(THREE_PART_MAIL);
+    }
+
+    @Test
+    public void testWindowsLines() throws Exception {
+        doThreePartMailTest(THREE_PART_MAIL.replace("\n", "\r\n"));
+    }
+
+    @Test
+    public void testMacLines() throws Exception {
+        doThreePartMailTest(THREE_PART_MAIL.replace('\n', '\r'));
+    }
+
+    /**
+     * Performs a check that the multipart of the email was handled correctly.
+     *
+     * @param text the email text.
+     * @throws Exception if an error occurs.
+     */
+    private void doThreePartMailTest(String text) throws Exception {
+        Session session = Session.getDefaultInstance(new Properties());
+        MimeMessage mimeMessage = new MimeMessage(session,
+			new ByteArrayInputStream(text.getBytes("US-ASCII")));
+
+        MimeMultipart topMultipart = (MimeMultipart) mimeMessage.getContent();
+        assertEquals("Wrong preamble",
+	    "This is a multi-part message in MIME format.",
+	    topMultipart.getPreamble().trim());
+        assertEquals("Wrong number of parts", 3, topMultipart.getCount());
+
+        BodyPart part1 = topMultipart.getBodyPart(0);
+        assertEquals("Wrong content type for part 1",
+	    "text/plain;charset=\"us-ascii\"", part1.getContentType());
+
+        BodyPart part2 = topMultipart.getBodyPart(1);
+        assertEquals("Wrong content type for part 2",
+	    "application/pdf;name=\"Receipt 1.pdf\"", part2.getContentType());
+
+        BodyPart part3 = topMultipart.getBodyPart(2);
+        assertEquals("Wrong content type for part 3",
+	    "application/pdf;name=\"Receipt 2.pdf\"", part3.getContentType());
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/MimeMultipartPropertyTest.java b/mail/src/test/java/javax/mail/internet/MimeMultipartPropertyTest.java
new file mode 100644
index 0000000..cec3910
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/MimeMultipartPropertyTest.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import com.sun.mail.test.AsciiStringInputStream;
+import com.sun.mail.test.NullOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.MessagingException;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test the properties that control the MimeMultipart class.
+ * Since the properties are now read in the parse method, all
+ * these tests can be run in the same JVM.
+ */
+public class MimeMultipartPropertyTest {
+ 
+    private static Session s = Session.getInstance(new Properties());
+
+    /**
+     * Clear all properties before each test.
+     */
+    @Before
+    public void beforeTest() {
+	clearAll();
+    }
+
+    @Test
+    public void testBoundary() throws Exception {
+	MimeMessage m = createMessage("x", "x", true);
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	assertEquals(mp.getCount(), 2);
+    }
+
+    @Test
+    public void testBoundaryIgnore() throws Exception {
+        System.setProperty(
+	    "mail.mime.multipart.ignoreexistingboundaryparameter", "true");
+	MimeMessage m = createMessage("x", "-", true);
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	assertEquals(mp.getCount(), 2);
+    }
+
+    @Test
+    public void testBoundaryMissing() throws Exception {
+	MimeMessage m = createMessage(null, "x", true);
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	assertEquals(mp.getCount(), 2);
+    }
+
+    @Test(expected=MessagingException.class)
+    public void testBoundaryMissingEx() throws Exception {
+        System.setProperty(
+	    "mail.mime.multipart.ignoremissingboundaryparameter", "false");
+	MimeMessage m = createMessage(null, "x", true);
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	mp.getCount();		// throw exception
+	assertTrue(false);	// never get here
+    }
+
+    @Test
+    public void testEndBoundaryMissing() throws Exception {
+	MimeMessage m = createMessage("x", "x", false);
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	assertEquals(mp.getCount(), 2);
+    }
+
+    @Test(expected=MessagingException.class)
+    public void testEndBoundaryMissingEx() throws Exception {
+        System.setProperty(
+	    "mail.mime.multipart.ignoremissingendboundary", "false");
+	MimeMessage m = createMessage("x", "x", false);
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	mp.getCount();		// throw exception
+	assertTrue(false);	// never get here
+    }
+
+    @Test
+    public void testAllowEmpty() throws Exception {
+        System.setProperty( "mail.mime.multipart.allowempty", "true");
+	MimeMessage m = createEmptyMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	assertEquals(mp.getCount(), 0);
+    }
+
+    @Test(expected=MessagingException.class)
+    public void testAllowEmptyEx() throws Exception {
+	MimeMessage m = createEmptyMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	mp.getCount();		// throw exception
+	assertTrue(false);	// never get here
+    }
+
+    @Test
+    public void testAllowEmptyOutput() throws Exception {
+        System.setProperty( "mail.mime.multipart.allowempty", "true");
+	MimeMessage m = new MimeMessage(s);
+	MimeMultipart mp = new MimeMultipart();
+	m.setContent(mp);
+	m.writeTo(new NullOutputStream());
+	assertEquals(mp.getCount(), 0);
+    }
+
+    @Test(expected=IOException.class)
+    public void testAllowEmptyOutputEx() throws Exception {
+	MimeMessage m = new MimeMessage(s);
+	MimeMultipart mp = new MimeMultipart();
+	m.setContent(mp);
+	m.writeTo(new NullOutputStream());	// throw exception
+	assertTrue(false);	// never get here
+    }
+
+    /**
+     * Clear all properties after all tests.
+     */
+    @AfterClass
+    public static void after() {
+        clearAll();
+    }
+
+    private static void clearAll() {
+        System.clearProperty(
+	    "mail.mime.multipart.ignoreexistingboundaryparameter");
+        System.clearProperty(
+	    "mail.mime.multipart.ignoremissingboundaryparameter");
+        System.clearProperty(
+	    "mail.mime.multipart.ignoremissingendboundary");
+        System.clearProperty(
+	    "mail.mime.multipart.allowempty");
+    }
+
+    /**
+     * Create a test message.
+     * If param is not null, it specifies the boundary parameter.
+     * The actual boundary is specified by "actual".
+     * If "end" is true, include the end boundary.
+     */
+    private static MimeMessage createMessage(String param, String actual,
+				boolean end) throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Subject: Example\n" +
+	    "Content-Type: multipart/mixed; " +
+		(param != null ? "boundary=\"" + param + "\"" : "") + "\n" +
+	    "\n" +
+	    "preamble\n" +
+	    "--" + actual + "\n" +
+	    "\n" +
+	    "first part\n" +
+	    "\n" +
+	    "--" + actual + "\n" +
+	    "\n" +
+	    "second part\n" +
+	    "\n" +
+	    (end ? "--" + actual + "--\n" : "");
+ 
+	return new MimeMessage(s, new AsciiStringInputStream(content));
+    }
+
+    /**
+     * Create a test message with no parts.
+     */
+    private static MimeMessage createEmptyMessage() throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Subject: Example\n" +
+	    "Content-Type: multipart/mixed; boundary=\"x\"\n\n";
+ 
+	return new MimeMessage(s, new AsciiStringInputStream(content));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/MimeUtilityTest.java b/mail/src/test/java/javax/mail/internet/MimeUtilityTest.java
new file mode 100644
index 0000000..3bf945a
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/MimeUtilityTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.File;
+import java.util.Set;
+import java.util.HashSet;
+import javax.activation.*;
+import javax.mail.util.ByteArrayDataSource;
+
+import org.junit.*;
+import static org.junit.Assert.*;
+
+/**
+ * Test MimeUtility methods.
+ *
+ * XXX - just a beginning...
+ *
+ * @author Bill Shannon
+ */
+public class MimeUtilityTest {
+    private static final byte[] utf16beBytes = {
+	(byte)0xfe, (byte)0x5b, (byte)0xdc, (byte)0x5f,
+	(byte)0x92, (byte)0x30, (byte)0x88, (byte)0x30,
+	(byte)0x8d, (byte)0x30, (byte)0x57, (byte)0x30,
+	(byte)0x4f, (byte)0x30, (byte)0x4a, (byte)0x30,
+	(byte)0x6d, (byte)0x30, (byte)0x4c, (byte)0x30,
+	(byte)0x44, (byte)0x30, (byte)0x57, (byte)0x30,
+	(byte)0x7e, (byte)0x30, (byte)0x59, (byte)0x30,
+	(byte)0x0d, (byte)0x00, (byte)0x0a, (byte)0x00
+    };
+
+    @SuppressWarnings("serial")
+    private static final Set<String> encodings = new HashSet<String>() {{
+	add("7bit"); add("8bit"); add("quoted-printable"); add("base64"); }};
+
+    /**
+     * Test that utf-16be data is encoded with base64 and not quoted-printable.
+     */
+    @Test
+    public void testNonAsciiEncoding() throws Exception {
+	DataSource ds = new ByteArrayDataSource(utf16beBytes,
+						"text/plain; charset=utf-16be");
+	String en = MimeUtility.getEncoding(ds);
+	assertEquals("non-ASCII encoding", "base64", en);
+    }
+
+    /**
+     * Test that getEncoding returns a valid value even if the file
+     * doesn't exist.  The return value should be a valid
+     * Content-Transfer-Encoding, but mostly we care that it doesn't
+     * throw NullPointerException.
+     */
+    @Test
+    public void getEncodingMissingFile() throws Exception {
+	File missing = new File(getClass().getName());
+	assertFalse(missing.toString(), missing.exists());
+	FileDataSource fds = new FileDataSource(missing);
+	assertEquals(fds.getName(), missing.getName());
+	assertTrue("getEncoding(DataSource)",
+	    encodings.contains(MimeUtility.getEncoding(fds)));
+	assertTrue("getEncoding(DataHandler)",
+	    encodings.contains(MimeUtility.getEncoding(new DataHandler(fds))));
+    }
+
+    /**
+     * Test that getEncoding returns a valid value even if the content
+     * type is bad.  The return value should be a valid
+     * Content-Transfer-Encoding, but mostly we care that it doesn't
+     * throw NullPointerException.
+     */
+    @Test
+    public void getEncodingBadContent() throws Exception {
+	String content = "bad-content-type";
+	ContentType type = null;
+	try {
+	    type = new ContentType(content);
+	    fail(type.toString());
+	} catch (ParseException expect) {
+	    if (type != null) {
+	       throw expect; 
+	    }
+	}
+
+	ByteArrayDataSource bads = new ByteArrayDataSource("", content);
+	bads.setName(null);
+	assertTrue(encodings.contains(MimeUtility.getEncoding(bads)));
+	assertTrue(encodings.contains(
+			MimeUtility.getEncoding(new DataHandler(bads))));
+
+	bads.setName("");
+	assertTrue(encodings.contains(MimeUtility.getEncoding(bads)));
+	assertTrue(encodings.contains(
+			MimeUtility.getEncoding(new DataHandler(bads))));
+
+	bads.setName(getClass().getName());
+	assertTrue(encodings.contains(MimeUtility.getEncoding(bads)));
+	assertTrue(encodings.contains(
+			MimeUtility.getEncoding(new DataHandler(bads))));
+    }
+
+    /**
+     * Test that encoding a Unicode string with surrogate pairs
+     * doesn't split the encoding between the pairs.
+     */
+    @Test
+    public void testSurrogatePairs() throws Exception {
+	// test a specific case
+	String sp = "a" +
+		    "\ud801\udc00\ud801\udc00\ud801\udc00\ud801\udc00" +
+		    "\ud801\udc00\ud801\udc00\ud801\udc00\ud801\udc00" +
+		    "\ud801\udc00\ud801\udc00\ud801\udc00\ud801\udc00" +
+		    "\ud801\udc00\ud801\udc00\ud801\udc00\ud801\udc00";
+	String en = MimeUtility.encodeText(sp, "utf-8", "B");
+	String dt = MimeUtility.decodeText(en);
+	// encoding it and decoding it shouldn't change it
+	assertEquals(dt, sp);
+	String[] w = en.split(" ");
+	// the first word should end with the second half of a pair
+	String dw = MimeUtility.decodeWord(w[0]);
+	assertTrue(dw.charAt(dw.length()-1) == '\udc00');
+	// and the second word should start with the first half of a pair
+	dw = MimeUtility.decodeWord(w[1]);
+	assertTrue(dw.charAt(0) == '\ud801');
+
+	// test various string lengths
+	int ch = 0xFE000;
+	String test = "";
+	for (int i = 0; i < 50; i++) {
+	    test += new String(Character.toChars(ch));
+	    String encoded = MimeUtility.encodeText(test, "UTF-8", "B");
+	    String decoded = MimeUtility.decodeText(encoded);
+	    assertEquals(decoded, test);
+	}
+    }
+
+    /**
+     * Test that encoded words with the wrong Chinese charset still
+     * decode correctly.
+     */
+    @Test
+    public void testBadChineseCharsets() throws Exception {
+	String badgb2312 = "=?gb2312?B?xbfUqqLjIChFVVIpttK7u4EwhDYgKENOWSk=?=";
+	String badgbk = "=?gbk?B?xbfUqqLjIChFVVIpttK7u4EwhDYgKENOWSk=?=";
+	String badms936 = "=?ms936?B?xbfUqqLjIChFVVIpttK7u4EwhDYgKENOWSk=?=";
+	String badcp936 = "=?cp936?B?xbfUqqLjIChFVVIpttK7u4EwhDYgKENOWSk=?=";
+	String good = "=?gb18030?B?xbfUqqLjIChFVVIpttK7u4EwhDYgKENOWSk=?=";
+	String goodDecoded = MimeUtility.decodeWord(good);
+
+	assertEquals("gb2312", goodDecoded, MimeUtility.decodeWord(badgb2312));
+	assertEquals("gbk", goodDecoded, MimeUtility.decodeWord(badgbk));
+	assertEquals("ms936", goodDecoded, MimeUtility.decodeWord(badms936));
+	assertEquals("cp936", goodDecoded, MimeUtility.decodeWord(badcp936));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/ModifyMessageTest.java b/mail/src/test/java/javax/mail/internet/ModifyMessageTest.java
new file mode 100644
index 0000000..275b1cf
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ModifyMessageTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import com.sun.mail.test.AsciiStringInputStream;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.MessagingException;
+import javax.mail.BodyPart;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test some of the ways you might modify a message that has been
+ * read from an input stream.
+ */
+public class ModifyMessageTest {
+ 
+    private static Session s = Session.getInstance(new Properties());
+
+    @Test
+    public void testAddHeader() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+        m.setHeader("a", "b");
+        m.saveChanges();
+
+	MimeMessage m2 = new MimeMessage(m);
+	assertEquals("b", m2.getHeader("a", null));
+    }
+
+    @Test
+    public void testChangeHeader() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+        m.setHeader("Subject", "test");
+        m.saveChanges();
+
+	MimeMessage m2 = new MimeMessage(m);
+	assertEquals("test", m2.getHeader("Subject", null));
+    }
+
+    @Test
+    public void testAddContent() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	MimeBodyPart mbp = new MimeBodyPart();
+        mbp.setText("test");
+	mp.addBodyPart(mbp);
+        m.saveChanges();
+
+	MimeMessage m2 = new MimeMessage(m);
+	mp = (MimeMultipart)m2.getContent();
+	BodyPart bp = mp.getBodyPart(2);
+	assertEquals("test", bp.getContent());
+	// make sure nothing else changed
+	bp = mp.getBodyPart(0);
+	assertEquals("first part\n", bp.getContent());
+	bp = mp.getBodyPart(1);
+	assertEquals("second part\n", bp.getContent());
+    }
+
+    @Test
+    public void testChangeContent() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	BodyPart bp = mp.getBodyPart(0);
+        bp.setText("test");
+        m.saveChanges();
+
+	MimeMessage m2 = new MimeMessage(m);
+	mp = (MimeMultipart)m2.getContent();
+	bp = mp.getBodyPart(0);
+	assertEquals("test", bp.getContent());
+    }
+
+    @Test
+    public void testChangeNestedContent() throws Exception {
+        MimeMessage m = createNestedMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	mp = (MimeMultipart)mp.getBodyPart(0).getContent();
+	BodyPart bp = mp.getBodyPart(0);
+        bp.setText("test");
+        m.saveChanges();
+
+	MimeMessage m2 = new MimeMessage(m);
+	mp = (MimeMultipart)m2.getContent();
+	mp = (MimeMultipart)mp.getBodyPart(0).getContent();
+	bp = mp.getBodyPart(0);
+	assertEquals("test", bp.getContent());
+	// make sure other content is not changed or re-encoded
+	MimeBodyPart mbp = (MimeBodyPart)mp.getBodyPart(1);
+	assertEquals("second part\n", mbp.getContent());
+	assertEquals("quoted-printable", mbp.getEncoding());
+	mbp = (MimeBodyPart)mp.getBodyPart(2);
+	assertEquals("third part\n", mbp.getContent());
+	assertEquals("base64", mbp.getEncoding());
+    }
+
+    private static MimeMessage createMessage() throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Subject: Example\n" +
+	    "Content-Type: multipart/mixed; boundary=\"-\"\n" +
+	    "\n" +
+	    "preamble\n" +
+	    "---\n" +
+	    "\n" +
+	    "first part\n" +
+	    "\n" +
+	    "---\n" +
+	    "\n" +
+	    "second part\n" +
+	    "\n" +
+	    "-----\n";
+
+	return new MimeMessage(s, new AsciiStringInputStream(content));
+    }
+
+    private static MimeMessage createNestedMessage() throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Subject: Example\n" +
+	    "Content-Type: multipart/mixed; boundary=\"-\"\n" +
+	    "\n" +
+	    "preamble\n" +
+	    "---\n" +
+	    "Content-Type: multipart/mixed; boundary=\"x\"\n" +
+	    "\n" +
+	    "--x\n" +
+	    "\n" +
+	    "first part\n" +
+	    "\n" +
+	    "--x\n" +
+	    "Content-Transfer-Encoding: quoted-printable\n" +
+	    "\n" +
+	    "second part\n" +
+	    "\n" +
+	    "--x\n" +
+	    "Content-Transfer-Encoding: base64\n" +
+	    "\n" +
+	    "dGhpcmQgcGFydAo=\n" +	// "third part\n", base64 encoded
+	    "\n" +
+	    "--x--\n" +
+	    "-----\n";
+
+	return new MimeMessage(s, new AsciiStringInputStream(content));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/NewsAddressTest.java b/mail/src/test/java/javax/mail/internet/NewsAddressTest.java
new file mode 100644
index 0000000..93cce3b
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/NewsAddressTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2014, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test the NewsAddress class.
+ *
+ * XXX - for now, just some simple regression tests for reported bugs.
+ */
+public class NewsAddressTest {
+ 
+    @Test
+    public void testReflexiveEquality() throws Exception {
+	NewsAddress a = new NewsAddress();
+	assertEquals(a, a);	// bug 6365
+	a = new NewsAddress("net.unix");
+	assertEquals(a, a);
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/NoEncodeFileName.java b/mail/src/test/java/javax/mail/internet/NoEncodeFileName.java
new file mode 100644
index 0000000..5253f4c
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/NoEncodeFileName.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2015, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.UnsupportedEncodingException;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeBodyPart;
+
+import org.junit.*;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test "mail.mime.encodefilename" System property not set.
+ */
+public class NoEncodeFileName {
+ 
+    protected static String fileName;
+
+    // a bunch of non-ASCII characters
+    private static String encodedFileName =
+	"=?utf-8?B?w4DDgcOFw4bDgMOBw4XDhsOHw4jDicOKw4vDjMONw47Dj8OQw4DD" +
+	"gcOFw4bDh8OIw4nDisOLw4zDjcOOw4/DkMORw5LDk8OUw5XDlsOYw5nDmsObw5" +
+	"zDncOew5/DoMOhw6LDo8Okw6XDpsOnw6jDqcOqw6vDrMOtw67Dr8Oww7HDssOz" +
+	"w7TDtcO2w7jDucO6w7vDvMO9w77Dv8OAw4HDhcOGw4cuZG9j?=";
+
+    static {
+	try {
+	    fileName = MimeUtility.decodeText(encodedFileName);
+	} catch (UnsupportedEncodingException ex) {
+	    // should never happen
+	}
+	
+    }
+
+    // RFC 2231 encoding
+    private static String expected =
+	"utf-8''%C3%80%C3%81%C3%85%C3%86%C3%80%C3%81%C3%85%C3%86%C3%87%C3%88" +
+	"%C3%89%C3%8A%C3%8B%C3%8C%C3%8D%C3%8E%C3%8F%C3%90%C3%80%C3%81%C3%85" +
+	"%C3%86%C3%87%C3%88%C3%89%C3%8A%C3%8B%C3%8C%C3%8D%C3%8E%C3%8F%C3%90" +
+	"%C3%91%C3%92%C3%93%C3%94%C3%95%C3%96%C3%98%C3%99%C3%9A%C3%9B%C3%9C" +
+	"%C3%9D%C3%9E%C3%9F%C3%A0%C3%A1%C3%A2%C3%A3%C3%A4%C3%A5%C3%A6%C3%A7" +
+	"%C3%A8%C3%A9%C3%AA%C3%AB%C3%AC%C3%AD%C3%AE%C3%AF%C3%B0%C3%B1%C3%B2" +
+	"%C3%B3%C3%B4%C3%B5%C3%B6%C3%B8%C3%B9%C3%BA%C3%BB%C3%BC%C3%BD%C3%BE" +
+	"%C3%BF%C3%80%C3%81%C3%85%C3%86%C3%87.doc";
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("NoEncodeFileName");
+	System.setProperty("mail.mime.charset", "utf-8");
+	System.clearProperty("mail.mime.encodefilename");
+    }
+
+    @Test
+    public void test() throws Exception {
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setText("test");
+	mbp.setFileName(fileName);
+	mbp.updateHeaders();
+	String h = mbp.getHeader("Content-Type", "");
+	assertTrue(h.contains("name*="));
+	assertTrue(h.contains(expected));
+	h = mbp.getHeader("Content-Disposition", "");
+	assertTrue(h.contains("filename*="));
+	assertTrue(h.contains(expected));
+    }
+
+    @AfterClass
+    public static void after() {
+	// should be unnecessary
+	System.clearProperty("mail.mime.charset");
+	System.clearProperty("mail.mime.encodefilename");
+	System.clearProperty("mail.mime.encodeparameters");
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/NonAsciiBoundaryTest.java b/mail/src/test/java/javax/mail/internet/NonAsciiBoundaryTest.java
new file mode 100644
index 0000000..d54b9ff
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/NonAsciiBoundaryTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import com.sun.mail.test.AsciiStringInputStream;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.MessagingException;
+import javax.mail.BodyPart;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that non-ASCII boundary strings are handled reasonably,
+ * even though, strictly speaking, the MIME spec doesn't allow them.
+ */
+public class NonAsciiBoundaryTest {
+ 
+    private static Session s = Session.getInstance(new Properties());
+
+    @Test
+    public void test() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	assertEquals(2, mp.getCount());
+	BodyPart bp = mp.getBodyPart(0);
+	assertEquals("first part\n", bp.getContent());
+	bp = mp.getBodyPart(1);
+	assertEquals("second part\n", bp.getContent());
+    }
+
+    private static MimeMessage createMessage() throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Subject: Example\n" +
+	    "Content-Type: multipart/mixed; boundary=\"\u00A9\"\n" +
+	    "\n" +
+	    "--\u00A9\n" +
+	    "\n" +
+	    "first part\n" +
+	    "\n" +
+	    "--\u00A9\n" +
+	    "\n" +
+	    "second part\n" +
+	    "\n" +
+	    "--\u00A9--\n";
+
+	return new MimeMessage(s, new AsciiStringInputStream(content, false));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/NonAsciiFileNames.java b/mail/src/test/java/javax/mail/internet/NonAsciiFileNames.java
new file mode 100644
index 0000000..8b79d37
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/NonAsciiFileNames.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test that non-ASCII file names are encoded by default.
+ */
+public class NonAsciiFileNames {
+
+    private static String charset;
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("NonAsciiFileNames");
+	charset = System.getProperty("mail.mime.charset");
+	System.setProperty("mail.mime.charset", "utf-8");
+    }
+
+    /**
+     * Test that non-ASCII filenames are encoded by default.
+     */
+    @Test
+    public void testNonAsciiFileName() throws Exception {
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setText("test\n");
+	mbp.setFileName("test\u00a1\u00a2\u00a3");
+	MimeBodyPart.updateHeaders(mbp);
+
+	String s = mbp.getHeader("Content-Disposition", null);
+	assertTrue("Content-Disposition filename", s.indexOf("filename*") >= 0);
+	s = mbp.getHeader("Content-Type", null);
+	assertTrue("Content-Type name", s.indexOf("name*") >= 0);
+    }
+
+    /**
+     * Test that non-ASCII filenames are encoded by default.
+     * Make sure an existing Content-Type header is updated.
+     */
+    @Test
+    public void testNonAsciiFileNameWithContentType() throws Exception {
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setText("test\n");
+	mbp.setHeader("Content-Type", "text/x-test");
+	mbp.setFileName("test\u00a1\u00a2\u00a3");
+	MimeBodyPart.updateHeaders(mbp);
+
+	String s = mbp.getHeader("Content-Disposition", null);
+	assertTrue("Content-Disposition filename", s.indexOf("filename*") >= 0);
+	s = mbp.getHeader("Content-Type", null);
+	assertTrue("Content-Type name", s.indexOf("name*") >= 0);
+    }
+
+    @AfterClass
+    public static void after() {
+	if (charset == null)
+	    System.clearProperty("mail.mime.charset");
+	else
+	    System.setProperty("mail.mime.charset", charset);
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/ParameterListDecode.java b/mail/src/test/java/javax/mail/internet/ParameterListDecode.java
new file mode 100644
index 0000000..0c54ed2
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ParameterListDecode.java
@@ -0,0 +1,427 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.io.*;
+import java.util.*;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.util.*;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test parameter list parsing.
+ *
+ * XXX - this should be a JUnit parameterized test,
+ *	 but I can't figure out how to run parameterized
+ *	 tests under my ClassLoaderSuite.
+ *
+ * @author Bill Shannon
+ */
+
+public class ParameterListDecode {
+    static boolean gen_test_input = false;	// output good for input to -p
+    static boolean parse_mail = false;		// parse input in mail format
+    static boolean test_mail = false;		// test using a mail server
+    static int errors = 0;			// number of errors detected
+
+    static String protocol;
+    static String host = null;
+    static String user = null;
+    static String password = null;
+    static String mbox = null;
+    static String url = null;
+    static int port = -1;
+    static boolean debug = false;
+
+    static Session session;
+    static Store store;
+    static Folder folder;
+
+    static boolean junit;
+
+    protected static void testDecode(String paramData) throws Exception {
+	junit = true;
+	parse(new BufferedReader(new InputStreamReader(
+	    ParameterListDecode.class.getResourceAsStream(paramData))));
+    }
+
+    public static void main(String argv[]) throws Exception {
+	System.getProperties().put("mail.mime.decodeparameters", "true");
+	int optind;
+	for (optind = 0; optind < argv.length; optind++) {
+	    if (argv[optind].equals("-")) {
+		// ignore
+	    } else if (argv[optind].equals("-g")) {
+		gen_test_input = true;
+	    } else if (argv[optind].equals("-p")) {
+		parse_mail = true;
+	    } else if (argv[optind].equals("-m")) {
+		test_mail = true;
+	    } else if (argv[optind].equals("-T")) {
+		protocol = argv[++optind];
+	    } else if (argv[optind].equals("-H")) {
+		host = argv[++optind];
+	    } else if (argv[optind].equals("-U")) {
+		user = argv[++optind];
+	    } else if (argv[optind].equals("-P")) {
+		password = argv[++optind];
+	    } else if (argv[optind].equals("-D")) {
+		debug = true;
+	    } else if (argv[optind].equals("-f")) {
+		mbox = argv[++optind];
+	    } else if (argv[optind].equals("-L")) {
+		url = argv[++optind];
+	    } else if (argv[optind].equals("-p")) {
+		port = Integer.parseInt(argv[++optind]);
+	    } else if (argv[optind].equals("-d")) {
+		debug = true;
+	    } else if (argv[optind].equals("--")) {
+		optind++;
+		break;
+	    } else if (argv[optind].startsWith("-")) {
+		System.out.println(
+		    "Usage: paramtest [-g] [-p] [-] [content-type ...]");
+		System.out.println(
+"or\tparamtest -m [-g] [-L url] [-T protocol] [-H host] [-p port] [-U user]");
+		System.out.println(
+"\t[-P password] [-f mailbox] [-d]");
+		System.exit(1);
+	    } else {
+		break;
+	    }
+	}
+
+	if (test_mail)
+	    initMail();
+
+	/*
+	 * If there's any args left on the command line,
+	 * concatenate them into a string and test that.
+	 */
+	if (optind < argv.length) {
+	    StringBuilder sb = new StringBuilder();
+	    for (int i = optind; i < argv.length; i++) {
+		sb.append(argv[i]);
+		sb.append(" ");
+	    }
+	    test("Content-Type", sb.toString(), null);
+	} else {
+	    // read from stdin
+	    BufferedReader in =
+		new BufferedReader(new InputStreamReader(System.in));
+	    String s;
+
+	    if (parse_mail)
+		parse(in);
+	    else if (test_mail)
+		testMail();
+	    else {
+		while ((s = in.readLine()) != null)
+		    test("Content-Type", s, null);
+	    }
+	}
+
+	if (test_mail)
+	    doneMail();
+
+	System.exit(errors);
+
+    }
+
+    /*
+     * Parse the input in "mail" format, extracting the Content-Type
+     * headers and testing them.  The parse is rather crude, but sufficient
+     * to test against most existing UNIX mailboxes.
+     */
+    public static void parse(BufferedReader in) throws Exception {
+	String header = "";
+
+	for (;;) {
+	    String s = in.readLine();
+	    if (s != null && s.length() > 0) {
+		char c = s.charAt(0);
+		if (c == ' ' || c == '\t') {
+		    // a continuation line, add it to the current header
+		    header += '\n' + s;
+		    continue;
+		}
+	    }
+	    // "s" is the next header, "header" is the last complete header
+	    if (header.regionMatches(true, 0, "Content-Type: ", 0, 14)) {
+		int i;
+		String[] expect = null;
+		if (s != null && s.startsWith("Expect: ")) {
+		    try {
+			int nexpect = Integer.parseInt(s.substring(8));
+			expect = new String[nexpect];
+			for (i = 0; i < nexpect; i++)
+			    expect[i] = decode(trim(in.readLine()));
+		    } catch (NumberFormatException e) {
+			try {
+			    if (s.substring(8, 17).equals("Exception")) {
+				expect = new String[1];
+				expect[0] = "Exception";
+			    }
+			} catch (StringIndexOutOfBoundsException se) {
+			    // ignore it
+			}
+		    }
+		}
+		i = header.indexOf(':');
+		try {
+		    test(header.substring(0, i), header.substring(i + 2),
+			expect);
+		} catch (StringIndexOutOfBoundsException e) {
+		    // ignore
+		}
+	    }
+	    if (s == null)
+		return;		// EOF
+	    if (s.length() == 0) {
+		while ((s = in.readLine()) != null) {
+		    if (s.startsWith("From "))
+			break;
+		}
+		if (s == null)
+		    return;
+	    }
+	    header = s;
+	}
+    }
+
+    /**
+     * Like String.trim, but only the left side.
+     */
+    public static String trim(String s) {
+	int i = 0;
+	while (i < s.length() && s.charAt(i) <= ' ')
+	    i++;
+	return s.substring(i);
+    }
+
+    /**
+     * Decode Unicode escapes.
+     */
+    public static String decode(String s) {
+	StringBuilder sb = new StringBuilder();
+	for (int i = 0; i < s.length(); i++) {
+	    char c = s.charAt(i);
+	    if (c == '\\' && s.charAt(i + 1) == 'u') {
+		c = (char)Integer.parseInt(s.substring(i + 2, i + 6), 16);
+		i += 5;
+	    }
+	    sb.append(c);
+	}
+	return sb.toString();
+    }
+
+    /**
+     * Test the header's value to see if we can parse it as expected.
+     */
+    public static void test(String header, String value, String expect[])
+		throws Exception {
+	PrintStream out = System.out;
+	ByteArrayOutputStream bos = null;
+	if (gen_test_input) {
+	    if (test_mail) {
+		bos = new ByteArrayOutputStream();
+		out = new PrintStream(bos);
+	    } else {
+		out.println(header + ": " + value);
+	    }
+	} else if (!junit)
+	    out.println("Test: " + value);
+
+	try {
+	    ContentType ct = new ContentType(value);
+	    ParameterList pl = ct.getParameterList();
+	    if (gen_test_input)
+		out.println("Expect: " + pl.size());
+	    else if (junit)
+		assertEquals("Number of parameters",
+		    expect.length, pl.size());
+	    else {
+		out.println("Got " + pl.size() + " parameters:");
+		if (expect != null && pl.size() != expect.length) {
+		    out.println("Expected " + expect.length + " parameters");
+		    errors++;
+		}
+	    }
+	    Enumeration<String> e = pl.getNames();
+	    for (int i = 0; e.hasMoreElements(); i++) {
+		String name = e.nextElement();
+		String pvalue = pl.get(name);
+		if (gen_test_input)
+		    out.println("\t" + name + "=" + pvalue);	// XXX - newline
+		else if (junit) {
+		    if (i < expect.length)
+			assertEquals("Parameter value",
+			    expect[i], name + "=" + pvalue);
+		} else {
+		    out.println("\t[" + (i+1) + "] Name: " + name +
+			"\t\tValue: " + pvalue);
+		    if (expect != null && i < expect.length &&
+				!expect[i].equals(name + "=" + pvalue)) {
+			out.println("\tExpected:\t" + expect[i]);
+			errors++;
+		    }
+		}
+	    }
+	} catch (ParseException e) {
+	    if (gen_test_input)
+		out.println("Expect: Exception " + e);
+	    else if (junit)
+		assertTrue("Expected exception",
+		    expect.length == 1 && expect[0].equals("Exception"));
+	    else {
+		out.println("Got Exception: " + e);
+		if (expect != null &&
+		   (expect.length != 1 || !expect[0].equals("Exception"))) {
+		    out.println("Expected " + expect.length + " parameters");
+		    for (int i = 0; i < expect.length; i++)
+			out.println("\tExpected:\t" + expect[i]);
+		    errors++;
+		}
+	    }
+	}
+	if (gen_test_input && test_mail) {
+	    MimeMessage msg = new MimeMessage(session);
+	    byte[] buf = bos.toByteArray();
+	    msg.setDataHandler(new DataHandler(
+		new ByteArrayDataSource(buf, value)));
+	    msg.saveChanges();
+	    //msg.writeTo(System.out);
+	    folder.appendMessages(new Message[] { msg });
+	}
+    }
+
+    /**
+     * Initialize the Session, Store, and Folder.
+     */
+    private static void initMail() {
+        try {
+	    // Get a Properties object
+	    Properties props = System.getProperties();
+
+	    // Get a Session object
+	    session = Session.getInstance(props, null);
+	    session.setDebug(debug);
+
+	    // Get a Store object
+	    if (url != null) {
+		URLName urln = new URLName(url);
+		store = session.getStore(urln);
+		store.connect();
+	    } else {
+		if (protocol != null)		
+		    store = session.getStore(protocol);
+		else
+		    store = session.getStore();
+
+		// Connect
+		if (host != null || user != null || password != null)
+		    store.connect(host, port, user, password);
+		else
+		    store.connect();
+	    }
+
+	    // Open the Folder
+
+	    folder = store.getDefaultFolder();
+	    if (folder == null) {
+	        System.out.println("No default folder");
+	        System.exit(1);
+	    }
+
+	    if (mbox == null)
+		mbox = "parameter-list-test";
+	    folder = folder.getFolder(mbox);
+	    if (folder == null) {
+	        System.out.println("Invalid folder");
+	        System.exit(1);
+	    }
+
+	    if (gen_test_input) {
+		folder.delete(false);
+		folder.create(Folder.HOLDS_MESSAGES);
+		folder.open(Folder.READ_WRITE);
+	    } else {
+		folder.open(Folder.READ_ONLY);
+	    }
+
+	} catch (Exception ex) {
+	    System.out.println("Oops, got exception! " + ex.getMessage());
+	    ex.printStackTrace();
+	    System.exit(1);
+	}
+    }
+
+    /**
+     * Close the Folder and Store.
+     */
+    private static void doneMail() throws Exception {
+	folder.close(false);
+	store.close();
+    }
+
+    /**
+     * Use the messages in the Folder for testing.
+     */
+    private static void testMail() throws Exception {
+	Message[] msgs = folder.getMessages();
+
+	for (int i = 0; i < msgs.length; i++)
+	    testMessage(msgs[i]);
+    }
+
+    /**
+     * Test an individual message.
+     */
+    private static void testMessage(Message msg) throws Exception {
+	String[] expect = null;
+
+	BufferedReader in = new BufferedReader(
+			    new InputStreamReader(msg.getInputStream()));
+
+	String s = in.readLine();
+	if (s != null && s.startsWith("Expect: ")) {
+	    try {
+		int nexpect = Integer.parseInt(s.substring(8));
+		expect = new String[nexpect];
+		for (int i = 0; i < nexpect; i++)
+		    expect[i] = trim(in.readLine());
+	    } catch (NumberFormatException e) {
+		try {
+		    if (s.substring(8, 17).equals("Exception")) {
+			expect = new String[1];
+			expect[0] = "Exception";
+		    }
+		} catch (StringIndexOutOfBoundsException se) {
+		    // ignore it
+		}
+	    }
+	}
+
+	String ct = msg.getContentType();
+	test("Content-Type: ", ct, expect);
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/ParameterListTestSuite.java b/mail/src/test/java/javax/mail/internet/ParameterListTestSuite.java
new file mode 100644
index 0000000..66c32d4
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ParameterListTestSuite.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite.SuiteClasses;
+
+import com.sun.mail.test.ClassLoaderSuite;
+import com.sun.mail.test.ClassLoaderSuite.TestClass;
+
+/**
+ * Suite of ParameterList tests that need to be run in a separate class loader.
+ */
+@RunWith(ClassLoaderSuite.class)
+@TestClass(ParameterList.class)
+@SuiteClasses( {
+    ParameterListTests.class,
+    WindowsFileNames.class,
+    AppleFileNames.class,
+    NonAsciiFileNames.class,
+    DecodeParameters.class,
+    ParametersNoStrict.class
+})
+public class ParameterListTestSuite {
+}
diff --git a/mail/src/test/java/javax/mail/internet/ParameterListTests.java b/mail/src/test/java/javax/mail/internet/ParameterListTests.java
new file mode 100644
index 0000000..8df0266
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ParameterListTests.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * XXX - add more tests
+ */
+public class ParameterListTests {
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("ParameterListTests");
+	System.clearProperty("mail.mime.windowsfilenames");
+	System.clearProperty("mail.mime.applefilenames");
+    }
+
+    /**
+     * Test that backslashes are properly removed.
+     */
+    @Test
+    public void testBackslash() throws Exception {
+	System.clearProperty("mail.mime.windowsfilenames");
+	ParameterList pl = new ParameterList("; filename=\"\\a\\b\\c.txt\"");
+	assertEquals(pl.get("filename"), "abc.txt");
+    }
+
+    /**
+     * Test that a long parameter that's been split into segments
+     * is parsed correctly.
+     */
+    @Test
+    public void testLongParse() throws Exception {
+	String p0 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+	String p1 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+	ParameterList pl = new ParameterList("; p*0="+p0+"; p*1="+p1);
+	assertEquals(p0 + p1, pl.get("p"));
+    }
+
+    /**
+     * Test that a long parameter that's set programmatically is split
+     * into segments.
+     */
+    @Test
+    public void testLongSet() throws Exception {
+	String p0 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+	String p1 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+	ParameterList pl = new ParameterList();
+	pl.set("p", p0 + p1);
+	assertEquals(p0 + p1, pl.get("p"));
+	String pls = pl.toString();
+	assertTrue(pls.indexOf("p*0=") >= 0);
+	assertTrue(pls.indexOf("p*1=") >= 0);
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/ParametersNoStrict.java b/mail/src/test/java/javax/mail/internet/ParametersNoStrict.java
new file mode 100644
index 0000000..6367797
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ParametersNoStrict.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that the "mail.mime.parameters.strict" System property
+ * set to false allows bogus parameters to be parsed.
+ */
+public class ParametersNoStrict extends ParameterListDecode {
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("ParametersNoStrict");
+	System.setProperty("mail.mime.parameters.strict", "false");
+    }
+
+    @Test
+    public void testDecode() throws Exception {
+	testDecode("paramdatanostrict");
+    }
+
+    @AfterClass
+    public static void after() {
+	// should be unnecessary
+	System.clearProperty("mail.mime.parameters.strict");
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/ReferencesTest.java b/mail/src/test/java/javax/mail/internet/ReferencesTest.java
new file mode 100644
index 0000000..62ba5b2
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/ReferencesTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import java.util.Properties;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test setting of the References header.
+ *
+ * @author Bill Shannon
+ */
+public class ReferencesTest {
+    private static Session session = Session.getInstance(new Properties());
+
+    /*
+     * Test cases:
+     * 
+     * Message-Id	References	In-Reply-To	Expected Result
+     */
+
+    @Test
+    public void test1() throws MessagingException {
+	test(null,	null,		null,		null);
+    }
+
+    @Test
+    public void test2() throws MessagingException {
+	test(null,	null,		"<1@a>",	"<1@a>");
+    }
+
+    @Test
+    public void test3() throws MessagingException {
+	test(null,	"<2@b>",	null,		"<2@b>");
+    }
+
+    @Test
+    public void test4() throws MessagingException {
+	test(null,	"<2@b>",	"<1@a>",	"<2@b>");
+    }
+
+    @Test
+    public void test5() throws MessagingException {
+	test("<3@c>",	null,		null,		"<3@c>");
+    }
+
+    @Test
+    public void test6() throws MessagingException {
+	test("<3@c>",	null,		"<1@a>",	"<1@a> <3@c>");
+    }
+
+    @Test
+    public void test7() throws MessagingException {
+	test("<3@c>",	"<2@b>",	null,		"<2@b> <3@c>");
+    }
+
+    @Test
+    public void test8() throws MessagingException {
+	test("<3@c>",	"<2@b>",	"<1@a>",	"<2@b> <3@c>");
+    }
+
+    private static void test(String msgid, String ref, String irt, String res)
+				throws MessagingException {
+	MimeMessage msg = new MimeMessage(session);
+	msg.setFrom();
+	msg.setRecipients(Message.RecipientType.TO, "you@example.com");
+	msg.setSubject("test");
+	if (msgid != null)
+	    msg.setHeader("Message-Id", msgid);
+	if (ref != null)
+	    msg.setHeader("References", ref);
+	if (irt != null)
+	    msg.setHeader("In-Reply-To", irt);
+	msg.setText("text");
+
+	MimeMessage reply = (MimeMessage)msg.reply(false);
+	String rref = reply.getHeader("References", " ");
+
+	assertEquals(res, rref);
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/RestrictEncodingTest.java b/mail/src/test/java/javax/mail/internet/RestrictEncodingTest.java
new file mode 100644
index 0000000..8d32a0b
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/RestrictEncodingTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import com.sun.mail.test.AsciiStringInputStream;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.MessagingException;
+import javax.mail.BodyPart;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that the Content-Transfer-Encoding header is ignored
+ * for composite parts.
+ *
+ * XXX - We don't test any of the properties that control this behavior.
+ */
+public class RestrictEncodingTest {
+ 
+    private static Session s = Session.getInstance(new Properties());
+
+    @Test
+    public void testMultipart() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+	assertEquals(2, mp.getCount());
+
+	BodyPart bp = mp.getBodyPart(0);
+	assertEquals("first part=\n", bp.getContent());
+    }
+
+    @Test
+    public void testMessage() throws Exception {
+        MimeMessage m = createMessage();
+	MimeMultipart mp = (MimeMultipart)m.getContent();
+
+	BodyPart bp = mp.getBodyPart(1);
+	MimeMessage m2 = (MimeMessage)bp.getContent();
+	assertEquals("message=\n", m2.getContent());
+    }
+
+    @Test
+    public void testWrite() throws Exception {
+        MimeMessage m = new MimeMessage(s);
+	MimeMultipart mp = new MimeMultipart();
+	MimeBodyPart mbp = new MimeBodyPart();
+	mbp.setText("first part");
+	mp.addBodyPart(mbp);
+	MimeMessage m2 = new MimeMessage(s);
+	m2.setSubject("example");
+	m2.setText("message=\n");
+	mbp = new MimeBodyPart();
+	mbp.setContent(m2, "message/rfc822");
+	mbp.setHeader("Content-Transfer-Encoding", "quoted-printable");
+	mp.addBodyPart(mbp);
+	m.setContent(mp);
+	m.setHeader("Content-Transfer-Encoding", "quoted-printable");
+
+	m = new MimeMessage(m);		// copy it
+	mp = (MimeMultipart)m.getContent();
+
+	BodyPart bp = mp.getBodyPart(1);
+	m2 = (MimeMessage)bp.getContent();
+	assertEquals("message=\n", m2.getContent());
+    }
+
+    private static MimeMessage createMessage() throws MessagingException {
+        String content =
+	    "Mime-Version: 1.0\n" +
+	    "Content-Type: multipart/mixed; boundary=\"=3D\"\n" +
+	    "Content-Transfer-Encoding: quoted-printable\n" +
+	    "\n" +
+	    "--=3D\n" +
+	    "\n" +
+	    "first part=\n" +
+	    "\n" +
+	    "--=3D\n" +
+	    "Content-Type: message/rfc822\n" +
+	    "Content-Transfer-Encoding: quoted-printable\n" +
+	    "\n" +
+	    "Subject: example\n" +
+	    "\n" +
+	    "message=\n" +
+	    "\n" +
+	    "--=3D--\n";
+
+	return new MimeMessage(s, new AsciiStringInputStream(content));
+    }
+}
diff --git a/mail/src/test/java/javax/mail/internet/WindowsFileNames.java b/mail/src/test/java/javax/mail/internet/WindowsFileNames.java
new file mode 100644
index 0000000..b11c96b
--- /dev/null
+++ b/mail/src/test/java/javax/mail/internet/WindowsFileNames.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.internet;
+
+import org.junit.*;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Test that the "mail.mime.windowsfilenames" System property
+ * causes the filename to be returned with backslashes preserved.
+ */
+public class WindowsFileNames {
+
+    @BeforeClass
+    public static void before() {
+	System.out.println("WindowsFileNames");
+	System.setProperty("mail.mime.windowsfilenames", "true");
+    }
+
+    @Test
+    public void testProp() throws Exception {
+	ParameterList pl = new ParameterList("; filename=\"\\a\\b\\c.txt\"");
+	assertEquals(pl.get("filename"), "\\a\\b\\c.txt");
+    }
+
+    @AfterClass
+    public static void after() {
+	// should be unnecessary
+	System.clearProperty("mail.mime.windowsfilenames");
+    }
+}
diff --git a/mail/src/test/java/javax/mail/search/SearchTermSerializationTest.java b/mail/src/test/java/javax/mail/search/SearchTermSerializationTest.java
new file mode 100644
index 0000000..0c03c4b
--- /dev/null
+++ b/mail/src/test/java/javax/mail/search/SearchTermSerializationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package javax.mail.search;
+
+import java.io.*;
+import java.util.Date;
+
+import javax.mail.*;
+import javax.mail.search.*;
+import javax.mail.internet.*;
+
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * SearchTerm serialization test.
+ *
+ * @author Bill Shannon
+ */
+
+public class SearchTermSerializationTest {
+
+    @Test
+    public void testSerialization() throws IOException, ClassNotFoundException {
+	// construct a SearchTerm using all SearchTerm types
+	SearchTerm term = new AndTerm(new SearchTerm[] {
+	    new BodyTerm("text"),
+	    new FlagTerm(new Flags(Flags.Flag.RECENT), true),
+	    new FromStringTerm("foo@bar"),
+	    new HeaderTerm("X-Mailer", "dtmail"),
+	    new MessageIDTerm("12345@sun.com"),
+	    new MessageNumberTerm(42),
+	    new NotTerm(
+		new OrTerm(
+		    new ReceivedDateTerm(ReceivedDateTerm.LT, new Date()),
+		    new RecipientStringTerm(Message.RecipientType.CC, "foo")
+		)
+	    ),
+	    new RecipientTerm(MimeMessage.RecipientType.NEWSGROUPS,
+				new NewsAddress("comp.lang.java", "newshost")),
+	    new SentDateTerm(SentDateTerm.NE, new Date()),
+	    new SizeTerm(SizeTerm.LT, 1000),
+	    new SubjectTerm("test")
+	});
+
+	// serialize it to a byte array
+	ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	ObjectOutputStream oos = new ObjectOutputStream(bos);
+	oos.writeObject(term);
+	bos.close();
+
+	// read it back in
+	ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+	ObjectInputStream ois = new ObjectInputStream(bis);
+	SearchTerm term2 = (SearchTerm)ois.readObject();
+
+	// compare it with the original
+	assertEquals(term, term2);
+    }
+}
diff --git a/mail/src/test/resources/com/sun/mail/imap/protocol/uiddata b/mail/src/test/resources/com/sun/mail/imap/protocol/uiddata
new file mode 100644
index 0000000..006e0ff
--- /dev/null
+++ b/mail/src/test/resources/com/sun/mail/imap/protocol/uiddata
@@ -0,0 +1,76 @@
+#
+# Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+#
+# Data to test UIDSet.
+#
+
+TEST one UID
+DATA 1
+EXPECT 1
+
+TEST two UIDs
+DATA 1,3
+EXPECT 1 3
+
+TEST UID range
+DATA 1:2
+EXPECT 1 2
+
+TEST bigger UID range
+DATA 1:3
+EXPECT 1 2 3
+
+TEST two ranges
+DATA 1:3,5:7
+EXPECT 1 2 3 5 6 7
+
+TEST ranges and singles
+DATA 1:3,5,7:9
+EXPECT 1 2 3 5 7 8 9
+
+TEST many singles
+DATA 1,3,5,7,9
+EXPECT 1 3 5 7 9
+
+TEST max
+DATA 1
+MAX 1
+EXPECT 1
+
+TEST max2
+DATA 2
+MAX 2
+EXPECT 2
+
+TEST max3
+DATA 1:2
+MAX 2
+EXPECT 1 2
+
+TEST max4
+DATA 1:2
+MAX 1 1
+EXPECT 1
+
+TEST max5
+DATA 1:4
+MAX 3 1:3
+EXPECT 1 2 3
+
+TEST empty
+DATA EMPTY
+EXPECT EMPTY
diff --git a/mail/src/test/resources/com/sun/mail/util/uudata b/mail/src/test/resources/com/sun/mail/util/uudata
new file mode 100644
index 0000000..56d29de
--- /dev/null
+++ b/mail/src/test/resources/com/sun/mail/util/uudata
@@ -0,0 +1,197 @@
+#
+# Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+#
+# Data to test uudecoder.
+#
+# Mostly tests error cases and ability to ignore errors.
+#
+
+TEST a simple decode test
+DATA
+begin 644 encoder.buf
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+this is a very very very very very long line to test the decoder
+END
+
+TEST no begin
+DATA
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+EXCEPTION com.sun.mail.util.DecodingException
+END
+
+TEST no end
+DATA
+begin 644 encoder.buf
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+EXPECT
+EXCEPTION com.sun.mail.util.DecodingException
+END
+
+TEST no end, no empty line
+DATA
+begin 644 encoder.buf
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+EXPECT
+EXCEPTION com.sun.mail.util.DecodingException
+END
+
+TEST no begin, ignore errors
+DATA ignoreMissingBeginEnd
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+this is a very very very very very long line to test the decoder
+END
+
+TEST no end, ignore errors
+DATA ignoreMissingBeginEnd
+begin 644 encoder.buf
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+EXPECT
+this is a very very very very very long line to test the decoder
+END
+
+TEST no begin, no end, ignore errors
+DATA ignoreMissingBeginEnd
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+EXPECT
+this is a very very very very very long line to test the decoder
+END
+
+TEST empty line, ignore errors
+DATA ignoreMissingBeginEnd
+
+begin 644 encoder.buf
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+EXPECT
+this is a very very very very very long line to test the decoder
+END
+
+TEST empty line, no begin, ignore errors
+DATA ignoreMissingBeginEnd
+
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+EXPECT
+this is a very very very very very long line to test the decoder
+END
+
+TEST bad mode
+DATA
+begin xxx encoder.buf
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+EXCEPTION com.sun.mail.util.DecodingException
+END
+
+TEST bad mode, ignore errors
+DATA ignoreErrors
+begin xxx encoder.buf
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+this is a very very very very very long line to test the decoder
+END
+
+TEST bad filename
+DATA
+begin 644
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+EXCEPTION com.sun.mail.util.DecodingException
+END
+
+TEST bad filename, ignore errors
+DATA ignoreErrors
+begin 644
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+this is a very very very very very long line to test the decoder
+END
+
+TEST garbage data
+DATA
+begin 644 encoder.buf
+XXX
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+EXCEPTION com.sun.mail.util.DecodingException
+END
+
+TEST garbage data (tab)
+DATA
+begin 644 encoder.buf
+	
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+EXCEPTION com.sun.mail.util.DecodingException
+END
+
+TEST garbage data, ignore errors
+DATA ignoreErrors
+begin 644 encoder.buf
+XXX
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+ 
+end
+EXPECT
+this is a very very very very very long line to test the decoder
+END
+
+TEST ignore both kinds of errors
+DATA ignoreErrors ignoreMissingBeginEnd
+XXX
+M=&AI<R!I<R!A('9E<GD@=F5R>2!V97)Y('9E<GD@=F5R>2!L;VYG(&QI;F4@
+4=&\@=&5S="!T:&4@9&5C;V1E<@H!
+EXPECT
+this is a very very very very very long line to test the decoder
+END
diff --git a/mail/src/test/resources/javax/mail/internet/MailDateFormat_new.ser b/mail/src/test/resources/javax/mail/internet/MailDateFormat_new.ser
new file mode 100644
index 0000000..2b31027
--- /dev/null
+++ b/mail/src/test/resources/javax/mail/internet/MailDateFormat_new.ser
Binary files differ
diff --git a/mail/src/test/resources/javax/mail/internet/MailDateFormat_old.ser b/mail/src/test/resources/javax/mail/internet/MailDateFormat_old.ser
new file mode 100644
index 0000000..4c33b9b
--- /dev/null
+++ b/mail/src/test/resources/javax/mail/internet/MailDateFormat_old.ser
Binary files differ
diff --git a/mail/src/test/resources/javax/mail/internet/addrfolddata b/mail/src/test/resources/javax/mail/internet/addrfolddata
new file mode 100644
index 0000000..11c0f48
--- /dev/null
+++ b/mail/src/test/resources/javax/mail/internet/addrfolddata
Binary files differ
diff --git a/mail/src/test/resources/javax/mail/internet/addrlist b/mail/src/test/resources/javax/mail/internet/addrlist
new file mode 100644
index 0000000..b1845a4
--- /dev/null
+++ b/mail/src/test/resources/javax/mail/internet/addrlist
@@ -0,0 +1,773 @@
+Comment:
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+Comment:
+
+	A set of test addresses stolen from the dtmail test suite,
+	with a few of my own.
+	Use with "addrtest -p <addrlist".
+
+	grep for "Expected:" in output to find errors
+
+	CAREFUL: a blank line in this file causes everything following it
+		 to be ignored until a line starting with "From " (so that
+		 addrtest -p can be pointed at a real mailbox).
+
+From start here
+Strict: false
+Comment:
+	This first set of addresses are all good.
+To: ggere
+Expect: 1
+	ggere
+To: /tmp/mail.out
+Expect: 1
+	/tmp/mail.out
+To: +mailbox
+Expect: 1
+	+mailbox
+To: ~user/mailbox
+Expect: 1
+	~user/mailbox
+To: ~/mailbox
+Expect: 1
+	~/mailbox
+To: /PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay
+Expect: 1
+	/PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay
+To: ggere, /tmp/mail.out, +mailbox, ~user/mailbox, ~/mailbox, /PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay
+Expect: 6
+	ggere
+	/tmp/mail.out
+	+mailbox
+	~user/mailbox
+	~/mailbox
+	/PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay
+To: ggere /tmp/mail.out +mailbox ~user/mailbox ~/mailbox /PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay
+Expect: 6
+	ggere
+	/tmp/mail.out
+	+mailbox
+	~user/mailbox
+	~/mailbox
+	/PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay
+To: (x)<y@a>(z)
+Expect: 1
+	y@a
+Comment: as above, but strict
+Strict: true
+To: ggere /tmp/mail.out +mailbox ~user/mailbox ~/mailbox /PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay
+Expect: Exception javax.mail.internet.AddressException: Local address contains control or whitespace in string ``ggere /tmp/mail.out +mailbox ~user/mailbox ~/mailbox /PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay''
+Strict: false
+To: Mad Genius <george@boole>
+Expect: 1
+	george@boole
+To: "C'est bien moche" <paris@france>
+Expect: 1
+	paris@france
+To: "know, any, famous" <french@physicists>
+Expect: 1
+	french@physicists
+To: laborious (But Bug Free)
+Expect: 1
+	laborious
+To: confused (about, being, french)
+Expect: 1
+	confused
+To: it (takes, no (time, at) all)
+Expect: 1
+	it
+To: two@weeks (It Will Take)
+Expect: 1
+	two@weeks
+To: it@is (brilliant (genius, and) superb)
+Expect: 1
+	it@is
+To: if@you (could, see (almost, as, (badly, you) would) agree)
+Expect: 1
+	if@you
+To: cannot@waste (My, Intellectual, Cycles)
+Expect: 1
+	cannot@waste
+To: users:get,what,they,deserve;
+Expect: 1
+	users:get,what,they,deserve;
+To: "C'est bien moche" <paris@france>, Mad Genius <george@boole>, two@weeks (It Will Take), /tmp/mail.out, laborious (But Bug Free), cannot@waste (My, Intellectual, Cycles), users:get,what,they,deserve;, it (takes, no (time, at) all), if@you (could, see (almost, as, (badly, you) would) agree), "know, any, famous" <French@physicists>, it@is (brilliant (genius, and) superb), confused (about, being, french)
+Expect: 12
+	paris@france
+	george@boole
+	two@weeks
+	/tmp/mail.out
+	laborious
+	cannot@waste
+	users:get,what,they,deserve;
+	it
+	if@you
+	French@physicists
+	it@is
+	confused
+To: ggere, /tmp/mail.out, +mailbox, ~user/mailbox, ~/mailbox, /PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay, "C'est bien moche" <paris@france>, Mad Genius <george@boole>, two@weeks (It Will Take), /tmp/mail.out, laborious (But Bug Free), cannot@waste (My, Intellectual, Cycles), users:get,what,they,deserve;, it (takes, no (time, at) all), if@you (could, see (almost, as, (badly, you) would) agree), "know, any, famous" <French@physicists>, it@is (brilliant (genius, and) superb), confused (about, being, french)
+Expect: 18
+	ggere
+	/tmp/mail.out
+	+mailbox
+	~user/mailbox
+	~/mailbox
+	/PN=x400.address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci.ebay
+	paris@france
+	george@boole
+	two@weeks
+	/tmp/mail.out
+	laborious
+	cannot@waste
+	users:get,what,they,deserve;
+	it
+	if@you
+	French@physicists
+	it@is
+	confused
+To: testa testb testc /tmp/mail.out ~ggere/mail.out testd teste
+Expect: 7
+	testa
+	testb
+	testc
+	/tmp/mail.out
+	~ggere/mail.out
+	testd
+	teste
+Comment: as above, but strict
+Strict: true
+To: testa testb testc /tmp/mail.out ~ggere/mail.out testd teste
+Expect: Exception javax.mail.internet.AddressException: Local address contains control or whitespace in string ``testa testb testc /tmp/mail.out ~ggere/mail.out testd teste''
+Strict: false
+To: testa, testb , testc /tmp/mail.out ,~ggere/mail.out, ,testd,teste,,,
+Expect: 7
+	testa
+	testb
+	testc
+	/tmp/mail.out
+	~ggere/mail.out
+	testd
+	teste
+Comment: as above, but strict
+Strict: true
+To: testa, testb , testc /tmp/mail.out ,~ggere/mail.out, ,testd,teste,,,
+Expect: Exception javax.mail.internet.AddressException: Local address contains control or whitespace in string ``testa testb testc /tmp/mail.out ~ggere/mail.out testd teste''
+Strict: false
+To: testa testb <testc@testd>
+Expect: 1
+	testc@testd
+To: Adam S Moskowitz <adamm@onion.inset.com>,
+        Andrew Gollan <adjg@softway.sw.OZ.AU>,
+        Bret Anthony Marquis <bam@trout.nosc.MIL>,
+        Bill Shannon <shannon@Sun.COM>, Bob Gray <rgray@UsWest.COM>,
+        Brian Ellis <bri@Boulder.Colorado.EDU>,
+        Don Coleman <coleman@legato.COM>,
+        Dennis Ritchie <dmr@research.ATT.COM>, Evi Nemeth <evi@Colorado.EDU>,
+        Lori Grob <grob@chorus.FR>, Paula Hawthorn <hawthorn@HPL.HP.COM>,
+        Andrew Hume <andrew@research.ATT.COM>,
+        Jaap Akkerhuis <jaap@research.ATT.COM>,
+        Jim R Oldroyd <jr@onion.inset.com>,
+        John Quarterman <jsq@longway.TIC.COM>,
+        Judy DesHarnais <judy@USENIX.ORG>, Karen Shannon <kas@Eng>,
+        Ken McDonell <kenj@Pyramid.COM>,
+        Rob Kolstad <kolstad@rmtc.Central.Sun.COM>,
+        Paul Kooros <kooros@tigger.cs.Colorado.EDU>,
+        Robert Elz <kre@Munnari.OZ.AU>,
+        Miriam Amos Nihart <miriam@decwet.enet.DEC.COM>,
+        "Mike O'Dell" <mo@gizmo.Bellcore.COM>,
+        Sharon Murrel <eowyn@research.ATT.COM>,
+        Peg Schafer <peg@media-lab.media.MIT.EDU>, Peter Salus <peter@SUG.ORG>,
+        Bill Shannon <shannon@Sun.COM>, Stu Feldman <sif@Bellcore.COM>,
+        Stephen Williams <stevew@netboss1.trg.saic.com>,
+        Teus Hagen <teus@oce.NL>, Trent Hein <trent@xor.COM>,
+        Thomas Alan Wood <twood@capmkt.COM>,
+        Michael Ubell <"mttam::ubell"@SFBay.ENet.DEC.COM>,
+        Martha Zimet <zimet@Corp>
+Expect: 34
+	adamm@onion.inset.com
+	adjg@softway.sw.OZ.AU
+	bam@trout.nosc.MIL
+	shannon@Sun.COM
+	rgray@UsWest.COM
+	bri@Boulder.Colorado.EDU
+	coleman@legato.COM
+	dmr@research.ATT.COM
+	evi@Colorado.EDU
+	grob@chorus.FR
+	hawthorn@HPL.HP.COM
+	andrew@research.ATT.COM
+	jaap@research.ATT.COM
+	jr@onion.inset.com
+	jsq@longway.TIC.COM
+	judy@USENIX.ORG
+	kas@Eng
+	kenj@Pyramid.COM
+	kolstad@rmtc.Central.Sun.COM
+	kooros@tigger.cs.Colorado.EDU
+	kre@Munnari.OZ.AU
+	miriam@decwet.enet.DEC.COM
+	mo@gizmo.Bellcore.COM
+	eowyn@research.ATT.COM
+	peg@media-lab.media.MIT.EDU
+	peter@SUG.ORG
+	shannon@Sun.COM
+	sif@Bellcore.COM
+	stevew@netboss1.trg.saic.com
+	teus@oce.NL
+	trent@xor.COM
+	twood@capmkt.COM
+	"mttam::ubell"@SFBay.ENet.DEC.COM
+	zimet@Corp
+To: '"a,b"'
+Expect: 1
+	'"a,b"'
+To: 'a, b'
+Expect: 2
+	'a
+	b'
+To: "a,b"
+Expect: 1
+	"a,b"
+To: shakey!"a,b"
+Expect: 1
+	shakey!"a,b"
+To: "foo bar"
+Expect: 1
+	"foo bar"
+To: (Foo Bar) foo@bar
+Expect: 1
+	foo@bar
+To: (Foo Bar) foo@bar, (My Name) my@name
+Expect: 2
+	foo@bar
+	my@name
+To: Ben Keitch <">"@openworld.co.uk> 
+Expect: 1
+	">"@openworld.co.uk
+To: Ben Keitch <">\""\>@openworld.co.uk> 
+Expect: 1
+	">\""\>@openworld.co.uk
+To: A Group:Chris Jones <c@a.test>,joe@where.test,John <jdoe@one.test>; 
+Expect: 1
+	A Group:Chris Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;
+To: Undisclosed-Recipients:;
+Expect: 1
+	Undisclosed-Recipients:;
+To:  "Mark Hapner" <mark.hapner@eng.sun.com>, <mark.himelstein@eng.sun.com>
+Expect: 2
+	mark.hapner@eng.sun.com
+	mark.himelstein@eng.sun.com
+To: <rem@rockit> (Ralph Miller), <shannon@datsun> (Bill Shannon)
+Expect: 2
+	rem@rockit
+	shannon@datsun
+To: "" <javamail@sun.com>
+Expect: 1
+	javamail@sun.com
+To: sun!joe (joe@sun)
+Expect: 1
+	sun!joe
+To: "john doe" (john.doe@a.com)
+Expect: 1
+	"john doe"
+To: (test domain literal verification) b@[1.2.3.4]
+Expect: 1
+	b@[1.2.3.4]
+Comment: parsed as a header, we pick out the address from the "comment"
+Header: true
+To: "john doe" (john.doe@a.com)
+Expect: 1
+	john.doe@a.com
+Comment: test that headers are unfolded before parsing
+To: "John
+	Smith" <jsmith@
+	crappy-email-archiving-company.com>
+Expect: 1
+	jsmith@	crappy-email-archiving-company.com
+Header: false
+To: (Test) <djoe@zimbra.com>,djoe@zimbra.com (Test) 
+Expect: 2
+	djoe@zimbra.com
+	djoe@zimbra.com
+Comment:
+	All the following addresses are bad.
+To: ,
+Expect: 0
+To: <
+Expect: Exception javax.mail.internet.AddressException: Missing '>' in string ``<'' at position 1
+To: >
+Expect: Exception javax.mail.internet.AddressException: Missing '<' in string ``>'' at position 0
+To: "
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"'' at position 1
+To: >>>
+Expect: Exception javax.mail.internet.AddressException: Missing '<' in string ``>>>'' at position 0
+To: <<<
+Expect: Exception javax.mail.internet.AddressException: Missing '>' in string ``<<<'' at position 3
+To: paris@france>
+Expect: Exception javax.mail.internet.AddressException: Missing '<' in string ``paris@france>'' at position 12
+To: "C'est bien moche <paris@france>
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"C'est bien moche <paris@france>'' at position 32
+To: "C'est bien moche" <paris@france
+Expect: Exception javax.mail.internet.AddressException: Missing '>' in string ``"C'est bien moche" <paris@france'' at position 32
+To: "C'est bien moche" paris@france>
+Expect: Exception javax.mail.internet.AddressException: Missing '<' in string ``"C'est bien moche" paris@france>'' at position 31
+To: "C'est bien moche" <paris@>
+Expect: Exception javax.mail.internet.AddressException: Missing domain in string ``paris@''
+To: laborious but (Bug Free)
+Expect: Exception javax.mail.internet.AddressException: Local address contains control or whitespace in string ``laborious but''
+To: confused (about, being, french
+Expect: Exception javax.mail.internet.AddressException: Missing ')' in string ``confused (about, being, french'' at position 30
+To: it@is (brilliant (genius, and superb)
+Expect: Exception javax.mail.internet.AddressException: Missing ')' in string ``it@is (brilliant (genius, and superb)'' at position 37
+To: it@is (brilliant (genius), and superb
+Expect: Exception javax.mail.internet.AddressException: Missing ')' in string ``it@is (brilliant (genius), and superb'' at position 37
+To: if@you (could, see (almost, as, (badly, you would) agree)
+Expect: Exception javax.mail.internet.AddressException: Missing ')' in string ``if@you (could, see (almost, as, (badly, you would) agree)'' at position 57
+To: if@you (could, see (almost, as, (badly, you) would) agree
+Expect: Exception javax.mail.internet.AddressException: Missing ')' in string ``if@you (could, see (almost, as, (badly, you) would) agree'' at position 57
+To: two@ (It Will Take)
+Expect: Exception javax.mail.internet.AddressException: Missing domain in string ``two@''
+To: two@
+Expect: Exception javax.mail.internet.AddressException: Missing domain in string ``two@''
+To: two@weeks (It Will Take
+Expect: Exception javax.mail.internet.AddressException: Missing ')' in string ``two@weeks (It Will Take'' at position 23
+To: <paris@france
+Expect: Exception javax.mail.internet.AddressException: Missing '>' in string ``<paris@france'' at position 13
+To: paris@france>
+Expect: Exception javax.mail.internet.AddressException: Missing '<' in string ``paris@france>'' at position 12
+To: @france
+Expect: Exception javax.mail.internet.AddressException: Missing local name in string ``@france''
+To: @
+Expect: Exception javax.mail.internet.AddressException: Missing local name in string ``@''
+To: <
+Expect: Exception javax.mail.internet.AddressException: Missing '>' in string ``<'' at position 1
+To: >
+Expect: Exception javax.mail.internet.AddressException: Missing '<' in string ``>'' at position 0
+To: (
+Expect: Exception javax.mail.internet.AddressException: Missing ')' in string ``('' at position 1
+To: )
+Expect: Exception javax.mail.internet.AddressException: Missing '(' in string ``)'' at position 0
+To: "
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"'' at position 1
+To: friends-list:;@mastodon.CS.Berkeley.EDU
+Expect: Exception javax.mail.internet.AddressException: Missing local name in string ``@mastodon.CS.Berkeley.EDU''
+To: a@.com
+Expect: Exception javax.mail.internet.AddressException: Domain starts with dot in string ``a@.com''
+To: a@com.
+Expect: Exception javax.mail.internet.AddressException: Domain ends with dot in string ``a@com.''
+To: a@b..com
+Expect: Exception javax.mail.internet.AddressException: Domain contains dot-dot in string ``a@b..com''
+To: .a@b.com
+Expect: Exception javax.mail.internet.AddressException: Local address starts with dot in string ``.a@b.com''
+To: a.@b.com
+Expect: Exception javax.mail.internet.AddressException: Local address ends with dot in string ``a.@b.com''
+To: a..b@b.com
+Expect: Exception javax.mail.internet.AddressException: Local address contains dot-dot in string ``a..b@b.com''
+To: a@com
+Expect: Exception javax.mail.internet.AddressException: Domain contains control or whitespace in string ``a@com''
+To: a@b.com
+Expect: Exception javax.mail.internet.AddressException: Local address contains control or whitespace in string ``a@b.com''
+To: ab
+Expect: Exception javax.mail.internet.AddressException: Local address contains control or whitespace in string ``ab''
+To: @b.com
+Expect: Exception javax.mail.internet.AddressException: Missing local name in string ``@b.com''
+To: javamail@Sun.COM;
+Expect: Exception javax.mail.internet.AddressException: Illegal semicolon, not in group in string ``javamail@Sun.COM;'' at position 16
+To: Undisclosed-Recipient:;@jupiter.activeisp.com;;;;;
+Expect: Exception javax.mail.internet.AddressException: Illegal semicolon, not in group in string ``Undisclosed-Recipient:;@jupiter.activeisp.com;;;;;'' at position 45
+To: Undisclosed-Recipient:;@java.sun.com;;;
+Expect: Exception javax.mail.internet.AddressException: Illegal semicolon, not in group in string ``Undisclosed-Recipient:;@java.sun.com;;;'' at position 36
+To: "bejjzcfr@aol.com
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"bejjzcfr@aol.com'' at position 17
+To: "butbp@msn.com
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"butbp@msn.com'' at position 14
+To: "@msn.com
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"@msn.com'' at position 9
+To: "@starband.net
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"@starband.net'' at position 14
+To: jko23l1@klds.java.sun.com;, ad@java.sun.com
+Expect: Exception javax.mail.internet.AddressException: Illegal semicolon, not in group in string ``jko23l1@klds.java.sun.com;, ad@java.sun.com'' at position 25
+To: jko23l1@klds;ad;;;;
+Expect: Exception javax.mail.internet.AddressException: Illegal semicolon, not in group in string ``jko23l1@klds;ad;;;;'' at position 12
+To: mailto:mailto:snoa101custserv@hotmail.com
+Expect: Exception javax.mail.internet.AddressException: Nested group in string ``mailto:mailto:snoa101custserv@hotmail.com'' at position 13
+To: mailto:mailto:snoa101custserv@hotmail.com, foo@bar.com
+Expect: Exception javax.mail.internet.AddressException: Nested group in string ``mailto:mailto:snoa101custserv@hotmail.com, foo@bar.com'' at position 13
+To: "@foo.com, a@b.com
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"@foo.com, a@b.com'' at position 18
+To: <@foo.com, a@b.com
+Expect: Exception javax.mail.internet.AddressException: Missing '>' in string ``<@foo.com, a@b.com'' at position 18
+To: <a@b.com> <c@d.com>
+Expect: Exception javax.mail.internet.AddressException: Extra route-addr in string ``<a@b.com> <c@d.com>'' at position 10
+To: yyiohohjjlj[ojolhji@hotmail.com
+Expect: Exception javax.mail.internet.AddressException: Missing ']' in string ``yyiohohjjlj[ojolhji@hotmail.com'' at position 31
+To: www.adslexpress.com.tw--?????q?H?[?o??-0@kathmandu.sun.com
+Expect: Exception javax.mail.internet.AddressException: Missing ']' in string ``www.adslexpress.com.tw--?????q?H?[?o??-0@kathmandu.sun.com'' at position 58
+Cc: webmail.hinet.net@nwkea-mail-1.sun.com (¤¤µØºô­¶¶l¥ó),
+   www.adslexpress.com.tw--¤¤µØ¹q«H¥[ªo¯¸-0@nwkea-mail-1.sun.com,
+   www.cityfamily.com.tw---ºô¸ô¦P¾Ç·|-0@nwkea-mail-1.sun.com
+Expect: Exception javax.mail.internet.AddressException: Missing ']' in string ``webmail.hinet.net@nwkea-mail-1.sun.com (?????????l??),
+   www.adslexpress.com.tw--?????q?H?[?o??-0@nwkea-mail-1.sun.com,
+   www.cityfamily.com.tw---?????P???|-0@nwkea-mail-1.sun.com'' at position 181
+To: www.adslexpress.com.tw--¤¤µØ¹q«H¥[ªo¯¸-0@patan.sun.com
+Expect: Exception javax.mail.internet.AddressException: Missing ']' in string ``www.adslexpress.com.tw--?????q?H?[?o??-0@patan.sun.com'' at position 54
+To: Giant.White.Beans;Great.Northern.Beans;Lima.Beans;Navy.Beans@4email-03;;;;;;;;;;;;;;;;;;
+Expect: Exception javax.mail.internet.AddressException: Illegal semicolon, not in group in string ``Giant.White.Beans;Great.Northern.Beans;Lima.Beans;Navy.Beans@4email-03;;;;;;;;;;;;;;;;;;'' at position 17
+To: \(^^/@nwkea-mail-2.sun.com
+Expect: Exception javax.mail.internet.AddressException: Missing ')' in string ``\(^^/@nwkea-mail-2.sun.com'' at position 26
+Cc: <tom.ross@sun.com>, Cc: <javamail@sun.com>, Cc: <javahi@sun.com>,
+   Cc: <javadoc-tool@sun.com>, Cc: <javacard-feedback@sun.com>
+Expect: Exception javax.mail.internet.AddressException: Nested group in string ``<tom.ross@sun.com>, Cc: <javamail@sun.com>, Cc: <javahi@sun.com>,
+   Cc: <javadoc-tool@sun.com>, Cc: <javacard-feedback@sun.com>'' at position 46
+From: <hello@sun.com>, java2d-interest@sun.com, java3d-interest@sun.com,
+	javadoc-tool@sun.com, javamail@sun.com, javasound-interest@sun.com,
+	javaspeech-interest@sun.com, jdbc@sun.com, jim.cook@sun.com,
+	joachim.heck@sun.com, kammy@sun.com, kenneth.cheung@sun.com,
+	Wed@brmea-mail-3.sun.com, multipart/alternative@brmea-mail-3.sun.com,
+	text/html@brmea-mail-3.sun.com;
+Expect: Exception javax.mail.internet.AddressException: Illegal semicolon, not in group in string ``<hello@sun.com>, java2d-interest@sun.com, java3d-interest@sun.com,
+	javadoc-tool@sun.com, javamail@sun.com, javasound-interest@sun.com,
+	javaspeech-interest@sun.com, jdbc@sun.com, jim.cook@sun.com,
+	joachim.heck@sun.com, kammy@sun.com, kenneth.cheung@sun.com,
+	Wed@brmea-mail-3.sun.com, multipart/alternative@brmea-mail-3.sun.com,
+	text/html@brmea-mail-3.sun.com;'' at position 362
+To: "???Y???z???~?j?q?n??
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"???Y???z???~?j?q?n??'' at position 21
+To: "ZETA CELULARES
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"ZETA CELULARES'' at position 15
+To: "Investigaci?n para el desarrollo de micro-emprendimientos Schreiber TE 4431-7050
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"Investigaci?n para el desarrollo de micro-emprendimientos Schreiber TE 4431-7050'' at position 81
+To: "Aproveche*
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"Aproveche*'' at position 11
+To: "ULTIMA. OPORTUNIDAD*
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``"ULTIMA. OPORTUNIDAD*'' at position 21
+Cc: 55:55@07, 57:18@07, 36:39@10, 36:42@10, streetwalker!1987, streetwalker!5,
+        streetwalker!87, <8710051455.AA08484@oak.sunecd.com>, <oak!kinnear>,
+        streetwalker!AA00159;, streetwalker!AA08484;, streetwalker!AA16178;,
+        streetwalker!Date:, streetwalker!EDT, streetwalker!Ecd.Sun.COM,
+        streetwalker!From, streetwalker!From:, streetwalker!Message-Id:,
+        streetwalker!Mon, streetwalker!Oct, streetwalker!PDT, streetwalker!RO,
+        streetwalker!Received:, streetwalker!Return-Path:,
+        streetwalker!Status:, streetwalker!To:, streetwalker!by,
+        streetwalker!from, streetwalker!id, streetwalker!oak.sunecd.com,
+        streetwalker!root, streetwalker!rr, streetwalker!streetwalker.sun.com
+Expect: Exception javax.mail.internet.AddressException: Nested group in string ``55:55@07, 57:18@07, 36:39@10, 36:42@10, streetwalker!1987, streetwalker!5,
+        streetwalker!87, <8710051455.AA08484@oak.sunecd.com>, <oak!kinnear>,
+        streetwalker!AA00159;, streetwalker!AA08484;, streetwalker!AA16178;,
+        streetwalker!Date:, streetwalker!EDT, streetwalker!Ecd.Sun.COM,
+        streetwalker!From, streetwalker!From:, streetwalker!Message-Id:,
+        streetwalker!Mon, streetwalker!Oct, streetwalker!PDT, streetwalker!RO,
+        streetwalker!Received:, streetwalker!Return-Path:,
+        streetwalker!Status:, streetwalker!To:, streetwalker!by,
+        streetwalker!from, streetwalker!id, streetwalker!oak.sunecd.com,
+        streetwalker!root, streetwalker!rr, streetwalker!streetwalker.sun.com'' at position 12
+To: Re:@cachaca, Subject:@cachaca, and@cachaca, getmclist@cachaca, glenn@ivrel,
+        iconv@cachaca, questions@cachaca
+Expect: Exception javax.mail.internet.AddressException: Nested group in string ``Re:@cachaca, Subject:@cachaca, and@cachaca, getmclist@cachaca, glenn@ivrel,
+        iconv@cachaca, questions@cachaca'' at position 20
+To: " <string@string.ru>  
+Expect: Exception javax.mail.internet.AddressException: Missing '"' in string ``" <string@string.ru>  '' at position 22
+To: [xxx]foo@bar.org
+Expect: Exception in thread "main" javax.mail.internet.AddressException: Local address contains illegal character in string ``[xxx]foo@bar.org''
+Comment:
+	Now parse all the above bad addresses as if they were in a header,
+	expecting no failures.
+Header: true
+To: ,
+Expect: 0
+To: <
+Expect: 1
+	<
+To: >
+Expect: 1
+	>
+To: "
+Expect: 1
+	"
+To: >>>
+Expect: 1
+	>>>
+To: <<<
+Expect: 1
+	<<<
+To: paris@france>
+Expect: 1
+	paris@france>
+To: "C'est bien moche <paris@france>
+Expect: 1
+	paris@france
+To: "C'est bien moche" <paris@france
+Expect: 1
+	"C'est bien moche" <paris@france
+To: "C'est bien moche" paris@france>
+Expect: 1
+	"C'est bien moche" paris@france>
+To: "C'est bien moche" <paris@>
+Expect: 1
+	paris@
+To: laborious but (Bug Free)
+Expect: 1
+	laborious but
+To: confused (about, being, french
+Expect: 3
+	confused
+	being
+	french
+To: it@is (brilliant (genius, and superb)
+Expect: 1
+	it@is
+To: it@is (brilliant (genius), and superb
+Expect: 2
+	it@is
+	and superb
+To: if@you (could, see (almost, as, (badly, you would) agree)
+Expect: 2
+	if@you
+	see
+To: if@you (could, see (almost, as, (badly, you) would) agree
+Expect: 2
+	if@you
+	see
+To: two@ (It Will Take)
+Expect: 1
+	two@
+To: two@
+Expect: 1
+	two@
+To: two@weeks (It Will Take
+Expect: 1
+	two@weeks
+To: <paris@france
+Expect: 1
+	<paris@france
+To: paris@france>
+Expect: 1
+	paris@france>
+To: @france
+Expect: 1
+	@france
+To: @
+Expect: 1
+	@
+To: <
+Expect: 1
+	<
+To: >
+Expect: 1
+	>
+To: (
+Expect: 0
+To: )
+Expect: 1
+	)
+To: "
+Expect: 1
+	"
+To: friends-list:;@mastodon.CS.Berkeley.EDU
+Expect: 1
+	friends-list:;@mastodon.CS.Berkeley.EDU
+To: a@.com
+Expect: 1
+	a@.com
+To: a@com.
+Expect: 1
+	a@com.
+To: a@b..com
+Expect: 1
+	a@b..com
+To: a@com
+Expect: 1
+	a@com
+To: a@b.com
+Expect: 1
+	a@b.com
+To: ab
+Expect: 1
+	ab
+To: @b.com
+Expect: 1
+	@b.com
+To: javamail@Sun.COM;
+Expect: 1
+	javamail@Sun.COM
+To: Undisclosed-Recipient:;@jupiter.activeisp.com;;;;;
+Expect: 1
+	Undisclosed-Recipient:;@jupiter.activeisp.com
+To: Undisclosed-Recipient:;@java.sun.com;;;
+Expect: 1
+	Undisclosed-Recipient:;@java.sun.com
+To: "bejjzcfr@aol.com
+Expect: 1
+	"bejjzcfr@aol.com
+To: "butbp@msn.com
+Expect: 1
+	"butbp@msn.com
+To: "@msn.com
+Expect: 1
+	"@msn.com
+To: "@starband.net
+Expect: 1
+	"@starband.net
+To: jko23l1@klds.java.sun.com;, ad@java.sun.com
+Expect: 2
+	jko23l1@klds.java.sun.com
+	ad@java.sun.com
+To: jko23l1@klds;ad;;;;
+Expect: 2
+	jko23l1@klds
+	ad
+To: mailto:mailto:snoa101custserv@hotmail.com
+Expect: 1
+	snoa101custserv@hotmail.com
+To: mailto:mailto:snoa101custserv@hotmail.com, foo@bar.com
+Expect: 2
+	snoa101custserv@hotmail.com
+	foo@bar.com
+To: "@foo.com, a@b.com
+Expect: 2
+	"@foo.com
+	a@b.com
+To: <@foo.com, a@b.com
+Expect: 2
+	<@foo.com
+	a@b.com
+To: <a@b.com> <c@d.com>
+Expect: 2
+	a@b.com
+	c@d.com
+To: yyiohohjjlj[ojolhji@hotmail.com
+Expect: 1
+	yyiohohjjlj[ojolhji@hotmail.com
+To: www.adslexpress.com.tw--?????q?H?[?o??-0@kathmandu.sun.com
+Expect: 1
+	www.adslexpress.com.tw--?????q?H?[?o??-0@kathmandu.sun.com
+Cc: webmail.hinet.net@nwkea-mail-1.sun.com (¤¤µØºô­¶¶l¥ó),
+   www.adslexpress.com.tw--¤¤µØ¹q«H¥[ªo¯¸-0@nwkea-mail-1.sun.com,
+   www.cityfamily.com.tw---ºô¸ô¦P¾Ç·|-0@nwkea-mail-1.sun.com
+Expect: 3
+	webmail.hinet.net@nwkea-mail-1.sun.com
+	www.adslexpress.com.tw--¤¤µØ¹q«H¥[ªo¯¸-0@nwkea-mail-1.sun.com
+	www.cityfamily.com.tw---ºô¸ô¦P¾Ç·|-0@nwkea-mail-1.sun.com
+From: ?R?a????@patan.sun.com
+Expect: 1
+	?R?a????@patan.sun.com
+To: www.adslexpress.com.tw--?????q?H?[?o??-0@patan.sun.com
+Expect: 1
+	www.adslexpress.com.tw--?????q?H?[?o??-0@patan.sun.com
+To: Giant.White.Beans;Great.Northern.Beans;Lima.Beans;Navy.Beans@4email-03;;;;;;;;;;;;;;;;;;
+Expect: 4
+	Giant.White.Beans
+	Great.Northern.Beans
+	Lima.Beans
+	Navy.Beans@4email-03
+To: \(^^/@nwkea-mail-2.sun.com
+Expect: 1
+	\\
+Cc: <tom.ross@sun.com>, Cc: <javamail@sun.com>, Cc: <javahi@sun.com>,
+   Cc: <javadoc-tool@sun.com>, Cc: <javacard-feedback@sun.com>
+Expect: 5
+	tom.ross@sun.com
+	javamail@sun.com
+	javahi@sun.com
+	javadoc-tool@sun.com
+	javacard-feedback@sun.com
+From: <hello@sun.com>, java2d-interest@sun.com, java3d-interest@sun.com,
+	javadoc-tool@sun.com, javamail@sun.com, javasound-interest@sun.com,
+	javaspeech-interest@sun.com, jdbc@sun.com, jim.cook@sun.com,
+	joachim.heck@sun.com, kammy@sun.com, kenneth.cheung@sun.com,
+	Wed@brmea-mail-3.sun.com, multipart/alternative@brmea-mail-3.sun.com,
+	text/html@brmea-mail-3.sun.com;
+Expect: 15
+	hello@sun.com
+	java2d-interest@sun.com
+	java3d-interest@sun.com
+	javadoc-tool@sun.com
+	javamail@sun.com
+	javasound-interest@sun.com
+	javaspeech-interest@sun.com
+	jdbc@sun.com
+	jim.cook@sun.com
+	joachim.heck@sun.com
+	kammy@sun.com
+	kenneth.cheung@sun.com
+	Wed@brmea-mail-3.sun.com
+	multipart/alternative@brmea-mail-3.sun.com
+	text/html@brmea-mail-3.sun.com
+To: "???Y???z???~?j?q?n??
+Expect: 1
+	"???Y???z???~?j?q?n??
+To: "ZETA CELULARES
+Expect: 1
+	"ZETA CELULARES
+To: "Investigaci?n para el desarrollo de micro-emprendimientos Schreiber TE 4431-7050
+Expect: 1
+	"Investigaci?n para el desarrollo de micro-emprendimientos Schreiber TE 4431-7050
+To: "Aproveche*
+Expect: 1
+	"Aproveche*
+To: "ULTIMA. OPORTUNIDAD*
+Expect: 1
+	"ULTIMA. OPORTUNIDAD*
+Cc: 55:55@07, 57:18@07, 36:39@10, 36:42@10, streetwalker!1987, streetwalker!5,
+        streetwalker!87, <8710051455.AA08484@oak.sunecd.com>, <oak!kinnear>,
+        streetwalker!AA00159;, streetwalker!AA08484;, streetwalker!AA16178;,
+        streetwalker!Date:, streetwalker!EDT, streetwalker!Ecd.Sun.COM,
+        streetwalker!From, streetwalker!From:, streetwalker!Message-Id:,
+        streetwalker!Mon, streetwalker!Oct, streetwalker!PDT, streetwalker!RO,
+        streetwalker!Received:, streetwalker!Return-Path:,
+        streetwalker!Status:, streetwalker!To:, streetwalker!by,
+        streetwalker!from, streetwalker!id, streetwalker!oak.sunecd.com,
+        streetwalker!root, streetwalker!rr, streetwalker!streetwalker.sun.com
+Expect: 24
+	55:55@07, 57:18@07, 36:39@10, 36:42@10, streetwalker!1987, streetwalker!5,        streetwalker!87, <8710051455.AA08484@oak.sunecd.com>, <oak!kinnear>,        streetwalker!AA00159;
+	streetwalker!AA08484
+	streetwalker!AA16178
+	streetwalker!Date:
+	streetwalker!EDT
+	streetwalker!Ecd.Sun.COM
+	streetwalker!From
+	streetwalker!From:
+	streetwalker!Message-Id:
+	streetwalker!Mon
+	streetwalker!Oct
+	streetwalker!PDT
+	streetwalker!RO
+	streetwalker!Received:
+	streetwalker!Return-Path:
+	streetwalker!Status:
+	streetwalker!To:
+	streetwalker!by
+	streetwalker!from
+	streetwalker!id
+	streetwalker!oak.sunecd.com
+	streetwalker!root
+	streetwalker!rr
+	streetwalker!streetwalker.sun.com
+To: Re:@cachaca, Subject:@cachaca, and@cachaca, getmclist@cachaca, glenn@ivrel,
+        iconv@cachaca, questions@cachaca
+Expect: 7
+	@cachaca
+	@cachaca
+	and@cachaca
+	getmclist@cachaca
+	glenn@ivrel
+	iconv@cachaca
+	questions@cachaca
+To: " <string@string.ru>  
+Expect: 1
+	string@string.ru
+Header: false
diff --git a/mail/src/test/resources/javax/mail/internet/folddata b/mail/src/test/resources/javax/mail/internet/folddata
new file mode 100644
index 0000000..4acc077
--- /dev/null
+++ b/mail/src/test/resources/javax/mail/internet/folddata
Binary files differ
diff --git a/mail/src/test/resources/javax/mail/internet/paramdata b/mail/src/test/resources/javax/mail/internet/paramdata
new file mode 100644
index 0000000..48e673f
--- /dev/null
+++ b/mail/src/test/resources/javax/mail/internet/paramdata
@@ -0,0 +1,139 @@
+Comment:
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+Comment:
+
+        A set of test headers to test parameter list parsing and
+	especially RFC 2231 decoding.
+        Use with "paramtest -p <paramdata".
+
+	All tests assume "mail.mime.decodeparameters" is set to "true",
+	which is done at the start of paramtest.
+
+        CAREFUL: a blank line in this file causes everything following it
+                 to be ignored until a line starting with "From " (so that
+                 paramtest -p can be pointed at a real mailbox).
+
+From start here
+Comment:
+	This set of headers are all good and test RFC 2231 decoding.
+Content-Type: application/x-stuff;
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	title*1*=%2A%2A%2Afun%2A%2A%2A%20;
+	title*2="isn't it!"
+Expect: 1
+	title=This is even more ***fun*** isn't it!
+Content-Type: application/x-stuff;
+	title*0*=''This%20is%20even%20more%20;
+	title*1*=%2A%2A%2Afun%2A%2A%2A%20;
+	title*2="isn't it!"
+Expect: 1
+	title=This is even more ***fun*** isn't it!
+Content-Type: application/x-stuff;
+	title*2="isn't it!";
+	title*1*=%2A%2A%2Afun%2A%2A%2A%20;
+	title*0*=us-ascii'en'This%20is%20even%20more%20
+Expect: 1
+	title=This is even more ***fun*** isn't it!
+Content-Type: application/x-stuff;
+	title*2="isn't it!";
+	title*1*=%2A%2A%2Afun%2A%2A%2A%20;
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	name*0="one ";
+	name*1="two ";
+	name*2="three";
+	p*=us-ascii'en'This%20is%20fun;
+Expect: 3
+	title=This is even more ***fun*** isn't it!
+	name=one two three
+	p=This is fun
+Content-Type: application/x-stuff;
+	title*2="isn't it!";
+	title*1*=%2A%2A%2Afun%2A%2A%2A%20;
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	name*0="one ";
+	name*1="two ";
+	name*2="three";
+	p*=us-ascii'en'This%20is%20fun
+Expect: 3
+	title=This is even more ***fun*** isn't it!
+	name=one two three
+	p=This is fun
+Content-Type: application/x-stuff;
+	title*2="isn't it!";
+	name*1="two ";
+	title*1*=%2A%2A%2Afun%2A%2A%2A%20;
+	name*0="one ";
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	name*2="three";
+	p*=us-ascii'en'This%20is%20fun
+Expect: 3
+	title=This is even more ***fun*** isn't it!
+	name=one two three
+	p=This is fun
+Comment:
+	These headers test error cases that don't generate parse exceptions.
+	.
+Content-Type: application/x-stuff;
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	title*1*=%2A%2A%2Afun%2A%2A%2A;
+	title*3="isn't it!"
+Expect: 2
+	title=This is even more ***fun***
+	title*3=isn't it!
+Content-Type: application/x-stuff;
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	title*1*=%2A%2A%2Afun%2A%2A%2A;
+	title*3*="isn't it!"
+Expect: 2
+	title=This is even more ***fun***
+	title*3=isn't it!
+Comment:
+	This is an error case with no right answer.
+	Should we assume earlier segments are missing and just decode
+	this segment?  Or should we assume that the "*3" is part of
+	the parameter name and this is a single segment encoded parameter?
+	Currently we do the former.
+Content-Type: application/x-stuff; title*3*=us-ascii'en'isn't%20it!
+Expect: 1
+	title*3=us-ascii'en'isn't it!
+Content-Type: application/x-stuff; title*3*=unknown'en'isn't%20it!
+Expect: 1
+	title*3=unknown'en'isn't it!
+Content-Type: application/x-stuff;
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	title*1*=%XX%2A%2Afun%2A%2A%2A%20;
+	title*2="isn't it!"
+Expect: 1
+	title=This is even more %XX**fun*** isn't it!
+Content-Type: application/x-stuff;
+	title*0*=us-ascii'en'This%20is%20even%20more%20;
+	title*1="***fun***";
+	title*2*=%20isn't%20it!
+Expect: 1
+	title=This is even more ***fun*** isn't it!
+Content-Type: image/png;
+	name*0*=ISO-2022-JP''%1B%24B%24%22%24%24%24%26%24%28%24*%24%22%24%24%24%26;
+	name*1*=%24%28%24*%24%22%24%24%24%26%24%28%24*%24%22%24%24%24%26%24%28;
+	name*2*=%24*%1B%28B.png
+Expect: 1
+	name=\u3042\u3044\u3046\u3048\u304a\u3042\u3044\u3046\u3048\u304a\u3042\u3044\u3046\u3048\u304a\u3042\u3044\u3046\u3048\u304a.png
+Content-Type: image/png;
+	filename*0*=ISO-2022-JP''%1B%24B%24%22%24%24%24%26%24%28%24*%24%22%24%24;
+	filename*1*=%24%26%24%28%24*%24%22%24%24%24%26%24%28%24*%24%22%24%24%24;
+	filename*2*=%26%24%28%24*%1B%28B.png
+Expect: 1
+	filename=\u3042\u3044\u3046\u3048\u304a\u3042\u3044\u3046\u3048\u304a\u3042\u3044\u3046\u3048\u304a\u3042\u3044\u3046\u3048\u304a.png
diff --git a/mail/src/test/resources/javax/mail/internet/paramdatanostrict b/mail/src/test/resources/javax/mail/internet/paramdatanostrict
new file mode 100644
index 0000000..a155889
--- /dev/null
+++ b/mail/src/test/resources/javax/mail/internet/paramdatanostrict
@@ -0,0 +1,70 @@
+Comment:
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+Comment:
+
+        A set of test headers to test parameter list parsing
+	with mail.mime.parameters.strict=false.
+        Use with:
+	java -Dmail.mime.parameters.strict=false paramtest -p <paramdatanostrict
+
+        CAREFUL: a blank line in this file causes everything following it
+                 to be ignored until a line starting with "From " (so that
+                 paramtest -p can be pointed at a real mailbox).
+
+From start here
+Content-Type: text/plain;
+ creation-date=Tue, 22 Jul 2008 10:03:09 GMT;
+ filename="test1kb.file";
+ modification-date=Tue, 22 Jul 2008 10:03:24 GMT
+Expect: 3
+	creation-date=Tue, 22 Jul 2008 10:03:09 GMT
+	filename=test1kb.file
+	modification-date=Tue, 22 Jul 2008 10:03:24 GMT
+Comment: embedded whitespace
+Content-Type: text/plain; name=file name.txt
+Expect: 1
+	name=file name.txt
+Comment: trailing whitespace
+Content-Type: text/plain; name=file name.txt 
+Expect: 1
+	name=file name.txt
+Comment: leading and trailing whitespace
+Content-Type: text/plain; name= file name.txt 
+Expect: 1
+	name=file name.txt
+Comment: trailing newline
+Content-Type: text/plain; name=file name.txt
+	;
+Expect: 1
+	name=file name.txt
+Content-Type: text/plain; name=file name.txt ; time= now
+Expect: 2
+	name=file name.txt
+	time=now
+Content-Type: text/plain; name=file name.txt ; 
+	time= now
+Expect: 2
+	name=file name.txt
+	time=now
+Content-Type: text/plain; name=file name.txt 
+	; time = now 
+Expect: 2
+	name=file name.txt
+	time=now
+Content-Type: text/plain; filename==?Windows-1251?B?8OXq4ujn6PL7IMjPLmRvYw?=
+Expect: 1
+	filename==?Windows-1251?B?8OXq4ujn6PL7IMjPLmRvYw?=
diff --git a/mail/src/test/resources/javax/mail/internet/tokenlist b/mail/src/test/resources/javax/mail/internet/tokenlist
new file mode 100644
index 0000000..d014ee0
--- /dev/null
+++ b/mail/src/test/resources/javax/mail/internet/tokenlist
@@ -0,0 +1,736 @@
+Comment:
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+Comment:
+
+	A set of test addresses stolen from the dtmail test suite,
+	with a few of my own.  Used here to test the header tokenizer.
+	Use with "tokenizertest -p <tokenlist".
+
+	CAREFUL: a blank line in this file causes everything following it
+		 to be ignored until a line starting with "From " (so that
+		 tokenizertest -p can be pointed at a real mailbox).
+
+From start here
+Comment:
+	The following should all be tokenized successfully.
+To: ggere
+Expect: 1
+	ATOM	ggere
+To: /tmp/mail-out
+Expect: 1
+	ATOM	/tmp/mail-out
+To: +mailbox
+Expect: 1
+	ATOM	+mailbox
+To: ~user/mailbox
+Expect: 1
+	ATOM	~user/mailbox
+To: ~/mailbox
+Expect: 1
+	ATOM	~/mailbox
+To: /PN=x400-address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci-ebay
+Expect: 3
+	ATOM	/PN=x400-address/PRMD=ibmmail/ADMD=ibmx400/C=us/
+	SPECIAL	@
+	ATOM	mhs-mci-ebay
+To: ggere, /tmp/mail-out, +mailbox, ~user/mailbox, ~/mailbox, /PN=x400-address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci-ebay
+Expect: 13
+	ATOM	ggere
+	SPECIAL	,
+	ATOM	/tmp/mail-out
+	SPECIAL	,
+	ATOM	+mailbox
+	SPECIAL	,
+	ATOM	~user/mailbox
+	SPECIAL	,
+	ATOM	~/mailbox
+	SPECIAL	,
+	ATOM	/PN=x400-address/PRMD=ibmmail/ADMD=ibmx400/C=us/
+	SPECIAL	@
+	ATOM	mhs-mci-ebay
+To: ggere /tmp/mail-out +mailbox ~user/mailbox ~/mailbox /PN=x400-address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci-ebay
+Expect: 8
+	ATOM	ggere
+	ATOM	/tmp/mail-out
+	ATOM	+mailbox
+	ATOM	~user/mailbox
+	ATOM	~/mailbox
+	ATOM	/PN=x400-address/PRMD=ibmmail/ADMD=ibmx400/C=us/
+	SPECIAL	@
+	ATOM	mhs-mci-ebay
+To: Mad Genius <george@boole>
+Expect: 7
+	ATOM	Mad
+	ATOM	Genius
+	SPECIAL	<
+	ATOM	george
+	SPECIAL	@
+	ATOM	boole
+	SPECIAL	>
+To: "C'est bien moche" <paris@france>
+Expect: 6
+	QUOTEDSTRING	C'est bien moche
+	SPECIAL	<
+	ATOM	paris
+	SPECIAL	@
+	ATOM	france
+	SPECIAL	>
+To: "know, any, famous" <french@physicists>
+Expect: 6
+	QUOTEDSTRING	know, any, famous
+	SPECIAL	<
+	ATOM	french
+	SPECIAL	@
+	ATOM	physicists
+	SPECIAL	>
+To: laborious (But Bug Free)
+Expect: 1
+	ATOM	laborious
+To: confused (about, being, french)
+Expect: 1
+	ATOM	confused
+To: it (takes, no (time, at) all)
+Expect: 1
+	ATOM	it
+To: two@weeks (It Will Take)
+Expect: 3
+	ATOM	two
+	SPECIAL	@
+	ATOM	weeks
+To: it@is (brilliant (genius, and) superb)
+Expect: 3
+	ATOM	it
+	SPECIAL	@
+	ATOM	is
+To: if@you (could, see (almost, as, (badly, you) would) agree)
+Expect: 3
+	ATOM	if
+	SPECIAL	@
+	ATOM	you
+To: cannot@waste (My, Intellectual, Cycles)
+Expect: 3
+	ATOM	cannot
+	SPECIAL	@
+	ATOM	waste
+To: users:get,what,they,deserve;
+Expect: 10
+	ATOM	users
+	SPECIAL	:
+	ATOM	get
+	SPECIAL	,
+	ATOM	what
+	SPECIAL	,
+	ATOM	they
+	SPECIAL	,
+	ATOM	deserve
+	SPECIAL	;
+To: "C'est bien moche" <paris@france>, Mad Genius <george@boole>, two@weeks (It Will Take), /tmp/mail-out, laborious (But Bug Free), cannot@waste (My, Intellectual, Cycles), users:get,what,they,deserve;, it (takes, no (time, at) all), if@you (could, see (almost, as, (badly, you) would) agree), "know, any, famous" <French@physicists>, it@is (brilliant (genius, and) superb), confused (about, being, french)
+Expect: 56
+	QUOTEDSTRING	C'est bien moche
+	SPECIAL	<
+	ATOM	paris
+	SPECIAL	@
+	ATOM	france
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Mad
+	ATOM	Genius
+	SPECIAL	<
+	ATOM	george
+	SPECIAL	@
+	ATOM	boole
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	two
+	SPECIAL	@
+	ATOM	weeks
+	SPECIAL	,
+	ATOM	/tmp/mail-out
+	SPECIAL	,
+	ATOM	laborious
+	SPECIAL	,
+	ATOM	cannot
+	SPECIAL	@
+	ATOM	waste
+	SPECIAL	,
+	ATOM	users
+	SPECIAL	:
+	ATOM	get
+	SPECIAL	,
+	ATOM	what
+	SPECIAL	,
+	ATOM	they
+	SPECIAL	,
+	ATOM	deserve
+	SPECIAL	;
+	SPECIAL	,
+	ATOM	it
+	SPECIAL	,
+	ATOM	if
+	SPECIAL	@
+	ATOM	you
+	SPECIAL	,
+	QUOTEDSTRING	know, any, famous
+	SPECIAL	<
+	ATOM	French
+	SPECIAL	@
+	ATOM	physicists
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	it
+	SPECIAL	@
+	ATOM	is
+	SPECIAL	,
+	ATOM	confused
+To: ggere, /tmp/mail-out, +mailbox, ~user/mailbox, ~/mailbox, /PN=x400-address/PRMD=ibmmail/ADMD=ibmx400/C=us/@mhs-mci-ebay, "C'est bien moche" <paris@france>, Mad Genius <george@boole>, two@weeks (It Will Take), /tmp/mail-out, laborious (But Bug Free), cannot@waste (My, Intellectual, Cycles), users:get,what,they,deserve;, it (takes, no (time, at) all), if@you (could, see (almost, as, (badly, you) would) agree), "know, any, famous" <French@physicists>, it@is (brilliant (genius, and) superb), confused (about, being, french)
+Expect: 70
+	ATOM	ggere
+	SPECIAL	,
+	ATOM	/tmp/mail-out
+	SPECIAL	,
+	ATOM	+mailbox
+	SPECIAL	,
+	ATOM	~user/mailbox
+	SPECIAL	,
+	ATOM	~/mailbox
+	SPECIAL	,
+	ATOM	/PN=x400-address/PRMD=ibmmail/ADMD=ibmx400/C=us/
+	SPECIAL	@
+	ATOM	mhs-mci-ebay
+	SPECIAL	,
+	QUOTEDSTRING	C'est bien moche
+	SPECIAL	<
+	ATOM	paris
+	SPECIAL	@
+	ATOM	france
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Mad
+	ATOM	Genius
+	SPECIAL	<
+	ATOM	george
+	SPECIAL	@
+	ATOM	boole
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	two
+	SPECIAL	@
+	ATOM	weeks
+	SPECIAL	,
+	ATOM	/tmp/mail-out
+	SPECIAL	,
+	ATOM	laborious
+	SPECIAL	,
+	ATOM	cannot
+	SPECIAL	@
+	ATOM	waste
+	SPECIAL	,
+	ATOM	users
+	SPECIAL	:
+	ATOM	get
+	SPECIAL	,
+	ATOM	what
+	SPECIAL	,
+	ATOM	they
+	SPECIAL	,
+	ATOM	deserve
+	SPECIAL	;
+	SPECIAL	,
+	ATOM	it
+	SPECIAL	,
+	ATOM	if
+	SPECIAL	@
+	ATOM	you
+	SPECIAL	,
+	QUOTEDSTRING	know, any, famous
+	SPECIAL	<
+	ATOM	French
+	SPECIAL	@
+	ATOM	physicists
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	it
+	SPECIAL	@
+	ATOM	is
+	SPECIAL	,
+	ATOM	confused
+To: testa testb testc /tmp/mail-out ~ggere/mail-out testd teste
+Expect: 7
+	ATOM	testa
+	ATOM	testb
+	ATOM	testc
+	ATOM	/tmp/mail-out
+	ATOM	~ggere/mail-out
+	ATOM	testd
+	ATOM	teste
+To: testa, testb , testc /tmp/mail-out ,~ggere/mail-out, ,testd,teste,,,
+Expect: 16
+	ATOM	testa
+	SPECIAL	,
+	ATOM	testb
+	SPECIAL	,
+	ATOM	testc
+	ATOM	/tmp/mail-out
+	SPECIAL	,
+	ATOM	~ggere/mail-out
+	SPECIAL	,
+	SPECIAL	,
+	ATOM	testd
+	SPECIAL	,
+	ATOM	teste
+	SPECIAL	,
+	SPECIAL	,
+	SPECIAL	,
+To: testa testb <testc@testd>
+Expect: 7
+	ATOM	testa
+	ATOM	testb
+	SPECIAL	<
+	ATOM	testc
+	SPECIAL	@
+	ATOM	testd
+	SPECIAL	>
+To: Adam S Moskowitz <adamm@onion-inset-com>,
+        Andrew Gollan <adjg@softway-sw-OZ-AU>,
+        Bret Anthony Marquis <bam@trout-nosc-MIL>,
+        Bill Shannon <shannon@Sun-COM>, Bob Gray <rgray@UsWest-COM>,
+        Brian Ellis <bri@Boulder-Colorado-EDU>,
+        Don Coleman <coleman@legato-COM>,
+        Dennis Ritchie <dmr@research-ATT-COM>, Evi Nemeth <evi@Colorado-EDU>,
+        Lori Grob <grob@chorus-FR>, Paula Hawthorn <hawthorn@HPL-HP-COM>,
+        Andrew Hume <andrew@research-ATT-COM>,
+        Jaap Akkerhuis <jaap@research-ATT-COM>,
+        Jim R Oldroyd <jr@onion-inset-com>,
+        John Quarterman <jsq@longway-TIC-COM>,
+        Judy DesHarnais <judy@USENIX-ORG>, Karen Shannon <kas@Eng>,
+        Ken McDonell <kenj@Pyramid-COM>,
+        Rob Kolstad <kolstad@rmtc-Central-Sun-COM>,
+        Paul Kooros <kooros@tigger-cs-Colorado-EDU>,
+        Robert Elz <kre@Munnari-OZ-AU>,
+        Miriam Amos Nihart <miriam@decwet-enet-DEC-COM>,
+        "Mike O'Dell" <mo@gizmo-Bellcore-COM>,
+        Sharon Murrel <eowyn@research-ATT-COM>,
+        Peg Schafer <peg@media-lab-media-MIT-EDU>, Peter Salus <peter@SUG-ORG>,
+        Bill Shannon <shannon@Sun-COM>, Stu Feldman <sif@Bellcore-COM>,
+        Stephen Williams <stevew@netboss1-trg-saic-com>,
+        Teus Hagen <teus@oce-NL>, Trent Hein <trent@xor-COM>,
+        Thomas Alan Wood <twood@capmkt-COM>,
+        Michael Ubell <"mttam::ubell"@SFBay-ENet-DEC-COM>,
+        Martha Zimet <zimet@Corp>
+Expect: 275
+	ATOM	Adam
+	ATOM	S
+	ATOM	Moskowitz
+	SPECIAL	<
+	ATOM	adamm
+	SPECIAL	@
+	ATOM	onion-inset-com
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Andrew
+	ATOM	Gollan
+	SPECIAL	<
+	ATOM	adjg
+	SPECIAL	@
+	ATOM	softway-sw-OZ-AU
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Bret
+	ATOM	Anthony
+	ATOM	Marquis
+	SPECIAL	<
+	ATOM	bam
+	SPECIAL	@
+	ATOM	trout-nosc-MIL
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Bill
+	ATOM	Shannon
+	SPECIAL	<
+	ATOM	shannon
+	SPECIAL	@
+	ATOM	Sun-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Bob
+	ATOM	Gray
+	SPECIAL	<
+	ATOM	rgray
+	SPECIAL	@
+	ATOM	UsWest-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Brian
+	ATOM	Ellis
+	SPECIAL	<
+	ATOM	bri
+	SPECIAL	@
+	ATOM	Boulder-Colorado-EDU
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Don
+	ATOM	Coleman
+	SPECIAL	<
+	ATOM	coleman
+	SPECIAL	@
+	ATOM	legato-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Dennis
+	ATOM	Ritchie
+	SPECIAL	<
+	ATOM	dmr
+	SPECIAL	@
+	ATOM	research-ATT-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Evi
+	ATOM	Nemeth
+	SPECIAL	<
+	ATOM	evi
+	SPECIAL	@
+	ATOM	Colorado-EDU
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Lori
+	ATOM	Grob
+	SPECIAL	<
+	ATOM	grob
+	SPECIAL	@
+	ATOM	chorus-FR
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Paula
+	ATOM	Hawthorn
+	SPECIAL	<
+	ATOM	hawthorn
+	SPECIAL	@
+	ATOM	HPL-HP-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Andrew
+	ATOM	Hume
+	SPECIAL	<
+	ATOM	andrew
+	SPECIAL	@
+	ATOM	research-ATT-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Jaap
+	ATOM	Akkerhuis
+	SPECIAL	<
+	ATOM	jaap
+	SPECIAL	@
+	ATOM	research-ATT-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Jim
+	ATOM	R
+	ATOM	Oldroyd
+	SPECIAL	<
+	ATOM	jr
+	SPECIAL	@
+	ATOM	onion-inset-com
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	John
+	ATOM	Quarterman
+	SPECIAL	<
+	ATOM	jsq
+	SPECIAL	@
+	ATOM	longway-TIC-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Judy
+	ATOM	DesHarnais
+	SPECIAL	<
+	ATOM	judy
+	SPECIAL	@
+	ATOM	USENIX-ORG
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Karen
+	ATOM	Shannon
+	SPECIAL	<
+	ATOM	kas
+	SPECIAL	@
+	ATOM	Eng
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Ken
+	ATOM	McDonell
+	SPECIAL	<
+	ATOM	kenj
+	SPECIAL	@
+	ATOM	Pyramid-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Rob
+	ATOM	Kolstad
+	SPECIAL	<
+	ATOM	kolstad
+	SPECIAL	@
+	ATOM	rmtc-Central-Sun-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Paul
+	ATOM	Kooros
+	SPECIAL	<
+	ATOM	kooros
+	SPECIAL	@
+	ATOM	tigger-cs-Colorado-EDU
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Robert
+	ATOM	Elz
+	SPECIAL	<
+	ATOM	kre
+	SPECIAL	@
+	ATOM	Munnari-OZ-AU
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Miriam
+	ATOM	Amos
+	ATOM	Nihart
+	SPECIAL	<
+	ATOM	miriam
+	SPECIAL	@
+	ATOM	decwet-enet-DEC-COM
+	SPECIAL	>
+	SPECIAL	,
+	QUOTEDSTRING	Mike O'Dell
+	SPECIAL	<
+	ATOM	mo
+	SPECIAL	@
+	ATOM	gizmo-Bellcore-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Sharon
+	ATOM	Murrel
+	SPECIAL	<
+	ATOM	eowyn
+	SPECIAL	@
+	ATOM	research-ATT-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Peg
+	ATOM	Schafer
+	SPECIAL	<
+	ATOM	peg
+	SPECIAL	@
+	ATOM	media-lab-media-MIT-EDU
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Peter
+	ATOM	Salus
+	SPECIAL	<
+	ATOM	peter
+	SPECIAL	@
+	ATOM	SUG-ORG
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Bill
+	ATOM	Shannon
+	SPECIAL	<
+	ATOM	shannon
+	SPECIAL	@
+	ATOM	Sun-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Stu
+	ATOM	Feldman
+	SPECIAL	<
+	ATOM	sif
+	SPECIAL	@
+	ATOM	Bellcore-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Stephen
+	ATOM	Williams
+	SPECIAL	<
+	ATOM	stevew
+	SPECIAL	@
+	ATOM	netboss1-trg-saic-com
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Teus
+	ATOM	Hagen
+	SPECIAL	<
+	ATOM	teus
+	SPECIAL	@
+	ATOM	oce-NL
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Trent
+	ATOM	Hein
+	SPECIAL	<
+	ATOM	trent
+	SPECIAL	@
+	ATOM	xor-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Thomas
+	ATOM	Alan
+	ATOM	Wood
+	SPECIAL	<
+	ATOM	twood
+	SPECIAL	@
+	ATOM	capmkt-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Michael
+	ATOM	Ubell
+	SPECIAL	<
+	QUOTEDSTRING	mttam::ubell
+	SPECIAL	@
+	ATOM	SFBay-ENet-DEC-COM
+	SPECIAL	>
+	SPECIAL	,
+	ATOM	Martha
+	ATOM	Zimet
+	SPECIAL	<
+	ATOM	zimet
+	SPECIAL	@
+	ATOM	Corp
+	SPECIAL	>
+To: '"a,b"'
+Expect: 3
+	ATOM	'
+	QUOTEDSTRING	a,b
+	ATOM	'
+To: 'a, b'
+Expect: 3
+	ATOM	'a
+	SPECIAL	,
+	ATOM	b'
+To: "a,b"
+Expect: 1
+	QUOTEDSTRING	a,b
+To: shakey!"a,b"
+Expect: 2
+	ATOM	shakey!
+	QUOTEDSTRING	a,b
+To: "foo bar"
+Expect: 1
+	QUOTEDSTRING	foo bar
+To: (Foo Bar) foo@bar
+Expect: 3
+	ATOM	foo
+	SPECIAL	@
+	ATOM	bar
+To: (Foo Bar) foo@bar, (My Name) my@name
+Expect: 7
+	ATOM	foo
+	SPECIAL	@
+	ATOM	bar
+	SPECIAL	,
+	ATOM	my
+	SPECIAL	@
+	ATOM	name
+To: ,
+Expect: 1
+	SPECIAL	,
+To: "C'est bien moche" <paris@france
+Expect: 5
+	QUOTEDSTRING	C'est bien moche
+	SPECIAL	<
+	ATOM	paris
+	SPECIAL	@
+	ATOM	france
+To: "C'est bien moche" paris@france>
+Expect: 5
+	QUOTEDSTRING	C'est bien moche
+	ATOM	paris
+	SPECIAL	@
+	ATOM	france
+	SPECIAL	>
+To: "C'est bien moche" <paris@>
+Expect: 5
+	QUOTEDSTRING	C'est bien moche
+	SPECIAL	<
+	ATOM	paris
+	SPECIAL	@
+	SPECIAL	>
+To: laborious but (Bug Free)
+Expect: 2
+	ATOM	laborious
+	ATOM	but
+To: two@ (It Will Take)
+Expect: 2
+	ATOM	two
+	SPECIAL	@
+To: two@
+Expect: 2
+	ATOM	two
+	SPECIAL	@
+To: <paris@france
+Expect: 4
+	SPECIAL	<
+	ATOM	paris
+	SPECIAL	@
+	ATOM	france
+To: paris@france>
+Expect: 4
+	ATOM	paris
+	SPECIAL	@
+	ATOM	france
+	SPECIAL	>
+To: @france
+Expect: 2
+	SPECIAL	@
+	ATOM	france
+To: @
+Expect: 1
+	SPECIAL	@
+To: <
+Expect: 1
+	SPECIAL	<
+To: >
+Expect: 1
+	SPECIAL	>
+Comment:
+	I'm not sure the following should be valid.
+To: )
+Expect: 1
+	SPECIAL	)
+To: friends-list:;@mastodon-CS-Berkeley-EDU
+Expect: 5
+	ATOM	friends-list
+	SPECIAL	:
+	SPECIAL	;
+	SPECIAL	@
+	ATOM	mastodon-CS-Berkeley-EDU
+Comment:
+	The following should all generate exceptions.
+To: "C'est bien moche <paris@france>
+Expect: Exception javax.mail.internet.ParseException: Unbalanced quoted string
+To: confused (about, being, french
+Expect: Exception javax.mail.internet.ParseException: Unbalanced comments
+To: it@is (brilliant (genius, and superb)
+Expect: Exception javax.mail.internet.ParseException: Unbalanced comments
+To: it@is (brilliant (genius), and superb
+Expect: Exception javax.mail.internet.ParseException: Unbalanced comments
+To: if@you (could, see (almost, as, (badly, you would) agree)
+Expect: Exception javax.mail.internet.ParseException: Unbalanced comments
+To: if@you (could, see (almost, as, (badly, you) would) agree
+Expect: Exception javax.mail.internet.ParseException: Unbalanced comments
+To: "
+Expect: Exception javax.mail.internet.ParseException: Unbalanced quoted string
+To: two@weeks (It Will Take
+Expect: Exception javax.mail.internet.ParseException: Unbalanced comments
+To: (
+Expect: Exception javax.mail.internet.ParseException: Unbalanced comments
diff --git a/mailapi/pom.xml b/mailapi/pom.xml
new file mode 100644
index 0000000..0fa1e9c
--- /dev/null
+++ b/mailapi/pom.xml
@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<!--
+    This project builds the JavaMail "mailapi.jar" file.  This jar file
+    contains a fully operational JavaMail implementation but without any
+    protocol providers.  It can be used with the separate protocol provider
+    jar files (e.g., smtp.jar, imap.jar) to provide a full JavaMail runtime
+    implementation.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>mailapi</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API (no providers)</name>
+
+    <properties>
+	<mail.packages.export>
+	    javax.mail.*; version=${mail.spec.version},
+	    com.sun.mail.util; version=${mail.osgiversion},
+	    com.sun.mail.auth; version=${mail.osgiversion},
+	    com.sun.mail.handlers; version=${mail.osgiversion}
+	</mail.packages.export>
+	<mail.bundle.symbolicName>
+	    jakarta.mail.api
+	</mail.bundle.symbolicName>
+    </properties>
+
+    <profiles>
+	<!--
+	    A special profile for compiling with the real JDK 1.7 compiler.
+	    Exclude MailSessionDefinition and MailSessionDefinitions since
+	    the former requires JDK 1.8 and the latter depends on the former.
+	-->
+	<profile>
+	    <id>1.7</id>
+	    <build>
+		<plugins>
+		    <plugin>
+			<artifactId>maven-compiler-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>default-compile</id>
+				<configuration>
+				    <excludes>
+					<exclude>
+					  javax/mail/MailSessionDefinition.java
+					</exclude>
+					<exclude>
+					  javax/mail/MailSessionDefinitions.java
+					</exclude>
+					<exclude>
+					  module-info.java
+					</exclude>
+				    </excludes>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		</plugins>
+	    </build>
+	</profile>
+
+	<!--
+	    A special profile for compiling with JDK 9.
+	-->
+	<profile>
+	    <id>9</id>
+	    <activation>
+		<jdk>9</jdk>
+	    </activation>
+	    <build>
+		<plugins>
+		    <plugin>
+			<artifactId>maven-compiler-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>default-compile</id>
+				<configuration>
+				    <release>9</release>
+				    <source>9</source>
+				    <target>9</target>
+				    <compilerArgs>
+					<arg>-Xlint</arg>
+					<arg>-Xlint:-options</arg>
+					<arg>-Xlint:-path</arg>
+					<!--
+					    Too many finalize warnings.
+					<arg>-Werror</arg>
+					-->
+				    </compilerArgs>
+				    <!-- un-exclude module-info.java -->
+				    <excludes combine.self="override">
+				    </excludes>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		    <!--
+			Require JDK 9.
+		    -->
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-enforcer-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>enforce-version</id>
+				<goals>
+				    <goal>enforce</goal>
+				</goals>
+				<configuration>
+				    <rules>
+					<requireMavenVersion>
+					    <version>[3.0.3,)</version>
+					</requireMavenVersion>
+					<requireJavaVersion>
+					    <version>9</version>
+					</requireJavaVersion>
+				    </rules>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		</plugins>
+	    </build>
+	</profile>
+    </profiles>
+
+    <build>
+	<plugins>
+	    <plugin>
+		<artifactId>maven-dependency-plugin</artifactId>
+		<executions>
+		    <execution>
+			<!-- download the binaries -->
+			<id>get-binaries</id>
+			<phase>generate-sources</phase>
+			<goals>
+			    <goal>unpack</goal>
+			</goals>
+		    </execution>
+		    <execution>
+			<!-- download the sources -->
+			<id>get-sources</id>
+			<phase>generate-sources</phase>
+			<goals>
+			    <goal>unpack</goal>
+			</goals>
+			<configuration>
+			    <artifactItems>
+				<artifactItem>
+				    <groupId>com.sun.mail</groupId>
+				    <artifactId>jakarta.mail</artifactId>
+				    <version>${mail.version}</version>
+				    <classifier>sources</classifier>
+				    <outputDirectory>
+					${project.build.directory}/sources
+				    </outputDirectory>
+				</artifactItem>
+			    </artifactItems>
+			</configuration>
+		    </execution>
+		</executions>
+		<configuration>
+		    <artifactItems>
+			<artifactItem>
+			    <groupId>com.sun.mail</groupId>
+			    <artifactId>jakarta.mail</artifactId>
+			    <version>${mail.version}</version>
+			</artifactItem>
+		    </artifactItems>
+		    <outputDirectory>
+			${project.build.outputDirectory}
+		    </outputDirectory>
+		    <includes>
+			javax/**,
+			com/sun/mail/util/**,
+			com/sun/mail/auth/**,
+			com/sun/mail/handlers/**,
+			META-INF/*
+		    </includes>
+		    <!--
+			Have to exclude package.html because it links to
+			com.sun.mail.{imap,pop3,smtp} packages and jdk 9
+			javadoc fails that during deploy.
+		    -->
+		    <excludes>
+			javax/mail/package.html,
+			com/sun/mail/util/logging/**,
+			META-INF/javamail.default.*
+		    </excludes>
+		</configuration>
+	    </plugin>
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<configuration>
+		    <finalName>${project.artifactId}</finalName>
+		    <archive>
+			<manifestFile>
+			  ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+			</manifestFile>
+		    </archive>
+		</configuration>
+	    </plugin>
+	</plugins>
+    </build>
+</project>
diff --git a/mailapi/src/main/java/module-info.java b/mailapi/src/main/java/module-info.java
new file mode 100644
index 0000000..76f9f3e
--- /dev/null
+++ b/mailapi/src/main/java/module-info.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+module jakarta.mail {
+    exports javax.mail;
+    exports javax.mail.event;
+    exports javax.mail.internet;
+    exports javax.mail.search;
+    exports javax.mail.util;
+    exports com.sun.mail.util;
+    exports com.sun.mail.auth;
+    exports com.sun.mail.handlers;
+
+    requires transitive jakarta.activation;
+    requires java.logging;
+    requires java.xml;		// for text/xml handler
+    requires java.desktop;	// for image/jpeg handler
+    requires java.security.sasl; // for OAuth2 support
+    uses javax.mail.Provider;
+}
diff --git a/mailapijar/pom.xml b/mailapijar/pom.xml
new file mode 100644
index 0000000..c5affd1
--- /dev/null
+++ b/mailapijar/pom.xml
@@ -0,0 +1,119 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<!--
+    This project builds the JavaMail API jar file, which contains only
+    the javax.mail.* API definitions and is *only* intended to be used
+    for programs to compile against.  Note that it includes none of the
+    implementation-specific classes that the javax.mail.* classes rely on.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>jakarta.mail</groupId>
+    <artifactId>jakarta.mail-api</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API jar</name>
+
+    <properties>
+	<mail.extensionName>
+	    jakarta.mail
+	</mail.extensionName>
+	<mail.packages.export>
+	    javax.mail.*; version=${mail.spec.version}
+	</mail.packages.export>
+	<mail.bundle.symbolicName>
+	    jakarta.mail-api
+	</mail.bundle.symbolicName>
+    </properties>
+
+    <build>
+        <plugins>
+	    <plugin>
+		<artifactId>maven-dependency-plugin</artifactId>
+		<executions>
+		    <execution>
+			<!-- download the binaries -->
+			<id>get-binaries</id>
+			<phase>compile</phase>
+			<goals>
+			    <goal>unpack</goal>
+			</goals>
+		    </execution>
+		    <execution>
+			<!-- download the sources -->
+			<id>get-sources</id>
+			<phase>compile</phase>
+			<goals>
+			    <goal>unpack</goal>
+			</goals>
+			<configuration>
+			    <artifactItems>
+				<artifactItem>
+				    <groupId>com.sun.mail</groupId>
+				    <artifactId>jakarta.mail</artifactId>
+				    <version>${mail.version}</version>
+				    <classifier>sources</classifier>
+				    <outputDirectory>
+					${project.build.directory}/sources
+				    </outputDirectory>
+				</artifactItem>
+			    </artifactItems>
+			</configuration>
+		    </execution>
+		</executions>
+		<configuration>
+		    <artifactItems>
+			<artifactItem>
+			    <groupId>com.sun.mail</groupId>
+			    <artifactId>jakarta.mail</artifactId>
+			    <version>${mail.version}</version>
+			</artifactItem>
+		    </artifactItems>
+		    <outputDirectory>
+			${project.build.outputDirectory}
+		    </outputDirectory>
+		    <includes>
+			javax/**,
+			META-INF/LICENSE.txt
+		    </includes>
+		</configuration>
+	    </plugin>
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<configuration>
+		    <finalName>${project.artifactId}</finalName>
+		    <archive>
+			<manifestFile>
+			  ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+			</manifestFile>
+		    </archive>
+		</configuration>
+	    </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/mailhandler/pom.xml b/mailhandler/pom.xml
new file mode 100644
index 0000000..dc8aaaa
--- /dev/null
+++ b/mailhandler/pom.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>parent-distrib</artifactId>
+	<version>1.6.3</version>
+	<relativePath>../parent-distrib/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>logging-mailhandler</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API logging handler</name>
+
+    <properties>
+	<mail.packages.export>
+	    com.sun.mail.util.logging; version=${mail.osgiversion}
+	</mail.packages.export>
+	<mail.classes>com/sun/mail/util/logging/**</mail.classes>
+    </properties>
+
+</project>
diff --git a/mailhandler/src/main/java/module-info.java b/mailhandler/src/main/java/module-info.java
new file mode 100644
index 0000000..268c8e0
--- /dev/null
+++ b/mailhandler/src/main/java/module-info.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+module com.sun.mail.util.logging {
+    exports com.sun.mail.util.logging;
+
+    requires jakarta.mail;
+    requires java.logging;
+}
diff --git a/mbox.xml b/mbox.xml
new file mode 100644
index 0000000..1ee9685
--- /dev/null
+++ b/mbox.xml
@@ -0,0 +1,206 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<!DOCTYPE project [
+  <!ENTITY commonBuild SYSTEM "ant-common.xml">
+]>
+<project name="JavaMail" default="all" basedir="."
+	xmlns:cpptasks="antlib:org.sf.net.antcontrib.cpptasks">
+
+    <taskdef resource="cpptasks.tasks"/>
+    <typedef resource="cpptasks.types"/>
+
+<!-- ========== Initialize Properties =================================== -->
+
+    <!--
+        component.name: required property.  the value should be the
+                        name of the component directory
+    -->
+    <property name="component.name" value="mbox"/>
+
+    <property file="./build.properties"/>
+
+    <property name="cc.debug" value="false"/>
+    <property name="generated.dir" value="mbox/target/generated"/>
+    <property name="obj.dir" value="mbox/target/native/${os.name}-${os.arch}"/>
+    <property name="lib.dir" value="mbox/target/lib/${os.name}-${os.arch}"/>
+    <condition property="is-solaris">
+	<os name="SunOS"/>
+    </condition>
+    
+    &commonBuild;
+
+    <!-- all -->
+    <target name="all" depends="mboxcompile, mboxcc, mboxjar"
+            description="Build entire mbox component">
+    </target>
+
+    <!-- build -->
+    <target name="build" depends="mboxcompile, mboxcc, mboxjar"
+            description="Build entire mbox component">
+    </target>
+
+    <!-- init. Initialization involves creating publishing directories and
+         OS specific targets. -->
+    <target name="init" description="${component.name} initialization">
+        <tstamp>
+            <format property="start.time" pattern="MM/dd/yyyy hh:mm aa"/>
+        </tstamp>
+        <echo message="Building component ${component.name}"/>
+        <mkdir dir="${component.classes.mbox.dir}"/>
+        <mkdir dir="${generated.dir}"/>
+        <mkdir dir="${obj.dir}"/>
+        <mkdir dir="${lib.dir}"/>
+    </target>
+
+    <!-- prepare manifest files for jars -->
+    <target name="cook-manifest" depends="init"
+            description="Generate MANIFEST.MF files">
+        <mkdir dir="${component.classes.mbox.dir}/manifest"/>
+        <copy file="${resources.mbox.dir}/META-INF/MANIFEST.MF"
+		tofile="${component.classes.mbox.dir}/manifest/MANIFEST.MF">
+            <filterset begintoken="{" endtoken="}">
+		<filter token="mail.spec.version"
+		    value="${release.specversion}"/>
+		<filter token="mail.version" value="${release.version}"/>
+            </filterset>
+        </copy>
+    </target>
+
+    <!-- mbox compile -->
+    <target name="mboxcompile" depends="init"
+            description="Compile com/sun/mail/mbox sources">
+
+        <javac srcdir="${src.mbox.dir}"
+               destdir="${component.classes.mbox.dir}"
+               debug="${javac.debug}"
+               optimize="${javac.optimize}"
+               source="${javac.source}"
+               deprecation="${javac.deprecation}"
+               failonerror="true"
+               target="${javac.target}">
+            <classpath>
+                <pathelement location="${component.classes.dir}"/>
+                <pathelement location="${activation.jar}"/>
+            </classpath>
+	    <!--
+            <include name="com/sun/mail/mbox/**"/>
+	    -->
+        </javac>
+    </target>
+
+    <target name="mboxheaders" depends="mboxcompile">
+	<javah destdir="${generated.dir}"
+		force="yes"
+		verbose="yes"
+		classpath="${component.classes.mbox.dir}:${component.classes.dir}">
+            <class name="com.sun.mail.mbox.UNIXFile"/>
+            <class name="com.sun.mail.mbox.UNIXInbox"/>
+	</javah>
+    </target>
+
+    <target name="mboxcc" depends="mboxheaders, mboxcc-solaris"/>
+
+    <!--
+    make-based build uses these compiler and linker options:
+
+    cc -Xa -xO2 -v -D_REENTRANT   -Dsparc -DSOLARIS2 
+
+    cc -G -o ../../../../../../build/solaris/lib/sparc/libmbox.so 
+	    obj/sparc/./UNIXFile.o obj/sparc/./UNIXInbox.o    
+	    -L/java/re/jdk/1.4/archive/fcs/binaries/solsparc/jre/lib/sparc 
+	    -L../../../../../../build/solaris/lib/sparc  
+	    -lmail 
+	    -L../../../../../../build/solaris/lib/sparc 
+	    -ljava  
+	    -lc
+    -->
+
+    <target name="mboxcc-solaris" depends="mboxheaders" if="is-solaris">
+	<cc debug="${cc.debug}"
+		link="shared"
+		outfile="${lib.dir}/mbox"
+		objdir="${obj.dir}">
+	    <compiler name="sunc89"/>
+	    <fileset dir="mbox/src/main/cpp" includes="**/*.c"/>
+	    <includepath location="${generated.dir}"/>
+	    <sysincludepath location="${java.home}/../include"/>
+	    <sysincludepath location="${java.home}/../include/solaris"/>
+	    <defineset>
+		<define name="_REENTRANT"/>
+	    </defineset>
+	    <compilerarg value="-Xa"/>
+	    <compilerarg value="-xO2"/>
+	    <linker name="sunc89">
+		<syslibset libs="mail,java,c"/>
+	    </linker>
+	</cc>
+    </target>
+
+    <target name="clean" description="Clean the build">
+        <delete includeEmptyDirs="true" failonerror="false">
+            <fileset dir="${component.classes.mbox.dir}"/>
+        </delete>
+    </target>
+
+    <!-- JavaMail bundle build targets -->
+
+    <target name="mboxrelease" depends="init, mboxjar, mboxcc">
+        <property name="rel" value="mbox-${release.version}"/>
+        <property name="zipname" value="${rel}.zip"/>
+        <delete file="${basedir}/mbox/target/${zipname}"/>
+        <echo message="Creating mbox bundle ${basedir}/mbox/target/${zipname}"/>
+        <zip destfile="${basedir}/mbox/target/${zipname}">
+	    <zipfileset dir="${resources.legal.dir}/META-INF" prefix="${rel}"
+                    includes="LICENSE.txt"/>
+	    <zipfileset dir="${release.dir}/lib" prefix="${rel}"
+		    includes="mbox.jar"/>
+            <zipfileset dir="${lib.dir}/.." prefix="${rel}/lib"
+		    includes="**/*.so"/>
+        </zip>
+    </target>
+
+     <target name="mboxjar" depends="init, mboxcompile, cook-manifest">
+        <mkdir dir="${release.dir}/lib"/>
+        <jar jarfile="${release.mbox.jar}"
+		manifest="${component.classes.mbox.dir}/manifest/MANIFEST.MF">
+            <metainf dir="${resources.legal.dir}/META-INF"
+		    includes="LICENSE.txt"/>
+            <fileset dir="${component.classes.mbox.dir}">
+                <include name="com/sun/mail/mbox/*.class"/>
+                <include name="com/sun/mail/mbox/mailcap"/>
+            </fileset>
+        </jar>
+     </target>
+
+    <target name="push-to-maven-prepare" depends="-push-to-maven-init, mboxjar"
+        description="creates an image for the 'push-to-maven' goal">
+        <delete dir="target/maven-repo" /><!-- clean it -->
+        <maven-repository-importer destdir="target/maven-repo"
+		version="${release.version}">
+            <artifact jar="target/release/mbox.jar" pom="mbox.pom"
+		    srczip="target/mbox.src.zip" />
+        </maven-repository-importer>
+    </target>
+
+    <target name="push-to-maven" depends="push-to-maven-prepare"
+        description="pushes jars to the java.net maven repository">
+        <cvs-import src="target/maven-repo" dest="glassfish/repo" />
+    </target>
+</project>
diff --git a/mbox/exclude.xml b/mbox/exclude.xml
new file mode 100644
index 0000000..659e6e7
--- /dev/null
+++ b/mbox/exclude.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<!-- FindBugs exclude list for JavaMail mbox provider -->
+
+<FindBugsFilter>
+    <!--
+	Without multi-catch we just catch all possible exceptions.
+    -->
+    <Match>
+	<Class name="com.sun.mail.mbox.MboxStore"/>
+	<Method name="&lt;init&gt;"/> <!-- match constructor -->
+	<Bug pattern="REC_CATCH_EXCEPTION"/>
+    </Match>
+
+    <!--
+	We purposely return null instead of an empty list.
+    -->
+    <Match>
+	<Class name="com.sun.mail.mbox.MboxFolder"/>
+	<Method name="load"/>
+	<Bug pattern="PZLA_PREFER_ZERO_LENGTH_ARRAYS"/>
+    </Match>
+
+    <!--
+	We purposely don't close this stream, which is just a wrapper
+	around the original stream that needs to remain open.
+    -->
+    <Match>
+	<Class name="com.sun.mail.mbox.SunV3Multipart"/>
+	<Method name="parse"/>
+	<Bug pattern="OS_OPEN_STREAM"/>
+    </Match>
+
+    <!--
+	When deleting the temp file fails, there's really nothing to be done.
+    -->
+    <Match>
+	<Class name="com.sun.mail.mbox.TempFile"/>
+	<Method name="close"/>
+	<Bug pattern="RV_RETURN_VALUE_IGNORED_BAD_PRACTICE"/>
+    </Match>
+
+    <!--
+	We never serialize this class.
+    -->
+    <Match>
+	<Class name="com.sun.mail.mbox.UNIXInbox"/>
+	<Field name="lockfileName"/>
+	<Bug pattern="SE_TRANSIENT_FIELD_NOT_RESTORED"/>
+    </Match>
+
+    <!--
+	If close fails, what are we supposed to do?
+    -->
+    <Match>
+	<Class name="com.sun.mail.mbox.MboxFolder"/>
+	<Method name="appendMessages"/>
+	<Bug pattern="DE_MIGHT_IGNORE"/>
+    </Match>
+
+    <!--
+	If getSentDate fails, treat it the same as returning null.
+    -->
+    <Match>
+	<Class name="com.sun.mail.mbox.MboxMessage"/>
+	<Method name="getUnixFromLine"/>
+	<Bug pattern="DE_MIGHT_IGNORE"/>
+    </Match>
+</FindBugsFilter>
diff --git a/mbox/native/pom.xml b/mbox/native/pom.xml
new file mode 100644
index 0000000..b9220c9
--- /dev/null
+++ b/mbox/native/pom.xml
@@ -0,0 +1,153 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+	<relativePath>../../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>libmbox</artifactId>
+    <packaging>so</packaging>
+    <name>JavaMail API mbox native library</name>
+    <description>JavaMail API mbox native library</description>
+    <url>http://java.sun.com/projects/javamail</url>
+
+    <properties>
+	<m64>
+	</m64>
+	<compiler.name>
+	    c89
+	</compiler.name>
+	<compiler.start.options>
+	    ${m64} -Xa -xO2 -v -D_REENTRANT -KPIC
+	    -I${env.JAVA_HOME}/include -I${env.JAVA_HOME}/include/solaris
+	</compiler.start.options>
+	<linker.name>
+	    c89
+	</linker.name>
+	<linker.start.options>
+	    -G ${m64} -KPIC -z text
+	</linker.start.options>
+	<linker.arch>
+	    ${env.MACH}
+	</linker.arch>
+	<linker.end.options>
+	    -L${env.JAVA_HOME}/jre/lib/${linker.arch} -lmail -ljava -lc
+	</linker.end.options>
+    </properties>
+
+    <profiles>
+	<profile>
+	    <!--
+		Override the settings necessary to build with JDK 1.8.
+	    -->
+	    <id>1.8</id>
+	    <properties>
+		<m64>
+		    -m64
+		</m64>
+		<linker.arch>
+		    amd64
+		</linker.arch>
+	    </properties>
+	</profile>
+    </profiles>
+
+    <build>
+        <plugins>
+	    <plugin>
+		<groupId>org.codehaus.mojo</groupId>
+		<artifactId>native-maven-plugin</artifactId>
+		<version>1.0-alpha-7</version>
+		<extensions>true</extensions>
+		<configuration>
+		    <compilerProvider>generic</compilerProvider>
+		    <compilerExecutable>${compiler.name}</compilerExecutable>
+		    <linkerExecutable>${linker.name}</linkerExecutable>
+		    <compilerStartOptions>
+			<compilerStartOption>
+			    ${compiler.start.options}
+			</compilerStartOption>
+		    </compilerStartOptions>
+		    <linkerStartOptions>
+			<linkerStartOption>
+			    ${linker.start.options}
+			</linkerStartOption>
+		    </linkerStartOptions>
+		    <linkerEndOptions>
+			<linkerEndOption>
+			    ${linker.end.options}
+			</linkerEndOption>
+		    </linkerEndOptions>
+		    <sources>
+			<source>
+			    <directory>../src/main/cpp</directory>
+			    <includes>
+				<include>**/*.c</include>
+			    </includes>
+			</source>
+			<source>
+			    <directory>target/native/javah</directory>
+			    <dependencyAnalysisParticipation>
+				false
+			    </dependencyAnalysisParticipation>
+			</source>
+		    </sources>
+		</configuration>
+		<executions>
+		    <execution>
+			<id>javah</id>
+			<phase>generate-sources</phase>
+			<goals>
+			    <goal>javah</goal>
+			</goals>
+			<configuration>
+			    <classNames>
+				<className>
+				    com.sun.mail.mbox.UNIXFile
+				</className>
+				<className>
+				    com.sun.mail.mbox.UNIXInbox
+				</className>
+			    </classNames>
+			</configuration>
+		    </execution>
+		</executions>
+	    </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>mbox</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/mbox/pom.xml b/mbox/pom.xml
new file mode 100644
index 0000000..7ac3d54
--- /dev/null
+++ b/mbox/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>mbox</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API mbox provider</name>
+
+    <properties>
+	<mail.packages.export>
+	    com.sun.mail.mbox; version=${mail.osgiversion}
+	</mail.packages.export>
+	<findbugs.skip>
+	    false
+	</findbugs.skip>
+	<findbugs.exclude>
+	    ${project.basedir}/exclude.xml
+	</findbugs.exclude>
+    </properties>
+
+    <build>
+	<plugins>
+	    <!--
+		Configure test plugin to find *TestSuite classes.
+	    -->
+	    <plugin>
+		<groupId>org.apache.maven.plugins</groupId>
+		<artifactId>maven-surefire-plugin</artifactId>
+		<configuration>
+		    <includes>
+			<include>**/*Test.java</include>
+			<include>**/*TestSuite.java</include>
+		    </includes>
+		</configuration>
+	    </plugin>
+	</plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+	<dependency>
+	    <groupId>junit</groupId>
+	    <artifactId>junit</artifactId>
+	    <version>4.12</version>
+	    <scope>test</scope>
+	    <optional>true</optional>
+	</dependency>
+    </dependencies>
+</project>
diff --git a/mbox/src/main/cpp/com/sun/mail/mbox/UNIXFile.c b/mbox/src/main/cpp/com/sun/mail/mbox/UNIXFile.c
new file mode 100644
index 0000000..e66a772
--- /dev/null
+++ b/mbox/src/main/cpp/com/sun/mail/mbox/UNIXFile.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+#include <jni.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+extern int _fcntl();
+
+#include "com_sun_mail_mbox_UNIXFile.h"
+
+static	jfieldID IO_fd_fdID;
+static	int fd_offset;
+
+/*
+ * Class:     com_sun_mail_mbox_UNIXFile
+ * Method:    initIDs
+ * Signature: (Ljava/lang/Class;Ljava/io/FileDescriptor;)V
+ */
+JNIEXPORT void JNICALL
+Java_com_sun_mail_mbox_UNIXFile_initIDs(JNIEnv *env, jclass ufClass,
+    jclass fdClass, jobject stdin_obj)
+{
+	IO_fd_fdID = (*env)->GetFieldID(env, fdClass, "fd", "I");
+	/*
+	 * Because pre-JDK 1.2 stored the "fd" as one more than
+	 * its actual value, we remember the value it stored for
+	 * stdin, which should be zero, and use it as the offset
+	 * for other fd's we extract.
+	 */
+	fd_offset = (*env)->GetIntField(env, stdin_obj, IO_fd_fdID);
+}
+
+/*
+ * Class:     com_sun_mail_mbox_UNIXFile
+ * Method:    lock0
+ * Signature: (Ljava/io/FileDescriptor;Ljava/lang/String;Z)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_com_sun_mail_mbox_UNIXFile_lock0(JNIEnv *env, jclass clazz,
+    jobject fdobj, jstring umode, jboolean block)
+{
+	int fd;
+	const char *mode;
+	static struct flock flock0;
+	struct flock flock = flock0;
+
+	fd = (*env)->GetIntField(env, fdobj, IO_fd_fdID);
+	fd -= fd_offset;
+	/* XXX - a lot of work to examine one character in a string */
+	mode = (*env)->GetStringUTFChars(env, umode, 0);
+	flock.l_type = mode[1] == 'w' ? F_WRLCK : F_RDLCK;
+	(*env)->ReleaseStringUTFChars(env, umode, mode);
+	flock.l_whence = SEEK_SET;
+	flock.l_start = 0;
+	flock.l_len = 0;
+	return (_fcntl(fd, block ? F_SETLKW : F_SETLK, &flock) == 0 ?
+		JNI_TRUE : JNI_FALSE);
+}
+
+/*
+ * Class:     com_sun_mail_mbox_UNIXFile
+ * Method:    lastAccessed0
+ * Signature: (Ljava/lang/String;)J
+ */
+JNIEXPORT jlong JNICALL
+Java_com_sun_mail_mbox_UNIXFile_lastAccessed0(JNIEnv *env, jclass clazz,
+	jstring uname)
+{
+	const char *name;
+	jlong ret = -1;
+	struct stat st;
+
+	name = (*env)->GetStringUTFChars(env, uname, 0);
+	if (stat(name, &st) == 0) {
+		/*
+		 * Should be...
+		ret = (jlong)st.st_atim.tv_sec * 1000 +
+			st.st_atim.tv_nsec / 1000000;
+		 * but for compatibility with lastModified we use...
+		 */
+		ret = (jlong)st.st_atime * 1000;
+	}
+	(*env)->ReleaseStringUTFChars(env, uname, name);
+	return ret;
+}
diff --git a/mbox/src/main/cpp/com/sun/mail/mbox/UNIXInbox.c b/mbox/src/main/cpp/com/sun/mail/mbox/UNIXInbox.c
new file mode 100644
index 0000000..274a44e
--- /dev/null
+++ b/mbox/src/main/cpp/com/sun/mail/mbox/UNIXInbox.c
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+#include <jni.h>
+#include <maillock.h>
+extern void touchlock();	/* XXX - should be in maillock.h */
+
+#include "com_sun_mail_mbox_UNIXInbox.h"
+
+/*
+ * Class:     com_sun_mail_mbox_UNIXInbox
+ * Method:    maillock
+ * Signature: (Ljava/lang/String;I)Z
+ */
+JNIEXPORT jboolean JNICALL
+Java_com_sun_mail_mbox_UNIXInbox_maillock(JNIEnv *env, jobject obj,
+    jstring user, jint retry_count)
+{
+	jboolean ret;
+	const char *name = (*env)->GetStringUTFChars(env, user, 0);
+	ret = maillock((char *)name, retry_count) == L_SUCCESS ?
+	    JNI_TRUE : JNI_FALSE;
+	(*env)->ReleaseStringUTFChars(env, user, name);
+	return (ret);
+}
+
+/*
+ * Class:     com_sun_mail_mbox_UNIXInbox
+ * Method:    mailunlock
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+Java_com_sun_mail_mbox_UNIXInbox_mailunlock(JNIEnv *env, jobject obj)
+{
+	(void) mailunlock();
+}
+
+/*
+ * Class:     com_sun_mail_mbox_UNIXInbox
+ * Method:    touchlock0
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL
+Java_com_sun_mail_mbox_UNIXInbox_touchlock0(JNIEnv *env, jobject obj)
+{
+	(void) touchlock();
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/ContentLengthCounter.java b/mbox/src/main/java/com/sun/mail/mbox/ContentLengthCounter.java
new file mode 100644
index 0000000..f01aa59
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/ContentLengthCounter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+
+/**
+ * Count the number of bytes in the body of the message written to the stream.
+ */
+class ContentLengthCounter extends OutputStream {
+    private long size = 0;
+    private boolean inHeader = true;
+    private int lastb1 = -1, lastb2 = -1;
+
+    public void write(int b) throws IOException {
+	if (inHeader) {
+	    // if line terminator is CR
+	    if (b == '\r' && lastb1 == '\r')
+		inHeader = false;
+	    else if (b == '\n') {
+		// if line terminator is \n
+		if (lastb1 == '\n')
+		    inHeader = false;
+		// if line terminator is CRLF
+		else if (lastb1 == '\r' && lastb2 == '\n')
+		    inHeader = false;
+	    }
+	    lastb2 = lastb1;
+	    lastb1 = b;
+	} else
+	    size++;
+    }
+
+    public void write(byte[] b) throws IOException {
+	if (inHeader)
+	    super.write(b);
+	else
+	    size += b.length;
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+	if (inHeader)
+	    super.write(b, off, len);
+	else
+	    size += len;
+    }
+
+    public long getSize() {
+	return size;
+    }
+
+    /*
+    public static void main(String argv[]) throws Exception {
+	int b;
+	ContentLengthCounter os = new ContentLengthCounter();
+	while ((b = System.in.read()) >= 0)
+	    os.write(b);
+	System.out.println("size " + os.getSize());
+    }
+    */
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/ContentLengthUpdater.java b/mbox/src/main/java/com/sun/mail/mbox/ContentLengthUpdater.java
new file mode 100644
index 0000000..b6d4408
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/ContentLengthUpdater.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+
+/**
+ * Update the Content-Length header in the message written to the stream.
+ */
+class ContentLengthUpdater extends FilterOutputStream {
+    private String contentLength;
+    private boolean inHeader = true;
+    private boolean sawContentLength = false;
+    private int lastb1 = -1, lastb2 = -1;
+    private StringBuilder line = new StringBuilder();
+
+    public ContentLengthUpdater(OutputStream os, long contentLength) {
+	super(os);
+	this.contentLength = "Content-Length: " + contentLength;
+    }
+
+    public void write(int b) throws IOException {
+	if (inHeader) {
+	    String eol = "\n";
+	    // First, determine if we're still in the header.
+	    if (b == '\r') {
+		// if line terminator is CR
+		if (lastb1 == '\r') {
+		    inHeader = false;
+		    eol = "\r";
+		// else, if line terminator is CRLF
+		} else if (lastb1 == '\n' && lastb2 == '\r') {
+		    inHeader = false;
+		    eol = "\r\n";
+		}
+	    // else, if line terminator is \n
+	    } else if (b == '\n') {
+		if (lastb1 == '\n') {
+		    inHeader = false;
+		    eol = "\n";
+		}
+	    }
+
+	    // If we're no longer in the header, and we haven't seen
+	    // a Content-Length header yet, it's time to put one out.
+	    if (!inHeader && !sawContentLength) {
+		out.write(contentLength.getBytes("iso-8859-1"));
+		out.write(eol.getBytes("iso-8859-1"));
+	    }
+
+	    // If we have a full line, see if it's a Content-Length header.
+	    if (b == '\r' || (b == '\n' && lastb1 != '\r')) {
+		if (line.toString().regionMatches(true, 0,
+					"content-length:", 0, 15)) {
+		    // yup, got it
+		    sawContentLength = true;
+		    // put out the new version
+		    out.write(contentLength.getBytes("iso-8859-1"));
+		} else {
+		    // not a Content-Length header, just write it out
+		    out.write(line.toString().getBytes("iso-8859-1"));
+		}
+		line.setLength(0);	// clear buffer for next line
+	    }
+	    if (b == '\r' || b == '\n')
+		out.write(b);	// write out line terminator immediately
+	    else
+		line.append((char)b);	// accumulate characters of the line
+
+	    // rotate saved characters for next time through loop
+	    lastb2 = lastb1;
+	    lastb1 = b;
+	} else
+	    out.write(b);		// not in the header, just write it out
+    }
+
+    public void write(byte[] b) throws IOException {
+	if (inHeader)
+	    write(b, 0, b.length);
+	else
+	    out.write(b);
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+	if (inHeader) {
+	    for (int i = 0 ; i < len ; i++) {
+		write(b[off + i]);
+	    }
+	} else
+	    out.write(b, off, len);
+    }
+
+    // for testing
+    public static void main(String argv[]) throws Exception {
+	int b;
+	ContentLengthUpdater os =
+	    new ContentLengthUpdater(System.out, Long.parseLong(argv[0]));
+	while ((b = System.in.read()) >= 0)
+	    os.write(b);
+	os.flush();
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/DefaultMailbox.java b/mbox/src/main/java/com/sun/mail/mbox/DefaultMailbox.java
new file mode 100644
index 0000000..bac0b05
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/DefaultMailbox.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+
+public class DefaultMailbox extends Mailbox {
+    private final String home;
+
+    private static final boolean homeRelative =
+				Boolean.getBoolean("mail.mbox.homerelative");
+
+    public DefaultMailbox() {
+	home = System.getProperty("user.home");
+    }
+
+    public MailFile getMailFile(String user, String folder) {
+	return new DefaultMailFile(filename(user, folder));
+    }
+
+    public String filename(String user, String folder) {
+	try {
+	    char c = folder.charAt(0);
+	    if (c == File.separatorChar) {
+		return folder;
+	    } else if (c == '~') {
+		int i = folder.indexOf(File.separatorChar);
+		String tail = "";
+		if (i > 0) {
+		    tail = folder.substring(i);
+		    folder = folder.substring(0, i);
+		}
+		return home + tail;
+	    } else {
+		if (folder.equalsIgnoreCase("INBOX"))
+		    folder = "INBOX";
+		if (homeRelative)
+		    return home + File.separator + folder;
+		else
+		    return folder;
+	    }
+	} catch (StringIndexOutOfBoundsException e) {
+	    return folder;
+	}
+    }
+}
+
+class DefaultMailFile extends File implements MailFile {
+    protected transient RandomAccessFile file;
+
+    private static final long serialVersionUID = 3713116697523761684L;
+
+    DefaultMailFile(String name) {
+	super(name);
+    }
+
+    public boolean lock(String mode) {
+	try {
+	    file = new RandomAccessFile(this, mode);
+	    return true;
+	} catch (FileNotFoundException fe) {
+	    return false;
+	} catch (IOException ie) {
+	    file = null;
+	    return false;
+	}
+    }
+
+    public void unlock() { 
+	if (file != null) {
+	    try {
+		file.close();
+	    } catch (IOException e) {
+		// ignore it
+	    }
+	    file = null;
+	}
+    }
+
+    public void touchlock() {
+    }
+
+    public FileDescriptor getFD() {
+	if (file == null)
+	    return null;
+	try {
+	    return file.getFD();
+	} catch (IOException e) {
+	    return null;
+	}
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/FileInterface.java b/mbox/src/main/java/com/sun/mail/mbox/FileInterface.java
new file mode 100644
index 0000000..891f0d5
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/FileInterface.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.File;
+import java.io.FilenameFilter;
+
+public interface FileInterface {
+    /**
+     * Gets the name of the file. This method does not include the
+     * directory.
+     * @return the file name.
+     */
+    public String getName();
+
+    /**
+     * Gets the path of the file.
+     * @return the file path.
+     */
+    public String getPath();
+
+    /**
+     * Gets the absolute path of the file.
+     * @return the absolute file path.
+     */
+    public String getAbsolutePath();
+
+    /**
+     * Gets the official, canonical path of the File.
+     * @return canonical path
+     */
+    // XXX - JDK1.1
+    // public String getCanonicalPath();
+
+    /**
+     * Gets the name of the parent directory.
+     * @return the parent directory, or null if one is not found.
+     */
+    public String getParent();
+
+    /**
+     * Returns a boolean indicating whether or not a file exists.
+     */
+    public boolean exists();
+
+    /**
+     * Returns a boolean indicating whether or not a writable file 
+     * exists. 
+     */
+    public boolean canWrite();
+
+    /**
+     * Returns a boolean indicating whether or not a readable file 
+     * exists.
+     */
+    public boolean canRead();
+
+    /**
+     * Returns a boolean indicating whether or not a normal file 
+     * exists.
+     */
+    public boolean isFile();
+
+    /**
+     * Returns a boolean indicating whether or not a directory file 
+     * exists.
+     */
+    public boolean isDirectory();
+
+    /**
+     * Returns a boolean indicating whether the file name is absolute.
+     */
+    public boolean isAbsolute();
+
+    /**
+     * Returns the last modification time. The return value should
+     * only be used to compare modification dates. It is meaningless
+     * as an absolute time.
+     */
+    public long lastModified();
+
+    /**
+     * Returns the length of the file. 
+     */
+    public long length();
+
+    /**
+     * Creates a directory and returns a boolean indicating the
+     * success of the creation.  Will return false if the directory already
+     * exists.
+     */
+    public boolean mkdir();
+
+    /**
+     * Renames a file and returns a boolean indicating whether 
+     * or not this method was successful.
+     * @param dest the new file name
+     */
+    public boolean renameTo(File dest);
+
+    /**
+     * Creates all directories in this path.  This method 
+     * returns true if the target (deepest) directory was created,
+     * false if the target directory was not created (e.g., if it
+     * existed previously).
+     */
+    public boolean mkdirs();
+
+    /**
+     * Lists the files in a directory. Works only on directories.
+     * @return an array of file names.  This list will include all
+     * files in the directory except the equivalent of "." and ".." .
+     */
+    public String[] list();
+
+    /**
+     * Uses the specified filter to list files in a directory. 
+     * @param filter the filter used to select file names
+     * @return the filter selected files in this directory.
+     * @see FilenameFilter
+     */
+    public String[] list(FilenameFilter filter);
+
+    /**
+     * Deletes the specified file. Returns true
+     * if the file could be deleted.
+     */
+    public boolean delete();
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/InboxFile.java b/mbox/src/main/java/com/sun/mail/mbox/InboxFile.java
new file mode 100644
index 0000000..e4fffd5
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/InboxFile.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+public interface InboxFile extends MailFile {
+    public boolean openLock(String mode);
+    public void closeLock();
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/LineCounter.java b/mbox/src/main/java/com/sun/mail/mbox/LineCounter.java
new file mode 100644
index 0000000..1ae4084
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/LineCounter.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+
+/**
+ * Count number of lines output.
+ */
+class LineCounter extends FilterOutputStream {
+    private int lastb = -1;
+    protected int lineCount;
+
+    public LineCounter(OutputStream os) {
+	super(os);
+    }
+
+    public void write(int b) throws IOException {
+	// If we have a full line, count it.
+	if (b == '\r' || (b == '\n' && lastb != '\r'))
+	    lineCount++;
+	out.write(b);
+	lastb = b;
+    }
+
+    public void write(byte[] b) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+	for (int i = 0 ; i < len ; i++) {
+	    write(b[off + i]);
+	}
+    }
+
+    public int getLineCount() {
+	return lineCount;
+    }
+
+    // for testing
+    public static void main(String argv[]) throws Exception {
+	int b;
+	LineCounter os =
+	    new LineCounter(System.out);
+	while ((b = System.in.read()) >= 0)
+	    os.write(b);
+	os.flush();
+	System.out.println(os.getLineCount());
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/MailFile.java b/mbox/src/main/java/com/sun/mail/mbox/MailFile.java
new file mode 100644
index 0000000..f790fce
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/MailFile.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.FileDescriptor;
+
+public interface MailFile extends FileInterface {
+    public boolean lock(String mode);
+    public void unlock();
+    public void touchlock();
+    public FileDescriptor getFD();
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/Mailbox.java b/mbox/src/main/java/com/sun/mail/mbox/Mailbox.java
new file mode 100644
index 0000000..b902121
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/Mailbox.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+public abstract class Mailbox {
+    /**
+     * Return a MailFile object for the specified user's folder.
+     */
+    public abstract MailFile getMailFile(String user, String folder);
+
+    /**
+     * Return the file name corresponding to a folder with the given name.
+     */
+    public abstract String filename(String user, String folder);
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/MboxFolder.java b/mbox/src/main/java/com/sun/mail/mbox/MboxFolder.java
new file mode 100644
index 0000000..340b84f
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/MboxFolder.java
@@ -0,0 +1,1164 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import javax.mail.*;
+import javax.mail.event.*;
+import javax.mail.internet.*;
+import javax.mail.util.*;
+import java.io.*;
+import java.util.*;
+import com.sun.mail.util.LineInputStream;
+
+/**
+ * This class represents a mailbox file containing RFC822 style email messages. 
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+
+public class MboxFolder extends Folder {
+
+    private String name;	// null => the default folder
+    private boolean is_inbox = false;
+    private int total;		// total number of messages in mailbox
+    private volatile boolean opened = false;
+    private List<MessageMetadata> messages;
+    private TempFile temp;
+    private MboxStore mstore;
+    private MailFile folder;
+    private long file_size;	// the size the last time we read or wrote it
+    private long saved_file_size; // size at the last open, close, or expunge
+    private boolean special_imap_message;
+
+    private static final boolean homeRelative =
+				Boolean.getBoolean("mail.mbox.homerelative");
+
+    /**
+     * Metadata for each message, to avoid instantiating MboxMessage
+     * objects for messages we're not going to look at. <p>
+     *
+     * MboxFolder keeps an array of these objects corresponding to
+     * each message in the folder.  Access to the array elements is
+     * synchronized, but access to contents of this object is not.
+     * The metadata stored here is only accessed if the message field
+     * is null; otherwise the MboxMessage object contains the metadata.
+     */
+    static final class MessageMetadata {
+	public long start;	// offset in temp file of start of this message
+	// public long end;	// offset in temp file of end of this message
+	public long dataend;	// offset of end of message data, <= "end"
+	public MboxMessage message;	// the message itself
+	public boolean recent;	// message is recent?
+	public boolean deleted;	// message is marked deleted?
+	public boolean imap;	// special imap message?
+    }
+
+    public MboxFolder(MboxStore store, String name) {
+	super(store);
+	this.mstore = store;
+	this.name = name;
+
+	if (name != null && name.equalsIgnoreCase("INBOX"))
+	    is_inbox = true;
+
+	folder = mstore.getMailFile(name == null ? "~" : name);
+	if (folder.exists())
+	    saved_file_size = folder.length();
+	else
+	    saved_file_size = -1;
+    }
+
+    public char getSeparator() {
+	return File.separatorChar;
+    }
+
+    public Folder[] list(String pattern) throws MessagingException {
+	if (!folder.isDirectory())
+	    throw new MessagingException("not a directory");
+
+	if (name == null)
+	    return list(null, pattern, true);
+	else
+	    return list(name + File.separator, pattern, false);
+    }
+
+    /*
+     * Version of list shared by MboxStore and MboxFolder.
+     */
+    protected Folder[] list(String ref, String pattern, boolean fromStore)
+					throws MessagingException {
+	if (ref != null && ref.length() == 0)
+	    ref = null;
+	int i;
+	String refdir = null;
+	String realdir = null;
+
+	pattern = canonicalize(ref, pattern);
+	if ((i = indexOfAny(pattern, "%*")) >= 0) {
+	    refdir = pattern.substring(0, i);
+	} else {
+	    refdir = pattern;
+	}
+	if ((i = refdir.lastIndexOf(File.separatorChar)) >= 0) {
+	    // get rid of anything after directory name
+	    refdir = refdir.substring(0, i + 1);
+	    realdir = mstore.mb.filename(mstore.user, refdir);
+	} else if (refdir.length() == 0 || refdir.charAt(0) != '~') {
+	    // no separator and doesn't start with "~" => home or cwd
+	    refdir = null;
+	    if (homeRelative)
+		realdir = mstore.home;
+	    else
+		realdir = ".";
+	} else {
+	    realdir = mstore.mb.filename(mstore.user, refdir);
+	}
+	List<String> flist = new ArrayList<String>();
+	listWork(realdir, refdir, pattern, fromStore ? 0 : 1, flist);
+	if (Match.path("INBOX", pattern, '\0'))
+	    flist.add("INBOX");
+
+	Folder fl[] = new Folder[flist.size()];
+	for (i = 0; i < fl.length; i++) {
+	    fl[i] = createFolder(mstore, flist.get(i));
+	}
+	return fl;
+    }
+
+    public String getName() {
+	if (name == null)
+	    return "";
+	else if (is_inbox)
+	    return "INBOX";
+	else
+	    return folder.getName();
+    }
+
+    public String getFullName() {
+	if (name == null)
+	    return "";
+	else
+	    return name;
+    }
+
+    public Folder getParent() {
+	if (name == null)
+	    return null;
+	else if (is_inbox)
+	    return createFolder(mstore, null);
+	else
+	    // XXX - have to recognize other folders under default folder
+	    return createFolder(mstore, folder.getParent());
+    }
+
+    public boolean exists() {
+	return folder.exists();
+    }
+
+    public int getType() {
+	if (folder.isDirectory())
+	    return HOLDS_FOLDERS;
+	else
+	    return HOLDS_MESSAGES;
+    }
+
+    public Flags getPermanentFlags() {
+	return MboxStore.permFlags;
+    }
+
+    public synchronized boolean hasNewMessages() {
+	if (folder instanceof UNIXFile) {
+	    UNIXFile f = (UNIXFile)folder;
+	    if (f.length() > 0) {
+		long atime = f.lastAccessed();
+		long mtime = f.lastModified();
+//System.out.println(name + " atime " + atime + " mtime " + mtime);
+		return atime < mtime;
+	    }
+	    return false;
+	}
+	long current_size;
+	if (folder.exists())
+	    current_size = folder.length();
+	else
+	    current_size = -1;
+	// if we've never opened the folder, remember the size now
+	// (will cause us to return false the first time)
+	if (saved_file_size < 0)
+	    saved_file_size = current_size;
+	return current_size > saved_file_size;
+    }
+
+    public synchronized Folder getFolder(String name)
+					throws MessagingException {
+	if (folder.exists() && !folder.isDirectory())
+	    throw new MessagingException("not a directory");
+	return createFolder(mstore,
+		(this.name == null ? "~" : this.name) + File.separator + name);
+    }
+
+    public synchronized boolean create(int type) throws MessagingException {
+	switch (type) {
+	case HOLDS_FOLDERS:
+	    if (!folder.mkdirs()) {
+		return false;
+	    }
+	    break;
+
+	case HOLDS_MESSAGES:
+	    if (folder.exists()) {
+		return false;
+	    }
+	    try {
+		(new FileOutputStream((File)folder)).close();
+	    } catch (FileNotFoundException fe) {
+		File parent = new File(folder.getParent());
+		if (!parent.mkdirs())
+		    throw new
+			MessagingException("can't create folder: " + name);
+		try {
+		    (new FileOutputStream((File)folder)).close();
+		} catch (IOException ex3) {
+		    throw new
+			MessagingException("can't create folder: " + name, ex3);
+		}
+	    } catch (IOException e) {
+		throw new
+		    MessagingException("can't create folder: " + name, e);
+	    }
+	    break;
+
+	default:
+	    throw new MessagingException("type not supported");
+	}
+	notifyFolderListeners(FolderEvent.CREATED);
+	return true;
+    }
+
+    public synchronized boolean delete(boolean recurse)
+					throws MessagingException {
+	checkClosed();
+	if (name == null)
+	    throw new MessagingException("can't delete default folder");
+	boolean ret = true;
+	if (recurse && folder.isDirectory())
+	    ret = delete(new File(folder.getPath()));
+	if (ret && folder.delete()) {
+	    notifyFolderListeners(FolderEvent.DELETED);
+	    return true;
+	}
+	return false;
+    }
+
+    /**
+     * Recursively delete the specified file/directory.
+     */
+    private boolean delete(File f) {
+	File[] files = f.listFiles();
+	boolean ret = true;
+	for (int i = 0; ret && i < files.length; i++) {
+	    if (files[i].isDirectory())
+		ret = delete(files[i]);
+	    else
+		ret = files[i].delete();
+	}
+	return ret;
+    }
+
+    public synchronized boolean renameTo(Folder f)
+				throws MessagingException {
+	checkClosed();
+	if (name == null)
+	    throw new MessagingException("can't rename default folder");
+	if (!(f instanceof MboxFolder))
+	    throw new MessagingException("can't rename to: " + f.getName());
+	String newname = ((MboxFolder)f).folder.getPath();
+	if (folder.renameTo(new File(newname))) {
+	    notifyFolderRenamedListeners(f);
+	    return true;
+	}
+	return false;
+    }
+
+    /* Ensure the folder is open */
+    void checkOpen() throws IllegalStateException {
+	if (!opened) 
+	    throw new IllegalStateException("Folder is not Open");
+    }
+
+    /* Ensure the folder is not open */
+    private void checkClosed() throws IllegalStateException {
+	if (opened) 
+	    throw new IllegalStateException("Folder is Open");
+    }
+
+    /*
+     * Check that the given message number is within the range
+     * of messages present in this folder. If the message
+     * number is out of range, we check to see if new messages
+     * have arrived.
+     */
+    private void checkRange(int msgno) throws MessagingException {
+	if (msgno < 1) // message-numbers start at 1
+	    throw new IndexOutOfBoundsException("message number < 1");
+
+	if (msgno <= total)
+	    return;
+
+	// Out of range, let's check if there are any new messages.
+	getMessageCount();
+
+	if (msgno > total) // Still out of range ? Throw up ...
+	    throw new IndexOutOfBoundsException(msgno + " > " + total);
+    }
+
+    /* Ensure the folder is open & readable */
+    private void checkReadable() throws IllegalStateException {
+	if (!opened || (mode != READ_ONLY && mode != READ_WRITE))
+	    throw new IllegalStateException("Folder is not Readable");
+    }
+
+    /* Ensure the folder is open & writable */
+    private void checkWritable() throws IllegalStateException {
+	if (!opened || mode != READ_WRITE)
+	    throw new IllegalStateException("Folder is not Writable");
+    }
+
+    public boolean isOpen() {
+        return opened;
+    }
+
+    /*
+     * Open the folder in the specified mode.
+     */
+    public synchronized void open(int mode) throws MessagingException {
+	if (opened)
+	    throw new IllegalStateException("Folder is already Open");
+
+	if (!folder.exists())
+	    throw new FolderNotFoundException(this, "Folder doesn't exist: " +
+					    folder.getPath());
+	this.mode = mode;
+	switch (mode) {
+	case READ_WRITE:
+	default:
+	    if (!folder.canWrite())
+		throw new MessagingException("Open Failure, can't write: " +
+					    folder.getPath());
+	    // fall through...
+
+	case READ_ONLY:
+	    if (!folder.canRead())
+		throw new MessagingException("Open Failure, can't read: " +
+					    folder.getPath());
+	    break;
+	}
+
+	if (is_inbox && folder instanceof InboxFile) {
+	    InboxFile inf = (InboxFile)folder;
+	    if (!inf.openLock(mode == READ_WRITE ? "rw" : "r"))
+		throw new MessagingException("Failed to lock INBOX");
+	}
+	if (!folder.lock("r"))
+	    throw new MessagingException("Failed to lock folder: " + name);
+	messages = new ArrayList<MessageMetadata>();
+	total = 0;
+	Message[] msglist = null;
+	try {
+	    temp = new TempFile(null);
+	    saved_file_size = folder.length();
+	    msglist = load(0L, false);
+	} catch (IOException e) {
+	    throw new MessagingException("IOException", e);
+	} finally {
+	    folder.unlock();
+	}
+	notifyConnectionListeners(ConnectionEvent.OPENED);
+	if (msglist != null)
+	    notifyMessageAddedListeners(msglist);
+	opened = true;		// now really opened
+    }
+
+    public synchronized void close(boolean expunge) throws MessagingException {
+	checkOpen();
+
+	try {
+	    if (mode == READ_WRITE) {
+		try {
+		    writeFolder(true, expunge);
+		} catch (IOException e) {
+		    throw new MessagingException("I/O Exception", e);
+		}
+	    }
+	    messages = null;
+	} finally {
+	    opened = false;
+	    if (is_inbox && folder instanceof InboxFile) {
+		InboxFile inf = (InboxFile)folder;
+		inf.closeLock();
+	    }
+	    temp.close();
+	    temp = null;
+	    notifyConnectionListeners(ConnectionEvent.CLOSED);
+	}
+    }
+
+    /**
+     * Re-write the folder with the current contents of the messages.
+     * If closing is true, turn off the RECENT flag.  If expunge is
+     * true, don't write out deleted messages (only used from close()
+     * when the message cache won't be accessed again).
+     *
+     * Return the number of messages written.
+     */
+    protected int writeFolder(boolean closing, boolean expunge)
+			throws IOException, MessagingException {
+
+	/*
+	 * First, see if there have been any changes.
+	 */
+	int modified = 0, deleted = 0, recent = 0;
+	for (int msgno = 1; msgno <= total; msgno++) {
+	    MessageMetadata md = messages.get(messageIndexOf(msgno));
+	    MboxMessage msg = md.message;
+	    if (msg != null) {
+		Flags flags = msg.getFlags();
+		if (msg.isModified() || !msg.origFlags.equals(flags))
+		    modified++;
+		if (flags.contains(Flags.Flag.DELETED))
+		    deleted++;
+		if (flags.contains(Flags.Flag.RECENT))
+		    recent++;
+	    } else {
+		if (md.deleted)
+		    deleted++;
+		if (md.recent)
+		    recent++;
+	    }
+	}
+	if ((!closing || recent == 0) && (!expunge || deleted == 0) &&
+		modified == 0)
+	    return 0;
+
+	/*
+	 * Have to save any new mail that's been appended to the
+	 * folder since we last loaded it.
+	 */
+	if (!folder.lock("rw"))
+	    throw new MessagingException("Failed to lock folder: " + name);
+	int oldtotal = total;	// XXX
+	Message[] msglist = null;
+	if (folder.length() != file_size)
+	    msglist = load(file_size, !closing);
+	// don't use the folder's FD, need to re-open in order to trunc the file
+	OutputStream os =
+		new BufferedOutputStream(new FileOutputStream((File)folder));
+	int wr = 0;
+	boolean keep = true;
+	try {
+	    if (special_imap_message)
+		appendStream(getMessageStream(0), os);
+	    for (int msgno = 1; msgno <= total; msgno++) {
+		MessageMetadata md = messages.get(messageIndexOf(msgno));
+		MboxMessage msg = md.message;
+		if (msg != null) {
+		    if (expunge && msg.isSet(Flags.Flag.DELETED))
+			continue;	// skip it;
+		    if (closing && msgno <= oldtotal &&
+						msg.isSet(Flags.Flag.RECENT))
+			msg.setFlag(Flags.Flag.RECENT, false);
+		    writeMboxMessage(msg, os);
+		} else {
+		    if (expunge && md.deleted)
+			continue;	// skip it;
+		    if (closing && msgno <= oldtotal && md.recent) {
+			// have to instantiate message so that we can
+			// clear the recent flag
+			msg = (MboxMessage)getMessage(msgno);
+			msg.setFlag(Flags.Flag.RECENT, false);
+			writeMboxMessage(msg, os);
+		    } else {
+			appendStream(getMessageStream(msgno), os);
+		    }
+		}
+		folder.touchlock();
+		wr++;
+	    }
+	    // If no messages in the mailbox, and we're closing,
+	    // maybe we should remove the mailbox.
+	    if (wr == 0 && closing) {
+		String skeep = ((MboxStore)store).getSession().
+					getProperty("mail.mbox.deleteEmpty");
+		if (skeep != null && skeep.equalsIgnoreCase("true"))
+		    keep = false;
+	    }
+	} catch (IOException e) {
+	    throw e;
+	} catch (MessagingException e) {
+	    throw e;
+	} catch (Exception e) {
+e.printStackTrace();
+	    throw new MessagingException("unexpected exception " + e);
+	} finally {
+	    // close the folder, flushing out the data
+	    try {
+		os.close();
+		file_size = saved_file_size = folder.length();
+		if (!keep) {
+		    folder.delete();
+		    file_size = 0;
+		}
+	    } catch (IOException ex) {}
+
+	    if (keep) {
+		// make sure the access time is greater than the mod time
+		// XXX - would be nice to have utime()
+		try {
+		    Thread.sleep(1000);		// sleep for a second
+		} catch (InterruptedException ex) {}
+		InputStream is = null;
+		try {
+		    is = new FileInputStream((File)folder);
+		    is.read();	// read a byte
+		} catch (IOException ex) {}	// ignore errors
+		try {
+		    if (is != null)
+			is.close();
+		    is = null;
+		} catch (IOException ex) {}	// ignore errors
+	    }
+
+	    folder.unlock();
+	    if (msglist != null)
+		notifyMessageAddedListeners(msglist);
+	}
+	return wr;
+    }
+
+    /**
+     * Append the input stream to the output stream, closing the
+     * input stream when done.
+     */
+    private static final void appendStream(InputStream is, OutputStream os)
+				throws IOException {
+	try {
+	    byte[] buf = new byte[64 * 1024];
+	    int len;
+	    while ((len = is.read(buf)) > 0)
+		os.write(buf, 0, len);
+	} finally {
+	    is.close();
+	}
+    }
+
+    /**
+     * Write a MimeMessage to the specified OutputStream in a
+     * format suitable for a UNIX mailbox, i.e., including a correct
+     * Content-Length header and with the local platform's line
+     * terminating convention. <p>
+     *
+     * If the message is really a MboxMessage, use its writeToFile
+     * method, which has access to the UNIX From line.  Otherwise, do
+     * all the work here, creating an appropriate UNIX From line.
+     */
+    public static void writeMboxMessage(MimeMessage msg, OutputStream os)
+				throws IOException, MessagingException {
+	try {
+	    if (msg instanceof MboxMessage) {
+		((MboxMessage)msg).writeToFile(os);
+	    } else {
+		// XXX - modify the message to preserve the flags in headers
+		MboxMessage.setHeadersFromFlags(msg);
+		ContentLengthCounter cos = new ContentLengthCounter();
+		NewlineOutputStream nos = new NewlineOutputStream(cos);
+		msg.writeTo(nos);
+		nos.flush();
+		os = new NewlineOutputStream(os, true);
+		os = new ContentLengthUpdater(os, cos.getSize());
+		PrintStream pos = new PrintStream(os, false, "iso-8859-1");
+		pos.println(getUnixFrom(msg));
+		msg.writeTo(pos);
+		pos.flush();
+	    }
+	} catch (MessagingException me) {
+	    throw me;
+	} catch (IOException ioe) {
+	    throw ioe;
+	}
+    }
+
+    /**
+     * Construct an appropriately formatted UNIX From line using
+     * the sender address and the date in the message.
+     */
+    protected static String getUnixFrom(MimeMessage msg) {
+	Address[] afrom;
+	String from;
+	Date ddate;
+	String date;
+	try {
+	    if ((afrom = msg.getFrom()) == null ||
+		    !(afrom[0] instanceof InternetAddress) ||
+		    (from = ((InternetAddress)afrom[0]).getAddress()) == null)
+		from = "UNKNOWN";
+	    if ((ddate = msg.getReceivedDate()) == null ||
+		    (ddate = msg.getSentDate()) == null)
+		ddate = new Date();
+	} catch (MessagingException e) {
+	    from = "UNKNOWN";
+	    ddate = new Date();
+	}
+	date = ddate.toString();
+	// date is of the form "Sat Aug 12 02:30:00 PDT 1995"
+	// need to strip out the timezone
+	return "From " + from + " " +
+		date.substring(0, 20) + date.substring(24);
+    }
+
+    public synchronized int getMessageCount() throws MessagingException {
+	if (!opened)
+	    return -1;
+
+	boolean locked = false;
+	Message[] msglist = null;
+	try {
+	    if (folder.length() != file_size) {
+		if (!folder.lock("r"))
+		    throw new MessagingException("Failed to lock folder: " +
+							name);
+		locked = true;
+		msglist = load(file_size, true);
+	    }
+	} catch (IOException e) {
+	    throw new MessagingException("I/O Exception", e);
+	} finally {
+	    if (locked) {
+		folder.unlock();
+		if (msglist != null)
+		    notifyMessageAddedListeners(msglist);
+	    }
+	}
+	return total;
+    }
+
+    /**
+     * Get the specified message.  Note that messages are numbered
+     * from 1.
+     */
+    public synchronized Message getMessage(int msgno)
+				throws MessagingException {
+	checkReadable();
+	checkRange(msgno);
+
+	MessageMetadata md = messages.get(messageIndexOf(msgno));
+	MboxMessage m = md.message;
+	if (m == null) {
+	    InputStream is = getMessageStream(msgno);
+	    try {
+		m = loadMessage(is, msgno, mode == READ_WRITE);
+	    } catch (IOException ex) {
+		MessagingException mex =
+		    new MessageRemovedException("mbox message gone", ex);
+		throw mex;
+	    }
+	    md.message = m;
+	}
+	return m;
+    }
+
+    private final int messageIndexOf(int msgno) {
+	return special_imap_message ? msgno : msgno - 1;
+    }
+
+    private InputStream getMessageStream(int msgno) {
+	int index = messageIndexOf(msgno);
+	MessageMetadata md = messages.get(index);
+	return temp.newStream(md.start, md.dataend);
+    }
+
+    public synchronized void appendMessages(Message[] msgs)
+				throws MessagingException {
+	if (!folder.lock("rw"))
+	    throw new MessagingException("Failed to lock folder: " + name);
+
+	OutputStream os = null;
+	boolean err = false;
+	try {
+	    os = new BufferedOutputStream(
+		new FileOutputStream(((File)folder).getPath(), true));
+		// XXX - should use getAbsolutePath()?
+	    for (int i = 0; i < msgs.length; i++) {
+		if (msgs[i] instanceof MimeMessage) {
+		    writeMboxMessage((MimeMessage)msgs[i], os);
+		} else {
+		    err = true;
+		    continue;
+		}
+		folder.touchlock();
+	    }
+	} catch (IOException e) {
+	    throw new MessagingException("I/O Exception", e);
+	} catch (MessagingException e) {
+	    throw e;
+	} catch (Exception e) {
+e.printStackTrace();
+	    throw new MessagingException("unexpected exception " + e);
+	} finally {
+	    if (os != null)
+		try {
+		    os.close();
+		} catch (IOException e) {
+		    // ignored
+		}
+	    folder.unlock();
+	}
+	if (opened)
+	    getMessageCount();	// loads new messages as a side effect
+	if (err)
+	    throw new MessagingException("Can't append non-Mime message");
+    }
+
+    public synchronized Message[] expunge() throws MessagingException {
+	checkWritable();
+
+	/*
+	 * First, write out the folder to make sure we have permission,
+	 * disk space, etc.
+	 */
+	int wr = total;		// number of messages written out
+	try {
+	    wr = writeFolder(false, true);
+	} catch (IOException e) {
+	    throw new MessagingException("expunge failed", e);
+	}
+	if (wr == total)	// wrote them all => nothing expunged
+	    return new Message[0];
+
+	/*
+	 * Now, actually get rid of the expunged messages.
+	 */
+	int del = 0;
+	Message[] msglist = new Message[total - wr];
+	int msgno = 1;
+	while (msgno <= total) {
+	    MessageMetadata md = messages.get(messageIndexOf(msgno));
+	    MboxMessage msg = md.message;
+	    if (msg != null) {
+		if (msg.isSet(Flags.Flag.DELETED)) {
+		    msg.setExpunged(true);
+		    msglist[del] = msg;
+		    del++;
+		    messages.remove(messageIndexOf(msgno));
+		    total--;
+		} else {
+		    msg.setMessageNumber(msgno);	// update message number
+		    msgno++;
+		}
+	    } else {
+		if (md.deleted) {
+		    // have to instantiate it for the notification
+		    msg = (MboxMessage)getMessage(msgno);
+		    msg.setExpunged(true);
+		    msglist[del] = msg;
+		    del++;
+		    messages.remove(messageIndexOf(msgno));
+		    total--;
+		} else {
+		    msgno++;
+		}
+	    }
+	}
+	if (del != msglist.length)		// this is really an assert
+	    throw new MessagingException("expunge delete count wrong");
+	notifyMessageRemovedListeners(true, msglist);
+	return msglist;
+    }
+
+    /*
+     * Load more messages from the folder starting at the specified offset.
+     */
+    private Message[] load(long offset, boolean notify)
+				throws MessagingException, IOException {
+	int oldtotal = total;
+	MessageLoader loader = new MessageLoader(temp);
+	int loaded = loader.load(folder.getFD(), offset, messages);
+	total += loaded;
+	file_size = folder.length();
+
+	if (offset == 0 && loaded > 0) {
+	    /*
+	     * If the first message is the special message that the
+	     * IMAP server adds to the mailbox, remember that we've
+	     * seen it so it won't be shown to the user.
+	     */
+	    MessageMetadata md = messages.get(0);
+	    if (md.imap) {
+		special_imap_message = true;
+		total--;
+	    }
+	}
+	if (notify) {
+	    Message[] msglist = new Message[total - oldtotal];
+	    for (int i = oldtotal, j = 0; i < total; i++, j++)
+		msglist[j] = getMessage(i + 1);
+	    return msglist;
+	} else
+	    return null;
+    }
+
+    /**
+     * Parse the input stream and return an appropriate message object.
+     * The InputStream must be a SharedInputStream.
+     */
+    private MboxMessage loadMessage(InputStream is, int msgno,
+		boolean writable) throws MessagingException, IOException {
+	LineInputStream in = new LineInputStream(is);
+
+	/*
+	 * Read lines until a UNIX From line,
+	 * skipping blank lines.
+	 */
+	String line;
+	String unix_from = null;
+	while ((line = in.readLine()) != null) {
+	    if (line.trim().length() == 0)
+		continue;
+	    if (line.startsWith("From ")) {
+		/*
+		 * A UNIX From line looks like:
+		 * From address Day Mon DD HH:MM:SS YYYY
+		 */
+		unix_from = line;
+		int i;
+		// find the space after the address, before the date
+		i = unix_from.indexOf(' ', 5);
+		if (i < 0)
+		    continue;	// not a valid UNIX From line
+		break;
+	    }
+	    throw new MessagingException("Garbage in mailbox: " + line);
+	}
+
+	if (unix_from == null)
+	    throw new EOFException("end of mailbox");
+
+	/*
+	 * Now load the RFC822 headers into an InternetHeaders object.
+	 */
+	InternetHeaders hdrs = new InternetHeaders(is);
+
+	// the rest is the message content
+	SharedInputStream sis = (SharedInputStream)is;
+	InputStream stream = sis.newStream(sis.getPosition(), -1);
+	return new MboxMessage(this, hdrs, stream, msgno, unix_from, writable);
+    }
+
+    /*
+     * Only here to make accessible to MboxMessage.
+     */
+    protected void notifyMessageChangedListeners(int type, Message m) {
+	super.notifyMessageChangedListeners(type, m);
+    }
+
+
+    /**
+     * this is an exact duplicate of the Folder.getURL except it doesn't
+     * add a beginning '/' to the URLName.
+     */
+    public URLName getURLName() {
+	// XXX - note:  this should not be done this way with the
+	// new javax.mail apis.
+
+	URLName storeURL = getStore().getURLName();
+	if (name == null)
+	    return storeURL;
+
+	char separator = getSeparator();
+	String fullname = getFullName();
+	StringBuilder encodedName = new StringBuilder();
+
+	// We need to encode each of the folder's names, and replace
+	// the store's separator char with the URL char '/'.
+	StringTokenizer tok = new StringTokenizer(
+	    fullname, Character.toString(separator), true);
+
+	while (tok.hasMoreTokens()) {
+	    String s = tok.nextToken();
+	    if (s.charAt(0) == separator)
+		encodedName.append("/");
+	    else
+		// XXX - should encode, but since there's no decoder...
+		//encodedName.append(java.net.URLEncoder.encode(s));
+		encodedName.append(s);
+	}
+
+	return new URLName(storeURL.getProtocol(), storeURL.getHost(),
+			    storeURL.getPort(), encodedName.toString(),
+			    storeURL.getUsername(),
+			    null /* no password */);
+    }
+
+    /**
+     * Create an MboxFolder object, or a subclass thereof.
+     * Can be overridden by subclasses of MboxFolder so that
+     * the appropriate subclass is created by the list method.
+     */
+    protected Folder createFolder(MboxStore store, String name) {
+	return new MboxFolder(store, name);
+    }
+
+    /*
+     * Support routines for list().
+     */
+
+    /**
+     * Return a canonicalized pattern given a reference name and a pattern.
+     */
+    private static String canonicalize(String ref, String pat) {
+	if (ref == null)
+	    return pat;
+	try {
+	    if (pat.length() == 0) {
+		return ref;
+	    } else if (pat.charAt(0) == File.separatorChar) {
+		return ref.substring(0, ref.indexOf(File.separatorChar)) + pat;
+	    } else {
+		return ref + pat;
+	    }
+	} catch (StringIndexOutOfBoundsException e) {
+	    return pat;
+	}
+    }
+
+    /**
+     * Return the first index of any of the characters in "any" in "s",
+     * or -1 if none are found.
+     *
+     * This should be a method on String.
+     */
+    private static int indexOfAny(String s, String any) {
+	try {
+	    int len = s.length();
+	    for (int i = 0; i < len; i++) {
+		if (any.indexOf(s.charAt(i)) >= 0)
+		    return i;
+	    }
+	    return -1;
+	} catch (StringIndexOutOfBoundsException e) {
+	    return -1;
+	}
+    }
+
+    /**
+     * The recursive part of generating the list of mailboxes.
+     * realdir is the full pathname to the directory to search.
+     * dir is the name the user uses, often a relative name that's
+     * relative to the user's home directory.  dir (if not null) always
+     * has a trailing file separator character.
+     *
+     * @param realdir	real pathname of directory to start looking in
+     * @param dir	user's name for realdir
+     * @param pat	pattern to match against
+     * @param level	level of the directory hierarchy we're in
+     * @param flist	list to which to add folder names that match
+     */
+    // Derived from the c-client listWork() function.
+    private void listWork(String realdir, String dir, String pat,
+					int level, List<String> flist) {
+	String sl[];
+	File fdir = new File(realdir);
+	try {
+	    sl = fdir.list();
+	} catch (SecurityException e) {
+	    return;	// can't read it, ignore it
+	}
+
+	if (level == 0 && dir != null &&
+		Match.path(dir, pat, File.separatorChar))
+	    flist.add(dir);
+
+	if (sl == null)
+	    return;	// nothing return, we're done
+
+	if (realdir.charAt(realdir.length() - 1) != File.separatorChar)
+	    realdir += File.separator;
+
+	for (int i = 0; i < sl.length; i++) {
+	    if (sl[i].charAt(0) == '.')
+		continue;	// ignore all "dot" files for now
+	    String md = realdir + sl[i];
+	    File mf = new File(md);
+	    if (!mf.exists())
+		continue;
+	    String name;
+	    if (dir != null)
+		name = dir + sl[i];
+	    else
+		name = sl[i];
+	    if (mf.isDirectory()) {
+		if (Match.path(name, pat, File.separatorChar)) {
+		    flist.add(name);
+		    name += File.separator;
+		} else {
+		    name += File.separator;
+		    if (Match.path(name, pat, File.separatorChar))
+			flist.add(name);
+		}
+		if (Match.dir(name, pat, File.separatorChar))
+		    listWork(md, name, pat, level + 1, flist);
+	    } else {
+		if (Match.path(name, pat, File.separatorChar))
+		    flist.add(name);
+	    }
+	}
+    }
+}
+
+/**
+ * Pattern matching support class for list().
+ * Should probably be more public.
+ */
+// Translated from the c-client functions pmatch_full() and dmatch().
+class Match {
+    /**
+     * Pathname pattern match
+     *
+     * @param s		base string
+     * @param pat	pattern string
+     * @param delim	delimiter character
+     * @return		true if base matches pattern
+     */
+    static public boolean path(String s, String pat, char delim) {
+	try {
+	    return path(s, 0, s.length(), pat, 0, pat.length(), delim);
+	} catch (StringIndexOutOfBoundsException e) {
+	    return false;
+	}
+    }
+
+    static private boolean path(String s, int s_index, int s_len,
+	String pat, int p_index, int p_len, char delim)
+	    throws StringIndexOutOfBoundsException {
+
+	while (p_index < p_len) {
+	    char c = pat.charAt(p_index);
+	    switch (c) {
+	    case '%':
+		if (++p_index >= p_len)		// % at end of pattern
+						// ok if no delimiters
+		    return delim == 0 || s.indexOf(delim, s_index) < 0;
+		// scan remainder until delimiter
+		do {
+		    if (path(s, s_index, s_len, pat, p_index, p_len, delim))
+			return true;
+		} while (s.charAt(s_index) != delim && ++s_index < s_len);
+		// ran into a delimiter or ran out of string without a match
+		return false;
+
+	    case '*':
+		if (++p_index >= p_len)		// end of pattern?
+		    return true;		// unconditional match
+		do {
+		    if (path(s, s_index, s_len, pat, p_index, p_len, delim))
+			return true;
+		} while (++s_index < s_len);
+		// ran out of string without a match
+		return false;
+
+	    default:
+		// if ran out of string or no match, fail
+		if (s_index >= s_len || c != s.charAt(s_index))
+		    return false;
+
+		// try the next string and pattern characters
+		s_index++;
+		p_index++;
+	    }
+	}
+	return s_index >= s_len;
+    }
+
+    /**
+     * Directory pattern match
+     *
+     * @param s		base string
+     * @param pat	pattern string
+     * @return		true if base is a matching directory of pattern
+     */
+    static public boolean dir(String s, String pat, char delim) {
+	try {
+	    return dir(s, 0, s.length(), pat, 0, pat.length(), delim);
+	} catch (StringIndexOutOfBoundsException e) {
+	    return false;
+	}
+    }
+
+    static private boolean dir(String s, int s_index, int s_len,
+	String pat, int p_index, int p_len, char delim)
+	    throws StringIndexOutOfBoundsException {
+
+	while (p_index < p_len) {
+	    char c = pat.charAt(p_index);
+	    switch (c) {
+	    case '%':
+		if (s_index >= s_len)		// end of base?
+		    return true;		// subset match
+		if (++p_index >= p_len)		// % at end of pattern?
+		    return false;		// no inferiors permitted
+		do {
+		    if (dir(s, s_index, s_len, pat, p_index, p_len, delim))
+			return true;
+		} while (s.charAt(s_index) != delim && ++s_index < s_len);
+
+		if (s_index + 1 == s_len)	// s ends with a delimiter
+		    return true;		// must be a subset of pattern
+		return dir(s, s_index, s_len, pat, p_index, p_len, delim);
+
+	    case '*':
+		return true;			// unconditional match
+
+	    default:
+		if (s_index >= s_len)		// end of base?
+		    return c == delim;		// matched if at delimiter
+
+		if (c != s.charAt(s_index))
+		    return false;
+
+		// try the next string and pattern characters
+		s_index++;
+		p_index++;
+	    }
+	}
+	return s_index >= s_len;
+    }
+}
+
+/**
+ * A ByteArrayOutputStream that allows us to share the byte array
+ * rather than copy it.  Eventually could replace this with something
+ * that doesn't require a single contiguous byte array.
+ */
+class SharedByteArrayOutputStream extends ByteArrayOutputStream {
+    public SharedByteArrayOutputStream(int size) {
+	super(size);
+    }
+
+    public InputStream toStream() {
+	return new SharedByteArrayInputStream(buf, 0, count);
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/MboxMessage.java b/mbox/src/main/java/com/sun/mail/mbox/MboxMessage.java
new file mode 100644
index 0000000..db57a26
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/MboxMessage.java
@@ -0,0 +1,532 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+import java.util.StringTokenizer;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.event.MessageChangedEvent;
+import com.sun.mail.util.LineInputStream;
+
+/**
+ * This class represents an RFC822 style email message that resides in a file.
+ *
+ * @author Bill Shannon
+ */
+
+public class MboxMessage extends MimeMessage {
+
+    boolean writable = false;
+    // original msg flags, used by MboxFolder to detect modification
+    Flags origFlags;
+    /*
+     * A UNIX From line looks like:
+     * From address Day Mon DD HH:MM:SS YYYY
+     */
+    String unix_from;
+    InternetAddress unix_from_user;
+    Date rcvDate;
+    int lineCount = -1;
+    private static OutputStream nullOutputStream = new OutputStream() {
+	public void write(int b) { }
+	public void write(byte[] b, int off, int len) { }
+    };
+
+    /**
+     * Construct an MboxMessage from the InputStream.
+     */
+    public MboxMessage(Session session, InputStream is)
+				throws MessagingException, IOException {
+	super(session);
+	BufferedInputStream bis;
+	if (is instanceof BufferedInputStream)
+	    bis = (BufferedInputStream)is;
+	else
+	    bis = new BufferedInputStream(is);
+	LineInputStream dis = new LineInputStream(bis);
+	bis.mark(1024);
+	String line = dis.readLine();
+	if (line != null && line.startsWith("From "))
+	    this.unix_from = line;
+	else
+	    bis.reset();
+	parse(bis);
+	saved = true;
+    }
+
+    /**
+     * Construct an MboxMessage using the given InternetHeaders object
+     * and content from an InputStream.
+     */
+    public MboxMessage(MboxFolder folder, InternetHeaders hdrs, InputStream is,
+				int msgno, String unix_from, boolean writable)
+				throws MessagingException {
+	super(folder, hdrs, null, msgno);
+	setFlagsFromHeaders();
+	origFlags = getFlags();
+	this.unix_from = unix_from;
+	this.writable = writable;
+	this.contentStream = is;
+    }
+
+    /**
+     * Returns the "From" attribute. The "From" attribute contains
+     * the identity of the person(s) who wished this message to 
+     * be sent. <p>
+     * 
+     * If our superclass doesn't have a value, we return the address
+     * from the UNIX From line.
+     *
+     * @return          array of Address objects
+     * @exception       MessagingException
+     */
+    public Address[] getFrom() throws MessagingException {
+	Address[] ret = super.getFrom();
+	if (ret == null) {
+	    InternetAddress ia = getUnixFrom();
+	    if (ia != null)
+		ret = new InternetAddress[] { ia };
+	}
+	return ret;
+    }
+
+    /**
+     * Returns the address from the UNIX "From" line.
+     *
+     * @return          UNIX From address
+     * @exception       MessagingException
+     */
+    public synchronized InternetAddress getUnixFrom()
+				throws MessagingException {
+	if (unix_from_user == null && unix_from != null) {
+	    int i;
+	    // find the space after the address, before the date
+	    i = unix_from.indexOf(' ', 5);
+	    if (i > 5) {
+		try {
+		    unix_from_user =
+			new InternetAddress(unix_from.substring(5, i));
+		} catch (AddressException e) {
+		    // ignore it
+		}
+	    }
+	}
+	return unix_from_user != null ?
+		(InternetAddress)unix_from_user.clone() : null;
+    }
+
+    private String getUnixFromLine() {
+	if (unix_from != null)
+	    return unix_from;
+	String from = "unknown";
+	try {
+	    Address[] froma = getFrom();
+	    if (froma != null && froma.length > 0 &&
+		    froma[0] instanceof InternetAddress)
+		from = ((InternetAddress)froma[0]).getAddress();
+	} catch (MessagingException ex) { }
+	Date d = null;
+	try {
+	    d = getSentDate();
+	} catch (MessagingException ex) {
+	    // ignore
+	}
+	if (d == null)
+	    d = new Date();
+	// From shannon Mon Jun 10 12:06:52 2002
+	SimpleDateFormat fmt = new SimpleDateFormat("EEE LLL dd HH:mm:ss yyyy");
+	return "From " + from + " " + fmt.format(d);
+    }
+
+    /**
+     * Get the date this message was received, from the UNIX From line.
+     *
+     * @return          the date this message was received
+     * @exception       MessagingException
+     */
+    @SuppressWarnings("deprecation")	// for Date constructor
+    public Date getReceivedDate() throws MessagingException {
+	if (rcvDate == null && unix_from != null) {
+	    int i;
+	    // find the space after the address, before the date
+	    i = unix_from.indexOf(' ', 5);
+	    if (i > 5) {
+		try {
+		    rcvDate = new Date(unix_from.substring(i));
+		} catch (IllegalArgumentException iae) {
+		    // ignore it
+		}
+	    }
+	}
+	return rcvDate == null ? null : new Date(rcvDate.getTime());
+    }
+
+    /**
+     * 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.
+     *
+     * @return          number of lines in the content.
+     * @exception	MessagingException
+     */  
+    public int getLineCount() throws MessagingException {
+	if (lineCount < 0 && isMimeType("text/plain")) {
+	    LineCounter lc = null;
+	    // writeTo will set the SEEN flag, remember the original state
+	    boolean seen = isSet(Flags.Flag.SEEN);
+	    try {
+		lc = new LineCounter(nullOutputStream);
+		getDataHandler().writeTo(lc);
+		lineCount = lc.getLineCount();
+	    } catch (IOException ex) {
+		// ignore it, can't happen
+	    } finally {
+		try {
+		    if (lc != null)
+			lc.close();
+		} catch (IOException ex) {
+		    // can't happen
+		}
+	    }
+	    if (!seen)
+		setFlag(Flags.Flag.SEEN, false);
+	}
+	return lineCount;
+     }
+
+    /**
+     * Set the specified flags on this message to the specified value.
+     *
+     * @param flags	the flags to be set
+     * @param set	the value to be set
+     */
+    public void setFlags(Flags newFlags, boolean set)
+				throws MessagingException {
+	Flags oldFlags = (Flags)flags.clone();
+	super.setFlags(newFlags, set);
+	if (!flags.equals(oldFlags)) {
+	    setHeadersFromFlags(this);
+	    if (folder != null)
+		((MboxFolder)folder).notifyMessageChangedListeners(
+				MessageChangedEvent.FLAGS_CHANGED, this);
+	}
+    }
+
+    /**
+     * Return the content type, mapping from SunV3 types to MIME types
+     * as necessary.
+     */
+    public String getContentType()  throws MessagingException {
+	String ct = super.getContentType();
+	if (ct.indexOf('/') < 0)
+	    ct = SunV3BodyPart.MimeV3Map.toMime(ct);
+	return ct;
+    }
+
+    /**
+     * 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 <code>content</code> byte array.
+     *
+     * @see #content
+     */
+    protected InputStream getContentStream() throws MessagingException {
+	if (folder != null)
+	    ((MboxFolder)folder).checkOpen();
+	if (isExpunged())
+	    throw new MessageRemovedException("mbox message expunged");
+	if (!isSet(Flags.Flag.SEEN))
+	    setFlag(Flags.Flag.SEEN, true);
+	return super.getContentStream();
+    }
+
+    /**                                                            
+     * Return a DataHandler for this Message's content.
+     * If this is a SunV3 multipart message, handle it specially.
+     *
+     * @exception	MessagingException
+     */
+    public synchronized DataHandler getDataHandler() 
+		throws MessagingException {
+	if (dh == null) {
+	    // XXX - Following is a kludge to avoid having to register
+	    // the "multipart/x-sun-attachment" data type with the JAF.
+	    String ct = getContentType();
+	    if (ct.equalsIgnoreCase("multipart/x-sun-attachment"))
+		dh = new DataHandler(
+		    new SunV3Multipart(new MimePartDataSource(this)), ct);
+	    else
+		return super.getDataHandler();	// will set "dh"
+	}
+	return dh;
+    }
+
+    // here only to allow package private access from MboxFolder
+    protected void setMessageNumber(int msgno) {
+	super.setMessageNumber(msgno);
+    }
+
+    // here to synchronize access to expunged field
+    public synchronized boolean isExpunged() {
+	return super.isExpunged();
+    }
+
+    // here to synchronize and to allow access from MboxFolder
+    protected synchronized void setExpunged(boolean expunged) {
+	super.setExpunged(expunged);
+    }
+
+    // XXX - We assume that only body parts that are part of a SunV3
+    // multipart will use the SunV3 headers (X-Sun-Content-Length,
+    // X-Sun-Content-Lines, X-Sun-Data-Type, X-Sun-Encoding-Info,
+    // X-Sun-Data-Description, X-Sun-Data-Name) so we don't handle
+    // them here.
+
+    /**
+     * Set the flags for this message based on the Status,
+     * X-Status, and X-Dt-Delete-Time headers.
+     *
+     * SIMS 2.0:
+     * "X-Status: DFAT", deleted, flagged, answered, draft.
+     * Unset flags represented as "$".
+     * User flags not supported.
+     *
+     * University of Washington IMAP server:
+     * "X-Status: DFAT", deleted, flagged, answered, draft.
+     * Unset flags not present.
+     * "X-Keywords: userflag1 userflag2"
+     */
+    private synchronized void setFlagsFromHeaders() {
+	flags = new Flags(Flags.Flag.RECENT);
+	try {
+	    String s = getHeader("Status", null);
+	    if (s != null) {
+		if (s.indexOf('R') >= 0)
+		    flags.add(Flags.Flag.SEEN);
+		if (s.indexOf('O') >= 0)
+		    flags.remove(Flags.Flag.RECENT);
+	    }
+	    s = getHeader("X-Dt-Delete-Time", null);	// set by dtmail
+	    if (s != null)
+		flags.add(Flags.Flag.DELETED);
+	    s = getHeader("X-Status", null);		// set by IMAP server
+	    if (s != null) {
+		if (s.indexOf('D') >= 0)
+		    flags.add(Flags.Flag.DELETED);
+		if (s.indexOf('F') >= 0)
+		    flags.add(Flags.Flag.FLAGGED);
+		if (s.indexOf('A') >= 0)
+		    flags.add(Flags.Flag.ANSWERED);
+		if (s.indexOf('T') >= 0)
+		    flags.add(Flags.Flag.DRAFT);
+	    }
+	    s = getHeader("X-Keywords", null);		// set by IMAP server
+	    if (s != null) {
+		StringTokenizer st = new StringTokenizer(s);
+		while (st.hasMoreTokens())
+		    flags.add(st.nextToken());
+	    }
+	} catch (MessagingException e) {
+	    // ignore it
+	}
+    }
+
+    /**
+     * Set the various header fields that represent the message flags.
+     */
+    static void setHeadersFromFlags(MimeMessage msg) {
+	try {
+	    Flags flags = msg.getFlags();
+	    StringBuilder status = new StringBuilder();
+	    if (flags.contains(Flags.Flag.SEEN))
+		status.append('R');
+	    if (!flags.contains(Flags.Flag.RECENT))
+		status.append('O');
+	    if (status.length() > 0)
+		msg.setHeader("Status", status.toString());
+	    else
+		msg.removeHeader("Status");
+
+	    boolean sims = false;
+	    String s = msg.getHeader("X-Status", null);
+	    // is it a SIMS 2.0 format X-Status header?
+	    sims = s != null && s.length() == 4 && s.indexOf('$') >= 0;
+	    status.setLength(0);
+	    if (flags.contains(Flags.Flag.DELETED))
+		status.append('D');
+	    else if (sims)
+		status.append('$');
+	    if (flags.contains(Flags.Flag.FLAGGED))
+		status.append('F');
+	    else if (sims)
+		status.append('$');
+	    if (flags.contains(Flags.Flag.ANSWERED))
+		status.append('A');
+	    else if (sims)
+		status.append('$');
+	    if (flags.contains(Flags.Flag.DRAFT))
+		status.append('T');
+	    else if (sims)
+		status.append('$');
+	    if (status.length() > 0)
+		msg.setHeader("X-Status", status.toString());
+	    else
+		msg.removeHeader("X-Status");
+
+	    String[] userFlags = flags.getUserFlags();
+	    if (userFlags.length > 0) {
+		status.setLength(0);
+		for (int i = 0; i < userFlags.length; i++)
+		    status.append(userFlags[i]).append(' ');
+		status.setLength(status.length() - 1);	// smash trailing space
+		msg.setHeader("X-Keywords", status.toString());
+	    }
+	    if (flags.contains(Flags.Flag.DELETED)) {
+		s = msg.getHeader("X-Dt-Delete-Time", null);
+		if (s == null)
+		    // XXX - should be time
+		    msg.setHeader("X-Dt-Delete-Time", "1");
+	    }
+	} catch (MessagingException e) {
+	    // ignore it
+	}
+    }
+
+    protected void updateHeaders() throws MessagingException {
+	super.updateHeaders();
+	setHeadersFromFlags(this);
+    }
+
+    /**
+     * Save any changes made to this message.
+     */
+    public void saveChanges() throws MessagingException {
+	if (folder != null)
+	    ((MboxFolder)folder).checkOpen();
+	if (isExpunged())
+	    throw new MessageRemovedException("mbox message expunged");
+	if (!writable)
+	    throw new MessagingException("Message is read-only");
+
+	super.saveChanges();
+
+	try {
+	    /*
+	     * Count the size of the body, in order to set the Content-Length
+	     * header.  (Should we only do this to update an existing
+	     * Content-Length header?)
+	     * XXX - We could cache the content bytes here, for use later
+	     * in writeTo.
+	     */
+	    ContentLengthCounter cos = new ContentLengthCounter();
+	    OutputStream os = new NewlineOutputStream(cos);
+	    super.writeTo(os);
+	    os.flush();
+	    setHeader("Content-Length", String.valueOf(cos.getSize()));
+	    // setContentSize((int)cos.getSize());
+	} catch (MessagingException e) {
+	    throw e;
+	} catch (Exception e) {
+	    throw new MessagingException("unexpected exception " + e);
+	}
+    }
+
+    /**
+     * Expose modified flag to MboxFolder.
+     */
+    boolean isModified() {
+	return modified;
+    }
+
+    /**
+     * Put out a byte stream suitable for saving to a file.
+     * XXX - ultimately implement "ignore headers" here?
+     */
+    public void writeToFile(OutputStream os) throws IOException {
+	try {
+	    if (getHeader("Content-Length") == null) {
+		/*
+		 * Count the size of the body, in order to set the
+		 * Content-Length header.
+		 */
+		ContentLengthCounter cos = new ContentLengthCounter();
+		OutputStream oos = new NewlineOutputStream(cos);
+		super.writeTo(oos, null);
+		oos.flush();
+		setHeader("Content-Length", String.valueOf(cos.getSize()));
+		// setContentSize((int)cos.getSize());
+	    }
+
+	    os = new NewlineOutputStream(os, true);
+	    PrintStream pos = new PrintStream(os, false, "iso-8859-1");
+
+	    pos.println(getUnixFromLine());
+	    super.writeTo(pos, null);
+	    pos.flush();
+	} catch (MessagingException e) {
+	    throw new IOException("unexpected exception " + e);
+	}
+    }
+
+    public void writeTo(OutputStream os, String[] ignoreList)
+				throws IOException, MessagingException {
+	// set the SEEN flag now, which will normally be set by
+	// getContentStream, so it will show up in our headers
+	if (!isSet(Flags.Flag.SEEN))
+	    setFlag(Flags.Flag.SEEN, true);
+	super.writeTo(os, ignoreList);
+    }
+
+    /**
+     * Interpose on superclass method to make sure folder is still open
+     * and message hasn't been expunged.
+     */
+    public String[] getHeader(String name)
+			throws MessagingException {
+	if (folder != null)
+	    ((MboxFolder)folder).checkOpen();
+	if (isExpunged())
+	    throw new MessageRemovedException("mbox message expunged");
+	return super.getHeader(name);
+    }
+
+    /**
+     * Interpose on superclass method to make sure folder is still open
+     * and message hasn't been expunged.
+     */
+    public String getHeader(String name, String delimiter)
+				throws MessagingException {
+	if (folder != null)
+	    ((MboxFolder)folder).checkOpen();
+	if (isExpunged())
+	    throw new MessageRemovedException("mbox message expunged");
+	return super.getHeader(name, delimiter);
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/MboxProvider.java b/mbox/src/main/java/com/sun/mail/mbox/MboxProvider.java
new file mode 100644
index 0000000..d0b056d
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/MboxProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import javax.mail.Provider;
+
+/**
+ * The Mbox protocol provider.
+ */
+public class MboxProvider extends Provider {
+    public MboxProvider() {
+	super(Provider.Type.STORE, "mbox", MboxStore.class.getName(),
+	    "Oracle", null);
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/MboxStore.java b/mbox/src/main/java/com/sun/mail/mbox/MboxStore.java
new file mode 100644
index 0000000..1ce39bc
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/MboxStore.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+import javax.mail.*;
+
+public class MboxStore extends Store {
+
+    String user;
+    String home;
+    Mailbox mb;
+    static Flags permFlags;
+
+    static {
+	// we support all flags
+	permFlags = new Flags();
+	permFlags.add(Flags.Flag.SEEN);
+	permFlags.add(Flags.Flag.RECENT);
+	permFlags.add(Flags.Flag.DELETED);
+	permFlags.add(Flags.Flag.FLAGGED);
+	permFlags.add(Flags.Flag.ANSWERED);
+	permFlags.add(Flags.Flag.DRAFT);
+	permFlags.add(Flags.Flag.USER);
+    }
+
+    public MboxStore(Session session, URLName url) {
+	super(session, url);
+
+	// XXX - handle security exception
+	user = System.getProperty("user.name");
+	home = System.getProperty("user.home");
+	String os = System.getProperty("os.name");
+	try {
+	    String cl = "com.sun.mail.mbox." + os + "Mailbox";
+	    mb = (Mailbox)Class.forName(cl).
+					getDeclaredConstructor().newInstance();
+	} catch (Exception e) {
+	    mb = new DefaultMailbox();
+	}
+    }
+
+    /**
+     * Since we do not have any authentication
+     * to do and we do not want a dialog put up asking the user for a 
+     * password we always succeed in connecting.
+     * But if we're given a password, that means the user is
+     * doing something wrong so fail the request.
+     */
+    protected boolean protocolConnect(String host, int port, String user,
+				String passwd) throws MessagingException {
+
+	if (passwd != null)
+	    throw new AuthenticationFailedException(
+				"mbox does not allow passwords");
+	// XXX - should we use the user?
+	return true;
+    }
+
+    protected void setURLName(URLName url) {
+	// host, user, password, and file don't matter so we strip them out
+	if (url != null && (url.getUsername() != null ||
+			    url.getHost() != null ||
+			    url.getFile() != null))
+	    url = new URLName(url.getProtocol(), null, -1, null, null, null);
+	super.setURLName(url);
+    }
+
+
+    public Folder getDefaultFolder() throws MessagingException {
+	checkConnected();
+
+	return new MboxFolder(this, null);
+    }
+
+    public Folder getFolder(String name) throws MessagingException {
+	checkConnected();
+
+	return new MboxFolder(this, name);
+    }
+
+    public Folder getFolder(URLName url) throws MessagingException {
+	checkConnected();
+	return getFolder(url.getFile());
+    }
+
+    private void checkConnected() throws MessagingException {
+	if (!isConnected())
+	    throw new MessagingException("Not connected");
+    }
+
+    MailFile getMailFile(String folder) {
+	return mb.getMailFile(user, folder);
+    }
+
+    Session getSession() {
+	return session;
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/MessageLoader.java b/mbox/src/main/java/com/sun/mail/mbox/MessageLoader.java
new file mode 100644
index 0000000..b5ecaaa
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/MessageLoader.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * A support class that contains the state and logic needed when
+ * loading messages from a folder.
+ */
+final class MessageLoader {
+    private final TempFile temp;
+    private FileInputStream fis = null;
+    private OutputStream fos = null;
+    private int pos, len;	// position in and length of buffer
+    private long off;		// current offset in temp file
+    private long prevend;	// the end of the previous message in temp file
+    private MboxFolder.MessageMetadata md;
+    private byte[] buf = null;
+    // the length of the longest header we'll need to look at
+    private static final int LINELEN = "Content-Length: XXXXXXXXXX".length();
+    private char[] line;
+
+    public MessageLoader(TempFile temp) {
+	this.temp = temp;
+    }
+
+    /**
+     * Load messages from the given file descriptor, starting at the
+     * specified offset, adding the MessageMetadata to the list. <p>
+     *
+     * The data is assumed to be in UNIX mbox format, with newlines
+     * only as the line terminators.
+     */
+    public int load(FileDescriptor fd, long offset,
+				List<MboxFolder.MessageMetadata> msgs)
+				throws IOException {
+	// XXX - could allocate and deallocate buffers here
+	int loaded = 0;
+	try {
+	    fis = new FileInputStream(fd);
+	    if (fis.skip(offset) != offset)
+		throw new EOFException("Failed to skip to offset " + offset);
+	    this.off = prevend = temp.length();
+	    pos = len = 0;
+	    line = new char[LINELEN];
+	    buf = new byte[64 * 1024];
+	    fos = temp.getAppendStream();
+	    int n;
+	    // keep loading messages as long as we have headers
+	    while ((n = skipHeader(loaded == 0)) >= 0) {
+		long start;
+		if (n == 0) {
+		    // didn't find a Content-Length, skip the body
+		    start = skipBody();
+		    if (start < 0) {
+			// md.end = -1;
+			md.dataend = -1;
+			msgs.add(md);
+			loaded++;
+			break;
+		    }
+		    md.dataend = start;
+		} else {
+		    // skip over the body
+		    skip(n);
+		    md.dataend = off;
+		    int b;
+		    // skip any blank lines after the body
+		    while ((b = get()) >= 0) {
+			if (b != '\n')
+			    break;
+		    }
+		    start = off;
+		    if (b >= 0)
+			start--;	// back up one byte if not at EOF
+		}
+		// md.end = start;
+		prevend = start;
+		msgs.add(md);
+		loaded++;
+	    }
+	} finally {
+	    try {
+		fis.close();
+	    } catch (IOException ex) {
+		// ignore
+	    }
+	    try {
+		fos.close();
+	    } catch (IOException ex) {
+		// ignore
+	    }
+	    line = null;
+	    buf = null;
+	}
+	return loaded;
+    }
+
+    /**
+     * Skip over the message header, returning the content length
+     * of the body, or 0 if no Content-Length header was seen.
+     * Update the MessageMetadata based on the headers seen.
+     * return -1 on EOF.
+     */
+    private int skipHeader(boolean first)  throws IOException {
+	int clen = 0;
+	boolean bol = true;
+	int lpos = -1;
+	int b;
+	boolean saw_unix_from = false;
+	int lineno = 0;
+	md = new MboxFolder.MessageMetadata();
+	md.start = prevend;
+	md.recent = true;
+	while ((b = get()) >= 0) {
+	    if (bol) {
+		if (b == '\n')
+		    break;
+		lpos = 0;
+	    }
+	    if (b == '\n') {
+		bol = true;
+		lineno++;
+		// newline at end of line, was the line one of the headers
+		// we're looking for?
+		if (lpos > 7) {
+		    // XXX - make this more efficient?
+		    String s = new String(line, 0, lpos);
+		    // fast check for Content-Length header
+		    if (lineno == 1 && line[0] == 'F' && isPrefix(s, "From ")) {
+			saw_unix_from = true;
+		    } else if (line[7] == '-' &&
+				isPrefix(s, "Content-Length:")) {
+			s = s.substring(15).trim();
+			try {
+			    clen = Integer.parseInt(s);
+			} catch (NumberFormatException ex) {
+			    // ignore it
+			}
+		    // fast check for Status header
+		    } else if ((line[1] == 't' || line[1] == 'T') &&
+				isPrefix(s, "Status:")) {
+			if (s.indexOf('O') >= 0)
+			    md.recent = false;
+		    // fast check for X-Status header
+		    } else if ((line[3] == 't' || line[3] == 'T') &&
+				isPrefix(s, "X-Status:")) {
+			if (s.indexOf('D') >= 0)
+			    md.deleted = true;
+		    // fast check for X-Dt-Delete-Time header
+		    } else if (line[4] == '-' &&
+				isPrefix(s, "X-Dt-Delete-Time:")) {
+			md.deleted = true;
+		    // fast check for X-IMAP header
+		    } else if (line[5] == 'P' && s.startsWith("X-IMAP:")) {
+			md.imap = true;
+		    }
+		}
+	    } else {
+		// accumlate data in line buffer
+		bol = false;
+		if (lpos < 0)	// ignoring this line
+		    continue;
+		if (lpos == 0 && (b == ' ' || b == '\t'))
+		    lpos = -1;	// ignore continuation lines
+		else if (lpos < line.length)
+		    line[lpos++] = (char)b;
+	    }
+	}
+
+	// if we hit EOF, or this is the first message we're loading and
+	// it doesn't have a UNIX From line, return EOF.
+	// (After the first message, UNIX From lines are seen by skipBody
+	// to terminate the message.)
+	if (b < 0 || (first && !saw_unix_from))
+	    return -1;
+	else
+	    return clen;
+    }
+
+    /**
+     * Does "s" start with "pre", ignoring case?
+     */
+    private static final boolean isPrefix(String s, String pre) {
+	return s.regionMatches(true, 0, pre, 0, pre.length());
+    }
+
+    /**
+     * Skip over the body of the message looking for a line that starts
+     * with "From ".  If found, return the offset of the beginning of
+     * that line.  Return -1 on EOF.
+     */
+    private long skipBody() throws IOException {
+	boolean bol = true;
+	int lpos = -1;
+	long loff = off;
+	int b;
+	while ((b = get()) >= 0) {
+	    if (bol) {
+		lpos = 0;
+		loff = off - 1;
+	    }
+	    if (b == '\n') {
+		bol = true;
+		if (lpos >= 5) {	// have enough data to test?
+		    if (line[0] == 'F' && line[1] == 'r' && line[2] == 'o' &&
+			line[3] == 'm' && line[4] == ' ')
+			return loff;
+		}
+	    } else {
+		bol = false;
+		if (lpos < 0)
+		    continue;
+		if (lpos == 0 && b != 'F')
+		    lpos = -1;		// ignore lines that don't start with F
+		else if (lpos < 5)	// only need first 5 chars to test
+		    line[lpos++] = (char)b;
+	    }
+	}
+	return -1;
+    }
+
+    /**
+     * Skip "n" bytes, returning how much we were able to skip.
+     */
+    private final int skip(int n) throws IOException {
+	int n0 = n;
+	if (pos + n < len) {
+	    pos += n;	// can do it all within this buffer
+	    off += n;
+	} else {
+	    do {
+		n -= (len - pos);	// skip rest of this buffer
+		off += (len - pos);
+		fill();
+		if (len <= 0)	// ran out of data
+		    return n0 - n;
+	    } while (n > len);
+	    pos += n;
+	    off += n;
+	}
+	return n0;
+    }
+
+    /**
+     * Return the next byte.
+     */
+    private final int get() throws IOException {
+	if (pos >= len)
+	    fill();
+	if (pos >= len)
+	    return -1;
+	else {
+	    off++;
+	    return buf[pos++] & 0xff;
+	}
+    }
+
+    /**
+     * Fill our buffer with more data.
+     * Every buffer we read is also written to the temp file.
+     */
+    private final void fill() throws IOException {
+	len = fis.read(buf);
+	pos = 0;
+	if (len > 0)
+	    fos.write(buf, 0, len);
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/NewlineOutputStream.java b/mbox/src/main/java/com/sun/mail/mbox/NewlineOutputStream.java
new file mode 100644
index 0000000..f4a31cb
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/NewlineOutputStream.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Convert the various newline conventions to the local platform's
+ * newline convention.  Optionally, make sure the output ends with
+ * a blank line.
+ */
+public class NewlineOutputStream extends FilterOutputStream {
+    private int lastb = -1;
+    private int bol = 1; // number of times in a row we're at beginning of line
+    private final boolean endWithBlankLine;
+    private static final byte[] newline;
+
+    static {
+	String s = null;
+	try {
+	    s = System.lineSeparator();
+	} catch (SecurityException sex) {
+	    // ignore, should never happen
+	}
+	if (s == null || s.length() <= 0)
+	    s = "\n";
+	newline = s.getBytes(StandardCharsets.ISO_8859_1);
+    }
+
+    public NewlineOutputStream(OutputStream os) {
+	this(os, false);
+    }
+
+    public NewlineOutputStream(OutputStream os, boolean endWithBlankLine) {
+	super(os);
+	this.endWithBlankLine = endWithBlankLine;
+    }
+
+    public void write(int b) throws IOException {
+	if (b == '\r') {
+	    out.write(newline);
+	    bol++;
+	} else if (b == '\n') {
+	    if (lastb != '\r') {
+		out.write(newline);
+		bol++;
+	    }
+	} else {
+	    out.write(b);
+	    bol = 0;	// no longer at beginning of line
+	}
+	lastb = b;
+    }
+
+    public void write(byte b[]) throws IOException {
+	write(b, 0, b.length);
+    }
+
+    public void write(byte b[], int off, int len) throws IOException {
+	for (int i = 0 ; i < len ; i++) {
+	    write(b[off + i]);
+	}
+    }
+
+    public void flush() throws IOException {
+	if (endWithBlankLine) {
+	    if (bol == 0) {
+		// not at bol, return to bol and add a blank line
+		out.write(newline);
+		out.write(newline);
+	    } else if (bol == 1) {
+		// at bol, add a blank line
+		out.write(newline);
+	    }
+	}
+	bol = 2;
+	out.flush();
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/SolarisMailbox.java b/mbox/src/main/java/com/sun/mail/mbox/SolarisMailbox.java
new file mode 100644
index 0000000..84f3abc
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/SolarisMailbox.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.File;
+
+public class SolarisMailbox extends Mailbox {
+    private final String home;
+    private final String user;
+
+    private static final boolean homeRelative =
+				Boolean.getBoolean("mail.mbox.homerelative");
+
+    public SolarisMailbox() {
+	String h = System.getenv("HOME");
+	if (h == null)
+	    h = System.getProperty("user.home");
+	home = h;
+	user = System.getProperty("user.name");
+    }
+
+    public MailFile getMailFile(String user, String folder) {
+	if (folder.equalsIgnoreCase("INBOX"))
+	    return new UNIXInbox(user, filename(user, folder));
+	else
+	    return new UNIXFolder(filename(user, folder));
+    }
+
+    /**
+     * Given a name of a mailbox folder, expand it to a full path name.
+     */
+    public String filename(String user, String folder) {
+	try {
+	    switch (folder.charAt(0)) {
+	    case '/':
+		return folder;
+	    case '~':
+		int i = folder.indexOf(File.separatorChar);
+		String tail = "";
+		if (i > 0) {
+		    tail = folder.substring(i);
+		    folder = folder.substring(0, i);
+		}
+		if (folder.length() == 1)
+		    return home + tail;
+		else
+		    return "/home/" + folder.substring(1) + tail;	// XXX
+	    default:
+		if (folder.equalsIgnoreCase("INBOX")) {
+		    if (user == null)	// XXX - should never happen
+			user = this.user;
+		    String inbox = System.getenv("MAIL");
+		    if (inbox == null)
+			inbox = "/var/mail/" + user;
+		    return inbox;
+		} else {
+		    if (homeRelative)
+			return home + File.separator + folder;
+		    else
+			return folder;
+		}
+	    }
+	} catch (StringIndexOutOfBoundsException e) {
+	    return folder;
+	}
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/SunOSMailbox.java b/mbox/src/main/java/com/sun/mail/mbox/SunOSMailbox.java
new file mode 100644
index 0000000..55230d1
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/SunOSMailbox.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+public class SunOSMailbox extends SolarisMailbox {
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/SunV3BodyPart.java b/mbox/src/main/java/com/sun/mail/mbox/SunV3BodyPart.java
new file mode 100644
index 0000000..189ff85
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/SunV3BodyPart.java
@@ -0,0 +1,314 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+import java.io.*;
+
+/**
+ * This class represents a SunV3 BodyPart.
+ *
+ * @author Bill Shannon
+ * @see javax.mail.Part
+ * @see javax.mail.internet.MimePart
+ * @see javax.mail.internet.MimeBodyPart
+ */
+
+public class SunV3BodyPart extends MimeBodyPart {
+    /**
+     * Constructs a SunV3BodyPart using the given header and
+     * content bytes. <p>
+     *
+     * Used by providers.
+     *
+     * @param	headers	The header of this part
+     * @param	content	bytes representing the body of this part.
+     */
+    public SunV3BodyPart(InternetHeaders headers, byte[] content) 
+			throws MessagingException {
+	super(headers, content);
+    }
+
+    /**
+     * Return the size of the content of this BodyPart 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>
+     *
+     * @return size in bytes
+     */
+    public int getSize() throws MessagingException {
+	String s = getHeader("X-Sun-Content-Length", null);
+	try {
+	    return Integer.parseInt(s);
+	} catch (NumberFormatException ex) {
+	    return -1;
+	}
+    }
+
+    /**
+     * Return the number of lines for the content of this Part.
+     * 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. 
+     */  
+     public int getLineCount() throws MessagingException {
+	String s = getHeader("X-Sun-Content-Lines", null);
+	try {
+	    return Integer.parseInt(s);
+	} catch (NumberFormatException ex) {
+	    return -1;
+	}
+    }
+
+    /*
+     * This is just enough to get us going.
+     *
+     * For more complete transformation from V3 to MIME, refer to
+     * sun_att.c from the Sun IMAP server code.
+     */
+    static class MimeV3Map {
+	String mime;
+	String v3;
+
+	MimeV3Map(String mime, String v3) {
+	    this.mime = mime;
+	    this.v3 = v3;
+	}
+
+	private static MimeV3Map[] mimeV3Table = new MimeV3Map[] {
+	    new MimeV3Map("text/plain", "text"),
+	    new MimeV3Map("text/plain", "default"),
+	    new MimeV3Map("multipart/x-sun-attachment", "X-sun-attachment"),
+	    new MimeV3Map("application/postscript", "postscript-file"),
+	    new MimeV3Map("image/gif", "gif-file")
+	    // audio-file
+	    // cshell-script
+	};
+
+	// V3 Content-Type to MIME Content-Type
+	static String toMime(String s) {
+	    for (int i = 0; i < mimeV3Table.length; i++) {
+		if (mimeV3Table[i].v3.equalsIgnoreCase(s))
+		    return mimeV3Table[i].mime;
+	    }
+	    return "application/x-" + s;
+	}
+
+	// MIME Content-Type to V3 Content-Type
+	static String toV3(String s) {
+	    for (int i = 0; i < mimeV3Table.length; i++) {
+		if (mimeV3Table[i].mime.equalsIgnoreCase(s))
+		    return mimeV3Table[i].v3;
+	    }
+	    return s;
+	}
+    }
+
+    /**
+     * Returns the value of the RFC822 "Content-Type" header field.
+     * This represents the content-type of the content of this
+     * BodyPart. This value must not be null. If this field is
+     * unavailable, "text/plain" should be returned. <p>
+     *
+     * This implementation uses <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     *
+     * @return	Content-Type of this BodyPart
+     */
+    public String getContentType() throws MessagingException {
+	String ct = getHeader("Content-Type", null);
+	if (ct == null)
+	    ct = getHeader("X-Sun-Data-Type", null);
+	if (ct == null)
+	    ct = "text/plain";
+	else if (ct.indexOf('/') < 0)
+	    ct = MimeV3Map.toMime(ct);
+	return ct;
+    }
+
+    /**
+     * Returns the value of the "Content-Transfer-Encoding" header
+     * field. Returns <code>null</code> if the header is unavailable
+     * or its value is absent. <p>
+     *
+     * This implementation uses <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     *
+     * @see #headers
+     */
+    public String getEncoding() throws MessagingException {
+	String enc = super.getEncoding();
+	if (enc == null)
+	    enc = getHeader("X-Sun-Encoding-Info", null);
+	return enc;
+    }
+
+    /**
+     * Returns the "Content-Description" header field of this BodyPart.
+     * 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 <code>getHeader(name)</code>
+     * to obtain the requisite header field.
+     * 
+     * @return	content-description
+     */
+    public String getDescription() throws MessagingException {
+	String desc = super.getDescription();
+	if (desc == null)
+	    desc = getHeader("X-Sun-Data-Description", null);
+	return desc;
+    }
+
+    /**
+     * Set the "Content-Description" header field for this BodyPart.
+     * If the description parameter is <code>null</code>, 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.
+     * 
+     * @param description content-description
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this BodyPart is
+     *			obtained from a READ_ONLY folder.
+     */
+    public void setDescription(String description) throws MessagingException {
+	throw new MethodNotSupportedException("SunV3BodyPart not writable");
+    }
+
+    /**
+     * Set the "Content-Description" header field for this BodyPart.
+     * If the description parameter is <code>null</code>, 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
+     * 
+     * @param	description	Description
+     * @param	charset		Charset for encoding
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this BodyPart is
+     *			obtained from a READ_ONLY folder.
+     */
+    public void setDescription(String description, String charset) 
+		throws MessagingException {
+	throw new MethodNotSupportedException("SunV3BodyPart not writable");
+    }
+
+    /**
+     * Get the filename associated with this BodyPart. <p>
+     *
+     * Returns the value of the "filename" parameter from the
+     * "Content-Disposition" header field of this BodyPart. If its
+     * not available, returns the value of the "name" parameter from
+     * the "Content-Type" header field of this BodyPart.
+     * Returns <code>null</code> if both are absent.
+     *
+     * @return	filename
+     */
+    public String getFileName() throws MessagingException {
+	String name = super.getFileName();
+	if (name == null)
+	    name = getHeader("X-Sun-Data-Name", null);
+	return name;
+    }
+
+    /**
+     * Set the filename associated with this BodyPart, if possible. <p>
+     *
+     * Sets the "filename" parameter of the "Content-Disposition"
+     * header field of this BodyPart.
+     *
+     * @exception	IllegalWriteException if the underlying
+     *			implementation does not support modification
+     * @exception	IllegalStateException if this BodyPart is
+     *			obtained from a READ_ONLY folder.
+     */
+    public void setFileName(String filename) throws MessagingException {
+	throw new MethodNotSupportedException("SunV3BodyPart not writable");
+    }
+
+    /**
+     * This method provides the mechanism to set this BodyPart'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
+     * @exception	IllegalStateException if this BodyPart is
+     *			obtained from a READ_ONLY folder.
+     */                 
+    public void setDataHandler(DataHandler dh) 
+		throws MessagingException {
+	throw new MethodNotSupportedException("SunV3BodyPart not writable");
+    }
+
+    /**
+     * Output the BodyPart as a RFC822 format stream.
+     *
+     * @exception MessagingException
+     * @exception IOException	if an error occurs writing to the
+     *				stream or if an error is generated
+     *				by the javax.activation layer.
+     * @see javax.activation.DataHandler#writeTo()
+     */
+    public void writeTo(OutputStream os)
+				throws IOException, MessagingException {
+	throw new MethodNotSupportedException("SunV3BodyPart writeTo");
+    }
+
+    /**
+     * This is the method that has the 'smarts' to query the 'content'
+     * and update the appropriate headers. Typical headers that get
+     * set here are: Content-Type, Content-Encoding, boundary (for
+     * multipart). Now, the tricky part here is when to actually
+     * activate this method:
+     *
+     * - A Message being crafted by a mail-application will certainly
+     * need to activate this method at some point to fill up its internal
+     * headers. Typically this is triggered off by our writeTo() method.
+     *
+     * - A message read-in from a MessageStore will have obtained
+     * all its headers from the store, and so does'nt need this.
+     * However, if this message is editable and if any edits have
+     * been made to either the content or message-structure, we might
+     * need to resync our headers. Typically this is triggered off by
+     * the Message.saveChanges() methods.
+     */
+    protected void updateHeaders() throws MessagingException {
+	throw new MethodNotSupportedException("SunV3BodyPart updateHeaders");
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/SunV3Multipart.java b/mbox/src/main/java/com/sun/mail/mbox/SunV3Multipart.java
new file mode 100644
index 0000000..8a642f1
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/SunV3Multipart.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+import java.util.*;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import com.sun.mail.util.LineInputStream;
+
+/**
+ * The SunV3Multipart class is an implementation of the abstract Multipart
+ * class that uses SunV3 conventions for the multipart data. <p>
+ *
+ * @author  Bill Shannon
+ */
+
+public class SunV3Multipart extends MimeMultipart {
+    private boolean parsing;
+
+    /**
+     * Constructs a SunV3Multipart object and its bodyparts from the 
+     * given DataSource. <p>
+     *
+     * @param	ds	DataSource, can be a MultipartDataSource
+     */
+    public SunV3Multipart(DataSource ds) throws MessagingException {
+	super(ds);
+    }
+
+    /**
+     * Set the subtype.  Throws MethodNotSupportedException.
+     *
+     * @param	subtype		Subtype
+     */
+    public void setSubType(String subtype) throws MessagingException {
+	throw new MethodNotSupportedException(
+		"can't change SunV3Multipart subtype");
+    }
+
+    /**
+     * Get the BodyPart referred to by the given ContentID (CID). 
+     * Throws MethodNotSupportException.
+     */
+    public synchronized BodyPart getBodyPart(String CID) 
+			throws MessagingException {
+	throw new MethodNotSupportedException(
+		"SunV3Multipart doesn't support Content-ID");
+    }
+
+    /**
+     * Update headers.  Throws MethodNotSupportException.
+     */
+    protected void updateHeaders() throws MessagingException {
+	throw new MethodNotSupportedException("SunV3Multipart not writable");
+    }
+
+    /**
+     * Iterates through all the parts and outputs each SunV3 part
+     * separated by a boundary.
+     */
+    public void writeTo(OutputStream os)
+				throws IOException, MessagingException {
+	throw new MethodNotSupportedException(
+		"SunV3Multipart writeTo not supported");
+    }
+
+    private static final String boundary = "----------";
+
+    /*
+     * Parse the contents of this multipart message and create the
+     * child body parts.
+     */
+    protected synchronized void parse() throws MessagingException {
+	/*
+	 * If the data has already been parsed, or we're in the middle of
+	 * parsing it, there's nothing to do.  The latter will occur when
+	 * we call addBodyPart, which will call parse again.  We really
+	 * want to be able to call super.super.addBodyPart.
+	 */
+	if (parsed || parsing)
+	    return;
+
+	InputStream in = null;
+
+	try {
+	    in = ds.getInputStream();
+	    if (!(in instanceof ByteArrayInputStream) &&
+		!(in instanceof BufferedInputStream))
+		in = new BufferedInputStream(in);
+	} catch (IOException ex) {
+	    throw new MessagingException("No inputstream from datasource");
+	} catch (RuntimeException ex) {
+	    throw new MessagingException("No inputstream from datasource");
+	}
+
+	byte[] bndbytes = boundary.getBytes(StandardCharsets.ISO_8859_1);
+	int bl = bndbytes.length;
+
+	String line;
+	parsing = true;
+	try {
+	    /*
+	     * Skip any kind of junk until we get to the first
+	     * boundary line.
+	     */
+	    LineInputStream lin = new LineInputStream(in);
+	    while ((line = lin.readLine()) != null) {
+		if (line.trim().equals(boundary))
+		    break;
+	    }
+	    if (line == null)
+		throw new MessagingException("Missing start boundary");
+
+	    /*
+	     * Read and process body parts until we see the
+	     * terminating boundary line (or EOF).
+	     */
+	    for (;;) {
+		/*
+		 * Collect the headers for this body part.
+		 */
+		InternetHeaders headers = new InternetHeaders(in);
+
+		if (!in.markSupported())
+		    throw new MessagingException("Stream doesn't support mark");
+
+		ByteArrayOutputStream buf = new ByteArrayOutputStream();
+		int b;
+
+		/*
+		 * Read and save the content bytes in buf.
+		 */
+		while ((b = in.read()) >= 0) {
+		    if (b == '\r' || b == '\n') {
+			/*
+			 * Found the end of a line, check whether the
+			 * next line is a boundary.
+			 */
+			int i;
+			in.mark(bl + 4 + 1);	// "4" for possible "--\r\n"
+			if (b == '\r' && in.read() != '\n') {
+			    in.reset();
+			    in.mark(bl + 4);
+			}
+			// read bytes, matching against the boundary
+			for (i = 0; i < bl; i++)
+			    if (in.read() != bndbytes[i])
+				break;
+			if (i == bl) {
+			    int b2 = in.read();
+			    // check for end of line
+			    if (b2 == '\n')
+				break;	// got it!  break out of the while loop
+			    if (b2 == '\r') {
+				in.mark(1);
+				if (in.read() != '\n')
+				    in.reset();
+				break;	// got it!  break out of the while loop
+			    }
+			}
+			// failed to match, reset and proceed normally
+			in.reset();
+		    }
+		    buf.write(b);
+		}
+
+		/*
+		 * Create a SunV3BodyPart to represent this body part.
+		 */
+		SunV3BodyPart body =
+			new SunV3BodyPart(headers, buf.toByteArray());
+		addBodyPart(body);
+		if (b < 0)
+		    break;
+	    }
+	} catch (IOException e) {
+	    throw new MessagingException("IO Error");	// XXX
+	} finally {
+	    parsing = false;
+	}
+
+	parsed = true;
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/TempFile.java b/mbox/src/main/java/com/sun/mail/mbox/TempFile.java
new file mode 100644
index 0000000..25f4bd8
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/TempFile.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.util.*;
+import java.net.*;
+import java.io.*;
+import java.security.*;
+
+import com.sun.mail.util.PropUtil;
+import javax.mail.util.SharedFileInputStream;
+
+/**
+ * A temporary file used to cache messages.
+ */
+class TempFile {
+
+    private File file;	// the temp file name
+    private WritableSharedFile sf;
+
+    /**
+     * Create a temp file in the specified directory (if not null).
+     * The file will be deleted when the JVM exits.
+     */
+    public TempFile(File dir) throws IOException {
+	file = File.createTempFile("mbox.", ".mbox", dir);
+	// XXX - need JDK 6 to set permissions on the file to owner-only
+	file.deleteOnExit();
+	sf = new WritableSharedFile(file);
+    }
+
+    /**
+     * Return a stream for appending to the temp file.
+     */
+    public AppendStream getAppendStream() throws IOException {
+	return sf.getAppendStream();
+    }
+
+    /**
+     * Return a stream for reading from part of the file.
+     */
+    public InputStream newStream(long start, long end) {
+	return sf.newStream(start, end);
+    }
+
+    public long length() {
+	return file.length();
+    }
+
+    /**
+     * Close and remove this temp file.
+     */
+    public void close() {
+	try {
+	    sf.close();
+	} catch (IOException ex) {
+	    // ignore it
+	}
+	file.delete();
+    }
+
+    protected void finalize() throws Throwable {
+	try {
+	    close();
+	} finally {
+	    super.finalize();
+	}
+    }
+}
+
+/**
+ * A subclass of SharedFileInputStream that also allows writing.
+ */
+class WritableSharedFile extends SharedFileInputStream {
+    private RandomAccessFile raf;
+    private AppendStream af;
+
+    public WritableSharedFile(File file) throws IOException {
+	super(file);
+	try {
+	    raf = new RandomAccessFile(file, "rw");
+	} catch (IOException ex) {
+	    // if anything goes wrong opening the writable file,
+	    // close the readable file too
+	    super.close();
+	}
+    }
+
+    /**
+     * Return the writable version of this file.
+     */
+    public RandomAccessFile getWritableFile() {
+	return raf;
+    }
+
+    /**
+     * Close the readable and writable files.
+     */
+    public void close() throws IOException {
+	try {
+	    super.close();
+	} finally {
+	    raf.close();
+	}
+    }
+
+    /**
+     * Update the size of the readable file after writing
+     * to the file.  Updates the length to be the current
+     * size of the file.
+     */
+    synchronized long updateLength() throws IOException {
+	datalen = in.length();
+	af = null;
+	return datalen;
+    }
+
+    /**
+     * Return a new AppendStream, but only if one isn't in active use.
+     */
+    public synchronized AppendStream getAppendStream() throws IOException {
+	if (af != null)
+	    throw new IOException(
+		"file cache only supports single threaded access");
+	af = new AppendStream(this);
+	return af;
+    }
+}
+
+/**
+ * A stream for writing to the temp file, and when done
+ * can return a stream for reading the data just written.
+ * NOTE: We assume that only one thread is writing to the
+ * file at a time.
+ */
+class AppendStream extends OutputStream {
+    private final WritableSharedFile tf;
+    private RandomAccessFile raf;
+    private final long start;
+    private long end;
+
+    public AppendStream(WritableSharedFile tf) throws IOException {
+	this.tf = tf;
+	raf = tf.getWritableFile();
+	start = raf.length();
+	raf.seek(start);
+    }
+
+    public void write(int b) throws IOException {
+	raf.write(b);
+    }
+
+    public void write(byte[] b) throws IOException {
+	raf.write(b);
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+	raf.write(b, off, len);
+    }
+
+    public synchronized void close() throws IOException {
+	end = tf.updateLength();
+	raf = null;	// no more writing allowed
+    }
+
+    public synchronized InputStream getInputStream() throws IOException {
+	return tf.newStream(start, end);
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/UNIXFile.java b/mbox/src/main/java/com/sun/mail/mbox/UNIXFile.java
new file mode 100644
index 0000000..ab1579e
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/UNIXFile.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.util.StringTokenizer;
+
+public class UNIXFile extends File {
+    protected static final boolean loaded;
+    protected static final int lockType;
+
+    private static final long serialVersionUID = -7972156315284146651L;
+
+    public UNIXFile(String name) {
+	super(name);
+    }
+
+    // lock type enum
+    protected static final int NONE = 0;
+    protected static final int NATIVE = 1;
+    protected static final int JAVA = 2;
+
+    static {
+	String lt = System.getProperty("mail.mbox.locktype", "native");
+	int type = NATIVE;
+	if (lt.equalsIgnoreCase("none"))
+	    type = NONE;
+	else if (lt.equalsIgnoreCase("java"))
+	    type = JAVA;
+	lockType = type;
+
+	boolean lloaded = false;
+	if (lockType == NATIVE) {
+	    try {
+		System.loadLibrary("mbox");
+		lloaded = true;
+	    } catch (UnsatisfiedLinkError e) {
+		String classpath = System.getProperty("java.class.path");
+		String sep = System.getProperty("path.separator");
+		String arch = System.getProperty("os.arch");
+		StringTokenizer st = new StringTokenizer(classpath, sep);
+		while (st.hasMoreTokens()) {
+		    String path = st.nextToken();
+		    if (path.endsWith("/classes") ||
+			    path.endsWith("/mail.jar") ||
+			    path.endsWith("/javax.mail.jar")) {
+			int i = path.lastIndexOf('/');
+			String libdir = path.substring(0, i + 1) + "lib/";
+			String lib = libdir + arch + "/libmbox.so";
+			try {
+			    System.load(lib);
+			    lloaded = true;
+			    break;
+			} catch (UnsatisfiedLinkError e2) {
+			    lib = libdir + "libmbox.so";
+			    try {
+				System.load(lib);
+				lloaded = true;
+				break;
+			    } catch (UnsatisfiedLinkError e3) {
+				continue;
+			    }
+			}
+		    }
+		}
+	    }
+	}
+	loaded = lloaded;
+	if (loaded)
+	    initIDs(FileDescriptor.class, FileDescriptor.in);
+    }
+
+    /**
+     * Return the access time of the file.
+     */
+    public static long lastAccessed(File file) {
+	return lastAccessed0(file.getPath());
+    }
+
+    public long lastAccessed() {
+	return lastAccessed0(getPath());
+    }
+
+    private static native void initIDs(Class<FileDescriptor> fdClass,
+					FileDescriptor stdin);
+
+    /**
+     * Lock the file referred to by fd.  The string mode is "r"
+     * for a read lock or "rw" for a write lock.  Don't block
+     * if lock can't be acquired.
+     */
+    public static boolean lock(FileDescriptor fd, String mode) {
+	return lock(fd, mode, false);
+    }
+
+    /**
+     * Lock the file referred to by fd.  The string mode is "r"
+     * for a read lock or "rw" for a write lock.  If block is set,
+     * block waiting for the lock if necessary.
+     */
+    private static boolean lock(FileDescriptor fd, String mode, boolean block) {
+	//return loaded && lock0(fd, mode);
+	if (loaded) {
+	    boolean ret;
+	    //System.out.println("UNIXFile.lock(" + fd + ", " + mode + ")");
+	    ret = lock0(fd, mode, block);
+	    //System.out.println("UNIXFile.lock returns " + ret);
+	    return ret;
+	}
+	return false;
+    }
+
+    private static native boolean lock0(FileDescriptor fd, String mode,
+								boolean block);
+
+    public static native long lastAccessed0(String name);
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/UNIXFolder.java b/mbox/src/main/java/com/sun/mail/mbox/UNIXFolder.java
new file mode 100644
index 0000000..13dc30a
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/UNIXFolder.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+
+public class UNIXFolder extends UNIXFile implements MailFile {
+    protected transient RandomAccessFile file;
+
+    private static final long serialVersionUID = -254578891263785591L;
+
+    public UNIXFolder(String name) {
+	super(name);
+    }
+
+    public boolean lock(String mode) {
+	try {
+	    file = new RandomAccessFile(this, mode);
+	    switch (lockType) {
+	    case NONE:
+		return true;
+	    case NATIVE:
+	    default:
+		return UNIXFile.lock(file.getFD(), mode);
+	    case JAVA:
+		return file.getChannel().
+		    tryLock(0L, Long.MAX_VALUE, !mode.equals("rw")) != null;
+	    }
+	} catch (FileNotFoundException fe) {
+	    return false;
+	} catch (IOException ie) {
+	    file = null;
+	    return false;
+	}
+    }
+
+    public void unlock() { 
+	if (file != null) {
+	    try {
+		file.close();
+	    } catch (IOException e) {
+		// ignore it
+	    }
+	    file = null;
+	}
+    }
+
+    public void touchlock() {
+    }
+
+    public FileDescriptor getFD() {
+	if (file == null)
+	    return null;
+	try {
+	    return file.getFD();
+	} catch (IOException e) {
+	    return null;
+	}
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/mbox/UNIXInbox.java b/mbox/src/main/java/com/sun/mail/mbox/UNIXInbox.java
new file mode 100644
index 0000000..70ce865
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/mbox/UNIXInbox.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.*;
+
+public class UNIXInbox extends UNIXFolder implements InboxFile {
+    private final String user;
+
+    private static final long serialVersionUID = 651261842162777620L;
+
+    /*
+     * Superclass UNIXFile loads the library containing all the
+     * native code and sets the "loaded" flag if successful.
+     */
+
+    public UNIXInbox(String user, String name) {
+	super(name);
+	this.user = user;
+	if (user == null)
+	    throw new NullPointerException("user name is null in UNIXInbox");
+    }
+
+    public boolean lock(String mode) {
+	if (lockType == NATIVE) {
+	    if (!loaded)
+		return false;
+	    if (!maillock(user, 5))
+		return false;
+	}
+	if (!super.lock(mode)) {
+	    if (loaded)
+		mailunlock();
+	    return false;
+	}
+	return true;
+    }
+
+    public void unlock() { 
+	super.unlock();
+	if (loaded)
+	    mailunlock();
+    }
+
+    public void touchlock() {
+	if (loaded)
+	    touchlock0();
+    }
+
+    private transient RandomAccessFile lockfile; // the user's ~/.Maillock file
+    private transient String lockfileName;	// its name
+
+    public boolean openLock(String mode) {
+	if (mode.equals("r"))
+	    return true;
+	if (lockfileName == null) {
+	    String home = System.getProperty("user.home");
+	    lockfileName = home + File.separator + ".Maillock";
+	}
+	try {
+	    lockfile = new RandomAccessFile(lockfileName, mode);
+	    boolean ret;
+	    switch (lockType) {
+	    case NONE:
+		ret = true;
+		break;
+	    case NATIVE:
+	    default:
+		ret = UNIXFile.lock(lockfile.getFD(), mode);
+		break;
+	    case JAVA:
+		ret = lockfile.getChannel().
+		    tryLock(0L, Long.MAX_VALUE, !mode.equals("rw")) != null;
+		break;
+	    }
+	    if (!ret)
+		closeLock();
+	    return ret;
+	} catch (IOException ex) {
+	}
+	return false;
+    }
+
+    public void closeLock() {
+	if (lockfile == null)
+	    return;
+	try {
+	    lockfile.close();
+	} catch (IOException ex) {
+	} finally {
+	    lockfile = null;
+	}
+    }
+
+    public boolean equals(Object o) {
+	if (!(o instanceof UNIXInbox))
+	    return false;
+	UNIXInbox other = (UNIXInbox)o;
+	return user.equals(other.user) && super.equals(other);
+    }
+
+    public int hashCode() {
+	return super.hashCode() + user.hashCode();
+    }
+
+    private native boolean maillock(String user, int retryCount);
+    private native void mailunlock();
+    private native void touchlock0();
+}
diff --git a/mbox/src/main/java/com/sun/mail/remote/POP3RemoteProvider.java b/mbox/src/main/java/com/sun/mail/remote/POP3RemoteProvider.java
new file mode 100644
index 0000000..e5e3987
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/remote/POP3RemoteProvider.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.remote;
+
+import javax.mail.Provider;
+
+/**
+ * The POP3 remote protocol provider.
+ */
+public class POP3RemoteProvider extends Provider {
+    public POP3RemoteProvider() {
+	super(Provider.Type.STORE, "pop3remote",
+	    POP3RemoteStore.class.getName(), "Oracle", null);
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/remote/POP3RemoteStore.java b/mbox/src/main/java/com/sun/mail/remote/POP3RemoteStore.java
new file mode 100644
index 0000000..fa6b1d4
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/remote/POP3RemoteStore.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.remote;
+
+import javax.mail.*;
+import com.sun.mail.pop3.POP3Store;
+
+/**
+ * A local store that uses POP3 to populate the INBOX.
+ *
+ * @author      Bill Shannon
+ */
+public class POP3RemoteStore extends RemoteStore {
+
+    public POP3RemoteStore(Session session, URLName url) {
+	super(session, url);
+    }
+
+    protected Store getRemoteStore(Session session, URLName url) {
+	return new POP3Store(session, url);
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/remote/RemoteDefaultFolder.java b/mbox/src/main/java/com/sun/mail/remote/RemoteDefaultFolder.java
new file mode 100644
index 0000000..81c0f90
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/remote/RemoteDefaultFolder.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.remote;
+
+import javax.mail.*;
+import com.sun.mail.mbox.*;
+
+/**
+ * The default folder for the "remote" protocol.
+ *
+ * @author Bill Shannon
+ */
+public class RemoteDefaultFolder extends MboxFolder {
+
+    protected RemoteDefaultFolder(RemoteStore store, String name) {
+	super(store, name);
+    }
+
+    /**
+     * Depending on the name of the requested folder, create an
+     * appropriate <code>Folder</code> subclass.  If the name is
+     * <code>null</code>, create a <code>RemoteDefaultFolder</code>.
+     * If the name is "INBOX" (ignoring case), create a
+     * <code>RemoteInbox</code>.  Otherwise, create an <code>MboxFolder</code>.
+     *
+     * @return	the new <code>Folder</code>
+     */
+    protected Folder createFolder(Store store, String name) {
+	if (name == null)
+	    return new RemoteDefaultFolder((RemoteStore)store, null);
+	else if (name.equalsIgnoreCase("INBOX"))
+	    return new RemoteInbox((RemoteStore)store, name);
+	else
+	    return new MboxFolder((MboxStore)store, name);
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/remote/RemoteInbox.java b/mbox/src/main/java/com/sun/mail/remote/RemoteInbox.java
new file mode 100644
index 0000000..5bfd232
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/remote/RemoteInbox.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.remote;
+
+import javax.mail.*;
+import com.sun.mail.mbox.*;
+
+/**
+ * A remote Inbox folder.  The data is actually managed by our subclass
+ * (<code>MboxFolder</code>).  We fetch data from the remote Inbox and
+ * add it to the local Inbox.
+ *
+ * @author Bill Shannon
+ */
+
+public class RemoteInbox extends MboxFolder {
+
+    private RemoteStore mstore;
+
+    protected RemoteInbox(RemoteStore store, String name) {
+	super(store, name);
+	this.mstore = store;
+    }
+
+    /**
+     * Poll the remote store for any new messages.
+     */
+    public synchronized boolean hasNewMessages() {
+	try {
+	    mstore.updateInbox();
+	} catch (MessagingException ex) {
+	    // ignore it
+	}
+	return super.hasNewMessages();
+    }
+
+    /**
+     * Open the folder in the specified mode.
+     * Poll the remote store for any new messages first.
+     */
+    public synchronized void open(int mode) throws MessagingException {
+	mstore.updateInbox();
+	super.open(mode);
+    }
+
+    /**
+     * Return the number of messages in this folder.
+     * Poll the remote store for any new messages first.
+     */
+    public synchronized int getMessageCount() throws MessagingException {
+	mstore.updateInbox();
+	return super.getMessageCount();
+    }
+}
diff --git a/mbox/src/main/java/com/sun/mail/remote/RemoteStore.java b/mbox/src/main/java/com/sun/mail/remote/RemoteStore.java
new file mode 100644
index 0000000..faf9dd4
--- /dev/null
+++ b/mbox/src/main/java/com/sun/mail/remote/RemoteStore.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.remote;
+
+import java.io.*;
+import javax.mail.*;
+import com.sun.mail.mbox.*;
+
+/**
+ * A wrapper around a local <code>MboxStore</code> that fetches data
+ * from the Inbox in a remote store and adds it to our local Inbox.
+ */
+public abstract class RemoteStore extends MboxStore {
+
+    protected Store remoteStore;
+    protected Folder remoteInbox;
+    protected Folder inbox;
+    protected String host, user, password;
+    protected int port;
+    protected long lastUpdate = 0;
+
+    public RemoteStore(Session session, URLName url) {
+	super(session, url);
+	remoteStore = getRemoteStore(session, url);
+    }
+
+    /**
+     * Subclasses override this method to return the appropriate
+     * <code>Store</code> object.  This method will be called by
+     * the <code>RemoteStore</code> constructor.
+     */
+    protected abstract Store getRemoteStore(Session session, URLName url);
+
+    /**
+     * Connect to the store.
+     */
+    public void connect(String host, int port, String user, String password)
+			throws MessagingException {
+	this.host = host;
+	this.port = port;
+	this.user = user;
+	this.password = password;
+	updateInbox();
+    }
+
+    /**
+     * Fetch any new mail in the remote INBOX and add it to the local INBOX.
+     */
+    protected void updateInbox() throws MessagingException {
+	// is it time to do an update yet?
+	// XXX - polling frequency, rules, etc. should be in properties
+	if (System.currentTimeMillis() < lastUpdate + (5 * 1000))
+	    return;
+	try {
+	    /*
+	     * Connect to the remote store, using the saved
+	     * authentication information.
+	     */
+	    remoteStore.connect(host, port, user, password);
+
+	    /*
+	     * If this store isn't connected yet, do it now, because
+	     * it needs to be connected to get the INBOX folder.
+	     */
+	    if (!isConnected())
+		super.connect(host, port, user, password);
+	    if (remoteInbox == null)
+		remoteInbox = remoteStore.getFolder("INBOX");
+	    if (inbox == null)
+		inbox = getFolder("INBOX");
+	    remoteInbox.open(Folder.READ_WRITE);
+	    Message[] msgs = remoteInbox.getMessages();
+	    inbox.appendMessages(msgs);
+	    remoteInbox.setFlags(msgs, new Flags(Flags.Flag.DELETED), true);
+	    remoteInbox.close(true);
+	    remoteStore.close();
+	} catch (MessagingException ex) {
+	    try {
+		if (remoteInbox != null && remoteInbox.isOpen())
+		    remoteInbox.close(false);
+	    } finally {
+		if (remoteStore != null && remoteStore.isConnected())
+		    remoteStore.close();
+	    }
+	    throw ex;
+	}
+    }
+
+    public Folder getDefaultFolder() throws MessagingException {
+	checkConnected();
+
+	return new RemoteDefaultFolder(this, null);
+    }
+
+    public Folder getFolder(String name) throws MessagingException {
+	checkConnected();
+
+	if (name.equalsIgnoreCase("INBOX"))
+	    return new RemoteInbox(this, name);
+	else
+	    return super.getFolder(name);
+    }
+
+    public Folder getFolder(URLName url) throws MessagingException {
+	checkConnected();
+	return getFolder(url.getFile());
+    }
+
+    private void checkConnected() throws MessagingException {
+	if (!isConnected())
+	    throw new MessagingException("Not connected");
+    }
+}
diff --git a/mbox/src/main/resources/META-INF/MANIFEST.MF b/mbox/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..820d8d2
--- /dev/null
+++ b/mbox/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Extension-Name: com.sun.mail.mbox
+Specification-Title: com.sun.mail.mbox
+Specification-Version: ${mail.spec.version}
+Specification-Vendor: ${project.organization.name}
+Implementation-Title: com.sun.mail.mbox
+Implementation-Version: ${mail.version}
+Implementation-Vendor: ${project.organization.name}
+Implementation-Vendor-Id: com.sun
diff --git a/mbox/src/main/resources/META-INF/javamail.providers b/mbox/src/main/resources/META-INF/javamail.providers
new file mode 100644
index 0000000..c2273dd
--- /dev/null
+++ b/mbox/src/main/resources/META-INF/javamail.providers
@@ -0,0 +1,2 @@
+protocol=mbox; type=store; class=com.sun.mail.mbox.MboxStore; vendor=Oracle;
+protocol=pop3remote; type=store; class=com.sun.mail.remote.POP3RemoteStore; vendor=Oracle;
diff --git a/mbox/src/main/resources/META-INF/services/javax.mail.Provider b/mbox/src/main/resources/META-INF/services/javax.mail.Provider
new file mode 100644
index 0000000..663358d
--- /dev/null
+++ b/mbox/src/main/resources/META-INF/services/javax.mail.Provider
@@ -0,0 +1,2 @@
+com.sun.mail.mbox.MboxProvider
+com.sun.mail.remote.POP3RemoteProvider
diff --git a/mbox/src/test/java/com/sun/mail/mbox/MboxFolderExpungeTest.java b/mbox/src/test/java/com/sun/mail/mbox/MboxFolderExpungeTest.java
new file mode 100644
index 0000000..594e125
--- /dev/null
+++ b/mbox/src/test/java/com/sun/mail/mbox/MboxFolderExpungeTest.java
@@ -0,0 +1,362 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.Date;
+
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.Flags;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.junit.Test;
+import org.junit.BeforeClass;
+import org.junit.AfterClass;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test expunge of mbox folders.
+ */
+public final class MboxFolderExpungeTest {
+
+    @BeforeClass
+    public static void before() {
+	System.setProperty("mail.mbox.locktype", "none");
+    }
+
+    @AfterClass
+    public static void after() {
+	System.getProperties().remove("mail.mbox.locktype");
+    }
+
+    @Test
+    public void testRemoveFirst() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message m = f.getMessage(1);
+	    m.setFlag(Flags.Flag.DELETED, true);
+	    f.expunge();
+	    m = f.getMessage(1);
+	    assertEquals("2", ((String)m.getContent()).trim());
+	    m.setFlag(Flags.Flag.DELETED, true);
+	    m = f.getMessage(2);
+	    assertEquals("3", ((String)m.getContent()).trim());
+	    f.expunge();
+	    m = f.getMessage(1);
+	    assertEquals("3", ((String)m.getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testRemoveMiddle() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message m = f.getMessage(2);
+	    m.setFlag(Flags.Flag.DELETED, true);
+	    f.expunge();
+	    m = f.getMessage(1);
+	    assertEquals("1", ((String)m.getContent()).trim());
+	    m = f.getMessage(2);
+	    assertEquals("3", ((String)m.getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testRemoveLast() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message m = f.getMessage(3);
+	    m.setFlag(Flags.Flag.DELETED, true);
+	    f.expunge();
+	    m = f.getMessage(1);
+	    assertEquals("1", ((String)m.getContent()).trim());
+	    m = f.getMessage(2);
+	    assertEquals("2", ((String)m.getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testRemoveFirstClose() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message m = f.getMessage(1);
+	    m.setFlag(Flags.Flag.DELETED, true);
+	    f.close(true);
+	    f.open(Folder.READ_WRITE);
+	    m = f.getMessage(1);
+	    assertEquals("2", ((String)m.getContent()).trim());
+	    m.setFlag(Flags.Flag.DELETED, true);
+	    m = f.getMessage(2);
+	    assertEquals("3", ((String)m.getContent()).trim());
+	    f.close(true);
+	    f.open(Folder.READ_WRITE);
+	    m = f.getMessage(1);
+	    assertEquals("3", ((String)m.getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testRemoveMiddleClose() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message m = f.getMessage(2);
+	    m.setFlag(Flags.Flag.DELETED, true);
+	    f.close(true);
+	    f.open(Folder.READ_WRITE);
+	    m = f.getMessage(1);
+	    assertEquals("1", ((String)m.getContent()).trim());
+	    m = f.getMessage(2);
+	    assertEquals("3", ((String)m.getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testRemoveLastClose() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message m = f.getMessage(3);
+	    m.setFlag(Flags.Flag.DELETED, true);
+	    f.close(true);
+	    f.open(Folder.READ_WRITE);
+	    m = f.getMessage(1);
+	    assertEquals("1", ((String)m.getContent()).trim());
+	    m = f.getMessage(2);
+	    assertEquals("2", ((String)m.getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testRemoveFirstMessages() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message[] msgs = f.getMessages(1, 3);
+	    msgs[0].setFlag(Flags.Flag.DELETED, true);
+	    f.expunge();
+	    assertEquals("2", ((String)msgs[1].getContent()).trim());
+	    assertEquals("3", ((String)msgs[2].getContent()).trim());
+	    msgs[1].setFlag(Flags.Flag.DELETED, true);
+	    f.expunge();
+	    assertEquals("3", ((String)msgs[2].getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testRemoveMiddleMessages() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message[] msgs = f.getMessages(1, 3);
+	    msgs[1].setFlag(Flags.Flag.DELETED, true);
+	    f.expunge();
+	    assertEquals("1", ((String)msgs[0].getContent()).trim());
+	    assertEquals("3", ((String)msgs[2].getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testRemoveLastMessages() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message[] msgs = f.getMessages(1, 3);
+	    msgs[2].setFlag(Flags.Flag.DELETED, true);
+	    f.expunge();
+	    assertEquals("1", ((String)msgs[0].getContent()).trim());
+	    assertEquals("2", ((String)msgs[1].getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testNewMessagesAfterExpunge() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message[] msgs = f.getMessages(1, 3);
+	    msgs[0].setFlag(Flags.Flag.DELETED, true);
+	    f.expunge();
+	    f.appendMessages(new Message[] { createMessage(null, 4) });
+	    assertEquals(3, f.getMessageCount());
+	    Message m = f.getMessage(3);
+	    assertEquals("4", ((String)m.getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    @Test
+    public void testNewMessagesAfterClose() throws Exception {
+	Folder f = createTestFolder();
+	try {
+	    f.open(Folder.READ_WRITE);
+	    Message[] msgs = f.getMessages(1, 3);
+	    msgs[0].setFlag(Flags.Flag.DELETED, true);
+	    f.close(true);
+	    f.appendMessages(new Message[] { createMessage(null, 4) });
+	    f.open(Folder.READ_WRITE);
+	    assertEquals(3, f.getMessageCount());
+	    Message m = f.getMessage(3);
+	    assertEquals("4", ((String)m.getContent()).trim());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    f.close(false);
+	    f.delete(false);
+	    f.getStore().close();
+	}
+    }
+
+    /**
+     * Create a temp file to use as a test folder and populate it
+     * with 3 messages.
+     */
+    private Folder createTestFolder() {
+	Properties properties = new Properties();
+	Session session = Session.getInstance(properties);
+	//session.setDebug(true);
+
+	Folder folder = null;
+	try {
+	    Store store = session.getStore("mbox");
+	    File temp = File.createTempFile("mbox", ".mbx");
+	    temp.deleteOnExit();
+	    store.connect();
+	    folder = store.getFolder(temp.getAbsolutePath());
+	    folder.create(Folder.HOLDS_MESSAGES);
+	    Message[] msgs = new Message[3];
+	    for (int i = 0; i < 3; i++)
+		msgs[i] = createMessage(session, i + 1);
+	    folder.appendMessages(msgs);
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	}
+	return folder;
+    }
+
+    /**
+     * Create a test message.
+     */
+    private Message createMessage(Session session, int msgno)
+				throws MessagingException {
+	MimeMessage msg = new MimeMessage(session);
+	msg.setFrom("test@example.com");
+	msg.setSentDate(new Date());
+	String subject = "test ";
+	// ensure each message is a different length
+	for (int i = 0; i < msgno; i++)
+	    subject += "test ";
+	msg.setSubject(subject + msgno);
+	msg.setText(msgno + "\n");
+	msg.saveChanges();
+	return msg;
+    }
+}
diff --git a/mbox/src/test/java/com/sun/mail/mbox/MboxFolderTest.java b/mbox/src/test/java/com/sun/mail/mbox/MboxFolderTest.java
new file mode 100644
index 0000000..24cdc86
--- /dev/null
+++ b/mbox/src/test/java/com/sun/mail/mbox/MboxFolderTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2009, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package com.sun.mail.mbox;
+
+import java.io.File;
+import java.io.PrintWriter;
+import java.util.Properties;
+
+import javax.mail.Session;
+import javax.mail.Store;
+import javax.mail.Folder;
+
+import org.junit.Test;
+import org.junit.BeforeClass;
+import org.junit.AfterClass;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * Test of mbox folders.
+ */
+public final class MboxFolderTest {
+
+    @BeforeClass
+    public static void before() {
+	System.setProperty("mail.mbox.locktype", "none");
+    }
+
+    @AfterClass
+    public static void after() {
+	System.getProperties().remove("mail.mbox.locktype");
+    }
+
+    /**
+     * Test that a mailbox that has garbage at the beginning
+     * (such as a gratuitous blank line) is handled without
+     * crashing and without corrupting the mailbox.
+     */
+    @Test
+    public void testGarbageAtStartOfFolder() throws Exception {
+	Folder f = null;
+	try {
+	    File temp = File.createTempFile("mbox", ".mbx");
+	    temp.deleteOnExit();
+	    PrintWriter pw = new PrintWriter(temp);
+	    pw.println();
+	    pw.println("From - Tue Aug 23 11:56:51 2011");
+	    pw.println();
+	    pw.println("test");
+	    pw.println();
+	    pw.close();
+	    long size = temp.length();
+
+	    Properties properties = new Properties();
+	    Session session = Session.getInstance(properties);
+	    Store store = session.getStore("mbox");
+	    store.connect();
+	    f = store.getFolder(temp.getAbsolutePath());
+	    f.open(Folder.READ_WRITE);
+	    assertEquals(0, f.getMessageCount());
+	    f.close(true);
+	    assertEquals(size, temp.length());
+	} catch (Exception ex) {
+	    System.out.println(ex);
+	    //ex.printStackTrace();
+	    fail(ex.toString());
+	} finally {
+	    if (f != null) {
+		f.delete(false);
+		f.getStore().close();
+	    }
+	}
+    }
+}
diff --git a/outlook/pom.xml b/outlook/pom.xml
new file mode 100644
index 0000000..60a1332
--- /dev/null
+++ b/outlook/pom.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>outlook</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API demo Outlook message support</name>
+
+    <build>
+        <plugins>
+	    <!--
+		Need to disable the maven-bundle-plugin because the
+		new version doesn't like classes in the default package.
+	    -->
+	    <plugin>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>maven-bundle-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>osgi-manifest</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Because the maven-jar-plugin depends on the manifest file
+		created by the maven-bundle-plugin, we need to disable it too.
+	    -->
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>default-jar</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/outlook/src/main/java/MSBodyPart.java b/outlook/src/main/java/MSBodyPart.java
new file mode 100644
index 0000000..4f64c06
--- /dev/null
+++ b/outlook/src/main/java/MSBodyPart.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import javax.activation.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * A special MimeBodyPart used with MSMessage.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+public class MSBodyPart extends MimeBodyPart {
+    private int start;
+    private int end;
+    private String type = UNKNOWN;
+    private String disposition;
+    private String encoding;
+    private String filename = UNKNOWN;
+
+    private static final String UNKNOWN = "UNKNOWN";
+
+    public MSBodyPart(byte[] content, int start, int end,
+		String disposition, String encoding) {
+	this.content = content;
+	this.start = start;
+	this.end = end;
+	this.disposition = disposition;
+	this.encoding = encoding;
+    }
+
+    public String getContentType() throws MessagingException {
+	// try to figure this out from the filename extension
+	if (type == UNKNOWN)
+	    processBegin();
+	return type;
+    }
+
+    public String getEncoding() throws MessagingException {
+	return encoding;
+    }
+
+    public String getDisposition() throws MessagingException {
+	return disposition;
+    }
+
+    public String getFileName() throws MessagingException {
+	// get filename from the "begin" line
+	if (filename == UNKNOWN)
+	    processBegin();
+	return filename;
+    }
+
+    protected InputStream getContentStream() {
+	return new ByteArrayInputStream(content, start, end - start);
+    }
+
+    /**
+     * Process the "begin" line to extract the filename,
+     * and from it determine the Content-Type.
+     */
+    private void processBegin() {
+	InputStream in = getContentStream();
+	try {
+	    BufferedReader r = new BufferedReader(new InputStreamReader(in));
+	    String begin = r.readLine();
+	    // format is "begin 666 filename.txt"
+	    if (begin != null && begin.regionMatches(true, 0, "begin ", 0, 6)) {
+		int i = begin.indexOf(' ', 6);
+		if (i > 0) {
+		    filename = begin.substring(i + 1);
+		    FileTypeMap map = FileTypeMap.getDefaultFileTypeMap();
+		    type = map.getContentType(filename);
+		    if (type == null)
+			type = "application/octet-stream";
+		}
+	    }
+	} catch (IOException ex) {
+	    // ignore
+	} finally {
+	    try {
+		in.close();
+	    } catch (IOException ex) {
+		// ignore it
+	    }
+	    if (filename == UNKNOWN)
+		filename = null;
+	    if (type == UNKNOWN || type == null)
+		type = "text/plain";
+	}
+    }
+}
diff --git a/outlook/src/main/java/MSMessage.java b/outlook/src/main/java/MSMessage.java
new file mode 100644
index 0000000..6d93878
--- /dev/null
+++ b/outlook/src/main/java/MSMessage.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+/**
+ * This class models a UUEncoded Message sent from MS Outlook etc. <p>
+ *
+ * The message structure looks like this :=
+ *  [text body] [uuencoded attachment]*
+ * <p>
+ * i.e., an optional text/plain main-body, followed by zero or more
+ * UUENCODE-ed attachments.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ * @see    javax.mail.internet.MimeMessage
+ */
+
+public class MSMessage extends MimeMessage {
+    private String type;
+
+    /**
+     * Constructor that converts a MimeMessage object into a MSMessage.
+     * 
+     * @exception   MessagingException if the given MimeMessage
+     *          is not a non-MIME MS message, or if an
+     *          IOException occurs when accessing the given
+     *          MimeMessage object
+     */
+    public MSMessage(Session session, MimeMessage msg) 
+		throws MessagingException {
+	super(session);
+
+	if (!isMSMessage(msg))   // sanity check
+	    throw new MessagingException("Not an MS message");
+
+	class FastByteArrayOutputStream extends ByteArrayOutputStream {
+	    ByteArrayInputStream toByteArrayInputStream() {
+		return new ByteArrayInputStream(buf, 0, count);
+	    }
+	}
+
+	// extract the bytes of the given message
+	// ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	FastByteArrayOutputStream bos = new FastByteArrayOutputStream();
+	try {
+	    msg.writeTo(bos);
+	} catch (IOException ioex) {
+	    throw new MessagingException("IOException", ioex);
+	} catch (Exception ex) {
+	    throw new MessagingException("Exception", ex);
+	}
+	//parse(new ByteArrayInputStream(bos.toByteArray()));
+	parse(bos.toByteArrayInputStream());
+    }
+
+    /**
+     * Constructor to create a MSMessage from the given InputStream.
+     */
+    public MSMessage(Session session, InputStream is) 
+		throws MessagingException {
+	super(session); // setup headerstore etc
+	parse(is);
+    }
+
+    // parse input stream
+    protected void parse(InputStream is) throws MessagingException {
+	// Create a buffered input stream for efficiency
+	if (!(is instanceof ByteArrayInputStream) &&
+	    !(is instanceof BufferedInputStream))
+	    is = new BufferedInputStream(is);
+
+	// Load headerstore
+	headers.load(is);
+
+	/*
+	 * Load the content into a byte[].
+	 * This byte[] is shared among the bodyparts.
+	 */
+	try {
+	    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+	    int b;
+	    // XXX - room for performance improvement
+	    while ((b = is.read()) != -1)
+		bos.write(b);
+	    content = bos.toByteArray();
+	} catch (IOException ioex) {
+	    throw new MessagingException("IOException", ioex);
+	}
+
+	/*
+	 * Check whether this is multipart.
+	 */
+	boolean isMulti = false;
+	// check for presence of X-MS-Attachment header
+	String[] att = getHeader("X-MS-Attachment");
+	if (att != null && att.length > 0)
+	    isMulti = true;
+	else {
+	    /*
+	     * Fall back to scanning the content.
+	     * We scan the content until we find a sequence that looks
+	     * like the start of a uuencoded block, i.e., "<newline>begin".
+	     * If found, we claim that this is a multipart message.
+	     */
+	    for (int i = 0; i < content.length; i++) {
+		int b = content[i] & 0xff; // mask higher byte
+		if (b == '\r' || b == '\n') {
+		    // start of a new line
+		    if ((i + 5) < content.length) {
+			// can there be a "begin" now?
+			String s = toString(content, i+1, i+6);
+			if (s.equalsIgnoreCase("begin")) {
+			    isMulti= true;
+			    break;
+			}
+		    }
+		}
+	    }
+	}
+
+	if (isMulti) {
+	    type = "multipart/mixed";
+	    dh = new DataHandler(new MSMultipartDataSource(this, content));
+	} else {
+	    type = "text/plain"; // charset = ?
+	    dh = new DataHandler(new MimePartDataSource(this));
+	}
+
+	modified = false;
+    }
+
+    /**
+     * Return content-type
+     */
+    public String getContentType() throws MessagingException {
+	return type;
+    }
+
+    /**
+     * Return content-disposition
+     */
+    public String getDisposition() throws MessagingException {
+	return "inline";
+    }
+
+    /**
+     * Return content-transfer-encoding
+     */
+    public String getEncoding() throws MessagingException {
+	return "7bit";
+    }
+
+    /**
+     * Check whether the given MimeMessage object represents a
+     * non-MIME message sent by Outlook.  Such a message will
+     * have no MIME-Version header, may have an X-Mailer header
+     * that includes the word "Microsoft", and will have at least
+     * one X-MS-Attachment header.
+     */
+    public static boolean isMSMessage(MimeMessage msg) 
+			throws MessagingException {
+	// Check whether the MIME header is present
+	if (msg.getHeader("MIME-Version") != null)
+	    // MIME-Version header present, should be a MIME message
+	    return false;
+
+	/*
+	 * XXX - disabled X-Mailer check because many sample messages
+	 * I saw didn't have an X-Mailer header at all.
+	 */
+	if (false) {
+	// Check X-Mailer
+	String mailer = msg.getHeader("X-mailer", null);
+	if (mailer == null) // No X-mailer ? 
+	    return false; // Oh well !
+	if (mailer.indexOf("Microsoft") == -1) // Not MS stuff ?
+	    return false;
+	}
+
+	// Check X-MS-Attachment header
+	// XXX - not all such messages have this header
+	String[] att = msg.getHeader("X-MS-Attachment");
+	if (att == null || att.length == 0)
+	    return false;
+
+	return true;
+    }
+
+    // convert given byte array of ASCII characters to string
+    static String toString(byte[] b, int start, int end) {
+	int size = end - start;
+	char[] theChars = new char[size];
+
+	for (int i = 0, j = start; i < size; )
+	    theChars[i++] = (char)b[j++];
+	return new String(theChars);
+    }
+}
diff --git a/outlook/src/main/java/MSMultipartDataSource.java b/outlook/src/main/java/MSMultipartDataSource.java
new file mode 100644
index 0000000..3c46fc3
--- /dev/null
+++ b/outlook/src/main/java/MSMultipartDataSource.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.util.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * A special MultipartDataSource used with MSMessage.
+ *
+ * @author John Mani
+ * @author Bill Shannon
+ */
+public class MSMultipartDataSource extends MimePartDataSource
+				implements MultipartDataSource {
+    //private List<MSBodyPart> parts;
+    private List parts;
+
+    public MSMultipartDataSource(MimePart part, byte[] content)
+				throws MessagingException {
+	super(part);
+	//parts = new ArrayList<MSBodyPart>();
+	parts = new ArrayList();
+
+	/*
+	 * Parse the text of the message to find the attachments.
+	 *
+	 * Currently we just look for the lines that mark the
+	 * begin and end of uuencoded data, but this can be
+	 * fooled by similar text in the message body.  Instead,
+	 * we could use the Encoding header, which indicates how
+	 * many lines are in each body part.  For example:
+	 *
+	 * Encoding: 41 TEXT, 38 UUENCODE, 3155 UUENCODE, 1096 UUENCODE
+	 *
+	 * Similarly, we could get the filenames of the attachments
+	 * from the X-MS-Attachment headers.  For example:
+	 *
+	 * X-MS-Attachment: ATT00000.htx 0 00-00-1980 00:00
+	 * X-MS-Attachment: Serengeti 2GG.mpp 0 00-00-1980 00:00
+	 * X-MS-Attachment: project team update 031298.doc 0 00-00-1980 00:00
+	 *
+	 * (Note that there might be unquoted spaces in the filename.)
+	 */
+	int pos = startsWith(content, 0, "begin");
+	if (pos == -1)
+	    throw new MessagingException("invalid multipart");
+	
+	if (pos > 0)	// we have an unencoded main body part
+	    parts.add(new MSBodyPart(content, 0, pos, "inline", "7bit"));
+	else		// no main body part
+	    pos = 0;
+
+	// now collect all the uuencoded individual body parts
+	int start;
+	for (;;) {
+	    start = startsWith(content, pos, "begin");
+	    if (start == -1)
+		break;
+	    pos = startsWith(content, start, "end");
+	    if (pos == -1)
+		break;
+	    pos += 3;	// skip to the end of "end"
+	    parts.add(new MSBodyPart(content, start, pos,
+					"attachment", "uuencode"));
+	}
+    }
+
+    public int getCount() {
+	return parts.size();
+    }
+
+    public BodyPart getBodyPart(int index) throws MessagingException {
+	return (BodyPart)parts.get(index);
+    }
+
+    /**
+     * This method scans the given byte[], beginning at "start", for
+     * lines that begin with the sequence "seq".  If found, the start
+     * position of the sequence within the byte[] is returned.
+     */
+    private int startsWith(byte[] content, int start, String seq) {
+	int slen = seq.length();
+	boolean bol = true;
+	for (int i = start; i < content.length; i++) {
+	    if (bol) {
+		if ((i + slen) < content.length) {
+		    String s = MSMessage.toString(content, i, i + slen);
+		    if (s.equalsIgnoreCase(seq))
+			return i;
+		}
+	    }
+	    int b = content[i] & 0xff;
+	    bol = b == '\r' || b == '\n';
+	}
+	return -1;
+    }
+}
diff --git a/outlook/src/main/java/README.txt b/outlook/src/main/java/README.txt
new file mode 100644
index 0000000..36cdea5
--- /dev/null
+++ b/outlook/src/main/java/README.txt
@@ -0,0 +1,9 @@
+The classes in this directory allow processing old style non-MIME
+messages created by Microsoft Outlook.  Use them like this:
+
+	if (MSMessage.isMSMessage(msg))
+	    msg = new MSMessage(session, msg);
+
+Note that these classes are not particularly efficient or optimized,
+but they show how to process these non-MIME messages and make them
+look like MIME messages.
diff --git a/parent-distrib/pom.xml b/parent-distrib/pom.xml
new file mode 100644
index 0000000..9a32f76
--- /dev/null
+++ b/parent-distrib/pom.xml
@@ -0,0 +1,168 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>parent-distrib</artifactId>
+    <packaging>pom</packaging>
+    <name>JavaMail API ${project.artifactId} provider</name>
+
+    <properties>
+	<mail.classes>com/sun/mail/${project.artifactId}/**</mail.classes>
+    </properties>
+
+    <profiles>
+	<!--
+	    A special profile for compiling with JDK 9.
+	-->
+	<profile>
+	    <id>9</id>
+	    <activation>
+		<jdk>9</jdk>
+	    </activation>
+	    <build>
+		<plugins>
+		    <plugin>
+			<artifactId>maven-compiler-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>default-compile</id>
+				<configuration>
+				    <release>9</release>
+				    <source>9</source>
+				    <target>9</target>
+				    <compilerArgs>
+					<arg>-Xlint</arg>
+					<arg>-Xlint:-options</arg>
+					<arg>-Xlint:-path</arg>
+					<!--
+					    Too many finalize warnings.
+					<arg>-Werror</arg>
+					-->
+				    </compilerArgs>
+				    <!-- un-exclude module-info.java -->
+				    <excludes combine.self="override">
+				    </excludes>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		    <!--
+			Require JDK 9.
+		    -->
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-enforcer-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>enforce-version</id>
+				<goals>
+				    <goal>enforce</goal>
+				</goals>
+				<configuration>
+				    <rules>
+					<requireMavenVersion>
+					    <version>[3.0.3,)</version>
+					</requireMavenVersion>
+					<requireJavaVersion>
+					    <version>9</version>
+					</requireJavaVersion>
+				    </rules>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		</plugins>
+	    </build>
+	</profile>
+    </profiles>
+
+    <build>
+        <plugins>
+	    <plugin>
+		<artifactId>maven-dependency-plugin</artifactId>
+		<executions>
+		    <execution>
+			<!-- download the binaries -->
+			<id>get-binaries</id>
+			<phase>generate-sources</phase>
+			<goals>
+			    <goal>unpack</goal>
+			</goals>
+		    </execution>
+		    <execution>
+			<!-- download the sources -->
+			<id>get-sources</id>
+			<phase>generate-sources</phase>
+			<goals>
+			    <goal>unpack</goal>
+			</goals>
+			<configuration>
+			    <artifactItems>
+				<artifactItem>
+				    <groupId>com.sun.mail</groupId>
+				    <artifactId>jakarta.mail</artifactId>
+				    <version>${mail.version}</version>
+				    <classifier>sources</classifier>
+				    <outputDirectory>
+					${project.build.directory}/sources
+				    </outputDirectory>
+				</artifactItem>
+			    </artifactItems>
+			</configuration>
+		    </execution>
+		</executions>
+		<configuration>
+		    <artifactItems>
+			<artifactItem>
+			    <groupId>com.sun.mail</groupId>
+			    <artifactId>jakarta.mail</artifactId>
+			    <version>${mail.version}</version>
+			</artifactItem>
+		    </artifactItems>
+		    <outputDirectory>
+			${project.build.outputDirectory}
+		    </outputDirectory>
+		    <includes>
+			META-INF/LICENSE.txt,
+			${mail.classes},
+			${mail.extraClasses}
+		    </includes>
+		</configuration>
+	    </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+	<dependency>
+	    <groupId>com.sun.mail</groupId>
+	    <artifactId>mailapi</artifactId>
+	    <version>${mail.version}</version>
+	</dependency>
+    </dependencies>
+</project>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..58efdc0
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,919 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>org.eclipse.ee4j</groupId>
+	<artifactId>project</artifactId>
+	<version>1.0.4</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>all</artifactId>
+    <packaging>pom</packaging>
+    <version>1.6.3</version>
+    <name>JavaMail API distribution</name>
+    <description>${project.name}</description>
+    <url>http://eclipse-ee4j.github.io/javamail</url>
+
+    <scm>
+	<connection>scm:git:ssh://git@github.com/eclipse-ee4j/javamail.git</connection>
+	<developerConnection>scm:git:ssh://git@github.com/eclipse-ee4j/javamail.git</developerConnection>
+	<url>https://github.com/eclipse-ee4j/javamail</url>
+    </scm>
+
+    <issueManagement>
+	<system>GitHub</system>
+	<url>https://github.com/eclipse-ee4j/javamail/issues</url>
+    </issueManagement>
+
+    <licenses>
+	<license>
+	    <name>EPL 2.0</name>
+	    <url>http://www.eclipse.org/legal/epl-2.0</url>
+	    <distribution>repo</distribution>
+	</license>
+	<license>
+	    <name>GPL2 w/ CPE</name>
+	    <url>https://www.gnu.org/software/classpath/license.html</url>
+	    <distribution>repo</distribution>
+	</license>
+	<license>
+	    <name>EDL 1.0</name>
+	    <url>http://www.eclipse.org/org/documents/edl-v10.php</url>
+	    <distribution>repo</distribution>
+	</license>
+    </licenses>
+
+    <organization>
+	<name>Oracle</name>
+	<url>http://www.oracle.com</url>
+    </organization>
+
+    <properties>
+	<mail.version>1.6.3</mail.version>
+	<!-- like mail.version, but with underscores instead of dots -->
+	<mail.zipversion>1_6_3</mail.zipversion>
+	<mail.spec.version>1.6</mail.spec.version>
+	<activation.version>1.2.1</activation.version>
+	<!-- defaults that are overridden in mail module -->
+	<mail.extensionName>
+	    ${project.groupId}.${project.artifactId}
+	</mail.extensionName>
+	<mail.specificationTitle>
+	    ${project.groupId}.${project.artifactId}
+	</mail.specificationTitle>
+	<mail.implementationTitle>
+	    ${project.groupId}.${project.artifactId}
+	</mail.implementationTitle>
+	<mail.bundle.symbolicName>
+	    ${project.groupId}.${project.artifactId}
+	</mail.bundle.symbolicName>
+	<mail.packages.export>
+	    javax.mail.*; version=${mail.spec.version}
+	</mail.packages.export>
+	<mail.packages.import>
+	    javax.activation;version=!,
+	    javax.security.sasl;resolution:=optional,
+	    sun.security.util;resolution:=optional,
+	    *
+	</mail.packages.import>
+	<mail.packages.private>
+	    com.sun.mail.*
+	</mail.packages.private>
+	<mail.probeFile/>
+	<!-- for the osgiversion-maven-plugin -->
+	<hk2.plugin.version>2.0.0</hk2.plugin.version>
+	<javac.path>/opt/jdk1.7/bin/javac</javac.path>
+	<project.build.sourceEncoding>iso-8859-1</project.build.sourceEncoding>
+	<findbugs.threshold>
+	    High
+	</findbugs.threshold>
+	<findbugs.version>
+	    3.1.0
+	</findbugs.version>
+	<findbugs.skip>
+	    true
+	</findbugs.skip>
+	<findbugs.exclude/>
+        <copyright-plugin.version>1.46</copyright-plugin.version>
+    </properties>
+
+    <developers>
+	<developer>
+	    <id>shannon</id>
+	    <name>Bill Shannon</name>
+	    <email>bill.shannon@oracle.com</email>
+	    <organization>Oracle</organization>
+	    <roles>
+		<role>lead</role>
+	    </roles>
+	</developer>
+    </developers>
+
+    <!-- following to enable use of "mvn site:stage" -->
+    <distributionManagement>
+	<site>
+	    <id>oracle.com</id>
+	    <url>file:/tmp</url> <!-- not used -->
+	</site>
+    </distributionManagement>
+
+    <modules>
+	<module>mail</module>
+	<module>mailapi</module>
+	<module>mailapijar</module>
+	<module>smtp</module>
+	<module>imap</module>
+	<module>gimap</module>
+	<module>pop3</module>
+	<module>dsn</module>
+	<module>mailhandler</module>
+	<module>android</module>
+    </modules>
+
+    <profiles>
+	<!--
+	    This profile contains modules that should only be built
+	    but not installed or deployed.
+	-->
+	<profile>
+	    <id>build-only</id>
+	    <modules>
+		<module>mbox</module>
+		<module>demo</module>
+		<module>client</module>
+		<module>servlet</module>
+		<module>webapp</module>
+		<module>taglib</module>
+		<module>logging</module>
+		<module>outlook</module>
+		<module>javadoc</module>
+		<module>publish</module>
+	    </modules>
+	    <activation>
+		<activeByDefault>true</activeByDefault>
+	    </activation>
+	</profile>
+
+	<!--
+	    This profile is used for deploying a JavaMail final release.
+
+	    Activating this profile manually for deployment causes
+	    the above profile to be deactivated, which works around
+	    an apparent bug in maven that prevents me from manually
+	    deactivating a profile.  This profile purposely has none
+	    of the modules I don't want to be deployed.
+	-->
+	<profile>
+	    <id>deploy-release</id>
+	    <modules>
+		<module>parent-distrib</module>
+	    </modules>
+	    <build>
+		<pluginManagement>
+		    <plugins>
+			<plugin>
+			    <!-- Enables step-by-step staging deployment -->
+			    <groupId>org.sonatype.plugins</groupId>
+			    <artifactId>nexus-staging-maven-plugin</artifactId>
+			    <version>1.6.7</version>
+			</plugin>
+			<plugin>
+			    <!-- Newer version then in parent -->
+			    <groupId>org.apache.maven.plugins</groupId>
+			    <artifactId>maven-gpg-plugin</artifactId>
+			    <version>1.6</version>
+			</plugin>
+		    </plugins>
+		</pluginManagement>
+		<plugins>
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-surefire-plugin</artifactId>
+			<configuration>
+			    <skip>true</skip>
+			</configuration>
+		    </plugin>
+
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-javadoc-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>attach-javadocs</id>
+				<goals>
+				    <goal>jar</goal>
+				</goals>
+			    </execution>
+			</executions>
+		    </plugin>
+
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-gpg-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>sign-artifacts</id>
+				<phase>verify</phase>
+				<goals>
+				    <goal>sign</goal>
+				</goals>
+			    </execution>
+			</executions>
+		    </plugin>
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-deploy-plugin</artifactId>
+			<configuration>
+			   <skip>true</skip> <!-- To prefer nexus-staging-maven-plugin -->
+			</configuration>
+		   </plugin>
+		   <plugin>
+			<groupId>org.sonatype.plugins</groupId>
+			<artifactId>nexus-staging-maven-plugin</artifactId>
+			<extensions>true</extensions>
+			<executions>
+			    <execution>
+				<id>default-deploy</id>
+				<phase>deploy</phase>
+				<goals>
+				    <goal>deploy</goal>
+				</goals>
+			    </execution>
+			</executions>
+			<configuration>
+			    <serverId>ossrh</serverId>
+			    <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+			    <autoReleaseAfterClose>false</autoReleaseAfterClose>
+			</configuration>
+		    </plugin>
+		</plugins>
+	    </build>
+	</profile>
+
+	<!--
+	    This profile is used for deploying a JavaMail SNAPSHOT release.
+	    It's identical to the above deploy-release profile except that
+	    artifacts aren't signed.
+	-->
+	<profile>
+	    <id>deploy-snapshot</id>
+	    <modules>
+		<module>parent-distrib</module>
+	    </modules>
+	    <build>
+		<pluginManagement>
+		    <plugins>
+			<plugin>
+			    <!-- Enables step-by-step staging deployment -->
+			    <groupId>org.sonatype.plugins</groupId>
+			    <artifactId>nexus-staging-maven-plugin</artifactId>
+			    <version>1.6.7</version>
+			</plugin>
+		    </plugins>
+		</pluginManagement>
+		<plugins>
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-surefire-plugin</artifactId>
+			<configuration>
+			    <skip>true</skip>
+			</configuration>
+		    </plugin>
+
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-javadoc-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>attach-javadocs</id>
+				<goals>
+				    <goal>jar</goal>
+				</goals>
+			    </execution>
+			</executions>
+		    </plugin>
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-deploy-plugin</artifactId>
+			<configuration>
+			   <skip>true</skip> <!-- To prefer nexus-staging-maven-plugin -->
+			</configuration>
+		   </plugin>
+		   <plugin>
+			<groupId>org.sonatype.plugins</groupId>
+			<artifactId>nexus-staging-maven-plugin</artifactId>
+			<extensions>true</extensions>
+			<executions>
+			    <execution>
+				<id>default-deploy</id>
+				<phase>deploy</phase>
+				<goals>
+				    <goal>deploy</goal>
+				</goals>
+			    </execution>
+			</executions>
+			<configuration>
+			    <serverId>ossrh</serverId>
+			    <nexusUrl>https://oss.sonatype.org/</nexusUrl>
+			    <autoReleaseAfterClose>false</autoReleaseAfterClose>
+			</configuration>
+		    </plugin>
+		</plugins>
+	    </build>
+	</profile>
+
+	<!--
+	    A special profile for compiling with the real JDK 1.7
+	    compiler, to make sure there are no accidental dependencies
+	    on JDK 1.8 or newer APIs.  Set the property javac.path to the path
+	    to the JDK 1.7 compiler, e.g.,
+	    "mvn -P1.7 -Djavac.path=/opt/jdk1.7/bin/javac".
+	-->
+	<profile>
+	    <id>1.7</id>
+	    <build>
+		<plugins>
+		    <plugin>
+			<artifactId>maven-compiler-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>default-compile</id>
+				<configuration>
+				    <fork>true</fork>
+				    <executable>${javac.path}</executable>
+				    <compilerVersion>1.7</compilerVersion>
+				    <source>1.7</source>
+				    <target>1.7</target>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		    <!--
+			Only require JDK 1.7.
+		    -->
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-enforcer-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>enforce-version</id>
+				<goals>
+				    <goal>enforce</goal>
+				</goals>
+				<configuration>
+				    <rules>
+					<requireMavenVersion>
+					    <version>[3.0.3,)</version>
+					</requireMavenVersion>
+					<requireJavaVersion>
+					    <version>1.7</version>
+					</requireJavaVersion>
+				    </rules>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		</plugins>
+	    </build>
+	</profile>
+
+	<!--
+	    A special profile for compiling with JDK 9.
+	-->
+	<profile>
+	    <id>9</id>
+	    <activation>
+		<jdk>9</jdk>
+	    </activation>
+	    <build>
+		<plugins>
+		    <plugin>
+			<artifactId>maven-compiler-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>default-compile</id>
+				<configuration>
+				    <compilerArgs>
+					<arg>-Xlint</arg>
+					<arg>-Xlint:-options</arg>
+					<arg>-Xlint:-path</arg>
+					<!--
+					    Too many finalize warnings.
+					<arg>-Werror</arg>
+					-->
+				    </compilerArgs>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		    <!--
+			Require JDK 9.
+		    -->
+		    <plugin>
+			<groupId>org.apache.maven.plugins</groupId>
+			<artifactId>maven-enforcer-plugin</artifactId>
+			<executions>
+			    <execution>
+				<id>enforce-version</id>
+				<goals>
+				    <goal>enforce</goal>
+				</goals>
+				<configuration>
+				    <rules>
+					<requireMavenVersion>
+					    <version>[3.0.3,)</version>
+					</requireMavenVersion>
+					<requireJavaVersion>
+					    <version>9</version>
+					</requireJavaVersion>
+				    </rules>
+				</configuration>
+			    </execution>
+			</executions>
+		    </plugin>
+		</plugins>
+	    </build>
+	</profile>
+    </profiles>
+
+    <build>
+	<defaultGoal>install</defaultGoal>
+	<plugins>
+	    <!--
+		Make sure we're using the correct version of maven.
+	    -->
+	    <plugin>
+		<groupId>org.apache.maven.plugins</groupId>
+		<artifactId>maven-enforcer-plugin</artifactId>
+		    <version>3.0.0-M1</version>
+		<executions>
+		    <execution>
+			<id>enforce-version</id>
+			<goals>
+			    <goal>enforce</goal>
+			</goals>
+			<configuration>
+			    <rules>
+				<requireMavenVersion>
+				    <version>[3.0.3,)</version>
+				</requireMavenVersion>
+				<requireJavaVersion>
+				    <version>1.8</version>
+				</requireJavaVersion>
+			    </rules>
+			</configuration>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		This plugin is reponsible for packaging artifacts
+		as OSGi bundles.  Please refer to
+		http://felix.apache.org/site/apache-felix-maven-bundle-plugin-bnd.html
+		for more information about how to use this plugin.
+	    -->
+	    <plugin>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>maven-bundle-plugin</artifactId>
+		<configuration>
+		    <instructions>
+			<Bundle-SymbolicName>
+			    ${mail.bundle.symbolicName}
+			</Bundle-SymbolicName>
+			<Export-Package>
+			    ${mail.packages.export}
+			</Export-Package>
+			<Import-Package>
+			    ${mail.packages.import}
+			</Import-Package>
+			<Private-Package>
+			    ${mail.packages.private}
+			</Private-Package>
+			<DynamicImport-Package>
+			    *
+			</DynamicImport-Package>
+		    </instructions>
+		    <niceManifest>true</niceManifest>
+		</configuration>
+		<!--
+		    Since we don't change the packaging type to bundle, we
+		    need to configure the plugin to execute the manifest goal
+		    during the process-classes phase of the build life cycle.
+		-->
+		<executions>
+		    <execution>
+			<id>osgi-manifest</id>
+			<phase>process-classes</phase>
+			<goals>
+			    <goal>manifest</goal>
+			</goals>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Since we don't want a qualifier like b05 or SNAPSHOT to
+		appear in the OSGi package version attribute, we use
+		the following plugin to populate a project property
+		with an OSGi version that is equivalent to the maven
+		version without the qualifier.
+	    -->
+	    <plugin>
+		<groupId>org.glassfish.hk2</groupId>
+		<artifactId>osgiversion-maven-plugin</artifactId>
+		<version>${hk2.plugin.version}</version>
+		<configuration>
+		    <dropVersionComponent>qualifier</dropVersionComponent>
+		    <versionPropertyName>mail.osgiversion</versionPropertyName>
+		</configuration>
+		<executions>
+		    <execution>
+			<id>compute-osgi-version</id>
+			<goals>
+			    <goal>compute-osgi-version</goal>
+			</goals>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Use the 1.8 compiler for JavaMail itself and the test classes,
+		but restrict it to 1.7 source and target.  The 1.8 compiler is
+		used *only* to pick up the definition of the Repeatable
+		annotation, needed by MailServiceDefinition.
+	    -->
+	    <plugin>
+		<artifactId>maven-compiler-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>default-compile</id>
+			<configuration>
+			    <source>1.7</source>
+			    <target>1.7</target>
+			    <!--
+				XXX - workaround for bug in maven compiler
+				plugin versions 3.0 - 3.3 (at least):
+				https://issues.apache.org/jira/browse/MCOMPILER-209
+			    -->
+			    <useIncrementalCompilation>false</useIncrementalCompilation>
+			    <excludes>
+				<exclude>
+				  module-info.java
+				</exclude>
+			    </excludes>
+			</configuration>
+		    </execution>
+		    <execution>
+			<id>default-testCompile</id>
+			<configuration>
+			    <source>1.7</source>
+			    <target>1.7</target>
+			</configuration>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<configuration>
+		    <finalName>${project.artifactId}</finalName>
+		    <archive>
+			<!--
+			    Configure the maven-jar-plugin to pick up
+			    META-INF/MANIFEST.MF that's generated by
+			    the maven-bundle-plugin.
+			-->
+			<manifestFile>
+			  ${project.build.outputDirectory}/META-INF/MANIFEST.MF
+			</manifestFile>
+			<manifestEntries>
+			    <Extension-Name>
+				${mail.extensionName}
+			    </Extension-Name>
+			    <Specification-Title>
+				${mail.specificationTitle}
+			    </Specification-Title>
+			    <Specification-Version>
+				${mail.spec.version}
+			    </Specification-Version>
+			    <Specification-Vendor>
+				${project.organization.name}
+			    </Specification-Vendor>
+			    <Implementation-Title>
+				${mail.implementationTitle}
+			    </Implementation-Title>
+			    <Implementation-Version>
+				${project.version}
+			    </Implementation-Version>
+			    <Implementation-Vendor>
+				${project.organization.name}
+			    </Implementation-Vendor>
+			    <Implementation-Vendor-Id>
+				com.sun
+			    </Implementation-Vendor-Id>
+			    <Probe-Provider-XML-File-Names>
+				${mail.probeFile}
+			    </Probe-Provider-XML-File-Names>
+			</manifestEntries>
+		    </archive>
+		    <excludes>
+			<exclude>**/*.java</exclude>
+		    </excludes>
+		</configuration>
+	    </plugin>
+
+	    <!--
+		Tell the source plugin about the sources that may have
+		been downloaded by the maven-dependency-plugin.
+
+		Also, need this plugin to define target/classes as another
+		source directory so that the filtered Version.java
+		that's copied there will also be compiled when using
+		the latest version of the maven-compiler-plugin.
+	    -->
+
+	    <plugin>
+		<groupId>org.codehaus.mojo</groupId>
+		<artifactId>build-helper-maven-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>add-source</id>
+			<phase>generate-sources</phase>
+			<goals>
+			    <goal>add-source</goal>
+			</goals>
+			<configuration>
+			    <sources>
+				<source> <!-- for dependencies -->
+				    ${project.build.directory}/sources
+				</source>
+				<source> <!-- for Version.java -->
+				    target/classes
+				</source>
+			    </sources>
+			</configuration>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Configure the source plugin here so that it will know
+		about the sources that may have been downloaded by the
+		maven-dependency-plugin and configured by the
+		build-helper-maven-plugin.
+	    -->
+	    <plugin>
+		<groupId>org.apache.maven.plugins</groupId>
+		<artifactId>maven-source-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>attach-sources</id>
+			<goals>
+			    <goal>jar-no-fork</goal> 
+			</goals>
+		    </execution>
+		</executions>
+		<configuration>
+		    <includePom>true</includePom>
+		    <!--
+			Since we added the classes directory using the
+			build-helper-maven-plugin above, we need to exclude
+			the class files from the source jar file.
+		    -->
+		    <excludes>
+			<exclude>**/*.class</exclude>
+		    </excludes>
+		</configuration>
+	    </plugin>
+
+<!-- not used
+	    <plugin>
+		<artifactId>maven-release-plugin</artifactId>
+		<configuration>
+		    <arguments>-P deploy</arguments>
+		</configuration>
+	    </plugin>
+-->
+	</plugins>
+
+	<pluginManagement>
+	    <plugins>
+		<plugin>
+		    <groupId>org.apache.maven.plugins</groupId>
+		    <artifactId>maven-compiler-plugin</artifactId>
+		    <version>3.7.0</version>
+		</plugin>
+		<plugin>
+		    <groupId>org.apache.maven.plugins</groupId>
+		    <artifactId>maven-surefire-plugin</artifactId>
+		    <version>2.4.3</version>
+		</plugin>
+		<plugin>
+		    <groupId>org.apache.maven.plugins</groupId>
+		    <artifactId>maven-jar-plugin</artifactId>
+		    <!-- need at least this version to make excludes work -->
+		    <version>2.4</version>
+		</plugin>
+		<plugin>
+		    <groupId>org.codehaus.mojo</groupId>
+		    <artifactId>build-helper-maven-plugin</artifactId>
+		    <version>1.7</version>
+		</plugin>
+		<plugin>
+		    <groupId>org.apache.maven.plugins</groupId>
+		    <artifactId>maven-assembly-plugin</artifactId>
+		    <version>2.4</version>
+		</plugin>
+		<plugin>
+		    <!--
+			By default, disable the SpotBugs plugin for all modules.
+			It's enabled in the modules where we actually want to
+			run it.
+		    -->
+		    <groupId>com.github.spotbugs</groupId>
+		    <artifactId>spotbugs-maven-plugin</artifactId>
+		    <version>${findbugs.version}</version>
+		    <configuration>
+			<skip>${findbugs.skip}</skip>
+			<threshold>${findbugs.threshold}</threshold>
+			<findbugsXmlWithMessages>true</findbugsXmlWithMessages>
+			<excludeFilterFile>
+			    ${findbugs.exclude}
+			</excludeFilterFile>
+		    </configuration>
+		</plugin>
+		<plugin>
+		    <groupId>org.apache.maven.plugins</groupId>
+		    <artifactId>maven-enforcer-plugin</artifactId>
+		    <version>3.0.0-M1</version>
+		</plugin>
+		<plugin>
+		    <groupId>org.apache.felix</groupId>
+		    <artifactId>maven-bundle-plugin</artifactId>
+		    <version>3.5.0</version>
+		</plugin>
+		<plugin>
+		    <groupId>org.apache.maven.plugins</groupId>
+		    <artifactId>maven-source-plugin</artifactId>
+		    <version>2.1.2</version>
+		</plugin>
+		<plugin>
+		    <groupId>org.apache.maven.plugins</groupId>
+		    <artifactId>maven-javadoc-plugin</artifactId>
+		    <version>3.0.0-M1</version>
+		    <configuration>
+			<!-- make all the APIs available for javadoc -->
+			<additionalDependencies>
+			    <additionalDependency>
+				<groupId>com.sun.mail</groupId>
+				<artifactId>jakarta.mail</artifactId>
+				<version>${mail.version}</version>
+			    </additionalDependency>
+			    <additionalDependency>
+				<groupId>com.sun.mail</groupId>
+				<artifactId>gimap</artifactId>
+				<version>${mail.version}</version>
+			    </additionalDependency>
+			</additionalDependencies>
+			<sourceFileExcludes>
+			    <sourceFileExclude>
+				module-info.java
+			    </sourceFileExclude>
+			</sourceFileExcludes>
+		    </configuration>
+		</plugin>
+		<plugin>
+		    <groupId>org.apache.maven.plugins</groupId>
+		    <artifactId>maven-war-plugin</artifactId>
+		    <version>2.2</version>
+		</plugin>
+		<plugin>
+		    <groupId>org.apache.maven.plugins</groupId>
+		    <artifactId>maven-project-info-reports-plugin</artifactId>
+		    <version>2.7</version>
+		</plugin>
+		<plugin>
+		    <groupId>org.glassfish.copyright</groupId>
+		    <artifactId>glassfish-copyright-maven-plugin</artifactId>
+		    <version>${copyright-plugin.version}</version>
+		    <configuration>
+			<scm>git</scm>
+			<scmOnly>true</scmOnly> 
+			<excludeFile>
+			    copyright-exclude
+			</excludeFile>
+		    </configuration>
+		</plugin>
+		<plugin>
+		    <groupId>org.sonatype.plugins</groupId>
+		    <artifactId>nexus-staging-maven-plugin</artifactId>
+		    <extensions>true</extensions>
+		    <configuration>
+			<serverId>ossrh</serverId>
+			<nexusUrl>https://oss.sonatype.org/</nexusUrl>
+		    </configuration>
+		</plugin>
+	    </plugins>
+	</pluginManagement>
+    </build>
+
+    <dependencyManagement>
+	<dependencies>
+	    <dependency>
+		<groupId>com.sun.activation</groupId>
+		<artifactId>jakarta.activation</artifactId>
+		<version>${activation.version}</version>
+	    </dependency>
+	    <dependency>
+		<groupId>com.sun.mail</groupId>
+		<artifactId>jakarta.mail</artifactId>
+		<version>${mail.version}</version>
+	    </dependency>
+	    <dependency>
+		<groupId>com.sun.mail</groupId>
+		<artifactId>dsn</artifactId>
+		<version>${mail.version}</version>
+	    </dependency>
+	    <dependency>
+		<groupId>com.sun.mail</groupId>
+		<artifactId>gimap</artifactId>
+		<version>${mail.version}</version>
+	    </dependency>
+	    <dependency>
+		<groupId>com.sun.mail</groupId>
+		<artifactId>mbox</artifactId>
+		<version>${mail.version}</version>
+	    </dependency>
+	    <dependency>
+		<groupId>com.sun.mail</groupId>
+		<artifactId>taglib</artifactId>
+		<version>${mail.version}</version>
+	    </dependency>
+	    <dependency>
+		<groupId>javax.servlet</groupId>
+		<artifactId>servlet-api</artifactId>
+		<version>2.5</version>
+	    </dependency>
+	    <dependency>
+		<groupId>javax.servlet.jsp</groupId>
+		<artifactId>jsp-api</artifactId>
+		<version>2.1</version>
+	    </dependency>
+	</dependencies>
+    </dependencyManagement>
+
+    <!--
+	I'm leaving this here but commented out to remind myself
+	NOT to add this back in because it breaks the Android
+	version of JavaMail.
+
+    <dependencies>
+	<dependency>
+	    <groupId>com.sun.activation</groupId>
+	    <artifactId>jakarta.activation</artifactId>
+	</dependency>
+    </dependencies>
+    -->
+
+    <reporting>
+	<plugins>
+	    <!--
+		Configure SpotBugs to run with "mvn site" and
+		generate html output that can be viewed directly.
+	    -->
+	    <plugin>
+		<groupId>com.github.spotbugs</groupId>
+		<artifactId>spotbugs-maven-plugin</artifactId>
+		<version>${findbugs.version}</version>
+		<configuration>
+		    <skip>${findbugs.skip}</skip>
+		    <threshold>${findbugs.threshold}</threshold>
+		    <excludeFilterFile>
+			${findbugs.exclude}
+		    </excludeFilterFile>
+		</configuration>
+	    </plugin>
+	</plugins>
+    </reporting>
+</project>
diff --git a/pop3/pom.xml b/pop3/pom.xml
new file mode 100644
index 0000000..09d8cf7
--- /dev/null
+++ b/pop3/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>parent-distrib</artifactId>
+	<version>1.6.3</version>
+	<relativePath>../parent-distrib/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>pop3</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API pop3 provider</name>
+
+    <properties>
+	<mail.packages.export>
+	    com.sun.mail.pop3; version=${mail.osgiversion}
+	</mail.packages.export>
+    </properties>
+</project>
diff --git a/pop3/src/main/java/module-info.java b/pop3/src/main/java/module-info.java
new file mode 100644
index 0000000..cc0e83e
--- /dev/null
+++ b/pop3/src/main/java/module-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+module com.sun.mail.pop3 {
+    exports com.sun.mail.pop3;
+    provides javax.mail.Provider with
+	com.sun.mail.pop3.POP3Provider, com.sun.mail.pop3.POP3SSLProvider;
+
+    requires jakarta.mail;
+    requires java.logging;
+    requires java.security.sasl;
+}
diff --git a/pop3/src/main/resources/META-INF/MANIFEST.MF b/pop3/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..ef209b2
--- /dev/null
+++ b/pop3/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Extension-Name: com.sun.mail.pop3
+Specification-Title: com.sun.mail.pop3
+Specification-Version: ${mail.spec.version}
+Specification-Vendor: ${project.organization.name}
+Implementation-Title: com.sun.mail.pop3
+Implementation-Version: ${mail.version}
+Implementation-Vendor: ${project.organization.name}
+Implementation-Vendor-Id: com.sun
diff --git a/pop3/src/main/resources/META-INF/javamail.providers b/pop3/src/main/resources/META-INF/javamail.providers
new file mode 100644
index 0000000..b6de993
--- /dev/null
+++ b/pop3/src/main/resources/META-INF/javamail.providers
@@ -0,0 +1,3 @@
+# JavaMail POP3 provider Oracle
+protocol=pop3; type=store; class=com.sun.mail.pop3.POP3Store; vendor=Oracle;
+protocol=pop3s; type=store; class=com.sun.mail.pop3.POP3SSLStore; vendor=Oracle;
diff --git a/pop3/src/main/resources/META-INF/services/javax.mail.Provider b/pop3/src/main/resources/META-INF/services/javax.mail.Provider
new file mode 100644
index 0000000..1ef4dd4
--- /dev/null
+++ b/pop3/src/main/resources/META-INF/services/javax.mail.Provider
@@ -0,0 +1,2 @@
+com.sun.mail.pop3.POP3Provider
+com.sun.mail.pop3.POP3SSLProvider
diff --git a/project.properties b/project.properties
new file mode 100644
index 0000000..f1cba98
--- /dev/null
+++ b/project.properties
@@ -0,0 +1,19 @@
+#
+# Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+glassfish.module.name=mail
+maven.mode.online=false
+activation.jar=$HOME/ext/activation.jar
diff --git a/project.xml b/project.xml
new file mode 100644
index 0000000..d3aa318
--- /dev/null
+++ b/project.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project>
+    <extend>../bootstrap/project.xml</extend>
+    <pomVersion>3</pomVersion>
+    <id>${glassfish.module.name}</id>
+    <artifactId>mail</artifactId>
+    <name>mail</name>
+    <groupId>${glassfish.image.name}</groupId>
+    <currentVersion>9.0-SNAPSHOT</currentVersion>
+    <organization>
+        <name>Sun Microsystems Inc.</name>
+        <url>http://www.sun.com/</url>
+        <logo>http://java.net/j2ee</logo>
+    </organization>
+    <inceptionYear>2005</inceptionYear>
+    <package>com.sun</package>
+    <logo>http://java.sun.com/j2ee</logo>
+    <description>SJSAS 9.0 Glassfish project.</description>
+    <shortDescription>Glassfish 9.0</shortDescription>
+    <url>http://java.sun.com/j2ee</url>
+    <issueTrackingUrl>Bugster</issueTrackingUrl>
+    <siteAddress>http://bugster</siteAddress>
+    <distributionDirectory>/java/re/sjsas_pe/</distributionDirectory>
+    <repository>
+        <connection>scm:${glassfish.cvsroot}</connection>
+        <url>${glassfish.viewcvs}</url>
+    </repository>
+</project>
+
diff --git a/publish/pom.xml b/publish/pom.xml
new file mode 100644
index 0000000..0533057
--- /dev/null
+++ b/publish/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>publish</artifactId>
+    <packaging>pom</packaging>
+    <version>1.6.3</version>
+    <name>JavaMail API publish project</name>
+
+    <build>
+	<plugins>
+	    <!--
+		This is the rule that collects the release artifacts and
+		creates the samples zip file for a release.
+	    -->
+	    <plugin>
+		<artifactId>maven-assembly-plugin</artifactId>
+		<inherited>false</inherited>
+		<executions>
+		    <execution>
+			<phase>package</phase>
+			<goals>
+			    <goal>single</goal>
+			</goals>
+			<configuration>
+			    <finalName>javamail</finalName>
+			    <descriptors>
+				<descriptor>publish.xml</descriptor>
+				<descriptor>samples.xml</descriptor>
+			    </descriptors>
+			</configuration>
+		    </execution>
+		</executions>
+	    </plugin>
+	</plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+	    <version>1.6.3</version>
+        </dependency>
+    <!--
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>demo</artifactId>
+	    <version>1.6.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>client</artifactId>
+	    <version>1.6.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>servlet</artifactId>
+	    <version>1.6.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>webapp</artifactId>
+	    <type>war</type>
+	    <version>1.6.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>taglib</artifactId>
+	    <version>1.6.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>logging</artifactId>
+	    <version>1.6.3</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>outlook</artifactId>
+	    <version>1.6.3</version>
+        </dependency>
+    -->
+    </dependencies>
+</project>
diff --git a/publish/publish.xml b/publish/publish.xml
new file mode 100644
index 0000000..a612348
--- /dev/null
+++ b/publish/publish.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<assembly>
+    <id>publish</id>
+    <formats>
+	<format>dir</format>
+    </formats>
+    <includeBaseDirectory>false</includeBaseDirectory>
+    <dependencySets>
+	<!-- include the main jakarta.mail.jar file -->
+	<dependencySet>
+	    <includes>
+		<include>com.sun.mail:jakarta.mail</include>
+	    </includes>
+	    <useTransitiveDependencies>false</useTransitiveDependencies>
+	    <unpack>false</unpack>
+	    <outputFileNameMapping>
+		jakarta.mail.jar
+	    </outputFileNameMapping>
+	</dependencySet>
+    </dependencySets>
+
+    <!-- include docs, specs, and javadocs -->
+    <fileSets>
+	<fileSet>
+	    <directory>../doc/release</directory>
+	    <outputDirectory></outputDirectory>
+	    <filtered>true</filtered>
+	    <includes>
+		<include>*.txt</include>
+	    </includes>
+	</fileSet>
+	<fileSet>
+	    <directory>../doc/spec</directory>
+	    <outputDirectory>docs</outputDirectory>
+	</fileSet>
+	<fileSet>
+	    <directory>../javadoc/target/site/apidocs</directory>
+	    <outputDirectory>docs/api</outputDirectory>
+	</fileSet>
+    </fileSets>
+</assembly>
diff --git a/publish/samples.xml b/publish/samples.xml
new file mode 100644
index 0000000..648f88f
--- /dev/null
+++ b/publish/samples.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<assembly>
+    <id>samples</id>
+    <formats>
+	<format>zip</format>
+    </formats>
+    <baseDirectory>javamail-samples</baseDirectory>
+    <fileSets>
+	<fileSet>
+	    <directory>../demo/src/main/java</directory>
+	    <outputDirectory>/</outputDirectory>
+	    <excludes>
+		<exclude>internal/**</exclude>
+	    </excludes>
+	</fileSet>
+	<fileSet>
+	    <directory>../client/src/main/java</directory>
+	    <outputDirectory>client</outputDirectory>
+	</fileSet>
+	<fileSet>
+	    <directory>../webapp/src/main/java</directory>
+	    <outputDirectory>webapp/src/classes</outputDirectory>
+	</fileSet>
+	<fileSet>
+	    <directory>../webapp/src/main/webapp</directory>
+	    <outputDirectory>webapp/src/docroot</outputDirectory>
+	</fileSet>
+	<fileSet>
+	    <directory>../webapp/</directory>
+	    <outputDirectory>webapp</outputDirectory>
+	    <includes>
+		<include>build.*</include>
+		<include>webapp.README.txt</include>
+	    </includes>
+	</fileSet>
+	<fileSet>
+	    <directory>../taglib/src/main/java</directory>
+	    <outputDirectory>webapp/src/taglib</outputDirectory>
+	</fileSet>
+	<fileSet>
+	    <directory>../taglib/src/main/resources</directory>
+	    <outputDirectory>webapp/src/taglib</outputDirectory>
+	</fileSet>
+	<fileSet>
+	    <directory>../outlook/src/main/java</directory>
+	    <outputDirectory>outlook</outputDirectory>
+	</fileSet>
+	<fileSet>
+	    <directory>../logging/src/main/java</directory>
+	    <outputDirectory>logging</outputDirectory>
+	</fileSet>
+    </fileSets>
+    <dependencySets>
+	<!-- include sources from the demo modules -->
+	<!--
+	<dependencySet>
+	    <includes>
+		<include>com.sun.mail:demo:jar:sources</include>
+	    </includes>
+	    <unpack>true</unpack>
+	</dependencySet>
+	-->
+    </dependencySets>
+</assembly>
diff --git a/servlet/pom.xml b/servlet/pom.xml
new file mode 100644
index 0000000..e77f59f
--- /dev/null
+++ b/servlet/pom.xml
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>servlet</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API demo servlet</name>
+
+    <build>
+        <plugins>
+	    <!--
+		Need to disable the maven-bundle-plugin because the
+		new version doesn't like classes in the default package.
+	    -->
+	    <plugin>
+		<groupId>org.apache.felix</groupId>
+		<artifactId>maven-bundle-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>osgi-manifest</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+
+	    <!--
+		Because the maven-jar-plugin depends on the manifest file
+		created by the maven-bundle-plugin, we need to disable it too.
+	    -->
+	    <plugin>
+		<artifactId>maven-jar-plugin</artifactId>
+		<executions>
+		    <execution>
+			<id>default-jar</id>
+			<phase>none</phase>
+		    </execution>
+		</executions>
+	    </plugin>
+        </plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/servlet/src/main/java/JavaMail.html b/servlet/src/main/java/JavaMail.html
new file mode 100644
index 0000000..0ce0f96
--- /dev/null
+++ b/servlet/src/main/java/JavaMail.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<HTML>
+<!--
+
+    Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<HEAD>
+	<META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=iso-8859-1">
+	<TITLE>JavaMail</TITLE>
+</HEAD>
+
+<BODY BGCOLOR="#CCCCFF">
+
+<FORM ACTION="/servlet/JavaMailServlet" METHOD="POST"
+	ENCTYPE="application/x-www-form-urlencoded">
+<P ALIGN="CENTER"><B><FONT SIZE="5" FACE="Arial, Helvetica">
+Welcome to JavaMail
+</FONT></B></P>
+
+<P ALIGN="CENTER"><B><FONT SIZE="5" FACE="Arial, Helvetica">
+HTML Email Reader Demo
+</FONT></B></P>
+<CENTER>
+<P>
+<TABLE BORDER="0" WIDTH="100%">
+	<TR>
+		<TD WIDTH="40%">
+			<P ALIGN="RIGHT"><FONT FACE="Arial, Helvetica">
+			IMAP Hostname:</FONT>
+		</TD>
+		<TD WIDTH="60%"><INPUT TYPE="TEXT" NAME="hostname" SIZE="25">
+		</TD>
+	</TR>
+	<TR>
+		<TD WIDTH="40%">
+			<P ALIGN="RIGHT"><FONT FACE="Arial, Helvetica">
+			Username:</FONT>
+		</TD>
+		<TD WIDTH="60%"><INPUT TYPE="TEXT" NAME="username" SIZE="25">
+		</TD>
+	</TR>
+	<TR>
+		<TD WIDTH="40%">
+			<P ALIGN="RIGHT"><FONT FACE="Arial, Helvetica">
+			Password:</FONT>
+		</TD>
+		<TD WIDTH="60%"><INPUT TYPE="PASSWORD" NAME="password"
+			SIZE="25"></TD>
+	</TR>
+</TABLE>
+<INPUT TYPE="SUBMIT" VALUE="Login">
+<INPUT TYPE="RESET" NAME="Reset" VALUE="Reset"></P>
+</CENTER>
+<P>
+<HR ALIGN="CENTER">
+</P>
+<P><B><I><FONT FACE="Arial, Helvetica">Overview:</FONT></I></B></P>
+
+<FONT SIZE="2" FACE="Arial, Helvetica">
+<b>THIS IS A DEMO!!!</b> Please see the JavaMailServlet.README.txt 
+file for information on how to
+compile and run the servlet.<br> <br>
+This is a servlet that demonstrates the use of JavaMail APIs
+in a 3-tier application. It allows the user to login to an 
+IMAP store, list all the messages in the INBOX folder, view
+selected messages, compose and send a message, and logout.
+</FONT>
+
+<P><B><I><FONT FACE="Arial, Helvetica">Features:</FONT></I></B></P>
+
+<UL>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">
+	HTML access to your IMAP mailbox</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">
+	Proxy-able anywhere HTTP can be proxied</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">FAST!</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">Easy to use</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">
+	Uses web browser's content handling capabilities</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">
+	Ultra small: 1 servlet (2 class files) and 1 html file (this one)</FONT>
+</UL>
+
+<P><B><I><FONT FACE="Arial, Helvetica">Limitations:</FONT></I></B></P>
+
+<UL>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">
+	Only INBOX support (no user folders)</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">
+	Can't delete, copy, move, print, save, forward, reply to, search in
+	messages --<BR>
+	but it could be done</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">
+	Doesn't check for new messages (have to log out and log back it)</FONT>
+</UL>
+
+<P>
+<HR ALIGN="CENTER">
+</P>
+
+<P><FONT FACE="Arial, Helvetica">Feedback to </FONT>
+<A HREF="mailto:javamail_ww@oracle.com">
+<FONT FACE="Arial, Helvetica">javamail_ww@oracle.com</FONT></A>
+</FORM>
+
+</BODY>
+
+</HTML>
diff --git a/servlet/src/main/java/JavaMailServlet.java b/servlet/src/main/java/JavaMailServlet.java
new file mode 100644
index 0000000..74c0775
--- /dev/null
+++ b/servlet/src/main/java/JavaMailServlet.java
@@ -0,0 +1,660 @@
+/*
+ * Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+import java.io.*;
+import java.util.*;
+import java.text.*;
+
+import javax.servlet.*;
+import javax.servlet.http.*;
+import javax.mail.*;
+import javax.mail.Part;
+import javax.mail.internet.*;
+import javax.activation.*;
+
+
+/**
+ * This is a servlet that demonstrates the use of JavaMail APIs
+ * in a 3-tier application. It allows the user to login to an 
+ * IMAP store, list all the messages in the INBOX folder, view
+ * selected messages, compose and send a message, and logout.
+ * <p>
+ * Please note: This is NOT an example of how to write servlets! 
+ * This is simply to show that JavaMail can be used in a servlet.
+ * <p>
+ * For more information on this servlet, see the 
+ * JavaMailServlet.README.txt file. 
+ * <p>
+ * For more information on servlets, see 
+ * <a href="http://java.sun.com/products/java-server/servlets/index.html">
+ * http://java.sun.com/products/java-server/servlets/index.html</a>
+ *
+ * @author Max Spivak
+ */
+public class JavaMailServlet extends HttpServlet implements SingleThreadModel {
+    String protocol = "imap";
+    String mbox = "INBOX";
+
+
+    /**
+     * This method handles the "POST" submission from two forms: the
+     * login form and the message compose form. The login form has the
+     * following parameters: <code>hostname</code>, <code>username</code>,
+     * and <code>password</code>. The <code>send</code> parameter denotes
+     * that the method is processing the compose form submission.
+     */
+    public  void doPost(HttpServletRequest req, HttpServletResponse res)
+	throws ServletException, IOException {
+
+	// get the session
+	HttpSession ssn = req.getSession(true);
+
+	String send = req.getParameter("send");
+	String host = req.getParameter("hostname");
+	String user = req.getParameter("username");
+	String passwd = req.getParameter("password");
+	URLName url = new URLName(protocol, host, -1, mbox, user, passwd);
+
+	ServletOutputStream out = res.getOutputStream();
+	res.setContentType("text/html");
+	out.println("<html><body bgcolor=\"#CCCCFF\">");
+
+	if (send != null) {
+	    // process message sending
+	    send(req, res, out, ssn);
+
+	} else {
+	    // initial login
+
+	    // create 
+	    MailUserData mud = new MailUserData(url);
+	    ssn.putValue("javamailservlet", mud);
+	    
+	    try {
+		Properties props = System.getProperties();
+		props.put("mail.smtp.host", host);
+		Session session = Session.getDefaultInstance(props, null);
+		session.setDebug(false);
+		Store store = session.getStore(url);
+		store.connect();
+		Folder folder = store.getDefaultFolder();
+		if (folder == null) 
+		    throw new MessagingException("No default folder");
+		
+		folder = folder.getFolder(mbox);
+		if (folder == null)
+		    throw new MessagingException("Invalid folder");
+		
+		folder.open(Folder.READ_WRITE);
+		int totalMessages = folder.getMessageCount();
+		Message[] msgs = folder.getMessages();
+		FetchProfile fp = new FetchProfile();
+		fp.add(FetchProfile.Item.ENVELOPE);
+		folder.fetch(msgs, fp);
+		
+		// track who logged in
+		System.out.println("Login from: " + store.getURLName());
+		
+		// save stuff into MUD
+		mud.setSession(session);
+		mud.setStore(store);
+		mud.setFolder(folder);
+		
+		// splash
+		out.print("<center>");
+		out.print("<font face=\"Arial,Helvetica\" font size=+3>");
+		out.println("<b>Welcome to JavaMail!</b></font></center><p>");
+
+		// folder table
+		out.println("<table width=\"50%\" border=0 align=center>");
+		// folder name column header
+		out.print("<tr><td width=\"75%\" bgcolor=\"#ffffcc\">");
+		out.print("<font face=\"Arial,Helvetica\" font size=-1>");
+		out.println("<b>FolderName</b></font></td><br>");
+		// msg count column header
+		out.print("<td width=\"25%\" bgcolor=\"#ffffcc\">");
+		out.print("<font face=\"Arial,Helvetica\" font size=-1>");
+		out.println("<b>Messages</b></font></td><br>");
+		out.println("</tr>");
+		// folder name
+		out.print("<tr><td width=\"75%\" bgcolor=\"#ffffff\">");
+		out.print("<a href=\"" + HttpUtils.getRequestURL(req) + "\">" +
+			  "Inbox" + "</a></td><br>");
+		// msg count
+		out.println("<td width=\"25%\" bgcolor=\"#ffffff\">" + 
+			    totalMessages + "</td>");
+		out.println("</tr>");
+		out.println("</table");
+	    } catch (Exception ex) {
+		out.println(ex.toString());            
+	    } finally {
+		out.println("</body></html>");
+		out.close();
+	    }
+	}
+    }
+
+
+    /**
+     * This method handles the GET requests for the client.
+     */
+    public void doGet (HttpServletRequest req, HttpServletResponse res)
+	throws ServletException, IOException {
+
+	HttpSession ses = req.getSession(false); // before we write to out
+	ServletOutputStream out = res.getOutputStream();
+	MailUserData mud = getMUD(ses);
+
+	if (mud == null) {
+	    res.setContentType("text/html");
+	    out.println("<html><body>Please Login (no session)</body></html>");
+	    out.close();
+	    return;
+	}
+
+	if (!mud.getStore().isConnected()) {
+	    res.setContentType("text/html");
+	    out.println("<html><body>Not Connected To Store</body></html>");
+	    out.close();
+	    return;
+	}
+
+
+	// mux that takes a GET request, based on parameters figures
+	// out what it should do, and routes it to the
+	// appropriate method
+
+	// get url parameters
+	String msgStr = req.getParameter("message");
+	String logout = req.getParameter("logout");
+	String compose = req.getParameter("compose");
+	String part = req.getParameter("part");
+	int msgNum = -1;
+	int partNum = -1;
+
+	// process url params
+	if (msgStr != null) {
+	    // operate on message "msgStr"
+	    msgNum = Integer.parseInt(msgStr);
+
+	    if (part == null) {
+		// display message "msgStr"
+		res.setContentType("text/html");
+		displayMessage(mud, req, out, msgNum);
+
+	    } else {
+		// display part "part" in message "msgStr"
+		partNum = Integer.parseInt(part);
+		displayPart(mud, msgNum, partNum, out, res);
+	    }
+
+	} else if (compose != null) {
+	    // display compose form
+	    compose(mud, res, out);
+
+	} else if (logout != null) {
+	    // process logout
+	    try {
+		mud.getFolder().close(false);
+		mud.getStore().close();
+		ses.invalidate();
+		out.println("<html><body>Logged out OK</body></html>");
+	    } catch (MessagingException mex) {
+		out.println(mex.toString());
+	    }
+
+	} else {
+	    // display headers
+	    displayHeaders(mud, req, out);
+	}
+    }
+
+    /* main method to display messages */
+    private void displayMessage(MailUserData mud, HttpServletRequest req, 
+				ServletOutputStream out, int msgNum) 
+	throws IOException {
+	    
+	out.println("<html>");
+	out.println("<HEAD><TITLE>JavaMail Servlet</TITLE></HEAD>");
+	out.println("<BODY bgcolor=\"#ccccff\">");
+	out.print("<center><font face=\"Arial,Helvetica\" ");
+	out.println("font size=\"+3\"><b>");
+	out.println("Message " + (msgNum+1) + " in folder " + 
+		    mud.getStore().getURLName() + 
+		    "/INBOX</b></font></center><p>");
+
+	try {
+	    Message msg = mud.getFolder().getMessage(msgNum);
+
+	    // first, display this message's headers
+	    displayMessageHeaders(mud, msg, out);
+
+	    // and now, handle the content
+	    Object o = msg.getContent();
+	    
+	    //if (o instanceof String) {
+	    if (msg.isMimeType("text/plain")) {
+		out.println("<pre>");
+		out.println((String)o);
+		out.println("</pre>");
+	    //} else if (o instanceof Multipart){
+	    } else if (msg.isMimeType("multipart/*")) {
+		Multipart mp = (Multipart)o;
+		int cnt = mp.getCount();
+		for (int i = 0; i < cnt; i++) {
+		    displayPart(mud, msgNum, mp.getBodyPart(i), i, req, out);
+		}
+	    } else {
+		out.println(msg.getContentType());
+	    }
+
+	} catch (MessagingException mex) {
+	    out.println(mex.toString());
+	}
+
+	out.println("</BODY></html>");
+	out.close();
+    }
+
+    /** 
+     * This method displays a message part. <code>text/plain</code>
+     * content parts are displayed inline. For all other parts,
+     * a URL is generated and displayed; clicking on the URL
+     * brings up the part in a separate page.
+     */
+    private void displayPart(MailUserData mud, int msgNum, Part part, 
+			     int partNum, HttpServletRequest req, 
+			     ServletOutputStream out) 
+	throws IOException {
+
+	if (partNum != 0)
+	    out.println("<p><hr>");
+
+	try {
+
+	    String sct = part.getContentType();
+	    if (sct == null) {
+		out.println("invalid part");
+		return;
+	    }
+	    ContentType ct = new ContentType(sct);
+	    
+	    if (partNum != 0)
+		out.println("<b>Attachment Type:</b> " +   
+			    ct.getBaseType() + "<br>");
+
+	    if (ct.match("text/plain")) {  
+		// display text/plain inline
+		out.println("<pre>");
+		out.println((String)part.getContent());
+		out.println("</pre>");
+
+	    } else {
+		// generate a url for this part
+		String s;
+		if ((s = part.getFileName()) != null)
+		    out.println("<b>Filename:</b> " + s + "<br>");
+		s = null;
+		if ((s = part.getDescription()) != null)
+		    out.println("<b>Description:</b> " + s + "<br>");
+		
+		out.println("<a href=\"" +
+			    HttpUtils.getRequestURL(req) + 
+			    "?message=" +
+			    msgNum + "&part=" +
+			    partNum + "\">Display Attachment</a>");
+	    }
+	} catch (MessagingException mex) {
+	    out.println(mex.toString());
+	}
+    }
+
+    /**
+     * This method gets the stream from for a given msg part and 
+     * pushes it out to the browser with the correct content type.
+     * Used to display attachments and relies on the browser's
+     * content handling capabilities.
+     */
+    private void displayPart(MailUserData mud, int msgNum,
+			     int partNum, ServletOutputStream out, 
+			     HttpServletResponse res) 
+	throws IOException {
+
+	Part part = null;
+	
+	try {
+	    Message msg = mud.getFolder().getMessage(msgNum);
+
+	    Multipart mp = (Multipart)msg.getContent();
+	    part = mp.getBodyPart(partNum);
+	    
+	    String sct = part.getContentType();
+	    if (sct == null) {
+		out.println("invalid part");
+		return;
+	    }
+	    ContentType ct = new ContentType(sct);
+
+	    res.setContentType(ct.getBaseType());
+	    InputStream is = part.getInputStream();
+	    int i;
+	    while ((i = is.read()) != -1)
+		out.write(i);
+	    out.flush();
+	    out.close();
+	} catch (MessagingException mex) {
+	    out.println(mex.toString());
+	}
+    }
+
+    /**
+     * This is a utility message that pretty-prints the message 
+     * headers for message that is being displayed.
+     */
+    private void displayMessageHeaders(MailUserData mud, Message msg, 
+				       ServletOutputStream out) 
+	throws IOException {
+
+	try {
+	    out.println("<b>Date:</b> " + msg.getSentDate() + "<br>");
+
+	    Address[] fr = msg.getFrom();
+	    if (fr != null) {
+		boolean tf = true;
+		out.print("<b>From:</b> ");
+		for (int i = 0; i < fr.length; i++) {
+		    out.print(((tf) ? " " : ", ") + getDisplayAddress(fr[i]));
+		    tf = false;
+		}
+		out.println("<br>");
+	    }
+
+	    Address[] to = msg.getRecipients(Message.RecipientType.TO);
+	    if (to != null) {
+		boolean tf = true;
+		out.print("<b>To:</b> ");
+		for (int i = 0; i < to.length; i++) {
+		    out.print(((tf) ? " " : ", ") + getDisplayAddress(to[i]));
+		    tf = false;
+		}
+		out.println("<br>");
+	    }
+
+	    Address[] cc = msg.getRecipients(Message.RecipientType.CC);
+	    if (cc != null) {
+		boolean cf = true;
+		out.print("<b>CC:</b> ");
+		for (int i = 0; i < cc.length; i++) {
+		    out.print(((cf) ? " " : ", ") + getDisplayAddress(cc[i]));
+		    cf = false;
+		}
+		out.println("<br>");
+	    }
+	    
+	    out.print("<b>Subject:</b> " + 
+		      ((msg.getSubject() !=null) ? msg.getSubject() : "") + 
+		      "<br>");
+
+	} catch (MessagingException mex) {
+	    out.println(msg.toString());
+	}
+    }
+
+    /**
+     * This method displays the URL's for the available commands and the
+     * INBOX headerlist 
+     */
+    private void displayHeaders(MailUserData mud,
+				HttpServletRequest req, 
+				ServletOutputStream out)
+	throws IOException {
+
+	SimpleDateFormat df = new SimpleDateFormat("EE M/d/yy");
+
+	out.println("<html>");
+	out.println("<HEAD><TITLE>JavaMail Servlet</TITLE></HEAD>");
+	out.println("<BODY bgcolor=\"#ccccff\"><hr>");
+	out.print("<center><font face=\"Arial,Helvetica\" font size=\"+3\">");
+	out.println("<b>Folder " + mud.getStore().getURLName() + 
+		    "/INBOX</b></font></center><p>");
+
+	// URL's for the commands that are available
+	out.println("<font face=\"Arial,Helvetica\" font size=\"+3\"><b>");
+	out.println("<a href=\"" +
+		    HttpUtils.getRequestURL(req) +
+		    "?logout=true\">Logout</a>");
+	out.println("<a href=\"" +
+		    HttpUtils.getRequestURL(req) +
+		    "?compose=true\" target=\"compose\">Compose</a>");
+	out.println("</b></font>");
+	out.println("<hr>");
+
+	// List headers in a table
+	out.print("<table cellpadding=1 cellspacing=1 "); // table
+	out.println("width=\"100%\" border=1>");          // settings
+
+	// sender column header
+	out.println("<tr><td width=\"25%\" bgcolor=\"ffffcc\">");
+	out.println("<font face=\"Arial,Helvetica\" font size=\"+1\">");
+	out.println("<b>Sender</b></font></td>");
+	// date column header
+	out.println("<td width=\"15%\" bgcolor=\"ffffcc\">");
+	out.println("<font face=\"Arial,Helvetica\" font size=\"+1\">");
+	out.println("<b>Date</b></font></td>");
+	// subject column header
+	out.println("<td bgcolor=\"ffffcc\">");
+	out.println("<font face=\"Arial,Helvetica\" font size=\"+1\">");
+	out.println("<b>Subject</b></font></td></tr>");
+
+	try {
+	    Folder f = mud.getFolder();
+	    int msgCount = f.getMessageCount();
+	    Message m = null;
+	    // for each message, show its headers
+	    for (int i = 1; i <= msgCount; i++) {
+		m = f.getMessage(i);
+		
+		// if message has the DELETED flag set, don't display it
+		if (m.isSet(Flags.Flag.DELETED))
+		    continue;
+
+		// from 
+		out.println("<tr valigh=middle>");
+		out.print("<td width=\"25%\" bgcolor=\"ffffff\">");
+		out.println("<font face=\"Arial,Helvetica\">" + 
+			    ((m.getFrom() != null) ? 
+				       m.getFrom()[0].toString() : 
+				       "" ) +
+			    "</font></td>");
+
+		// date
+		out.print("<td nowrap width=\"15%\" bgcolor=\"ffffff\">");
+		out.println("<font face=\"Arial,Helvetica\">" + 
+			    df.format((m.getSentDate()!=null) ? 
+				      m.getSentDate() : m.getReceivedDate()) +
+			    "</font></td>");
+
+		// subject & link
+		out.print("<td bgcolor=\"ffffff\">");
+		out.println("<font face=\"Arial,Helvetica\">" + 
+			    "<a href=\"" +
+			    HttpUtils.getRequestURL(req) + 
+			    "?message=" +
+			    i + "\">" +
+			    ((m.getSubject() != null) ? 
+				   m.getSubject() :
+				   "<i>No Subject</i>") +
+			    "</a>" +
+			    "</font></td>");
+		out.println("</tr>");
+	    }
+	} catch (MessagingException mex) {
+	    out.println("<tr><td>" + mex.toString() + "</td></tr>");
+	    mex.printStackTrace();
+	}
+
+	out.println("</table>");
+	out.println("</BODY></html>");
+	out.flush();
+	out.close();
+    }
+
+    /** 
+     * This method handles the request when the user hits the
+     * <i>Compose</i> link. It send the compose form to the browser.
+     */
+    private void compose(MailUserData mud, HttpServletResponse res,
+			 ServletOutputStream out) 
+	throws IOException {
+	
+	res.setContentType("text/html");
+	out.println(composeForm);
+	out.close();
+    }
+
+    /**
+     * This method processes the send request from the compose form
+     */
+    private void send(HttpServletRequest req, HttpServletResponse res,
+		      ServletOutputStream out, HttpSession ssn)
+	throws IOException {
+	    
+	String to = req.getParameter("to");
+	String cc = req.getParameter("cc");
+	String subj = req.getParameter("subject");
+	String text = req.getParameter("text");
+
+	try {
+	    MailUserData mud = getMUD(ssn);
+	    if (mud == null)
+		throw new Exception("trying to send, but not logged in");
+
+	    Message msg = new MimeMessage(mud.getSession());
+	    InternetAddress[] toAddrs = null, ccAddrs = null;
+
+	    if (to != null) {
+		toAddrs = InternetAddress.parse(to, false);
+		msg.setRecipients(Message.RecipientType.TO, toAddrs);
+	    } else
+		throw new MessagingException("No \"To\" address specified");
+
+	    if (cc != null) {
+		ccAddrs = InternetAddress.parse(cc, false);
+		msg.setRecipients(Message.RecipientType.CC, ccAddrs);
+	    }
+
+	    if (subj != null)
+		msg.setSubject(subj);
+
+	    URLName u = mud.getURLName();
+	    msg.setFrom(new InternetAddress(u.getUsername() + "@" +
+					    u.getHost()));
+
+	    if (text != null)
+		msg.setText(text);
+
+	    Transport.send(msg);
+	    
+	    out.println("<h1>Message sent successfully</h1></body></html>");
+	    out.close();
+	    
+	} catch (Exception mex) {
+	    out.println("<h1>Error sending message.</h1>");
+	    out.println(mex.toString());
+	    out.println("<br></body></html>");
+	}
+    }
+
+
+    // utility method; returns a string suitable for msg header display
+    private String getDisplayAddress(Address a) {
+	String pers = null;
+	String addr = null;
+	if (a instanceof InternetAddress &&
+	    ((pers = ((InternetAddress)a).getPersonal()) != null)) {
+	    
+	    addr = pers + "  "+"&lt;"+((InternetAddress)a).getAddress()+"&gt;";
+	} else 
+	    addr = a.toString();
+	
+	return addr;
+    }
+
+    // utility method; retrieve the MailUserData 
+    // from the HttpSession and return it
+    private MailUserData getMUD(HttpSession ses) throws IOException {
+	MailUserData mud = null;
+
+	if (ses == null) {
+	    return null;
+	} else {
+	    if ((mud = (MailUserData)ses.getValue("javamailservlet")) == null){
+		return null;
+	    }
+	}
+	return mud;
+    }
+
+
+    public String getServletInfo() {
+	return "A mail reader servlet";
+    }
+
+    /**
+     * This is the HTML code for the compose form. Another option would
+     * have been to use a separate html page.
+     */
+    private static String composeForm = "<HTML><HEAD><TITLE>JavaMail Compose</TITLE></HEAD><BODY BGCOLOR=\"#CCCCFF\"><FORM ACTION=\"/servlet/JavaMailServlet\" METHOD=\"POST\"><input type=\"hidden\" name=\"send\" value=\"send\"><P ALIGN=\"CENTER\"><B><FONT SIZE=\"4\" FACE=\"Verdana, Arial, Helvetica\">JavaMail Compose Message</FONT></B><P><TABLE BORDER=\"0\" WIDTH=\"100%\"><TR><TD WIDTH=\"16%\" HEIGHT=\"22\">	<P ALIGN=\"RIGHT\"><B><FONT FACE=\"Verdana, Arial, Helvetica\">To:</FONT></B></TD><TD WIDTH=\"84%\" HEIGHT=\"22\"><INPUT TYPE=\"TEXT\" NAME=\"to\" SIZE=\"30\"> <FONT SIZE=\"1\" FACE=\"Verdana, Arial, Helvetica\"> (separate addresses with commas)</FONT></TD></TR><TR><TD WIDTH=\"16%\"><P ALIGN=\"RIGHT\"><B><FONT FACE=\"Verdana, Arial, Helvetica\">CC:</FONT></B></TD><TD WIDTH=\"84%\"><INPUT TYPE=\"TEXT\" NAME=\"cc\" SIZE=\"30\"> <FONT SIZE=\"1\" FACE=\"Verdana, Arial, Helvetica\"> (separate addresses with commas)</FONT></TD></TR><TR><TD WIDTH=\"16%\"><P ALIGN=\"RIGHT\"><B><FONT FACE=\"Verdana, Arial, Helvetica\">Subject:</FONT></B></TD><TD WIDTH=\"84%\"><INPUT TYPE=\"TEXT\" NAME=\"subject\" SIZE=\"55\"></TD></TR><TR><TD WIDTH=\"16%\">&nbsp;</TD><TD WIDTH=\"84%\"><TEXTAREA NAME=\"text\" ROWS=\"15\" COLS=\"53\"></TEXTAREA></TD></TR><TR><TD WIDTH=\"16%\" HEIGHT=\"32\">&nbsp;</TD><TD WIDTH=\"84%\" HEIGHT=\"32\"><INPUT TYPE=\"SUBMIT\" NAME=\"Send\" VALUE=\"Send\"><INPUT TYPE=\"RESET\" NAME=\"Reset\" VALUE=\"Reset\"></TD></TR></TABLE></FORM></BODY></HTML>";
+
+}
+
+
+/**
+ * This class is used to store session data for each user's session. It
+ * is stored in the HttpSession.
+ */
+class MailUserData {
+    URLName url;
+    Session session;
+    Store store;
+    Folder folder;
+
+    public MailUserData(URLName urlname) {
+	url = urlname;
+    }
+
+    public URLName getURLName() {
+	return url;
+    }
+
+    public Session getSession() {
+	return session;
+    }
+
+    public void setSession(Session s) {
+	session = s;
+    }
+
+    public Store getStore() {
+	return store;
+    }
+
+    public void setStore(Store s) {
+	store = s;
+    }
+
+    public Folder getFolder() {
+	return folder;
+    }
+
+    public void setFolder(Folder f) {
+	folder = f;
+    }
+}
diff --git a/servlet/src/main/java/README.txt b/servlet/src/main/java/README.txt
new file mode 100644
index 0000000..ad2896a
--- /dev/null
+++ b/servlet/src/main/java/README.txt
@@ -0,0 +1,145 @@
+			     JavaMail Servlet
+			     ~~~~~~~~~~~~~~~~
+
+Overview:
+=========
+
+JavaMailServlet should not be taken as a demo of how to use the Java 
+Servlet API. It is rather an example of how the JavaMail APIs could 
+be used in a server in a three-tier environment described by the 
+following diagram:
+
+	+-----------+        +-----------+        +-----------+
+	|   IMAP    |        |           |        |           |
+	|  Server   |<-IMAP->| JavaMail  |<-HTTP->|    WWW    |
+	+-----------+        | Servlet   |--HTML->|  Browser  |
+	|   SMTP    |<-SMTP->|           |        |           |
+	|  Server   |        |           |        |           |
+	+-----------+        +-----------+        +-----------+
+
+
+The JavaMailServlet supports the following functionality:
+	* login to an IMAP server
+	* list all the messages in the INBOX folder
+	* view the selected message
+	* compose and send a message
+
+
+Setting up and running the demo:
+================================
+Note: These instructions explain how to compile and run the servlet 
+demo with Java Web Server (JWS). The procedure should be similar with 
+other web servers that support the Java Servlet API.
+
+	1. Download the latest version of the Java Web Server from
+	   http://www.sun.com/software/jwebserver/index.html and
+	   install according to the included documentation. Make
+	   sure JWS is listening to requests on port 80 (you may 
+	   need to modify it from default port 8080; use the JWS 
+	   Admin Applet). Make sure you can load the Java Web 
+	   Server welcome page when you connect with your browser 
+	   to the machine that the server is installed on. Also,
+	   make sure your web browser has cookie support turned on.
+
+	2. Set your classpath to include the following:
+	    * mail.jar:		in the JavaMail API distribution
+	    * activation.jar:	in the JAF distribution
+	    * jws.jar:		in the /lib/ directory in JWS installation
+
+	3. In javamail-1.1/demo/servlet directory, compile the 
+	   JavaMailServlet.java file. That produces two class files,
+	   JavaMailServlet.class and MailUserData.class. Copy these
+	   class files to the /servlets/ directory in the JWS 
+	   installation.
+
+	4. Copy the mail.jar and activation.jar to the /lib/
+	   directory in the JWS installation.
+
+	5. Copy the JavaMail.html file to the /public_html/
+	   directory in the JWS installation.
+
+	6. Restart Java Web Server to pick up the new jar files
+	   added to its lib directory. Check again that you can
+	   load the default JWS page to verify that the server
+	   is working fine.
+
+	7. Using a web browser, go to 
+	   http://<hostname>/JavaMail.html and login to a
+	   valid IMAP account. From here on, you can view 
+	   messages in your INBOX and create and send new 
+	   messages.
+
+
+
+JavaMailServlet Design:
+=======================
+
+The following is a brief description of JavaMailServlet class. It
+is not intended to serve as an example of how to develop servlets;
+see http://java.sun.com/products/servlet for information on the Java
+Servlet API. You may find it useful to refer to JavaMailServlet.java
+source while reading this.
+
+The JavaMailServlet uses two primary methods to process all
+requests: doPost() and doGet(). doPost() processes submissions
+from the login and compose forms. When the user logs in, the
+doPost() method gets a JavaMail Session and uses the values
+of the "hostname", "username" and "password" parameters to login
+to the IMAP Store and get the INBOX Folder. To preserve state
+between multiple HTTP requests, the necessary information
+(Session, Store, Folder, URLName) are collected in the
+MailUserData object which is stored using JWS's Session
+technology (don't confuse HttpSession and JavaMail's Session--
+they are different). Finally, the doPost() method outputs 
+a table listing the INBOX and the number of messages in it.
+
+Clicking on the "INBOX" link calls the doGet() method
+which displays a table of message headers. (See the doGet() 
+and displayHeaders() methods.)
+
+Clicking on a message generates a request to the servlet with
+the message sequence number as a parameter. The doGet() method
+receives the request and calls the displayMessage() method 
+passing it the message sequence number to display. The 
+displayMessage() method first lists the message headers 
+by calling the displayMessageHeaders() utility method. 
+For text/plain messages, the message content is then displayed
+as a string wrapped in HTML <pre>...</pre> tags. For multipart 
+messages, each part is passed to the displayPart() method.
+
+There are two displayPart() methods. The one with signature
+	displayPart(MailUserData mud, int msgNum, Part part, 
+		    int partNum, HttpServletRequest req, 
+		    ServletOutputStream out) 
+is called from the displayMessage() method for each part. For
+any part with text/plain content type, the content is output
+as a string wrapped in HTML <pre>...</pre> tags. For other
+content types, a link representing the part is displayed,
+along with the part filename and description, if available.
+
+Clicking in the part link generates a request to the servlet
+with two parameters: the message sequence number and the
+part number. The doGet() method interprets the parameters and 
+invokes the second displayPart() method with the signature 
+	displayPart(MailUserData mud, int msgNum,
+	            int partNum, ServletOutputStream out, 
+		    HttpServletResponse res) 
+This method retrieves the specified part from the message and
+streams it to the web browser, preceded by the MIME content type 
+of this part. For example, if the part has a MIME type image/gif,
+the method will set the servlet response MIME content type to 
+"image/gif" and then follow it with the bytes of the actual 
+image. This leverages the web browser's content handling
+ability to display different media types.
+
+Message composition and sending is very similar to message
+viewing. When the "Compose" link is clicked in the headerlist
+page, the servlet outputs the HTML source for the compose
+form stored in the composeForm variable. The user then fills
+in the destination address, subject, text, and presses
+send. This sends a POST request to the servlet, which 
+invokes the doPost() method. doPost() calls the servlet's 
+send() method, which creates a new MimeMessage and fills 
+it with data retrieved from the POST request. The message
+is then sent to its destination using the Transport.send()
+static method.
diff --git a/siggen b/siggen
new file mode 100755
index 0000000..8b95080
--- /dev/null
+++ b/siggen
@@ -0,0 +1,48 @@
+#!/bin/sh
+#
+# Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+#
+# Generate a new signature file.
+#
+# Usage: siggen javax.mail.jar mail.sig
+#
+SIGTEST=${SIGTEST:-/java/re/sigtest/4.0/promoted/fcs/latest/binaries/sigtest-4.0}
+SIGTEST_JAR=$SIGTEST/lib/sigtestdev.jar
+JAVA_HOME=${JAVA_HOME:-/opt/jdk1.8}
+PKG=javax.mail
+USAGE="siggen [-p package] javax.mail.jar mail.sig"
+
+while getopts p: opt
+do
+	case $opt in
+	p)	PKG="$OPTARG";;
+	\?)	echo $USAGE; exit 1;;
+	esac
+done
+shift `expr $OPTIND - 1`
+
+ver=$($JAVA_HOME/bin/java -version 2>&1 | sed -e 's/.*"\(.*\)".*/\1/;q')
+case "$ver" in
+1.[0-9]*)	xjimage=;
+		cp="$1:${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/jre/lib/jce.jar";;
+*)		xjimage="-xjimage ${JAVA_HOME}/bin/jimage";
+		cp="$1:${JAVA_HOME}/lib/modules";;
+esac
+
+${JAVA_HOME}/bin/java -jar $SIGTEST_JAR setup -static \
+	-classpath "$cp" $xjimage \
+	-filename "$2" -package "$PKG" -nonclosedfile
diff --git a/sigtest b/sigtest
new file mode 100755
index 0000000..0ec867a
--- /dev/null
+++ b/sigtest
@@ -0,0 +1,67 @@
+#!/bin/sh
+#
+# Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+#
+# Compare API against a signature file.
+#
+# Usage: sigtest javax.mail.jar mail.sig
+#
+SIGTEST=${SIGTEST:-/java/re/sigtest/4.0/promoted/fcs/latest/binaries/sigtest-4.0}
+# The apicheck program isn't working correctly, use sigtest instead.
+#APICHECK_JAR=$SIGTEST/lib/apicheck.jar
+SIGTEST_JAR=$SIGTEST/lib/sigtestdev.jar
+JAVA_HOME=${JAVA_HOME:-/opt/jdk1.8}
+USAGE="sigtest [-b] [-p package] javax.mail.jar mail.sig"
+PKG=javax.mail
+BACKWARD=
+PLATSIG=/tmp/$$.platform.sig
+
+while getopts bp: opt
+do
+	case $opt in
+	p)	PKG="$OPTARG";;
+	b)	BACKWARD=-backward;;
+	\?)	echo $USAGE; exit 1;;
+	esac
+done
+shift `expr $OPTIND - 1`
+
+JAR="$1"
+SIG="$2"
+shift 2
+
+ver=$($JAVA_HOME/bin/java -version 2>&1 | sed -e 's/.*"\(.*\)".*/\1/;q')
+case "$ver" in
+1.[0-9]*)	xjimage=;
+		cp="${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/jre/lib/jce.jar";;
+*)		xjimage="-xjimage ${JAVA_HOME}/bin/jimage";
+		cp="${JAVA_HOME}/lib/modules";;
+esac
+
+echo '*** Generate JDK signatures ***'
+${JAVA_HOME}/bin/java -jar $SIGTEST_JAR setup \
+	-classpath "$cp" $xjimage \
+	-filename "$PLATSIG" -package java -package javax -keepfile \
+	-verbose nowarn
+echo
+echo '*** Check JavaMail signatures ***'
+${JAVA_HOME}/bin/java -jar $SIGTEST_JAR test -static \
+	-classpath "$PLATSIG:$JAR" \
+	-filename "$SIG" -package "$PKG" -out /dev/stdout \
+	-nomerge -checkvalue -mode src $BACKWARD "$@"
+
+rm "$PLATSIG"
diff --git a/smtp/pom.xml b/smtp/pom.xml
new file mode 100644
index 0000000..c05d437
--- /dev/null
+++ b/smtp/pom.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>parent-distrib</artifactId>
+	<version>1.6.3</version>
+	<relativePath>../parent-distrib/pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>smtp</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API smtp provider</name>
+
+    <properties>
+	<mail.packages.export>
+	    com.sun.mail.smtp; version=${mail.osgiversion}
+	</mail.packages.export>
+    </properties>
+</project>
diff --git a/smtp/src/main/java/module-info.java b/smtp/src/main/java/module-info.java
new file mode 100644
index 0000000..544105d
--- /dev/null
+++ b/smtp/src/main/java/module-info.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+module com.sun.mail.smtp {
+    exports com.sun.mail.smtp;
+    provides javax.mail.Provider with
+	com.sun.mail.smtp.SMTPProvider, com.sun.mail.smtp.SMTPSSLProvider;
+
+    requires jakarta.mail;
+    requires java.logging;
+    requires java.security.sasl;
+}
diff --git a/smtp/src/main/resources/META-INF/MANIFEST.MF b/smtp/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..1843e1b
--- /dev/null
+++ b/smtp/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,9 @@
+Manifest-Version: 1.0
+Extension-Name: com.sun.mail.smtp
+Specification-Title: com.sun.mail.smtp
+Specification-Version: ${mail.spec.version}
+Specification-Vendor: ${project.organization.name}
+Implementation-Title: com.sun.mail.smtp
+Implementation-Version: ${mail.version}
+Implementation-Vendor: ${project.organization.name}
+Implementation-Vendor-Id: com.sun
diff --git a/smtp/src/main/resources/META-INF/javamail.address.map b/smtp/src/main/resources/META-INF/javamail.address.map
new file mode 100644
index 0000000..4ab5572
--- /dev/null
+++ b/smtp/src/main/resources/META-INF/javamail.address.map
@@ -0,0 +1 @@
+rfc822=smtp
diff --git a/smtp/src/main/resources/META-INF/javamail.providers b/smtp/src/main/resources/META-INF/javamail.providers
new file mode 100644
index 0000000..6ad6c98
--- /dev/null
+++ b/smtp/src/main/resources/META-INF/javamail.providers
@@ -0,0 +1,3 @@
+# JavaMail SMTP provider Oracle
+protocol=smtp; type=transport; class=com.sun.mail.smtp.SMTPTransport; vendor=Oracle;
+protocol=smtps; type=transport; class=com.sun.mail.smtp.SMTPSSLTransport; vendor=Oracle;
diff --git a/smtp/src/main/resources/META-INF/services/javax.mail.Provider b/smtp/src/main/resources/META-INF/services/javax.mail.Provider
new file mode 100644
index 0000000..cd5fb14
--- /dev/null
+++ b/smtp/src/main/resources/META-INF/services/javax.mail.Provider
@@ -0,0 +1,2 @@
+com.sun.mail.smtp.SMTPProvider
+com.sun.mail.smtp.SMTPSSLProvider
diff --git a/taglib/pom.xml b/taglib/pom.xml
new file mode 100644
index 0000000..8e6f509
--- /dev/null
+++ b/taglib/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>taglib</artifactId>
+    <packaging>jar</packaging>
+    <name>JavaMail API demo taglib</name>
+
+    <dependencies>
+        <dependency>
+            <groupId>javax.servlet.jsp</groupId>
+            <artifactId>jsp-api</artifactId>
+        </dependency>
+	<!-- XXX - javax.servlet.jsp should depend on this -->
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/taglib/src/main/java/demo/AttachmentInfo.java b/taglib/src/main/java/demo/AttachmentInfo.java
new file mode 100644
index 0000000..fc44d57
--- /dev/null
+++ b/taglib/src/main/java/demo/AttachmentInfo.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import java.io.*;
+import java.util.*;
+
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * Used to store attachment information.
+ */
+public class AttachmentInfo {
+    private Part part;
+    private int num;
+
+
+    /**
+     * Returns the attachment's content type.
+     */
+    public String getAttachmentType() throws MessagingException {
+	String contentType;
+	if ((contentType = part.getContentType()) == null)
+	    return "invalid part";
+	else
+	    return contentType;
+    }
+
+    /**
+     * Returns the attachment's content (if it is plain text).
+     */
+    public String getContent() throws IOException, MessagingException {
+	if (hasMimeType("text/plain"))
+	    return (String)part.getContent();
+	else
+	    return "";
+    }
+
+    /**
+     * Returns the attachment's description.
+     */
+    public String getDescription() throws MessagingException {
+	String description;
+	if ((description = part.getDescription()) != null)
+	    return description;
+	else
+	    return "";
+    }
+
+    /**
+     * Returns the attachment's filename.
+     */
+    public String getFilename() throws MessagingException {
+	String filename;
+	if ((filename = part.getFileName()) != null)
+	    return filename;
+	else
+	    return "";
+    }
+
+    /**
+     * Returns the attachment number.
+     */
+    public String getNum() {
+	return (Integer.toString(num));
+    }
+
+    /**
+     * Method for checking if the attachment has a description.
+     */
+    public boolean hasDescription() throws MessagingException {
+	return (part.getDescription() != null);
+    }
+
+    /**
+     * Method for checking if the attachment has a filename.
+     */
+    public boolean hasFilename() throws MessagingException {
+	return (part.getFileName() != null);
+    }
+
+    /**
+     * Method for checking if the attachment has the desired mime type.
+     */
+    public boolean hasMimeType(String mimeType) throws MessagingException {
+	return part.isMimeType(mimeType);
+    }
+
+    /**
+     * Method for checking the content disposition.
+     */
+    public boolean isInline() throws MessagingException {
+	if (part.getDisposition() != null)
+	    return part.getDisposition().equals(Part.INLINE);
+	else
+	    return true;
+    }
+
+    /**
+     * Method for mapping a message part to this AttachmentInfo class.
+     */
+    public void setPart(int num, Part part)
+	throws MessagingException, ParseException {
+
+	this.part = part;
+	this.num = num;
+    }
+}
diff --git a/taglib/src/main/java/demo/ListAttachmentsTEI.java b/taglib/src/main/java/demo/ListAttachmentsTEI.java
new file mode 100644
index 0000000..e08a15f
--- /dev/null
+++ b/taglib/src/main/java/demo/ListAttachmentsTEI.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import javax.servlet.jsp.*;
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * Extra information class to support the scripting variable created by the
+ * ListAttachmentsTag class. The scope of the variable is limited to the body
+ * of the tag.
+ */
+public class ListAttachmentsTEI extends TagExtraInfo {
+    
+    public ListAttachmentsTEI() {
+	super();
+    }
+    
+    public VariableInfo[] getVariableInfo(TagData data) {
+	VariableInfo info = new VariableInfo(data.getId(),"AttachmentInfo",
+	    true, VariableInfo.NESTED);
+	VariableInfo[] varInfo = { info };
+	return varInfo;
+    }
+}
+
diff --git a/taglib/src/main/java/demo/ListAttachmentsTag.java b/taglib/src/main/java/demo/ListAttachmentsTag.java
new file mode 100644
index 0000000..8522866
--- /dev/null
+++ b/taglib/src/main/java/demo/ListAttachmentsTag.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import java.io.*;
+import java.util.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.servlet.jsp.*;
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * Custom tag for listing message attachments. The scripting variable is only
+ * within the body of the tag.
+ */
+public class ListAttachmentsTag extends BodyTagSupport {
+    private String messageinfo;
+    private int partNum = 1;
+    private int numParts = 0;
+    private AttachmentInfo attachmentinfo;
+    private MessageInfo messageInfo;
+    private Multipart multipart;
+
+    /**
+     * messageinfo attribute getter method.
+     */
+    public String getMessageinfo() {
+	return messageinfo;
+    }
+    
+    /**
+     * messageinfo attribute setter method.
+     */
+    public void setMessageinfo(String messageinfo) {
+	this.messageinfo = messageinfo;
+    }
+
+    /**
+     * Method for processing the start of the tag.
+     */
+    public int doStartTag() throws JspException {
+	messageInfo = (MessageInfo)pageContext.getAttribute(getMessageinfo());
+	attachmentinfo = new AttachmentInfo();
+	
+	try {
+	    multipart = (Multipart)messageInfo.getMessage().getContent();
+	    numParts = multipart.getCount();
+	} catch (Exception ex) {
+	    throw new JspException(ex.getMessage());
+	}
+
+	getPart();
+
+	return BodyTag.EVAL_BODY_TAG;
+    }
+   
+    /**
+     * Method for processing the body content of the tag.
+     */
+    public int doAfterBody() throws JspException {
+	
+	BodyContent body = getBodyContent();
+	try {
+	    body.writeOut(getPreviousOut());
+	} catch (IOException e) {
+	    throw new JspTagException("IterationTag: " + e.getMessage());
+	}
+	
+	// clear up so the next time the body content is empty
+	body.clearBody();
+       
+	partNum++;
+	if (partNum < numParts) {
+	    getPart();
+	    return BodyTag.EVAL_BODY_TAG;
+	} else {
+	    return BodyTag.SKIP_BODY;
+	}
+    }
+    
+    /**
+     * Helper method for retrieving message parts.
+     */
+    private void getPart() throws JspException {
+	try {
+	    attachmentinfo.setPart(partNum, multipart.getBodyPart(partNum));
+	    pageContext.setAttribute(getId(), attachmentinfo);
+	} catch (Exception ex) {
+	    throw new JspException(ex.getMessage());
+	}
+    }
+}
+
diff --git a/taglib/src/main/java/demo/ListMessagesTEI.java b/taglib/src/main/java/demo/ListMessagesTEI.java
new file mode 100644
index 0000000..73ebaef
--- /dev/null
+++ b/taglib/src/main/java/demo/ListMessagesTEI.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import javax.servlet.jsp.*;
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * Extra information class to support the scripting variable created by the
+ * ListMessagesTag class. The scope of the variable is limited to the body 
+ * of the tag.
+ */
+public class ListMessagesTEI extends TagExtraInfo {
+    
+    public ListMessagesTEI() {
+	super();
+    }
+    
+    public VariableInfo[] getVariableInfo(TagData data) {
+	VariableInfo info = new VariableInfo(data.getId(),"MessageInfo",
+	    true, VariableInfo.NESTED);
+	VariableInfo[] varInfo = { info };
+	return varInfo;
+    }
+}
+
diff --git a/taglib/src/main/java/demo/ListMessagesTag.java b/taglib/src/main/java/demo/ListMessagesTag.java
new file mode 100644
index 0000000..9aa1152
--- /dev/null
+++ b/taglib/src/main/java/demo/ListMessagesTag.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import java.io.*;
+import java.util.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.mail.search.*;
+import javax.servlet.jsp.*;
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * Custom tag for listing messages. The scripting variable is only
+ * within the body of the tag.
+ */
+public class ListMessagesTag extends BodyTagSupport {
+    private String folder;
+    private String session;
+    private int msgNum = 0;
+    private int messageCount = 0;
+    private Message message;
+    private Message[] messages;
+    private MessageInfo messageinfo;
+    
+    /**
+     * folder attribute getter method.
+     */
+    public String getFolder() {
+	return folder;
+    }
+    
+    /**
+     * session attribute getter method.
+     */
+    public String getSession() {
+	return session;
+    }
+    
+    /**
+     * folder setter method.
+     */
+    public void setFolder(String folder) {
+	this.folder = folder;
+    }
+
+    /**
+     * session attribute setter method.
+     */
+    public void setSession(String session) {
+	this.session = session;
+    }
+
+    /**
+     * Method for processing the start of the tag.
+     */
+    public int doStartTag() throws JspException {
+	messageinfo = new MessageInfo();
+       
+	try {
+	    Folder folder = (Folder)pageContext.getAttribute(
+		getFolder(), PageContext.SESSION_SCOPE);
+	    FlagTerm ft = new FlagTerm(new Flags(Flags.Flag.DELETED), false);
+	    messages = folder.search(ft);
+	    messageCount = messages.length;
+	    msgNum = 0;
+	} catch (Exception ex) {
+	    throw new JspException(ex.getMessage());
+	}
+
+	if (messageCount > 0) {
+	    getMessage();
+	    return BodyTag.EVAL_BODY_TAG;
+	} else
+	    return BodyTag.SKIP_BODY;
+    }
+   
+    /**
+     * Method for processing the body content of the tag.
+     */
+    public int doAfterBody() throws JspException {
+	
+	BodyContent body = getBodyContent();
+	try {
+	    body.writeOut(getPreviousOut());
+	} catch (IOException e) {
+	    throw new JspTagException("IterationTag: " + e.getMessage());
+	}
+	
+	// clear up so the next time the body content is empty
+	body.clearBody();
+       
+	if (msgNum < messageCount) {
+	    getMessage();
+	    return BodyTag.EVAL_BODY_TAG;
+	} else {
+	    return BodyTag.SKIP_BODY;
+	}
+    }
+    
+    /**
+     * Helper method for retrieving messages.
+     */
+    private void getMessage() throws JspException {
+	message = messages[msgNum++];
+	messageinfo.setMessage(message);
+	pageContext.setAttribute(getId(), messageinfo);
+    }
+}
+
diff --git a/taglib/src/main/java/demo/MessageInfo.java b/taglib/src/main/java/demo/MessageInfo.java
new file mode 100644
index 0000000..cb6c7c3
--- /dev/null
+++ b/taglib/src/main/java/demo/MessageInfo.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import java.text.*;
+import java.util.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+
+/**
+ * Used to store message information.
+ */
+public class MessageInfo {
+    private Message message;
+    
+    
+    /**
+     * Returns the bcc field.
+     */
+    public String getBcc() throws MessagingException {
+	return formatAddresses(
+	    message.getRecipients(Message.RecipientType.BCC));
+    }
+    
+    /**
+     * Returns the body of the message (if it's plain text).
+     */
+    public String getBody() throws MessagingException, java.io.IOException {
+	Object content = message.getContent();
+	if (message.isMimeType("text/plain")) {
+	    return (String)content;
+	} else if (message.isMimeType("multipart/alternative")) {
+	    Multipart mp = (Multipart)message.getContent();
+	    int numParts = mp.getCount();
+	    for (int i = 0; i < numParts; ++i) {
+		if (mp.getBodyPart(i).isMimeType("text/plain"))
+		    return (String)mp.getBodyPart(i).getContent();
+	    }
+	    return "";   
+	} else if (message.isMimeType("multipart/*")) { 
+	    Multipart mp = (Multipart)content;
+	    if (mp.getBodyPart(0).isMimeType("text/plain"))
+		return (String)mp.getBodyPart(0).getContent();
+	    else
+		return "";
+	} else
+	    return "";
+    }
+    
+    /**
+     * Returns the cc field.
+     */
+    public String getCc() throws MessagingException {
+	return formatAddresses(
+	    message.getRecipients(Message.RecipientType.CC));
+    }
+    
+    /**
+     * Returns the date the message was sent (or received if the sent date
+     * is null.
+     */
+    public String getDate() throws MessagingException {
+	Date date;
+	SimpleDateFormat df = new SimpleDateFormat("EE M/d/yy");
+	if ((date = message.getSentDate()) != null)
+	    return (df.format(date));
+	else if ((date = message.getReceivedDate()) != null)
+	    return (df.format(date));
+	else
+	    return "";
+     }
+       
+    /**
+     * Returns the from field.
+     */
+    public String getFrom() throws MessagingException {
+	return formatAddresses(message.getFrom());
+    }
+
+    /**
+     * Returns the address to reply to.
+     */
+    public String getReplyTo() throws MessagingException {
+	Address[] a = message.getReplyTo();
+	if (a.length > 0)
+	    return ((InternetAddress)a[0]).getAddress();
+	else
+	    return "";
+    }
+    
+    /**
+     * Returns the javax.mail.Message object.
+     */
+    public Message getMessage() {
+	return message;
+    }
+    
+    /**
+     * Returns the message number.
+     */
+    public String getNum() {
+	return (Integer.toString(message.getMessageNumber()));
+    }
+    
+    /**
+     * Returns the received date field.
+     */
+    public String getReceivedDate() throws MessagingException {
+	if (hasReceivedDate())
+	    return (message.getReceivedDate().toString());
+	else
+	    return "";
+    }
+    
+    /**
+     * Returns the sent date field.
+     */
+    public String getSentDate() throws MessagingException {
+	if (hasSentDate())
+	    return (message.getSentDate().toString()); 
+	else
+	    return "";
+    }
+    
+    /**
+     * Returns the subject field.
+     */
+    public String getSubject() throws MessagingException {
+	if (hasSubject())
+	    return message.getSubject();
+	else
+	    return "";
+    }
+    
+    /**
+     * Returns the to field.
+     */
+    public String getTo() throws MessagingException {
+	return formatAddresses(
+	    message.getRecipients(Message.RecipientType.TO));
+    }
+    
+    /**
+     * Method for checking if the message has attachments.
+     */
+    public boolean hasAttachments() throws java.io.IOException, 
+					   MessagingException {
+	boolean hasAttachments = false;
+	if (message.isMimeType("multipart/*")) {
+	    Multipart mp = (Multipart)message.getContent();
+	    if (mp.getCount() > 1)
+		hasAttachments = true;
+	}
+	    
+	return hasAttachments;
+    }
+    
+    /**
+     * Method for checking if the message has a bcc field.
+     */
+    public boolean hasBcc() throws MessagingException {
+	return (message.getRecipients(Message.RecipientType.BCC) != null);
+    }
+    
+    /**
+     * Method for checking if the message has a cc field.
+     */
+    public boolean hasCc() throws MessagingException {
+	return (message.getRecipients(Message.RecipientType.CC) != null);
+    }
+    
+    /**
+     * Method for checking if the message has a date field.
+     */
+    public boolean hasDate() throws MessagingException {
+	return (hasSentDate() || hasReceivedDate());
+    }
+    
+    /**
+     * Method for checking if the message has a from field.
+     */
+    public boolean hasFrom() throws MessagingException {
+	return (message.getFrom() != null);
+    }
+    
+    /**
+     * Method for checking if the message has the desired mime type.
+     */
+    public boolean hasMimeType(String mimeType) throws MessagingException {
+	return message.isMimeType(mimeType);
+    }
+    
+    /**
+     * Method for checking if the message has a received date field.
+     */
+    public boolean hasReceivedDate() throws MessagingException {
+	return (message.getReceivedDate() != null);
+    }
+    
+    /**
+     * Method for checking if the message has a sent date field.
+     */
+    public boolean hasSentDate() throws MessagingException {
+	return (message.getSentDate() != null);
+    }
+    
+    /**
+     * Method for checking if the message has a subject field.
+     */
+    public boolean hasSubject() throws MessagingException {
+	return (message.getSubject() != null);
+    }
+    
+    /**
+     * Method for checking if the message has a to field.
+     */
+    public boolean hasTo() throws MessagingException {
+	return (message.getRecipients(Message.RecipientType.TO) != null);
+    }
+    
+    /**
+     * Method for mapping a message to this MessageInfo class.
+     */
+    public void setMessage(Message message) {
+	this.message = message;
+    }
+
+    /**
+     * Utility method for formatting msg header addresses.
+     */
+    private String formatAddresses(Address[] addrs) {
+	if (addrs == null)
+	    return "";
+	StringBuilder strBuf = new StringBuilder(getDisplayAddress(addrs[0]));
+	for (int i = 1; i < addrs.length; i++) {
+	    strBuf.append(", ").append(getDisplayAddress(addrs[i]));
+	}
+	return strBuf.toString();
+    }
+    
+    /**
+     * Utility method which returns a string suitable for msg header display.
+     */
+    private String getDisplayAddress(Address a) {
+	String pers = null;
+	String addr = null;
+	if (a instanceof InternetAddress &&
+	   ((pers = ((InternetAddress)a).getPersonal()) != null)) {
+	    addr = pers + "  "+"&lt;"+((InternetAddress)a).getAddress()+"&gt;";
+	} else 
+	    addr = a.toString();
+	return addr;
+    }
+}
+
diff --git a/taglib/src/main/java/demo/MessageTEI.java b/taglib/src/main/java/demo/MessageTEI.java
new file mode 100644
index 0000000..c829852
--- /dev/null
+++ b/taglib/src/main/java/demo/MessageTEI.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import javax.servlet.jsp.*;
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * Extra information class to support the scripting variable created by the
+ * MessagesTag class. The variable exists outside of the tag.
+ * 
+ */
+public class MessageTEI extends TagExtraInfo {
+    
+    public MessageTEI() {
+	super();
+    }
+    
+    public VariableInfo[] getVariableInfo(TagData data) {
+	VariableInfo info = new VariableInfo(data.getId(),"MessageInfo",
+	    true, VariableInfo.AT_END);
+	VariableInfo[] varInfo = { info };
+	return varInfo;
+    }
+}
+
diff --git a/taglib/src/main/java/demo/MessageTag.java b/taglib/src/main/java/demo/MessageTag.java
new file mode 100644
index 0000000..3c51d77
--- /dev/null
+++ b/taglib/src/main/java/demo/MessageTag.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import java.util.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.servlet.jsp.*;
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * Custom tag for retrieving a message.
+ */
+public class MessageTag extends TagSupport {
+    private String folder;
+    private String session;
+    private int num = 1;
+
+    /**
+     * folder attribute setter method.
+     */
+    public String getFolder() {
+	return folder;
+    }
+    
+    /**
+     * num attribute getter method.
+     */
+    public String getNum() {
+	return Integer.toString(num);
+    }
+    
+    /**
+     * session attribute getter method.
+     */
+    public String getSession() {
+	return session;
+    }
+    
+    /**
+     * folder setter method.
+     */
+    public void setFolder(String folder) {
+	this.folder = folder;
+    }
+
+    /**
+     * num attribute setter method.
+     */
+    public void setNum(String num) {
+	this.num = Integer.parseInt(num);
+    }
+    
+    /**
+     * session attribute setter method.
+     */
+    public void setSession(String session) {
+	this.session = session;
+    }
+
+    /**
+     * Method for processing the start of the tag.
+     */
+    public int doStartTag() throws JspException {
+	MessageInfo messageinfo = new MessageInfo();
+	try {
+	    Folder f = (Folder)pageContext.getAttribute(
+		getFolder(), PageContext.SESSION_SCOPE);
+	    Message message = f.getMessage(num);
+	    messageinfo.setMessage(message);
+	    pageContext.setAttribute(getId(), messageinfo);
+	} catch (Exception ex) {
+	    throw new JspException(ex.getMessage());
+	}
+ 
+	return SKIP_BODY;
+   }
+}
+
diff --git a/taglib/src/main/java/demo/SendTag.java b/taglib/src/main/java/demo/SendTag.java
new file mode 100644
index 0000000..b9e8669
--- /dev/null
+++ b/taglib/src/main/java/demo/SendTag.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import java.util.*;
+import java.net.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.servlet.jsp.*;
+import javax.servlet.jsp.tagext.*;
+
+/**
+ * Custom tag for sending messages.
+ */
+public class SendTag extends BodyTagSupport {
+    private String body;
+    private String cc;
+    private String host;
+    private String recipients;
+    private String sender;
+    private String subject;
+
+    /**
+     * host attribute setter method.
+     */
+    public void setHost(String host) {
+	this.host = host;
+    }
+    
+    /**
+     * recipient attribute setter method.
+     */
+    public void setRecipients(String recipients) {
+	this.recipients = recipients;
+    }
+
+    /**
+     * sender attribute setter method.
+     */
+    public void setSender(String sender) {
+	this.sender = sender;
+    }
+
+    /**
+     * cc attribute setter method.
+     */
+    public void setCc(String cc) {
+	this.cc = cc;
+    }
+
+    /**
+     * subject attribute setter method.
+     */
+    public void setSubject(String subject) {
+	this.subject = subject;
+    }
+
+    /**
+     * Method for processing the end of the tag.
+     */
+    public int doEndTag() throws JspException {
+	Properties props = System.getProperties();
+	
+	try {
+	    if (host != null)
+		props.put("mail.smtp.host", host);
+	    else if (props.getProperty("mail.smtp.host") == null)
+		props.put("mail.smtp.host", InetAddress.getLocalHost().
+		    getHostName());
+	} catch (Exception ex) {
+	    throw new JspException(ex.getMessage());
+	}
+	Session session = Session.getDefaultInstance(props, null);
+		
+	Message msg = new MimeMessage(session);
+	InternetAddress[] toAddrs = null, ccAddrs = null;
+
+	try {
+	    if (recipients != null) {
+		toAddrs = InternetAddress.parse(recipients, false);
+		msg.setRecipients(Message.RecipientType.TO, toAddrs);
+	    } else
+		throw new JspException("No recipient address specified");
+
+	    if (sender != null)
+		msg.setFrom(new InternetAddress(sender));
+	    else
+		throw new JspException("No sender address specified");
+
+	    if (cc != null) {
+		ccAddrs = InternetAddress.parse(cc, false);
+		msg.setRecipients(Message.RecipientType.CC, ccAddrs);
+	    }
+
+	    if (subject != null)
+		msg.setSubject(subject);
+
+	    if ((body = getBodyContent().getString()) != null)
+		msg.setText(body);
+	    else
+		msg.setText("");
+
+	    Transport.send(msg);
+	
+	} catch (Exception ex) {
+	    throw new JspException(ex.getMessage());
+	}
+
+	return(EVAL_PAGE);
+   }
+}
+
diff --git a/taglib/src/main/resources/META-INF/taglib.tld b/taglib/src/main/resources/META-INF/taglib.tld
new file mode 100644
index 0000000..23c2b8c
--- /dev/null
+++ b/taglib/src/main/resources/META-INF/taglib.tld
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE taglib PUBLIC "-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN" "http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<taglib>
+  <tlibversion>1.0</tlibversion>
+  <jspversion>1.1</jspversion>
+  <shortname>javamail</shortname>
+  <uri>http://java.sun.com/products/javamail/demo/webapp</uri>
+  <tag>
+    <name>listattachments</name>
+    <tagclass>demo.ListAttachmentsTag</tagclass>
+    <teiclass>demo.ListAttachmentsTEI</teiclass>
+    <bodycontent>JSP</bodycontent>
+    <info>
+        A listattachments tag
+    </info>
+    <attribute>
+      <name>id</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>messageinfo</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+  <tag>
+    <name>listmessages</name>
+    <tagclass>demo.ListMessagesTag</tagclass>
+    <teiclass>demo.ListMessagesTEI</teiclass>
+    <bodycontent>JSP</bodycontent>
+    <info>
+        A listmessages tag
+    </info>
+    <attribute>
+      <name>id</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>folder</name>
+      <required>false</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>session</name>
+      <required>false</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+  <tag>
+    <name>message</name>
+    <tagclass>demo.MessageTag</tagclass>
+    <teiclass>demo.MessageTEI</teiclass>
+    <bodycontent>empty</bodycontent>
+    <info>
+        A message tag
+    </info>
+    <attribute>
+      <name>id</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>folder</name>
+      <required>false</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>session</name>
+      <required>false</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>num</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+  <tag>
+    <name>sendmail</name>
+    <tagclass>demo.SendTag</tagclass>
+    <bodycontent>JSP</bodycontent>
+    <info>
+        A sendmail tag
+    </info>
+    <attribute>
+      <name>host</name>
+      <required>false</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>recipients</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>sender</name>
+      <required>true</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+    <attribute>
+      <name>subject</name>
+      <required>false</required>
+      <rtexprvalue>true</rtexprvalue>
+    </attribute>
+  </tag>
+</taglib>
+
diff --git a/update_version b/update_version
new file mode 100755
index 0000000..28828fe
--- /dev/null
+++ b/update_version
@@ -0,0 +1,46 @@
+#!/bin/sh
+#
+# Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Public License v. 2.0, which is available at
+# http://www.eclipse.org/legal/epl-2.0.
+#
+# This Source Code may also be made available under the following Secondary
+# Licenses when the conditions for such availability set forth in the
+# Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+# version 2 with the GNU Classpath Exception, which is available at
+# https://www.gnu.org/software/classpath/license.html.
+#
+# SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+#
+
+#
+# update version number in pom files, e.g., to change to release
+# version number in preparation for release, or to change back to
+# snapshot version number after release.
+#
+# Usage:	update_version new-version
+#
+new=$1
+# find the second version string in pom.xml, which is the old version
+# this is almost the same as "mvn versions:set -DnewVersion=$1"
+# except that misses mbox/native/pom.xml and oldmail/pom.xml
+old=`grep '<version>.*</version>' pom.xml | sed -n 2p | \
+	sed -e 's:.*<version>\(.*\)</version>.*:\1:'`
+for file in `find . -name 'pom.xml' `
+do
+	ed - "$file" <<-EOF
+	g:<version>$old</version>:s::<version>$new</version>:
+	w
+	q
+	EOF
+done
+zipold=`echo "$old" | sed -e 's/\\./_/g'`
+zipnew=`echo "$new" | sed -e 's/\\./_/g'`
+ed - pom.xml <<-EOF
+g:<mail.version>$old</mail.version>:s::<mail.version>$new</mail.version>:
+g:<mail.zipversion>$zipold</mail.zipversion>:s::<mail.zipversion>$zipnew</mail.zipversion>:
+w
+q
+EOF
diff --git a/webapp/build.bat b/webapp/build.bat
new file mode 100644
index 0000000..918716c
--- /dev/null
+++ b/webapp/build.bat
@@ -0,0 +1,42 @@
+@echo off
+REM
+REM  Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+REM
+REM  This program and the accompanying materials are made available under the
+REM  terms of the Eclipse Public License v. 2.0, which is available at
+REM  http://www.eclipse.org/legal/epl-2.0.
+REM
+REM  This Source Code may also be made available under the following Secondary
+REM  Licenses when the conditions for such availability set forth in the
+REM  Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+REM  version 2 with the GNU Classpath Exception, which is available at
+REM  https://www.gnu.org/software/classpath/license.html.
+REM
+REM  SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+REM
+mkdir src\docroot\WEB-INF\classes
+mkdir src\docroot\WEB-INF\classes\demo
+mkdir src\docroot\WEB-INF\lib
+cd src\classes
+echo compiling classes directory
+javac -d ..\docroot\WEB-INF\classes demo\*.java
+cd ..\taglib
+echo compiling lib directory
+javac -classpath "..\docroot\WEB-INF\classes;%CLASSPATH%" demo\*.java
+echo creating tag library archive
+jar cvf ..\docroot\WEB-INF\lib\taglib.jar META-INF demo\*.class
+del demo\*.class
+cd ..\docroot
+echo creating web archive
+jar cvf ..\..\javamail.war index.html *.jsp WEB-INF
+cd WEB-INF\classes\demo
+del *.*
+cd ..
+rmdir demo
+cd ..
+rmdir classes
+cd lib
+del *.*
+cd ..
+rmdir lib
+cd ..\..\..
diff --git a/webapp/build.sh b/webapp/build.sh
new file mode 100644
index 0000000..1468b76
--- /dev/null
+++ b/webapp/build.sh
@@ -0,0 +1,27 @@
+#
+# Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Eclipse Distribution License v. 1.0, which is available at
+# http://www.eclipse.org/org/documents/edl-v10.php.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+mkdir src/docroot/WEB-INF/classes
+mkdir src/docroot/WEB-INF/lib
+cd src/classes
+echo "compiling classes directory"
+javac -d ../docroot/WEB-INF/classes demo/*.java
+cd ../taglib
+echo "compiling lib directroy"
+javac -classpath ../docroot/WEB-INF/classes:$CLASSPATH demo/*.java
+echo "creating tag library archive"
+jar cvf ../docroot/WEB-INF/lib/taglib.jar META-INF demo/*.class
+rm demo/*.class
+cd ../docroot
+echo "creating web archive"
+jar cvf ../../javamail.war index.html *.jsp WEB-INF
+rm -r WEB-INF/classes
+rm -r WEB-INF/lib
+cd ../..
diff --git a/webapp/pom.xml b/webapp/pom.xml
new file mode 100644
index 0000000..7156ed7
--- /dev/null
+++ b/webapp/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!--
+
+    Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Public License v. 2.0, which is available at
+    http://www.eclipse.org/legal/epl-2.0.
+
+    This Source Code may also be made available under the following Secondary
+    Licenses when the conditions for such availability set forth in the
+    Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+    version 2 with the GNU Classpath Exception, which is available at
+    https://www.gnu.org/software/classpath/license.html.
+
+    SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
+			    http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+	<groupId>com.sun.mail</groupId>
+	<artifactId>all</artifactId>
+	<version>1.6.3</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>com.sun.mail</groupId>
+    <artifactId>webapp</artifactId>
+    <packaging>war</packaging>
+    <name>JavaMail API demo webapp</name>
+
+    <build>
+	<plugins>
+	    <plugin>
+		<artifactId>maven-war-plugin</artifactId>
+		<configuration>
+		    <warName>webapp</warName>
+		    <packagingExcludes>
+			WEB-INF/lib/activation*.jar,
+			WEB-INF/lib/javax.mail*.jar,
+			WEB-INF/lib/jsp*.jar,
+			WEB-INF/lib/servlet*.jar
+		    </packagingExcludes>
+		    <outputFileNameMapping>
+			@{artifactId}@.@{extension}@
+		    </outputFileNameMapping>
+		</configuration>
+	    </plugin>
+	</plugins>
+    </build>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>taglib</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/webapp/src/main/java/demo/AttachmentServlet.java b/webapp/src/main/java/demo/AttachmentServlet.java
new file mode 100644
index 0000000..72cc823
--- /dev/null
+++ b/webapp/src/main/java/demo/AttachmentServlet.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import java.io.*;
+import javax.mail.*;
+import javax.mail.internet.*;
+import javax.servlet.*;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+/**
+ * This servlet gets the input stream for a given msg part and 
+ * pushes it out to the browser with the correct content type.
+ * Used to display attachments and relies on the browser's
+ * content handling capabilities.
+ */
+public class AttachmentServlet extends HttpServlet {
+
+    /**
+     * This method handles the GET requests from the client.
+     */
+    public void doGet(HttpServletRequest request, HttpServletResponse  response)
+	throws IOException, ServletException {
+      
+	HttpSession session = request.getSession();
+	ServletOutputStream out = response.getOutputStream();
+	int msgNum = Integer.parseInt(request.getParameter("message"));
+	int partNum = Integer.parseInt(request.getParameter("part"));
+	MailUserBean mailuser = (MailUserBean)session.getAttribute("mailuser");
+
+	// check to be sure we're still logged in
+	if (mailuser.isLoggedIn()) {
+	    try {
+		Message msg = mailuser.getFolder().getMessage(msgNum);
+
+		Multipart multipart = (Multipart)msg.getContent();
+		Part part = multipart.getBodyPart(partNum);
+		
+		String sct = part.getContentType();
+		if (sct == null) {
+		    out.println("invalid part");
+		    return;
+		}
+		ContentType ct = new ContentType(sct);
+
+		response.setContentType(ct.getBaseType());
+		InputStream is = part.getInputStream();
+		int i;
+		while ((i = is.read()) != -1)
+		    out.write(i);
+		out.flush();
+		out.close();
+
+	    } catch (MessagingException ex) {
+		throw new ServletException(ex.getMessage());
+	    }
+	} else {
+	    getServletConfig().getServletContext().
+		getRequestDispatcher("/index.html").
+		forward(request, response);
+	}
+    }   
+}
diff --git a/webapp/src/main/java/demo/FilterServlet.java b/webapp/src/main/java/demo/FilterServlet.java
new file mode 100644
index 0000000..220dc74
--- /dev/null
+++ b/webapp/src/main/java/demo/FilterServlet.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import java.io.*;
+import javax.servlet.*;
+import javax.servlet.http.*;
+
+/**
+ * This servlet is used to determine whether the user is logged in before
+ * forwarding the request to the selected URL.
+ */
+public class FilterServlet extends HttpServlet {
+
+    /**
+     * This method handles the "POST" submission from two forms: the
+     * login form and the message compose form.
+     */
+    public void doPost(HttpServletRequest request, 
+		       HttpServletResponse  response) 
+		       throws IOException, ServletException {
+
+	String servletPath = request.getServletPath();
+	servletPath = servletPath.concat(".jsp");
+	
+	getServletConfig().getServletContext().
+	    getRequestDispatcher("/" + servletPath).forward(request, response);
+    }
+
+    /**
+     * This method handles the GET requests from the client.
+     */
+    public void doGet(HttpServletRequest request, 
+		      HttpServletResponse  response)
+		      throws IOException, ServletException {
+      
+	// check to be sure we're still logged in 
+	// before forwarding the request.
+	HttpSession session = request.getSession();
+	MailUserBean mailuser = (MailUserBean)session.getAttribute("mailuser");
+	String servletPath = request.getServletPath();
+	servletPath = servletPath.concat(".jsp");
+	
+	if (mailuser.isLoggedIn())
+	    getServletConfig().getServletContext().
+		getRequestDispatcher("/" + servletPath).
+		forward(request, response);
+	else
+	    getServletConfig().getServletContext().
+		getRequestDispatcher("/index.html").
+		forward(request, response);
+    }
+}
+
diff --git a/webapp/src/main/java/demo/MailUserBean.java b/webapp/src/main/java/demo/MailUserBean.java
new file mode 100644
index 0000000..f39e6a4
--- /dev/null
+++ b/webapp/src/main/java/demo/MailUserBean.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package demo;
+
+import java.util.*;
+import javax.mail.*;
+import javax.naming.*;
+
+/**
+ * This JavaBean is used to store mail user information.
+ */
+public class MailUserBean {
+    private Folder folder;
+    private String hostname;
+    private String username;
+    private String password;
+    private Session session;
+    private Store store;
+    private URLName url;
+    private String protocol = "imap";
+    private String mbox = "INBOX";	
+
+    public MailUserBean(){}
+
+    /**
+     * Returns the javax.mail.Folder object.
+     */
+    public Folder getFolder() {
+	return folder;
+    }
+    
+    /**
+     * Returns the number of messages in the folder.
+     */
+    public int getMessageCount() throws MessagingException {
+	return folder.getMessageCount();
+    }
+
+    /**
+     * hostname getter method.
+     */
+    public String getHostname() {
+	return hostname;
+    }
+    
+    /**
+     * hostname setter method.
+     */
+    public void setHostname(String hostname) {
+	this.hostname = hostname;
+    }
+	
+    /**
+     * username getter method.
+     */
+    public String getUsername() {
+	return username;
+    }
+
+    /**
+     * username setter method.
+     */
+    public void setUsername(String username) {
+	this.username = username;
+    }
+
+    /**
+     * password getter method.
+     */
+    public String getPassword() {
+	return password;
+    }
+
+    /**
+     * password setter method.
+     */
+    public void setPassword(String password) {
+	this.password = password;
+    }
+
+    /**
+     * session getter method.
+     */
+    public Session getSession() {
+	return session;
+    }
+
+    /**
+     * session setter method.
+     */
+    public void setSession(Session session) {
+	this.session = session;
+    }
+
+    /**
+     * store getter method.
+     */
+    public Store getStore() {
+	return store;
+    }
+
+    /**
+     * store setter method.
+     */
+    public void setStore(Store store) {
+	this.store = store;
+    }
+
+    /**
+     * url getter method.
+     */
+    public URLName getUrl() {
+	return url;
+    }
+
+    /**
+     * Method for checking if the user is logged in.
+     */
+    public boolean isLoggedIn() {
+	return store.isConnected();
+    }
+      
+    /**
+     * Method used to login to the mail host.
+     */
+    public void login() throws Exception {
+	url = new URLName(protocol, getHostname(), -1, mbox, 
+			  getUsername(), getPassword());
+	/*
+	 * First, try to get the session from JNDI,
+	 * as would be done under J2EE.
+	 */
+	try {
+	    InitialContext ic = new InitialContext();
+	    Context ctx = (Context)ic.lookup("java:comp/env");
+	    session = (Session)ctx.lookup("MySession");
+	} catch (Exception ex) {
+	    // ignore it
+	}
+
+	// if JNDI fails, try the old way that should work everywhere
+	if (session == null) {
+	    Properties props = null;
+	    try {
+		props = System.getProperties();
+	    } catch (SecurityException sex) {
+		props = new Properties();
+	    }
+	    session = Session.getInstance(props, null);
+	}
+	store = session.getStore(url);
+	store.connect();
+	folder = store.getFolder(url);
+	
+	folder.open(Folder.READ_WRITE);
+    }
+
+    /**
+     * Method used to login to the mail host.
+     */
+    public void login(String hostname, String username, String password) 
+	throws Exception {
+	    
+	this.hostname = hostname;
+	this.username = username;
+	this.password = password;
+	    
+	login();
+    }
+
+    /**
+     * Method used to logout from the mail host.
+     */
+    public void logout() throws MessagingException {
+	folder.close(false);
+	store.close();
+	store = null;
+	session = null;
+    }
+}
+
diff --git a/webapp/src/main/webapp/WEB-INF/web.xml b/webapp/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..c3f88bb
--- /dev/null
+++ b/webapp/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,66 @@
+<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd">
+<!--
+
+    Copyright (c) 2010, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<web-app>
+  <display-name>JspDemo</display-name>
+  <description>no description</description>
+  <servlet>
+    <servlet-name>FilterServlet</servlet-name>
+    <display-name>FilterServlet</display-name>
+    <description>no description</description>
+    <servlet-class>demo.FilterServlet</servlet-class>
+  </servlet>
+  <servlet>
+    <servlet-name>AttachmentServlet</servlet-name>
+    <display-name>AttachmentServlet</display-name>
+    <description>no description</description>
+    <servlet-class>demo.AttachmentServlet</servlet-class>
+  </servlet>
+  <servlet-mapping>
+    <servlet-name>FilterServlet</servlet-name>
+    <url-pattern>/compose</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>FilterServlet</servlet-name>
+    <url-pattern>/errordetails</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>FilterServlet</servlet-name>
+    <url-pattern>/login</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>FilterServlet</servlet-name>
+    <url-pattern>/logout</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>FilterServlet</servlet-name>
+    <url-pattern>/send</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>FilterServlet</servlet-name>
+    <url-pattern>/messageheaders</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>FilterServlet</servlet-name>
+    <url-pattern>/messagecontent</url-pattern>
+  </servlet-mapping>
+  <servlet-mapping>
+    <servlet-name>AttachmentServlet</servlet-name>
+    <url-pattern>/attachment</url-pattern>
+  </servlet-mapping>
+  <resource-ref>
+    <res-ref-name>MySession</res-ref-name>
+    <res-type>javax.mail.Session</res-type>
+    <res-auth>Container</res-auth>
+  </resource-ref>
+</web-app>
diff --git a/webapp/src/main/webapp/compose.jsp b/webapp/src/main/webapp/compose.jsp
new file mode 100644
index 0000000..3f08b62
--- /dev/null
+++ b/webapp/src/main/webapp/compose.jsp
@@ -0,0 +1,65 @@
+<%--
+
+    Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page language="java" %>
+<%@ page errorPage="errorpage.jsp" %>
+
+<html>
+<head>
+	<title>JavaMail compose</title>
+</head>
+
+<body bgcolor="#ccccff">
+<form ACTION="send" METHOD=POST>
+<input type="hidden" name="send" value="send">
+<p align="center">
+<b><font size="4" face="Verdana, Arial, Helvetica">
+JavaMail Compose Message</font></b>
+<p>
+<table border="0" width="100%">
+<tr>
+<td width="16%" height="22">	
+<p align="right">
+<b><font face="Verdana, Arial, Helvetica">To:</font></b></td>
+<td width="84%" height="22">
+<% if (request.getParameter("to") != null) { %>
+<input type="text" name="to" value="<%= request.getParameter("to") %>" size="30">
+<% } else { %>
+<input type="text" name="to" size="30"> 
+<% } %>
+<font size="1" face="Verdana, Arial, Helvetica">
+ (separate addresses with commas)</font></td></tr>
+<tr>
+<td width="16%"><p align="right">
+<b><font face="Verdana, Arial, Helvetica">From:</font></b></td>
+<td width="84%">
+<input type="text" name="from" size="30"> 
+<font size="1" face="Verdana, Arial, Helvetica">
+ (separate addresses with commas)</font></td></tr>
+<tr>
+<td width="16%"><p align="right">
+<b><font face="Verdana, Arial, Helvetica">Subject:</font></b></td>
+<td width="84%">
+<input type="text" name="subject" size="55"></td></tr>
+<tr>
+<td width="16%">&nbsp;</td>
+<td width="84%"><textarea name="text" rows="15" cols="53"></textarea></td></tr>
+<tr>
+<td width="16%" height="32">&nbsp;</td>
+<td width="84%" height="32">
+<input type="submit" name="Send" value="Send">
+<input type="reset" name="Reset" value="Reset"></td></tr>
+</table>
+</form>
+</body>
+</html>
+
diff --git a/webapp/src/main/webapp/errordetails.jsp b/webapp/src/main/webapp/errordetails.jsp
new file mode 100644
index 0000000..c946216
--- /dev/null
+++ b/webapp/src/main/webapp/errordetails.jsp
@@ -0,0 +1,22 @@
+<%--
+
+    Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page isErrorPage="true" %>
+<html>
+<head>
+<title>JavaMail errordetails</title>
+</head>
+<body bgcolor="white">
+<%= session.getValue("details") %>
+</body>
+</html>
+
diff --git a/webapp/src/main/webapp/errorpage.jsp b/webapp/src/main/webapp/errorpage.jsp
new file mode 100644
index 0000000..4df0407
--- /dev/null
+++ b/webapp/src/main/webapp/errorpage.jsp
@@ -0,0 +1,26 @@
+<%--
+
+    Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page isErrorPage="true" %>
+<html>
+<head>
+<title>JavaMail errorpage</title>
+</head>
+<body bgcolor="white">
+<form ACTION="errordetails" METHOD=POST>
+<% session.putValue("details", exception.toString()); %>
+<h2>An error occured while attempting to perform the operation you requested.
+</h2>
+<input type="submit" name="Error Details" value="Error Details">
+</body>
+</html>
+
diff --git a/webapp/src/main/webapp/folders.jsp b/webapp/src/main/webapp/folders.jsp
new file mode 100644
index 0000000..9eef9c0
--- /dev/null
+++ b/webapp/src/main/webapp/folders.jsp
@@ -0,0 +1,45 @@
+<%--
+
+    Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page language="java" import="javax.mail.*, demo.MailUserBean" %>
+<%@ page errorPage="errorpage.jsp" %>
+<jsp:useBean id="mailuser" scope="session" class="demo.MailUserBean" />
+	
+<html>
+<head>
+    <title>JavaMail folders</title>
+</head>
+	
+<body bgcolor="#ccccff">
+
+<center>
+<font face="Arial,Helvetica" font size=+3>
+<b>Welcome to JavaMail!</b> </font> </center>
+<table width="50%" border=0 align=center>
+<tr>
+<td width="75%" bgcolor="#ffffcc">
+<font face="Arial, Helvetica" font size=-1>
+<b>FolderName</b></font></td><br>
+<td width="25%" bgcolor="#ffffcc">
+<font face="Arial, Helvetica" font size=-1>
+<b>Messages</b></font></td><br>
+</tr>
+<tr>
+<td width="75%" bgcolor="#ffffff">
+<a href="messageheaders">Inbox</a></td><br>
+<td width="25%" bgcolor="#ffffff">
+<%= mailuser.getMessageCount() %>
+</tr>
+</table>
+</body>
+</html>
+
diff --git a/webapp/src/main/webapp/index.html b/webapp/src/main/webapp/index.html
new file mode 100644
index 0000000..9a6db21
--- /dev/null
+++ b/webapp/src/main/webapp/index.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<HTML>
+<!--
+
+    Copyright (c) 1998, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+-->
+
+<HEAD>
+	<META HTTP-EQUIV="Content-Type" CONTENT="text/html;CHARSET=iso-8859-1">
+	<TITLE>JavaMail</TITLE>
+</HEAD>
+
+<BODY BGCOLOR="#CCCCFF">
+
+<FORM ACTION="login" METHOD=POST ENCTYPE="application/x-www-form-urlencoded">
+<P ALIGN="CENTER"><B><FONT SIZE="5" FACE="Arial, Helvetica">Welcome to JavaMail</FONT></B></P>
+
+<P ALIGN="CENTER"><B><FONT SIZE="5" FACE="Arial, Helvetica">HTML Email Reader Demo</FONT></B></P>
+<CENTER>
+<P>
+<TABLE BORDER="0" WIDTH="100%">
+	<TR>
+		<TD WIDTH="40%">
+			<P ALIGN="RIGHT"><FONT FACE="Arial, Helvetica">IMAP Hostname:</FONT>
+		</TD>
+		<TD WIDTH="60%"><INPUT TYPE="TEXT" NAME="hostname" SIZE="25"></TD>
+	</TR>
+	<TR>
+		<TD WIDTH="40%">
+			<P ALIGN="RIGHT"><FONT FACE="Arial, Helvetica">Username:</FONT>
+		</TD>
+		<TD WIDTH="60%"><INPUT TYPE="TEXT" NAME="username" SIZE="25"></TD>
+	</TR>
+	<TR>
+		<TD WIDTH="40%">
+			<P ALIGN="RIGHT"><FONT FACE="Arial, Helvetica">Password:</FONT>
+		</TD>
+		<TD WIDTH="60%"><INPUT TYPE="PASSWORD" NAME="password" SIZE="25"></TD>
+	</TR>
+</TABLE>
+<INPUT TYPE="SUBMIT" VALUE="Login"><INPUT TYPE="RESET" NAME="Reset" VALUE="Reset"></P>
+</CENTER>
+<P>
+<HR ALIGN="CENTER">
+</P>
+<P><B><I><FONT FACE="Arial, Helvetica">Overview:</FONT></I></B></P>
+
+<FONT SIZE="2" FACE="Arial, Helvetica">
+<b>THIS IS A DEMO!!!</b> Please see the webapp.README.txt 
+file for information on how to
+compile and run the web application.<br> <br>
+This demo illustrates the use of JavaMail APIs in a 3-tiered web 
+application. It allows the user to login to an 
+IMAP store, list all the messages in the INBOX folder, view
+selected messages, compose and send a message, and logout.
+</FONT>
+
+<P><B><I><FONT FACE="Arial, Helvetica">Features:</FONT></I></B></P>
+
+<UL>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">HTML access to your IMAP mailbox</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">Proxy-able anywhere HTTP can be proxied</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">Easy to use</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">Uses web browser's content handling capabilities</FONT>
+</UL>
+
+<P><B><I><FONT FACE="Arial, Helvetica">Limitations:</FONT></I></B></P>
+
+<UL>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">Only INBOX support (no user folders)</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">Can't delete, copy, move, print, save, forward, reply to, search in
+	messages --<BR>
+	but it could be done</FONT>
+	<LI><FONT SIZE="2" FACE="Arial, Helvetica">Doesn't check for new messages (have to log out and log back it)</FONT>
+</UL>
+
+<P>
+<HR ALIGN="CENTER">
+</P>
+
+<P><FONT FACE="Arial, Helvetica">Feedback to </FONT><A HREF="mailto:javamail_ww@oracle.com"><FONT FACE="Arial, Helvetica">javamail_ww@oracle.com</FONT></A>
+</FORM>
+
+</BODY>
+
+</HTML>
diff --git a/webapp/src/main/webapp/login.jsp b/webapp/src/main/webapp/login.jsp
new file mode 100644
index 0000000..39b7702
--- /dev/null
+++ b/webapp/src/main/webapp/login.jsp
@@ -0,0 +1,35 @@
+<%--
+
+    Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page language="java" import="demo.MailUserBean" %>
+<%@ page errorPage="errorpage.jsp" %>
+<jsp:useBean id="mailuser" scope="session" class="demo.MailUserBean" />
+
+<html>
+<head>
+    <title>JavaMail login</title>
+</head>
+	
+<body bgcolor="white">
+	
+<%
+    mailuser.login(request.getParameter("hostname"),
+                   request.getParameter("username"),
+                   request.getParameter("password"));
+    session.setAttribute("folder", mailuser.getFolder());
+%>
+
+<jsp:forward page="folders.jsp" />
+
+</body>
+</html>
+
diff --git a/webapp/src/main/webapp/logout.jsp b/webapp/src/main/webapp/logout.jsp
new file mode 100644
index 0000000..0217e98
--- /dev/null
+++ b/webapp/src/main/webapp/logout.jsp
@@ -0,0 +1,28 @@
+<%--
+
+    Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page language="java" import="demo.MailUserBean" %>
+<%@ page errorPage="errorpage.jsp" %>
+<jsp:useBean id="mailuser" scope="session" class="demo.MailUserBean" />
+
+<html>
+<head>
+	<title>JavaMail logout</title>
+</head>
+
+<% mailuser.logout(); %>
+
+<body>
+<h2>Logged out OK</h2><a href=index.html>click here to login</a>
+</body>
+</html>
+
diff --git a/webapp/src/main/webapp/messagecontent.jsp b/webapp/src/main/webapp/messagecontent.jsp
new file mode 100644
index 0000000..d75c9af
--- /dev/null
+++ b/webapp/src/main/webapp/messagecontent.jsp
@@ -0,0 +1,96 @@
+<%--
+
+    Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page language="java" import="demo.MessageInfo, demo.AttachmentInfo" %>
+<%@ page errorPage="errorpage.jsp" %>
+<%@ taglib uri="http://java.sun.com/products/javamail/demo/webapp" 
+    prefix="javamail" %>
+ 
+
+<html>
+<head>
+    <title>JavaMail messagecontent</title>
+</head>
+
+<javamail:message 
+id="msginfo"
+folder="folder"
+num="<%= request.getParameter(\"message\") %>" 
+/>
+
+<body bgcolor="#ccccff">
+<center><font face="Arial,Helvetica" font size="+3">
+<b>Message<sp> 
+<sp>in folder /INBOX</b></font></center><p>
+
+<%-- first, display this message's headers --%>
+<b>Date:</b>
+<%= msginfo.getSentDate() %>
+<br>
+<% if (msginfo.hasFrom()) { %>
+<b>From:</b>
+<a href="compose?to=<%= msginfo.getReplyTo() %>"
+    target="reply<%= msginfo.getNum() %>">
+<%= msginfo.getFrom() %>
+</a>
+<br>
+<% } %>
+
+<% if (msginfo.hasTo()) { %>
+<b>To:</b>
+<%= msginfo.getTo() %>
+<br>
+<% } %>   
+
+<% if (msginfo.hasCc()) { %>
+<b>CC:</b>
+<%= msginfo.getCc() %>
+<br>
+<% } %>
+
+<b>Subject:</b>
+<% if (msginfo.hasSubject()) { %>
+<%= msginfo.getSubject() %>
+<% } %>
+<br>
+<pre>
+<%= msginfo.getBody() %>
+</pre>
+<% if (msginfo.hasAttachments()) { %>
+<javamail:listattachments
+ id="attachment"
+ messageinfo="msginfo">
+<p><hr>
+<b>Attachment Type:</b>
+<%= attachment.getAttachmentType() %>
+<br>
+<% if (attachment.hasMimeType("text/plain") && 
+       attachment.isInline()){ %>
+<pre>
+<%= attachment.getContent() %>
+</pre>
+<% } else { %>
+<b>Filename:</b>
+<%= attachment.getFilename() %>
+<br>
+<b>Description:</b>
+<%= attachment.getDescription() %>
+<br>
+<a href="attachment?message=
+<%= msginfo.getNum() %>&part=<%= attachment.getNum() %>">
+Display Attachment</a>
+<% } %>
+</javamail:listattachments>
+<% } %>
+</body>
+</html>
+
diff --git a/webapp/src/main/webapp/messageheaders.jsp b/webapp/src/main/webapp/messageheaders.jsp
new file mode 100644
index 0000000..2f6dfdd
--- /dev/null
+++ b/webapp/src/main/webapp/messageheaders.jsp
@@ -0,0 +1,82 @@
+<%--
+
+    Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page language="java" import="demo.MessageInfo" %>
+<%@ page errorPage="errorpage.jsp" %>
+<%@ taglib uri="http://java.sun.com/products/javamail/demo/webapp" 
+    prefix="javamail" %>
+
+<html>
+<head>
+	<title>JavaMail messageheaders</title>
+</head>
+
+<body bgcolor="#ccccff"><hr>
+    
+<center><font face="Arial,Helvetica" font size="+3">
+<b>Folder INBOX</b></font></center><p>
+   
+<font face="Arial,Helvetica" font size="+3">
+<b><a href="logout">Logout</a>
+<a href="compose" target="compose">Compose</a>
+</b></font>
+<hr>
+    
+<table cellpadding=1 cellspacing=1 width="100%" border=1>
+<tr>
+<td width="25%" bgcolor="ffffcc">
+<font face="Arial,Helvetica" font size="+1">
+<b>Sender</b></font></td>
+<td width="15%" bgcolor="ffffcc">
+<font face="Arial,Helvetica" font size="+1">
+<b>Date</b></font></td>
+<td bgcolor="ffffcc">
+<font face="Arial,Helvetica" font size="+1">
+<b>Subject</b></font></td>
+</tr>
+<javamail:listmessages
+ id="msginfo"
+ folder="folder">
+<%-- from --%>
+<tr valign=middle>
+<td width="25%" bgcolor="ffffff">
+<font face="Arial,Helvetica">
+<% if (msginfo.hasFrom()) { %>
+<%= msginfo.getFrom() %>
+</font>
+<% } else { %>
+<font face="Arial,Helvetica,sans-serif">
+Unknown
+<% } %>
+</font></td>
+<%-- date --%>
+<td nowrap width="15%" bgcolor="ffffff">
+<font face="Arial,Helvetica">
+<%= msginfo.getDate() %>
+</font></td>
+<%-- subject & link --%>
+<td bgcolor="ffffff">
+<font face="Arial,Helvetica">
+<a href="messagecontent?message=<%= msginfo.getNum() %>">
+<% if (msginfo.hasSubject()) { %>
+<%=    msginfo.getSubject() %>
+<% } else { %>
+<i>No Subject</i>
+<% } %>
+</a>
+</font></td>
+</tr>
+</javamail:listmessages>
+</table>
+</body>
+</html>
+
diff --git a/webapp/src/main/webapp/send.jsp b/webapp/src/main/webapp/send.jsp
new file mode 100644
index 0000000..6ef00a9
--- /dev/null
+++ b/webapp/src/main/webapp/send.jsp
@@ -0,0 +1,36 @@
+<%--
+
+    Copyright (c) 2001, 2018 Oracle and/or its affiliates. All rights reserved.
+
+    This program and the accompanying materials are made available under the
+    terms of the Eclipse Distribution License v. 1.0, which is available at
+    http://www.eclipse.org/org/documents/edl-v10.php.
+
+    SPDX-License-Identifier: BSD-3-Clause
+
+--%>
+
+<%@ page language="java" %>
+<%@ page errorPage="errorpage.jsp" %>
+<%@ taglib uri="http://java.sun.com/products/javamail/demo/webapp" 
+    prefix="javamail" %>
+ 
+<html>
+<head>
+	<title>JavaMail send</title>
+</head>
+	
+<body bgcolor="white">
+<javamail:sendmail 
+   recipients="<%= request.getParameter(\"to\") %>"
+   sender="<%= request.getParameter(\"from\") %>"
+   subject="<%= request.getParameter(\"subject\") %>"
+>
+<%= request.getParameter("text") %>
+</javamail:sendmail>
+	    
+<h1>Message sent successfully</h1>
+	
+</body>
+</html>
+
diff --git a/webapp/webapp.README.txt b/webapp/webapp.README.txt
new file mode 100644
index 0000000..6a90697
--- /dev/null
+++ b/webapp/webapp.README.txt
@@ -0,0 +1,189 @@
+This demo illustrates the use of JavaMail APIs in a 3-tiered web 
+application. It must be deployed on a web server that supports
+servlet and JSP-based web applications (e.g. Tomcat) using at least
+J2SE 1.3 and Servlet 2.2.
+
+	+-----------+        +-----------+        +-----------+
+	|   IMAP    |        |           |        |           |
+	|  Server   |<-IMAP->| JavaMail  |<-HTTP->|    WWW    |
+	+-----------+        | Web app   |--HTML->|  Browser  |
+	|   SMTP    |<-SMTP->|           |        |           |
+	|  Server   |        |           |        |           |
+	+-----------+        +-----------+        +-----------+
+
+
+The JavaMail Web application supports the following functionality:
+	* login to an IMAP server
+	* list all the messages in the INBOX folder
+	* view the selected message
+	* compose and send a message
+
+It is comprised of an HTML document and several Web components 
+(servlets, JSP pages and custom tags) and is packaged in a Web 
+archive with the following contents:
+
+	index.html
+	login.jsp
+	folders.jsp
+	messageheaders.jsp
+	messagecontent.jsp
+	compose.jsp
+	send.jsp
+	errorpage.jsp
+	errordetails.jsp
+	logout.jsp
+	WEB-INF/
+	WEB-INF/classes/
+	WEB-INF/classes/demo/AttachmentServlet.class
+	WEB-INF/classes/demo/FilterServlet.class
+	WEB-INF/classes/demo/MailUserBean.class
+	WEB-INF/lib/
+	WEB-INF/lib/jtl.jar
+	WEB-INF/web.xml
+	
+
+
+
+
+
+
+
+
+
+
+The collection of .html and .jsp files provide the client-side view 
+of the JavaMail Web application. 
+
+	  index.html
+	      |
+	  login.jsp
+	      |
+         folders.jsp
+	      |
+      messageheaders.jsp
+	      |
+             / \
+            /   \
+    compose.jsp  messagecontent.jsp
+         |
+     send.jsp
+
+
+The WEB-INF/web.xml file contains the web applications deployment
+descriptor. It is an XML document that contains configuration and
+deployment information for the application.
+
+As per the Servlet specification, the WEB-INF/classes directory
+contains servlet and utility classes used by the web application.
+The FilterServlet acts as the Controller of the JavaMail Web 
+application. All requests are mapped to this servlet. It checks
+to see that the user is logged in to the server before forwarding the
+request to the appropriate JSP page. The AttachmentServlet is used
+to render non-text attachments and the MailUserBean is a utility class
+used to maintain information about the mail user.
+
+The WEB-INF/lib directory (also defined in the Servlet specification)
+contains the Java archive file for the javamail custom tag library.
+
+The follow tags are furnished in the javamail tag library:
+
+<javamail:sendmail 
+ mailuser="mailuserbean"
+ [host="hostname"] [port="port"]
+ sender="email address" recipient="email address" 
+ [cc="email address"] [bcc="email address"] [subject="subject"]>
+body of the message
+</javamail:sendmail>
+
+Description: Used to send messages.
+
+
+<javamail:message id="messageinfo"
+ folder="folder"
+ num="message number"/>
+
+Description: Used to read a single message.
+
+
+<javamail:listmessages id="mailinfobean"
+ folder="folder"
+</javamail:listmessages>
+
+Description: Used to iterate through list of messages. The body of the 
+tag is repeated for each message in the list.
+
+
+
+
+
+
+
+
+
+
+
+Building and Packaging the JavaMail Web application
+
+All source for this demo can be found in the following directories:
+	src/classes
+	src/docroot
+	src/taglib
+
+Build scripts (build.sh and build.bat) are provided for building and
+packaging the demo. Before executing the build scripts, be sure that 
+the following are in your CLASSPATH:
+	mail.jar		the JavaMail jar file
+	activation.jar		the JAF jar file
+	servlet.jar		the servlet/JSP jar file
+
+The following steps are performed when building and packaging the demo.
+
+1. Create a directory named "src/docroot/WEB-INF/classes/demo".
+
+2. Create a directory named "src/docroot/WEB-INF/lib".
+
+3. Compile the files from the "src/classes/demo" directory and add them
+   to "src/docroot/WEB-INF/classes/demo".
+
+4. Compile the files from the "src/tablib" directory.
+
+5. Create an archive (jtl.jar) of the taglib classes and add it
+   to "src/docroot/WEB-INF/lib".
+
+6. Create a web archive file of the contents of "src/docroot" (and all
+   of its sub-directories).
+
+(For a list of the contents of the resulting web archive,
+ see the beginning of this document.)
+
+
+
+
+
+
+
+
+
+
+
+A note on sending mail
+
+In order to send mail using the JavaMail Web App, it is necessary to identify
+an SMTP host. This can be accomplished in a couple of ways.
+
+1. Use the TOMCAT_OPTS environment variable.
+
+   Add the following to the TOMCAT_OPTS environment variable:
+
+	-Dmail.smtp.host=yourSMTPmailservername
+
+   Restart your web server.
+
+
+2. Modify the send.jsp file and update the javamail.war file:
+
+   Add the following parameter to the <javamail:sendmail> tag:
+
+	host="yourSMTPmailservername"
+
+   Repackage the javamail.war file to include the modified send.jsp file.