changeset 17806:0d9457fb89b2

Merge
author prr
date Wed, 16 Aug 2017 11:31:56 -0700
parents aad7d9872662 f81344689826
children 53edc766d217
files
diffstat 38 files changed, 2907 insertions(+), 192 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/net/InetAddress.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/java/net/InetAddress.java	Wed Aug 16 11:31:56 2017 -0700
@@ -72,10 +72,13 @@
  *
  * <h3> Address types </h3>
  *
- * <blockquote><table class="borderless">
+ * <table class="striped" style="margin-left:2em">
  *   <caption style="display:none">Description of unicast and multicast address types</caption>
+ *   <thead>
+ *   <tr><th scope="col">Address Type</th><th scope="col">Description</th></tr>
+ *   </thead>
  *   <tbody>
- *   <tr><th style="vertical-align:top"><i>unicast</i></th>
+ *   <tr><th scope="row" style="vertical-align:top">unicast</th>
  *       <td>An identifier for a single interface. A packet sent to
  *         a unicast address is delivered to the interface identified by
  *         that address.
@@ -94,12 +97,12 @@
  *         IP address loops around and becomes IP input on the local
  *         host. This address is often used when testing a
  *         client.</td></tr>
- *   <tr><th style="vertical-align:top"><i>multicast</i></th>
+ *   <tr><th scope="row" style="vertical-align:top">multicast</th>
  *       <td>An identifier for a set of interfaces (typically belonging
  *         to different nodes). A packet sent to a multicast address is
  *         delivered to all interfaces identified by that address.</td></tr>
  * </tbody>
- * </table></blockquote>
+ * </table>
  *
  * <h4> IP address scope </h4>
  *
@@ -163,8 +166,7 @@
  * <p> Two Java security properties control the TTL values used for
  *  positive and negative host name resolution caching:
  *
- * <blockquote>
- * <dl>
+ * <dl style="margin-left:2em">
  * <dt><b>networkaddress.cache.ttl</b></dt>
  * <dd>Indicates the caching policy for successful name lookups from
  * the name service. The value is specified as an integer to indicate
@@ -183,7 +185,6 @@
  * A value of -1 indicates "cache forever".
  * </dd>
  * </dl>
- * </blockquote>
  *
  * @author  Chris Warth
  * @see     java.net.InetAddress#getByAddress(byte[])
--- a/src/java.base/share/classes/java/net/URI.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/java/net/URI.java	Wed Aug 16 11:31:56 2017 -0700
@@ -132,23 +132,23 @@
  *
  * <p> All told, then, a URI instance has the following nine components:
  *
- * <blockquote><table class="borderless">
+ * <table class="striped" style="margin-left:2em">
  * <caption style="display:none">Describes the components of a URI:scheme,scheme-specific-part,authority,user-info,host,port,path,query,fragment</caption>
  * <thead>
- * <tr><th><i>Component</i></th><th><i>Type</i></th></tr>
+ * <tr><th scope="col">Component</th><th scope="col">Type</th></tr>
  * </thead>
- * <tbody>
- * <tr><td>scheme</td><td>{@code String}</td></tr>
- * <tr><td>scheme-specific-part&nbsp;&nbsp;&nbsp;&nbsp;</td><td>{@code String}</td></tr>
- * <tr><td>authority</td><td>{@code String}</td></tr>
- * <tr><td>user-info</td><td>{@code String}</td></tr>
- * <tr><td>host</td><td>{@code String}</td></tr>
- * <tr><td>port</td><td>{@code int}</td></tr>
- * <tr><td>path</td><td>{@code String}</td></tr>
- * <tr><td>query</td><td>{@code String}</td></tr>
- * <tr><td>fragment</td><td>{@code String}</td></tr>
+ * <tbody style="text-align:left">
+ * <tr><th scope="row">scheme</th><td>{@code String}</td></tr>
+ * <tr><th scope="row">scheme-specific-part</th><td>{@code String}</td></tr>
+ * <tr><th scope="row">authority</th><td>{@code String}</td></tr>
+ * <tr><th scope="row">user-info</th><td>{@code String}</td></tr>
+ * <tr><th scope="row">host</th><td>{@code String}</td></tr>
+ * <tr><th scope="row">port</th><td>{@code int}</td></tr>
+ * <tr><th scope="row">path</th><td>{@code String}</td></tr>
+ * <tr><th scope="row">query</th><td>{@code String}</td></tr>
+ * <tr><th scope="row">fragment</th><td>{@code String}</td></tr>
  * </tbody>
- * </table></blockquote>
+ * </table>
  *
  * In a given instance any particular component is either <i>undefined</i> or
  * <i>defined</i> with a distinct value.  Undefined string components are
@@ -253,32 +253,35 @@
  * which are taken from that specification, are used below to describe these
  * constraints:
  *
- * <blockquote><table class="borderless">
+ * <table class="striped" style="margin-left:2em">
  * <caption style="display:none">Describes categories alpha,digit,alphanum,unreserved,punct,reserved,escaped,and other</caption>
- *   <tbody>
- *   <tr><th style="vertical-align:top"><i>alpha</i></th>
+ *   <thead>
+ *   <tr><th scope="col">Category</th><th scope="col">Description</th></tr>
+ *   </thead>
+ *   <tbody style="text-align:left">
+ *   <tr><th scope="row" style="vertical-align:top">alpha</th>
  *       <td>The US-ASCII alphabetic characters,
  *        {@code 'A'}&nbsp;through&nbsp;{@code 'Z'}
  *        and {@code 'a'}&nbsp;through&nbsp;{@code 'z'}</td></tr>
- *   <tr><th style="vertical-align:top"><i>digit</i></th>
+ *   <tr><th scope="row" style="vertical-align:top">digit</th>
  *       <td>The US-ASCII decimal digit characters,
  *       {@code '0'}&nbsp;through&nbsp;{@code '9'}</td></tr>
- *   <tr><th style="vertical-align:top"><i>alphanum</i></th>
+ *   <tr><th scope="row" style="vertical-align:top">alphanum</th>
  *       <td>All <i>alpha</i> and <i>digit</i> characters</td></tr>
- *   <tr><th style="vertical-align:top"><i>unreserved</i>&nbsp;&nbsp;&nbsp;&nbsp;</th>
+ *   <tr><th scope="row" style="vertical-align:top">unreserved</th>
  *       <td>All <i>alphanum</i> characters together with those in the string
  *        {@code "_-!.~'()*"}</td></tr>
- *   <tr><th style="vertical-align:top"><i>punct</i></th>
+ *   <tr><th scope="row" style="vertical-align:top">punct</th>
  *       <td>The characters in the string {@code ",;:$&+="}</td></tr>
- *   <tr><th style="vertical-align:top"><i>reserved</i></th>
+ *   <tr><th scope="row" style="vertical-align:top">reserved</th>
  *       <td>All <i>punct</i> characters together with those in the string
  *        {@code "?/[]@"}</td></tr>
- *   <tr><th style="vertical-align:top"><i>escaped</i></th>
+ *   <tr><th scope="row" style="vertical-align:top">escaped</th>
  *       <td>Escaped octets, that is, triplets consisting of the percent
  *           character ({@code '%'}) followed by two hexadecimal digits
  *           ({@code '0'}-{@code '9'}, {@code 'A'}-{@code 'F'}, and
  *           {@code 'a'}-{@code 'f'})</td></tr>
- *   <tr><th style="vertical-align:top"><i>other</i></th>
+ *   <tr><th scope="row" style="vertical-align:top">other</th>
  *       <td>The Unicode characters that are not in the US-ASCII character set,
  *           are not control characters (according to the {@link
  *           java.lang.Character#isISOControl(char) Character.isISOControl}
@@ -287,7 +290,7 @@
  *           method)&nbsp;&nbsp;<i>(<b>Deviation from RFC 2396</b>, which is
  *           limited to US-ASCII)</i></td></tr>
  * </tbody>
- * </table></blockquote>
+ * </table>
  *
  * <p><a id="legal-chars"></a> The set of all legal URI characters consists of
  * the <i>unreserved</i>, <i>reserved</i>, <i>escaped</i>, and <i>other</i>
--- a/src/java.base/share/classes/java/net/URLConnection.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/java/net/URLConnection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -51,31 +51,16 @@
  * The abstract class {@code URLConnection} is the superclass
  * of all classes that represent a communications link between the
  * application and a URL. Instances of this class can be used both to
- * read from and to write to the resource referenced by the URL. In
- * general, creating a connection to a URL is a multistep process:
+ * read from and to write to the resource referenced by the URL.
  *
- * <div style="text-align:center"><table class="plain" style="margin:0 auto">
- * <caption style="display:none">Describes the process of creating a connection to a URL: openConnection() and connect() over time.</caption>
- * <thead>
- * <tr><th>{@code openConnection()}</th>
- *     <th>{@code connect()}</th></tr>
- * </thead>
- * <tbody>
- * <tr><td>Manipulate parameters that affect the connection to the remote
- *         resource.</td>
- *     <td>Interact with the resource; query header fields and
- *         contents.</td></tr>
- * </tbody>
- * </table>
- * ----------------------------&gt;
- * <br>time</div>
- *
+ * <p>
+ * In general, creating a connection to a URL is a multistep process:
  * <ol>
  * <li>The connection object is created by invoking the
- *     {@code openConnection} method on a URL.
+ *     {@link URL#openConnection() openConnection} method on a URL.
  * <li>The setup parameters and general request properties are manipulated.
  * <li>The actual connection to the remote object is made, using the
- *    {@code connect} method.
+ *    {@link #connect() connect} method.
  * <li>The remote object becomes available. The header fields and the contents
  *     of the remote object can be accessed.
  * </ol>
--- a/src/java.base/share/classes/java/net/URLPermission.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/java/net/URLPermission.java	Wed Aug 16 11:31:56 2017 -0700
@@ -72,22 +72,22 @@
  * separated by '/' characters. <i>path</i> may also be empty. The path is specified
  * in a similar way to the path in {@link java.io.FilePermission}. There are
  * three different ways as the following examples show:
- * <table class="plain">
+ * <table class="striped">
  * <caption>URL Examples</caption>
  * <thead>
- * <tr><th>Example url</th><th>Description</th></tr>
+ * <tr><th scope="col">Example url</th><th scope="col">Description</th></tr>
  * </thead>
- * <tbody>
- * <tr><td style="white-space:nowrap;">http://www.oracle.com/a/b/c.html</td>
+ * <tbody style="text-align:left">
+ * <tr><th scope="row" style="white-space:nowrap;">http://www.oracle.com/a/b/c.html</th>
  *   <td>A url which identifies a specific (single) resource</td>
  * </tr>
- * <tr><td>http://www.oracle.com/a/b/*</td>
+ * <tr><th scope="row">http://www.oracle.com/a/b/*</th>
  *   <td>The '*' character refers to all resources in the same "directory" - in
  *       other words all resources with the same number of path components, and
  *       which only differ in the final path component, represented by the '*'.
  *   </td>
  * </tr>
- * <tr><td>http://www.oracle.com/a/b/-</td>
+ * <tr><th scope="row">http://www.oracle.com/a/b/-</th>
  *   <td>The '-' character refers to all resources recursively below the
  *       preceding path (eg. http://www.oracle.com/a/b/c/d/e.html matches this
  *       example).
@@ -114,11 +114,12 @@
  * methods and permitted request headers of the permission (respectively). The two lists
  * are separated by a colon ':' character and elements of each list are comma separated.
  * Some examples are:
- * <pre>
- *         "POST,GET,DELETE"
- *         "GET:X-Foo-Request,X-Bar-Request"
- *         "POST,GET:Header1,Header2"
- * </pre>
+ * <ul>
+ * <li>"POST,GET,DELETE"
+ * <li>"GET:X-Foo-Request,X-Bar-Request"
+ * <li>"POST,GET:Header1,Header2"
+ * </ul>
+ * <p>
  * The first example specifies the methods: POST, GET and DELETE, but no request headers.
  * The second example specifies one request method and two headers. The third
  * example specifies two request methods, and two headers.
@@ -253,16 +254,16 @@
      * <table class="plain">
      * <caption>Examples of Path Matching</caption>
      * <thead>
-     * <tr><th>this's path</th><th>p's path</th><th>match</th></tr>
+     * <tr><th scope="col">this's path</th><th scope="col">p's path</th><th>match</th></tr>
      * </thead>
-     * <tbody>
-     * <tr><td>/a/b</td><td>/a/b</td><td>yes</td></tr>
-     * <tr><td>/a/b/*</td><td>/a/b/c</td><td>yes</td></tr>
-     * <tr><td>/a/b/*</td><td>/a/b/c/d</td><td>no</td></tr>
-     * <tr><td>/a/b/-</td><td>/a/b/c/d</td><td>yes</td></tr>
-     * <tr><td>/a/b/-</td><td>/a/b/c/d/e</td><td>yes</td></tr>
-     * <tr><td>/a/b/-</td><td>/a/b/c/*</td><td>yes</td></tr>
-     * <tr><td>/a/b/*</td><td>/a/b/c/-</td><td>no</td></tr>
+     * <tbody style="text-align:left">
+     * <tr><th scope="row">/a/b</th><th scope="row">/a/b</th><td>yes</td></tr>
+     * <tr><th scope="row" rowspan="3">/a/b/*</th><th scope="row">/a/b/c</th><td>yes</td></tr>
+     * <tr>  <th scope="row">/a/b/c/d</th><td>no</td></tr>
+     * <tr>  <th scope="row">/a/b/c/-</th><td>no</td></tr>
+     * <tr><th scope="row" rowspan="3">/a/b/-</th><th scope="row">/a/b/c/d</th><td>yes</td></tr>
+     * <tr>  <th scope="row">/a/b/c/d/e</th><td>yes</td></tr>
+     * <tr>  <th scope="row">/a/b/c/*</th><td>yes</td></tr>
      * </tbody>
      * </table>
      */
--- a/src/java.base/share/classes/java/net/doc-files/net-properties.html	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/java/net/doc-files/net-properties.html	Wed Aug 16 11:31:56 2017 -0700
@@ -23,7 +23,7 @@
  or visit www.oracle.com if you need additional information or have any
  questions.
 -->
-<HTML>
+<HTML lang="EN">
 <HEAD>
 	<META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=iso-8859-1">
 	<TITLE>Networking Properties</TITLE>
@@ -35,7 +35,7 @@
 java.net package. Some are checked only once at startup of the VM,
 and therefore are best set using the -D option of the java command,
 while others have a more dynamic nature and can also be changed using
-the <a href="../../lang/System.html#setProperty(java.lang.String,%20java.lang.String)">System.setProperty()</a> API.
+the <a href="../../lang/System.html#setProperty-java.lang.String-java.lang.String-">System.setProperty()</a> API.
 The purpose of this document is to list
 and detail all of these properties.</P>
 <P>If there is no special note, a property value is checked every time it is used.</P>
--- a/src/java.base/share/classes/java/security/DrbgParameters.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/java/security/DrbgParameters.java	Wed Aug 16 11:31:56 2017 -0700
@@ -263,18 +263,18 @@
      * Capability effective = ((DrbgParametes.Initiate) s.getParameters())
      *         .getCapability();</pre>
      * </blockquote>
-     * <table class="plain">
+     * <table class="striped">
      * <caption style="display:none">requested and effective capabilities</caption>
      * <thead>
      * <tr>
-     * <th>Requested Value</th>
-     * <th>Possible Effective Values</th>
+     * <th scope="col">Requested Value</th>
+     * <th scope="col">Possible Effective Values</th>
      * </tr>
      * </thead>
-     * <tbody>
-     * <tr><td>NONE</td><td>NONE, RESEED_ONLY, PR_AND_RESEED</td></tr>
-     * <tr><td>RESEED_ONLY</td><td>RESEED_ONLY, PR_AND_RESEED</td></tr>
-     * <tr><td>PR_AND_RESEED</td><td>PR_AND_RESEED</td></tr>
+     * <tbody style="text-align:left">
+     * <tr><th scope="row">NONE</th><td>NONE, RESEED_ONLY, PR_AND_RESEED</td></tr>
+     * <tr><th scope="row">RESEED_ONLY</th><td>RESEED_ONLY, PR_AND_RESEED</td></tr>
+     * <tr><th scope="row">PR_AND_RESEED</th><td>PR_AND_RESEED</td></tr>
      * </tbody>
      * </table>
      * <p>
--- a/src/java.base/share/classes/java/security/Provider.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/java/security/Provider.java	Wed Aug 16 11:31:56 2017 -0700
@@ -61,19 +61,19 @@
  * security framework. Services of this type cannot be added, removed,
  * or modified by applications.
  * The following attributes are automatically placed in each Provider object:
- * <table class="plain">
+ * <table class="striped">
  * <caption><b>Attributes Automatically Placed in a Provider Object</b></caption>
  * <thead>
- * <tr><th>Name</th><th>Value</th>
+ * <tr><th scope="col">Name</th><th scope="col">Value</th>
  * </thead>
- * <tbody>
- * <tr><td>{@code Provider.id name}</td>
+ * <tbody style="text-align:left">
+ * <tr><th scope="row">{@code Provider.id name}</th>
  *     <td>{@code String.valueOf(provider.getName())}</td>
- * <tr><td>{@code Provider.id version}</td>
+ * <tr><th scope="row">{@code Provider.id version}</th>
  *     <td>{@code String.valueOf(provider.getVersionStr())}</td>
- * <tr><td>{@code Provider.id info}</td>
+ * <tr><th scope="row">{@code Provider.id info}</th>
  *     <td>{@code String.valueOf(provider.getInfo())}</td>
- * <tr><td>{@code Provider.id className}</td>
+ * <tr><th scope="row">{@code Provider.id className}</th>
  *     <td>{@code provider.getClass().getName()}</td>
  * </tbody>
  * </table>
--- a/src/java.base/share/classes/java/security/cert/X509Extension.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/java/security/cert/X509Extension.java	Wed Aug 16 11:31:56 2017 -0700
@@ -153,33 +153,33 @@
      * by periods.
      *
      * <p>For example:<br>
-     * <table class="plain">
+     * <table class="striped">
      * <caption style="display:none">Examples of OIDs and extension names</caption>
      * <thead>
      * <tr>
-     * <th>OID <em>(Object Identifier)</em></th>
-     * <th>Extension Name</th></tr>
+     * <th scope="col">OID <em>(Object Identifier)</em></th>
+     * <th scope="col">Extension Name</th></tr>
      * </thead>
-     * <tbody>
-     * <tr><td>2.5.29.14</td>
+     * <tbody style="text-align:left">
+     * <tr><th scope="row">2.5.29.14</th>
      * <td>SubjectKeyIdentifier</td></tr>
-     * <tr><td>2.5.29.15</td>
+     * <tr><th scope="row">2.5.29.15</th>
      * <td>KeyUsage</td></tr>
-     * <tr><td>2.5.29.16</td>
+     * <tr><th scope="row">2.5.29.16</th>
      * <td>PrivateKeyUsage</td></tr>
-     * <tr><td>2.5.29.17</td>
+     * <tr><th scope="row">2.5.29.17</th>
      * <td>SubjectAlternativeName</td></tr>
-     * <tr><td>2.5.29.18</td>
+     * <tr><th scope="row">2.5.29.18</th>
      * <td>IssuerAlternativeName</td></tr>
-     * <tr><td>2.5.29.19</td>
+     * <tr><th scope="row">2.5.29.19</th>
      * <td>BasicConstraints</td></tr>
-     * <tr><td>2.5.29.30</td>
+     * <tr><th scope="row">2.5.29.30</th>
      * <td>NameConstraints</td></tr>
-     * <tr><td>2.5.29.33</td>
+     * <tr><th scope="row">2.5.29.33</th>
      * <td>PolicyMappings</td></tr>
-     * <tr><td>2.5.29.35</td>
+     * <tr><th scope="row">2.5.29.35</th>
      * <td>AuthorityKeyIdentifier</td></tr>
-     * <tr><td>2.5.29.36</td>
+     * <tr><th scope="row">2.5.29.36</th>
      * <td>PolicyConstraints</td></tr>
      * </tbody>
      * </table>
--- a/src/java.base/share/classes/java/util/zip/ZipUtils.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/java/util/zip/ZipUtils.java	Wed Aug 16 11:31:56 2017 -0700
@@ -81,13 +81,24 @@
      * Converts DOS time to Java time (number of milliseconds since epoch).
      */
     public static long dosToJavaTime(long dtime) {
-        LocalDateTime ldt = LocalDateTime.of(
-                (int) (((dtime >> 25) & 0x7f) + 1980),
-                (int) ((dtime >> 21) & 0x0f),
-                (int) ((dtime >> 16) & 0x1f),
-                (int) ((dtime >> 11) & 0x1f),
-                (int) ((dtime >> 5) & 0x3f),
-                (int) ((dtime << 1) & 0x3e));
+        int year;
+        int month;
+        int day;
+        int hour = (int) ((dtime >> 11) & 0x1f);
+        int minute = (int) ((dtime >> 5) & 0x3f);
+        int second = (int) ((dtime << 1) & 0x3e);
+        if ((dtime >> 16) == 0) {
+            // Interpret the 0 DOS date as 1979-11-30 for compatibility with
+            // other implementations.
+            year = 1979;
+            month = 11;
+            day = 30;
+        } else {
+            year = (int) (((dtime >> 25) & 0x7f) + 1980);
+            month = (int) ((dtime >> 21) & 0x0f);
+            day = (int) ((dtime >> 16) & 0x1f);
+        }
+        LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
         return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
                 ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
     }
--- a/src/java.base/share/classes/javax/net/ssl/SSLEngine.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/javax/net/ssl/SSLEngine.java	Wed Aug 16 11:31:56 2017 -0700
@@ -1292,7 +1292,7 @@
      * href="http://www.ietf.org/rfc/rfc7301.txt"> RFC 7301 </a>, the
      * Application-Layer Protocol Negotiation (ALPN), can negotiate
      * application-level values between peers.
-     * <p>
+     *
      * @implSpec
      * The implementation in this class throws
      * {@code UnsupportedOperationException} and performs no other action.
@@ -1317,7 +1317,7 @@
      * Like {@link #getHandshakeSession()},
      * a connection may be in the middle of a handshake. The
      * application protocol may or may not yet be available.
-     * <p>
+     *
      * @implSpec
      * The implementation in this class throws
      * {@code UnsupportedOperationException} and performs no other action.
--- a/src/java.base/share/classes/javax/net/ssl/SSLParameters.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/javax/net/ssl/SSLParameters.java	Wed Aug 16 11:31:56 2017 -0700
@@ -646,7 +646,7 @@
      * requested by the peer, the underlying protocol will determine what
      * action to take.  (For example, ALPN will send a
      * {@code "no_application_protocol"} alert and terminate the connection.)
-     * <p>
+     *
      * @implSpec
      * This method will make a copy of the {@code protocols} array.
      *
--- a/src/java.base/share/classes/javax/net/ssl/SSLSocket.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/java.base/share/classes/javax/net/ssl/SSLSocket.java	Wed Aug 16 11:31:56 2017 -0700
@@ -702,7 +702,7 @@
      * href="http://www.ietf.org/rfc/rfc7301.txt"> RFC 7301 </a>, the
      * Application-Layer Protocol Negotiation (ALPN), can negotiate
      * application-level values between peers.
-     * <p>
+     *
      * @implSpec
      * The implementation in this class throws
      * {@code UnsupportedOperationException} and performs no other action.
@@ -727,7 +727,7 @@
      * Like {@link #getHandshakeSession()},
      * a connection may be in the middle of a handshake. The
      * application protocol may or may not yet be available.
-     * <p>
+     *
      * @implSpec
      * The implementation in this class throws
      * {@code UnsupportedOperationException} and performs no other action.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AbstractAsyncSSLConnection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CompletableFuture;
+import javax.net.ssl.SSLEngine;
+import jdk.incubator.http.internal.common.ExceptionallyCloseable;
+
+
+/**
+ * Asynchronous version of SSLConnection.
+ *
+ * There are two concrete implementations of this class: AsyncSSLConnection
+ * and AsyncSSLTunnelConnection.
+ * This abstraction is useful when downgrading from HTTP/2 to HTTP/1.1 over
+ * an SSL connection. See ExchangeImpl::get in the case where an ALPNException
+ * is thrown.
+ *
+ * Note: An AsyncSSLConnection wraps a PlainHttpConnection, while an
+ *       AsyncSSLTunnelConnection wraps a PlainTunnelingConnection.
+ *       If both these wrapped classes where made to inherit from a
+ *       common abstraction then it might be possible to merge
+ *       AsyncSSLConnection and AsyncSSLTunnelConnection back into
+ *       a single class - and simply use different factory methods to
+ *       create different wrappees, but this is left up for further cleanup.
+ *
+ */
+abstract class AbstractAsyncSSLConnection extends HttpConnection
+               implements AsyncConnection, ExceptionallyCloseable {
+
+
+    AbstractAsyncSSLConnection(InetSocketAddress addr, HttpClientImpl client) {
+        super(addr, client);
+    }
+
+    abstract SSLEngine getEngine();
+    abstract AsyncSSLDelegate sslDelegate();
+    abstract HttpConnection plainConnection();
+    abstract HttpConnection downgrade();
+
+    @Override
+    final boolean isSecure() {
+        return true;
+    }
+
+    // Blocking read functions not used here
+    @Override
+    protected final ByteBuffer readImpl() throws IOException {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+    // whenReceivedResponse only used in HTTP/1.1 (Http1Exchange)
+    // AbstractAsyncSSLConnection is only used with HTTP/2
+    @Override
+    final CompletableFuture<Void> whenReceivingResponse() {
+        throw new UnsupportedOperationException("Not supported.");
+    }
+
+}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLConnection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -35,14 +35,12 @@
 import javax.net.ssl.SSLEngine;
 
 import jdk.incubator.http.internal.common.ByteBufferReference;
-import jdk.incubator.http.internal.common.ExceptionallyCloseable;
 import jdk.incubator.http.internal.common.Utils;
 
 /**
  * Asynchronous version of SSLConnection.
  */
-class AsyncSSLConnection extends HttpConnection
-                         implements AsyncConnection, ExceptionallyCloseable {
+class AsyncSSLConnection extends AbstractAsyncSSLConnection {
 
     final AsyncSSLDelegate sslDelegate;
     final PlainHttpConnection plainConnection;
@@ -61,15 +59,14 @@
         plainConnection.configureMode(mode);
     }
 
-    private CompletableFuture<Void> configureModeAsync(Void ignore) {
-        CompletableFuture<Void> cf = new CompletableFuture<>();
-        try {
-            configureMode(Mode.ASYNC);
-            cf.complete(null);
-        } catch (Throwable t) {
-            cf.completeExceptionally(t);
-        }
-        return cf;
+    @Override
+    PlainHttpConnection plainConnection() {
+        return plainConnection;
+    }
+
+    @Override
+    AsyncSSLDelegate sslDelegate() {
+        return sslDelegate;
     }
 
     @Override
@@ -92,11 +89,6 @@
     }
 
     @Override
-    boolean isSecure() {
-        return true;
-    }
-
-    @Override
     boolean isProxied() {
         return false;
     }
@@ -172,6 +164,7 @@
         plainConnection.channel().shutdownOutput();
     }
 
+    @Override
     SSLEngine getEngine() {
         return sslDelegate.getEngine();
     }
@@ -184,18 +177,6 @@
         plainConnection.setAsyncCallbacks(sslDelegate::asyncReceive, errorReceiver, sslDelegate::getNetBuffer);
     }
 
-    // Blocking read functions not used here
-
-    @Override
-    protected ByteBuffer readImpl() throws IOException {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
-    @Override
-    CompletableFuture<Void> whenReceivingResponse() {
-        throw new UnsupportedOperationException("Not supported.");
-    }
-
     @Override
     public void startReading() {
         plainConnection.startReading();
@@ -206,4 +187,9 @@
     public void stopAsyncReading() {
         plainConnection.stopAsyncReading();
     }
+
+    @Override
+    SSLConnection downgrade() {
+        return new SSLConnection(this);
+    }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/AsyncSSLTunnelConnection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.SocketChannel;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLParameters;
+import jdk.incubator.http.internal.common.ByteBufferReference;
+import jdk.incubator.http.internal.common.Utils;
+
+/**
+ * An SSL tunnel built on a Plain (CONNECT) TCP tunnel.
+ */
+class AsyncSSLTunnelConnection extends AbstractAsyncSSLConnection {
+
+    final PlainTunnelingConnection plainConnection;
+    final AsyncSSLDelegate sslDelegate;
+    final String serverName;
+
+    @Override
+    public void connect() throws IOException, InterruptedException {
+        plainConnection.connect();
+        configureMode(Mode.ASYNC);
+        startReading();
+        sslDelegate.connect();
+    }
+
+    @Override
+    boolean connected() {
+        return plainConnection.connected() && sslDelegate.connected();
+    }
+
+    @Override
+    public CompletableFuture<Void> connectAsync() {
+        throw new InternalError();
+    }
+
+    AsyncSSLTunnelConnection(InetSocketAddress addr,
+                        HttpClientImpl client,
+                        String[] alpn,
+                        InetSocketAddress proxy)
+    {
+        super(addr, client);
+        this.serverName = Utils.getServerName(addr);
+        this.plainConnection = new PlainTunnelingConnection(addr, proxy, client);
+        this.sslDelegate = new AsyncSSLDelegate(plainConnection, client, alpn, serverName);
+    }
+
+    @Override
+    synchronized void configureMode(Mode mode) throws IOException {
+        super.configureMode(mode);
+        plainConnection.configureMode(mode);
+    }
+
+    @Override
+    SSLParameters sslParameters() {
+        return sslDelegate.getSSLParameters();
+    }
+
+    @Override
+    public String toString() {
+        return "AsyncSSLTunnelConnection: " + super.toString();
+    }
+
+    @Override
+    PlainTunnelingConnection plainConnection() {
+        return plainConnection;
+    }
+
+    @Override
+    AsyncSSLDelegate sslDelegate() {
+        return sslDelegate;
+    }
+
+    @Override
+    ConnectionPool.CacheKey cacheKey() {
+        return ConnectionPool.cacheKey(address, plainConnection.proxyAddr);
+    }
+
+    @Override
+    long write(ByteBuffer[] buffers, int start, int number) throws IOException {
+        //debugPrint("Send", buffers, start, number);
+        ByteBuffer[] bufs = Utils.reduce(buffers, start, number);
+        long n = Utils.remaining(bufs);
+        sslDelegate.writeAsync(ByteBufferReference.toReferences(bufs));
+        sslDelegate.flushAsync();
+        return n;
+    }
+
+    @Override
+    long write(ByteBuffer buffer) throws IOException {
+        //debugPrint("Send", buffer);
+        long n = buffer.remaining();
+        sslDelegate.writeAsync(ByteBufferReference.toReferences(buffer));
+        sslDelegate.flushAsync();
+        return n;
+    }
+
+    @Override
+    public void writeAsync(ByteBufferReference[] buffers) throws IOException {
+        sslDelegate.writeAsync(buffers);
+    }
+
+    @Override
+    public void writeAsyncUnordered(ByteBufferReference[] buffers) throws IOException {
+        sslDelegate.writeAsyncUnordered(buffers);
+    }
+
+    @Override
+    public void flushAsync() throws IOException {
+        sslDelegate.flushAsync();
+    }
+
+    @Override
+    public void close() {
+        Utils.close(sslDelegate, plainConnection.channel());
+    }
+
+    @Override
+    void shutdownInput() throws IOException {
+        plainConnection.channel().shutdownInput();
+    }
+
+    @Override
+    void shutdownOutput() throws IOException {
+        plainConnection.channel().shutdownOutput();
+    }
+
+    @Override
+    SocketChannel channel() {
+        return plainConnection.channel();
+    }
+
+    @Override
+    boolean isProxied() {
+        return true;
+    }
+
+    @Override
+    public void setAsyncCallbacks(Consumer<ByteBufferReference> asyncReceiver,
+                                  Consumer<Throwable> errorReceiver,
+                                  Supplier<ByteBufferReference> readBufferSupplier) {
+        sslDelegate.setAsyncCallbacks(asyncReceiver, errorReceiver, readBufferSupplier);
+        plainConnection.setAsyncCallbacks(sslDelegate::asyncReceive, errorReceiver, sslDelegate::getNetBuffer);
+    }
+
+    @Override
+    public void startReading() {
+        plainConnection.startReading();
+        sslDelegate.startReading();
+    }
+
+    @Override
+    public void stopAsyncReading() {
+        plainConnection.stopAsyncReading();
+    }
+
+    @Override
+    public void enableCallback() {
+        sslDelegate.enableCallback();
+    }
+
+    @Override
+    public void closeExceptionally(Throwable cause) throws IOException {
+        Utils.close(cause, sslDelegate, plainConnection.channel());
+    }
+
+    @Override
+    SSLEngine getEngine() {
+        return sslDelegate.getEngine();
+    }
+
+    @Override
+    SSLTunnelConnection downgrade() {
+        return new SSLTunnelConnection(this);
+    }
+}
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/ExchangeImpl.java	Wed Aug 16 11:31:56 2017 -0700
@@ -82,9 +82,9 @@
                 c = c2.getConnectionFor(request);
             } catch (Http2Connection.ALPNException e) {
                 // failed to negotiate "h2"
-                AsyncSSLConnection as = e.getConnection();
+                AbstractAsyncSSLConnection as = e.getConnection();
                 as.stopAsyncReading();
-                SSLConnection sslc = new SSLConnection(as);
+                HttpConnection sslc = as.downgrade();
                 ExchangeImpl<U> ex = new Http1Exchange<>(exchange, sslc);
                 return ex;
             }
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/Http2Connection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -211,12 +211,13 @@
         this.hpackIn = new Decoder(clientSettings.getParameter(HEADER_TABLE_SIZE));
         this.windowUpdater = new ConnectionWindowUpdateSender(this, client.getReceiveBufferSize());
     }
-        /**
-         * Case 1) Create from upgraded HTTP/1.1 connection.
-         * Is ready to use. Will not be SSL. exchange is the Exchange
-         * that initiated the connection, whose response will be delivered
-         * on a Stream.
-         */
+
+    /**
+     * Case 1) Create from upgraded HTTP/1.1 connection.
+     * Is ready to use. Will not be SSL. exchange is the Exchange
+     * that initiated the connection, whose response will be delivered
+     * on a Stream.
+     */
     Http2Connection(HttpConnection connection,
                     Http2ClientImpl client2,
                     Exchange<?> exchange,
@@ -280,7 +281,7 @@
      * Throws an IOException if h2 was not negotiated
      */
     private void checkSSLConfig() throws IOException {
-        AsyncSSLConnection aconn = (AsyncSSLConnection)connection;
+        AbstractAsyncSSLConnection aconn = (AbstractAsyncSSLConnection)connection;
         SSLEngine engine = aconn.getEngine();
         String alpn = engine.getApplicationProtocol();
         if (alpn == null || !alpn.equals("h2")) {
@@ -906,14 +907,14 @@
      */
     static final class ALPNException extends IOException {
         private static final long serialVersionUID = 23138275393635783L;
-        final AsyncSSLConnection connection;
+        final AbstractAsyncSSLConnection connection;
 
-        ALPNException(String msg, AsyncSSLConnection connection) {
+        ALPNException(String msg, AbstractAsyncSSLConnection connection) {
             super(msg);
             this.connection = connection;
         }
 
-        AsyncSSLConnection getConnection() {
+        AbstractAsyncSSLConnection getConnection() {
             return connection;
         }
     }
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/HttpConnection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -34,7 +34,6 @@
 import java.util.concurrent.CompletableFuture;
 
 import jdk.incubator.http.internal.common.ByteBufferReference;
-import jdk.incubator.http.internal.common.Utils;
 
 /**
  * Wraps socket channel layer and takes care of SSL also.
@@ -136,7 +135,11 @@
             String[] alpn, boolean isHttp2, HttpClientImpl client)
     {
         if (proxy != null) {
-            return new SSLTunnelConnection(addr, client, proxy);
+            if (!isHttp2) {
+                return new SSLTunnelConnection(addr, client, proxy);
+            } else {
+                return new AsyncSSLTunnelConnection(addr, client, alpn, proxy);
+            }
         } else if (!isHttp2) {
             return new SSLConnection(addr, client, alpn);
         } else {
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainTunnelingConnection.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/PlainTunnelingConnection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -34,12 +34,15 @@
 import java.nio.ByteBuffer;
 import java.nio.channels.SocketChannel;
 import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 /**
  * A plain text socket tunnel through a proxy. Uses "CONNECT" but does not
- * encrypt. Used by WebSocket. Subclassed in SSLTunnelConnection for encryption.
+ * encrypt. Used by WebSocket, as well as HTTP over SSL + Proxy.
+ * Wrapped in SSLTunnelConnection or AsyncSSLTunnelConnection for encryption.
  */
-class PlainTunnelingConnection extends HttpConnection {
+class PlainTunnelingConnection extends HttpConnection implements AsyncConnection {
 
     final PlainHttpConnection delegate;
     protected final InetSocketAddress proxyAddr;
@@ -116,17 +119,17 @@
     }
 
     @Override
-    void writeAsync(ByteBufferReference[] buffers) throws IOException {
+    public void writeAsync(ByteBufferReference[] buffers) throws IOException {
         delegate.writeAsync(buffers);
     }
 
     @Override
-    void writeAsyncUnordered(ByteBufferReference[] buffers) throws IOException {
+    public void writeAsyncUnordered(ByteBufferReference[] buffers) throws IOException {
         delegate.writeAsyncUnordered(buffers);
     }
 
     @Override
-    void flushAsync() throws IOException {
+    public void flushAsync() throws IOException {
         delegate.flushAsync();
     }
 
@@ -165,4 +168,32 @@
     boolean isProxied() {
         return true;
     }
+
+    @Override
+    public void setAsyncCallbacks(Consumer<ByteBufferReference> asyncReceiver,
+            Consumer<Throwable> errorReceiver,
+            Supplier<ByteBufferReference> readBufferSupplier) {
+        delegate.setAsyncCallbacks(asyncReceiver, errorReceiver, readBufferSupplier);
+    }
+
+    @Override
+    public void startReading() {
+        delegate.startReading();
+    }
+
+    @Override
+    public void stopAsyncReading() {
+        delegate.stopAsyncReading();
+    }
+
+    @Override
+    public void enableCallback() {
+        delegate.enableCallback();
+    }
+
+    @Override
+    synchronized void configureMode(Mode mode) throws IOException {
+        super.configureMode(mode);
+        delegate.configureMode(mode);
+    }
 }
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLConnection.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLConnection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -77,8 +77,8 @@
      */
     SSLConnection(AsyncSSLConnection c) {
         super(c.address, c.client);
-        this.delegate = c.plainConnection;
-        AsyncSSLDelegate adel = c.sslDelegate;
+        this.delegate = c.plainConnection();
+        AsyncSSLDelegate adel = c.sslDelegate();
         this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client, adel.serverName);
         this.alpn = adel.alpn;
         this.serverName = adel.serverName;
--- a/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLTunnelConnection.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/jdk.incubator.httpclient/share/classes/jdk/incubator/http/SSLTunnelConnection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -85,6 +85,19 @@
         delegate = new PlainTunnelingConnection(addr, proxy, client);
     }
 
+    /**
+     * Create an SSLTunnelConnection from an existing connected AsyncSSLTunnelConnection.
+     * Used when downgrading from HTTP/2 to HTTP/1.1
+     */
+    SSLTunnelConnection(AsyncSSLTunnelConnection c) {
+        super(c.address, c.client);
+        this.delegate = c.plainConnection();
+        AsyncSSLDelegate adel = c.sslDelegate();
+        this.sslDelegate = new SSLDelegate(adel.engine, delegate.channel(), client, adel.serverName);
+        this.serverName = adel.serverName;
+        connected = c.connected();
+    }
+
     @Override
     SSLParameters sslParameters() {
         return sslDelegate.getSSLParameters();
--- a/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/src/jdk.zipfs/share/classes/jdk/nio/zipfs/ZipUtils.java	Wed Aug 16 11:31:56 2017 -0700
@@ -106,13 +106,24 @@
      * Converts DOS time to Java time (number of milliseconds since epoch).
      */
     public static long dosToJavaTime(long dtime) {
-        LocalDateTime ldt = LocalDateTime.of(
-                (int) (((dtime >> 25) & 0x7f) + 1980),
-                (int) ((dtime >> 21) & 0x0f),
-                (int) ((dtime >> 16) & 0x1f),
-                (int) ((dtime >> 11) & 0x1f),
-                (int) ((dtime >> 5) & 0x3f),
-                (int) ((dtime << 1) & 0x3e));
+        int year;
+        int month;
+        int day;
+        int hour = (int) ((dtime >> 11) & 0x1f);
+        int minute = (int) ((dtime >> 5) & 0x3f);
+        int second = (int) ((dtime << 1) & 0x3e);
+        if ((dtime >> 16) == 0) {
+            // Interpret the 0 DOS date as 1979-11-30 for compatibility with
+            // other implementations.
+            year = 1979;
+            month = 11;
+            day = 30;
+        } else {
+            year = (int) (((dtime >> 25) & 0x7f) + 1980);
+            month = (int) ((dtime >> 21) & 0x0f);
+            day = (int) ((dtime >> 16) & 0x1f);
+        }
+        LocalDateTime ldt = LocalDateTime.of(year, month, day, hour, minute, second);
         return TimeUnit.MILLISECONDS.convert(ldt.toEpochSecond(
                 ZoneId.systemDefault().getRules().getOffset(ldt)), TimeUnit.SECONDS);
     }
--- a/test/java/awt/appletviewer/IOExceptionIfEncodedURLTest/IOExceptionIfEncodedURLTest.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/test/java/awt/appletviewer/IOExceptionIfEncodedURLTest/IOExceptionIfEncodedURLTest.java	Wed Aug 16 11:31:56 2017 -0700
@@ -21,17 +21,6 @@
  * questions.
  */
 
-/**
- * @test
- * @key headful
- * @bug 6193279
- * @summary REGRESSION: AppletViewer throws IOException when path is encoded URL
- * @author Dmitry Cherepanov: area=appletviewer
- * @run compile IOExceptionIfEncodedURLTest.java
- * @run main IOExceptionIfEncodedURLTest
- * @run shell IOExceptionIfEncodedURLTest.sh
- */
-
 import java.applet.Applet;
 import sun.net.www.ParseUtil;
 import java.io.File;
--- a/test/java/awt/appletviewer/IOExceptionIfEncodedURLTest/IOExceptionIfEncodedURLTest.sh	Mon Aug 14 10:47:56 2017 -0700
+++ b/test/java/awt/appletviewer/IOExceptionIfEncodedURLTest/IOExceptionIfEncodedURLTest.sh	Wed Aug 16 11:31:56 2017 -0700
@@ -25,7 +25,7 @@
 #
 #   @test    IOExceptionIfEncodedURLTest.sh
 #   @key     headful
-#   @bug     6193279 6619458 8137087
+#   @bug     6193279 6619458 8137087 8186259
 #   @summary REGRESSION: AppletViewer throws IOException when path is encoded URL
 #   @author  Dmitry Cherepanov: area=appletviewer
 #   @modules java.base/sun.net.www
--- a/test/java/net/httpclient/ProxyTest.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/test/java/net/httpclient/ProxyTest.java	Wed Aug 16 11:31:56 2017 -0700
@@ -56,9 +56,12 @@
 
 /**
  * @test
- * @bug 8185852
- * @summary verifies that passing a proxy with an unresolved address does
- *          not cause  java.nio.channels.UnresolvedAddressException
+ * @bug 8185852 8181422
+ * @summary Verifies that passing a proxy with an unresolved address does
+ *          not cause java.nio.channels.UnresolvedAddressException.
+ *          Verifies that downgrading from HTTP/2 to HTTP/1.1 works through
+ *          an SSL Tunnel connection when the client is HTTP/2 and the server
+ *          and proxy are HTTP/1.1
  * @modules jdk.incubator.httpclient
  * @library /lib/testlibrary/
  * @build jdk.testlibrary.SimpleSSLContext ProxyTest
@@ -111,7 +114,7 @@
         server.start();
         try {
             test(server, HttpClient.Version.HTTP_1_1);
-            // test(server, HttpClient.Version.HTTP_2);
+            test(server, HttpClient.Version.HTTP_2);
         } finally {
             server.stop(0);
             System.out.println("Server stopped");
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/net/httpclient/http2/ProxyTest2.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import com.sun.net.httpserver.HttpContext;
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+import com.sun.net.httpserver.HttpsConfigurator;
+import com.sun.net.httpserver.HttpsParameters;
+import com.sun.net.httpserver.HttpsServer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.net.HttpURLConnection;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.security.NoSuchAlgorithmException;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSession;
+import jdk.incubator.http.HttpClient;
+import jdk.incubator.http.HttpRequest;
+import jdk.incubator.http.HttpResponse;
+import jdk.testlibrary.SimpleSSLContext;
+import java.util.concurrent.*;
+
+/**
+ * @test
+ * @bug 8181422
+ * @summary  Verifies that you can access an HTTP/2 server over HTTPS by
+ *           tunnelling through an HTTP/1.1 proxy.
+ * @modules jdk.incubator.httpclient
+ * @library /lib/testlibrary server
+ * @modules jdk.incubator.httpclient/jdk.incubator.http.internal.common
+ *          jdk.incubator.httpclient/jdk.incubator.http.internal.frame
+ *          jdk.incubator.httpclient/jdk.incubator.http.internal.hpack
+ * @build jdk.testlibrary.SimpleSSLContext ProxyTest2
+ * @run main/othervm ProxyTest2
+ * @author danielfuchs
+ */
+public class ProxyTest2 {
+
+    static {
+        try {
+            HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
+                    public boolean verify(String hostname, SSLSession session) {
+                        return true;
+                    }
+                });
+            SSLContext.setDefault(new SimpleSSLContext().get());
+        } catch (IOException ex) {
+            throw new ExceptionInInitializerError(ex);
+        }
+    }
+
+    static final String RESPONSE = "<html><body><p>Hello World!</body></html>";
+    static final String PATH = "/foo/";
+
+    static Http2TestServer createHttpsServer(ExecutorService exec) throws Exception {
+        Http2TestServer server = new Http2TestServer(true, 0, exec, SSLContext.getDefault());
+        server.addHandler(new Http2Handler() {
+            @Override
+            public void handle(Http2TestExchange he) throws IOException {
+                he.getResponseHeaders().addHeader("encoding", "UTF-8");
+                he.sendResponseHeaders(200, RESPONSE.length());
+                he.getResponseBody().write(RESPONSE.getBytes(StandardCharsets.UTF_8));
+                he.close();
+            }
+        }, PATH);
+
+        return server;
+    }
+
+    public static void main(String[] args)
+            throws Exception
+    {
+        ExecutorService exec = Executors.newCachedThreadPool();
+        Http2TestServer server = createHttpsServer(exec);
+        server.start();
+        try {
+            // Http2TestServer over HTTPS does not support HTTP/1.1
+            // => only test with a HTTP/2 client
+            test(server, HttpClient.Version.HTTP_2);
+        } finally {
+            server.stop();
+            exec.shutdown();
+            System.out.println("Server stopped");
+        }
+    }
+
+    public static void test(Http2TestServer server, HttpClient.Version version)
+            throws Exception
+    {
+        System.out.println("Server is: " + server.getAddress().toString());
+        URI uri = new URI("https://localhost:" + server.getAddress().getPort() + PATH + "x");
+        TunnelingProxy proxy = new TunnelingProxy(server);
+        proxy.start();
+        try {
+            System.out.println("Proxy started");
+            Proxy p = new Proxy(Proxy.Type.HTTP,
+                    InetSocketAddress.createUnresolved("localhost", proxy.getAddress().getPort()));
+            System.out.println("Setting up request with HttpClient for version: "
+                    + version.name() + "URI=" + uri);
+            ProxySelector ps = ProxySelector.of(
+                    InetSocketAddress.createUnresolved("localhost", proxy.getAddress().getPort()));
+            HttpClient client = HttpClient.newBuilder()
+                .version(version)
+                .proxy(ps)
+                .build();
+            HttpRequest request = HttpRequest.newBuilder()
+                .uri(uri)
+                .GET()
+                .build();
+
+            System.out.println("Sending request with HttpClient");
+            HttpResponse<String> response
+                = client.send(request, HttpResponse.BodyHandler.asString());
+            System.out.println("Got response");
+            String resp = response.body();
+            System.out.println("Received: " + resp);
+            if (!RESPONSE.equals(resp)) {
+                throw new AssertionError("Unexpected response");
+            }
+        } finally {
+            System.out.println("Stopping proxy");
+            proxy.stop();
+            System.out.println("Proxy stopped");
+        }
+    }
+
+    static class TunnelingProxy {
+        final Thread accept;
+        final ServerSocket ss;
+        final boolean DEBUG = false;
+        final Http2TestServer serverImpl;
+        TunnelingProxy(Http2TestServer serverImpl) throws IOException {
+            this.serverImpl = serverImpl;
+            ss = new ServerSocket();
+            accept = new Thread(this::accept);
+        }
+
+        void start() throws IOException {
+            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
+            accept.start();
+        }
+
+        // Pipe the input stream to the output stream.
+        private synchronized Thread pipe(InputStream is, OutputStream os, char tag) {
+            return new Thread("TunnelPipe("+tag+")") {
+                @Override
+                public void run() {
+                    try {
+                        try {
+                            int c;
+                            while ((c = is.read()) != -1) {
+                                os.write(c);
+                                os.flush();
+                                // if DEBUG prints a + or a - for each transferred
+                                // character.
+                                if (DEBUG) System.out.print(tag);
+                            }
+                            is.close();
+                        } finally {
+                            os.close();
+                        }
+                    } catch (IOException ex) {
+                        if (DEBUG) ex.printStackTrace(System.out);
+                    }
+                }
+            };
+        }
+
+        public InetSocketAddress getAddress() {
+            return new InetSocketAddress(ss.getInetAddress(), ss.getLocalPort());
+        }
+
+        // This is a bit shaky. It doesn't handle continuation
+        // lines, but our client shouldn't send any.
+        // Read a line from the input stream, swallowing the final
+        // \r\n sequence. Stops at the first \n, doesn't complain
+        // if it wasn't preceded by '\r'.
+        //
+        String readLine(InputStream r) throws IOException {
+            StringBuilder b = new StringBuilder();
+            int c;
+            while ((c = r.read()) != -1) {
+                if (c == '\n') break;
+                b.appendCodePoint(c);
+            }
+            if (b.codePointAt(b.length() -1) == '\r') {
+                b.delete(b.length() -1, b.length());
+            }
+            return b.toString();
+        }
+
+        public void accept() {
+            Socket clientConnection = null;
+            try {
+                while (true) {
+                    System.out.println("Tunnel: Waiting for client");
+                    Socket previous = clientConnection;
+                    try {
+                        clientConnection = ss.accept();
+                    } catch (IOException io) {
+                        if (DEBUG) io.printStackTrace(System.out);
+                        break;
+                    } finally {
+                        // we have only 1 client at a time, so it is safe
+                        // to close the previous connection here
+                        if (previous != null) previous.close();
+                    }
+                    System.out.println("Tunnel: Client accepted");
+                    Socket targetConnection = null;
+                    InputStream  ccis = clientConnection.getInputStream();
+                    OutputStream ccos = clientConnection.getOutputStream();
+                    Writer w = new OutputStreamWriter(ccos, "UTF-8");
+                    PrintWriter pw = new PrintWriter(w);
+                    System.out.println("Tunnel: Reading request line");
+                    String requestLine = readLine(ccis);
+                    System.out.println("Tunnel: Request status line: " + requestLine);
+                    if (requestLine.startsWith("CONNECT ")) {
+                        // We should probably check that the next word following
+                        // CONNECT is the host:port of our HTTPS serverImpl.
+                        // Some improvement for a followup!
+
+                        // Read all headers until we find the empty line that
+                        // signals the end of all headers.
+                        while(!requestLine.equals("")) {
+                            System.out.println("Tunnel: Reading header: "
+                                               + (requestLine = readLine(ccis)));
+                        }
+
+                        // Open target connection
+                        targetConnection = new Socket(
+                                serverImpl.getAddress().getAddress(),
+                                serverImpl.getAddress().getPort());
+
+                        // Then send the 200 OK response to the client
+                        System.out.println("Tunnel: Sending "
+                                           + "HTTP/1.1 200 OK\r\n\r\n");
+                        pw.print("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
+                        pw.flush();
+                    } else {
+                        // This should not happen.
+                        throw new IOException("Tunnel: Unexpected status line: "
+                                           + requestLine);
+                    }
+
+                    // Pipe the input stream of the client connection to the
+                    // output stream of the target connection and conversely.
+                    // Now the client and target will just talk to each other.
+                    System.out.println("Tunnel: Starting tunnel pipes");
+                    Thread t1 = pipe(ccis, targetConnection.getOutputStream(), '+');
+                    Thread t2 = pipe(targetConnection.getInputStream(), ccos, '-');
+                    t1.start();
+                    t2.start();
+
+                    // We have only 1 client... wait until it has finished before
+                    // accepting a new connection request.
+                    // System.out.println("Tunnel: Waiting for pipes to close");
+                    t1.join();
+                    t2.join();
+                    System.out.println("Tunnel: Done - waiting for next client");
+                }
+            } catch (Throwable ex) {
+                try {
+                    ss.close();
+                } catch (IOException ex1) {
+                    ex.addSuppressed(ex1);
+                }
+                ex.printStackTrace(System.err);
+            }
+        }
+
+        void stop() throws IOException {
+            ss.close();
+        }
+
+    }
+
+    static class Configurator extends HttpsConfigurator {
+        public Configurator(SSLContext ctx) {
+            super(ctx);
+        }
+
+        @Override
+        public void configure (HttpsParameters params) {
+            params.setSSLParameters (getSSLContext().getSupportedSSLParameters());
+        }
+    }
+
+}
--- a/test/java/net/httpclient/http2/server/Http2TestServer.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/test/java/net/httpclient/http2/server/Http2TestServer.java	Wed Aug 16 11:31:56 2017 -0700
@@ -201,7 +201,17 @@
                     InetSocketAddress addr = (InetSocketAddress) socket.getRemoteSocketAddress();
                     Http2TestServerConnection c = new Http2TestServerConnection(this, socket);
                     connections.put(addr, c);
-                    c.run();
+                    try {
+                        c.run();
+                    } catch(Throwable e) {
+                        // we should not reach here, but if we do
+                        // the connection might not have been closed
+                        // and if so then the client might wait
+                        // forever.
+                        connections.remove(addr, c);
+                        c.close();
+                        throw e;
+                    }
                 }
             } catch (Throwable e) {
                 if (!stopping) {
--- a/test/java/net/httpclient/http2/server/Http2TestServerConnection.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/test/java/net/httpclient/http2/server/Http2TestServerConnection.java	Wed Aug 16 11:31:56 2017 -0700
@@ -133,10 +133,10 @@
     }
 
     void close() {
+        stopping = true;
         streams.forEach((i, q) -> {
             q.close();
         });
-        stopping = true;
         try {
             socket.close();
             // TODO: put a reset on each stream
@@ -557,7 +557,14 @@
     void writeLoop() {
         try {
             while (!stopping) {
-                Http2Frame frame = outputQ.take();
+                Http2Frame frame;
+                try {
+                    frame = outputQ.take();
+                } catch(IOException x) {
+                    if (stopping && x.getCause() instanceof InterruptedException) {
+                        break;
+                    } else throw x;
+                }
                 if (frame instanceof ResponseHeaders) {
                     ResponseHeaders rh = (ResponseHeaders)frame;
                     HeadersFrame hf = new HeadersFrame(rh.streamid(), rh.getFlags(), encodeHeaders(rh.headers));
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/java/util/zip/ZipFile/ZeroDate.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import static java.util.zip.ZipFile.CENOFF;
+import static java.util.zip.ZipFile.CENTIM;
+import static java.util.zip.ZipFile.ENDHDR;
+import static java.util.zip.ZipFile.ENDOFF;
+import static java.util.zip.ZipFile.LOCTIM;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipOutputStream;
+
+/* @test
+ * @bug 8184940
+ * @summary JDK 9 rejects zip files where the modified day or month is 0
+ * @author Liam Miller-Cushon
+ */
+public class ZeroDate {
+
+    public static void main(String[] args) throws Exception {
+        // create a zip file, and read it in as a byte array
+        Path path = Files.createTempFile("bad", ".zip");
+        try (OutputStream os = Files.newOutputStream(path);
+                ZipOutputStream zos = new ZipOutputStream(os)) {
+            ZipEntry e = new ZipEntry("x");
+            zos.putNextEntry(e);
+            zos.write((int) 'x');
+        }
+        int len = (int) Files.size(path);
+        byte[] data = new byte[len];
+        try (InputStream is = Files.newInputStream(path)) {
+            is.read(data);
+        }
+        Files.delete(path);
+
+        // year, month, day are zero
+        testDate(data.clone(), 0, LocalDate.of(1979, 11, 30));
+        // only year is zero
+        testDate(data.clone(), 0 << 25 | 4 << 21 | 5 << 16, LocalDate.of(1980, 4, 5));
+    }
+
+    private static void testDate(byte[] data, int date, LocalDate expected) throws IOException {
+        // set the datetime
+        int endpos = data.length - ENDHDR;
+        int cenpos = u16(data, endpos + ENDOFF);
+        int locpos = u16(data, cenpos + CENOFF);
+        writeU32(data, cenpos + CENTIM, date);
+        writeU32(data, locpos + LOCTIM, date);
+
+        // ensure that the archive is still readable, and the date is 1979-11-30
+        Path path = Files.createTempFile("out", ".zip");
+        try (OutputStream os = Files.newOutputStream(path)) {
+            os.write(data);
+        }
+        try (ZipFile zf = new ZipFile(path.toFile())) {
+            ZipEntry ze = zf.entries().nextElement();
+            Instant actualInstant = ze.getLastModifiedTime().toInstant();
+            Instant expectedInstant =
+                    expected.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
+            if (!actualInstant.equals(expectedInstant)) {
+                throw new AssertionError(
+                        String.format("actual: %s, expected: %s", actualInstant, expectedInstant));
+            }
+        } finally {
+            Files.delete(path);
+        }
+    }
+
+    static int u8(byte[] data, int offset) {
+        return data[offset] & 0xff;
+    }
+
+    static int u16(byte[] data, int offset) {
+        return u8(data, offset) + (u8(data, offset + 1) << 8);
+    }
+
+    private static void writeU32(byte[] data, int pos, int value) {
+        data[pos] = (byte) (value & 0xff);
+        data[pos + 1] = (byte) ((value >> 8) & 0xff);
+        data[pos + 2] = (byte) ((value >> 16) & 0xff);
+        data[pos + 3] = (byte) ((value >> 24) & 0xff);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/nio/zipfs/ZeroDate.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import static java.util.zip.ZipFile.CENOFF;
+import static java.util.zip.ZipFile.CENTIM;
+import static java.util.zip.ZipFile.ENDHDR;
+import static java.util.zip.ZipFile.ENDOFF;
+import static java.util.zip.ZipFile.LOCTIM;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.Collections;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/* @test
+ * @bug 8184940
+ * @summary JDK 9 rejects zip files where the modified day or month is 0
+ * @author Liam Miller-Cushon
+ */
+public class ZeroDate {
+
+    public static void main(String[] args) throws Exception {
+        // create a zip file, and read it in as a byte array
+        Path path = Files.createTempFile("bad", ".zip");
+        try (OutputStream os = Files.newOutputStream(path);
+                ZipOutputStream zos = new ZipOutputStream(os)) {
+            ZipEntry e = new ZipEntry("x");
+            zos.putNextEntry(e);
+            zos.write((int) 'x');
+        }
+        int len = (int) Files.size(path);
+        byte[] data = new byte[len];
+        try (InputStream is = Files.newInputStream(path)) {
+            is.read(data);
+        }
+        Files.delete(path);
+
+        // year, month, day are zero
+        testDate(data.clone(), 0, LocalDate.of(1979, 11, 30));
+        // only year is zero
+        testDate(data.clone(), 0 << 25 | 4 << 21 | 5 << 16, LocalDate.of(1980, 4, 5));
+    }
+
+    private static void testDate(byte[] data, int date, LocalDate expected) throws IOException {
+        // set the datetime
+        int endpos = data.length - ENDHDR;
+        int cenpos = u16(data, endpos + ENDOFF);
+        int locpos = u16(data, cenpos + CENOFF);
+        writeU32(data, cenpos + CENTIM, date);
+        writeU32(data, locpos + LOCTIM, date);
+
+        // ensure that the archive is still readable, and the date is 1979-11-30
+        Path path = Files.createTempFile("out", ".zip");
+        try (OutputStream os = Files.newOutputStream(path)) {
+            os.write(data);
+        }
+        URI uri = URI.create("jar:file://" + path.toAbsolutePath());
+        try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.emptyMap())) {
+            Path entry = fs.getPath("x");
+            Instant actualInstant =
+                    Files.readAttributes(entry, BasicFileAttributes.class)
+                            .lastModifiedTime()
+                            .toInstant();
+            Instant expectedInstant =
+                    expected.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant();
+            if (!actualInstant.equals(expectedInstant)) {
+                throw new AssertionError(
+                        String.format("actual: %s, expected: %s", actualInstant, expectedInstant));
+            }
+        } finally {
+            Files.delete(path);
+        }
+    }
+
+    static int u8(byte[] data, int offset) {
+        return data[offset] & 0xff;
+    }
+
+    static int u16(byte[] data, int offset) {
+        return u8(data, offset) + (u8(data, offset + 1) << 8);
+    }
+
+    private static void writeU32(byte[] data, int pos, int value) {
+        data[pos] = (byte) (value & 0xff);
+        data[pos + 1] = (byte) ((value >> 8) & 0xff);
+        data[pos + 2] = (byte) ((value >> 16) & 0xff);
+        data[pos + 3] = (byte) ((value >> 24) & 0xff);
+    }
+}
--- a/test/sun/security/mscapi/SmallPrimeExponentP.java	Mon Aug 14 10:47:56 2017 -0700
+++ b/test/sun/security/mscapi/SmallPrimeExponentP.java	Wed Aug 16 11:31:56 2017 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -55,6 +55,9 @@
         CertAndKeyGen ckg = new CertAndKeyGen("RSA", "SHA1withRSA");
         ckg.setRandom(new MySecureRandom(seed));
 
+        String alias = "anything";
+        int count = 0;
+
         boolean see63 = false;
         boolean see65 = false;
         while (!see63 || !see65) {
@@ -78,12 +81,19 @@
                         see65 = true;
                     }
                 }
-                ks.setKeyEntry("anything", k, null, new X509Certificate[]{
+                ks.setKeyEntry(alias, k, null, new X509Certificate[]{
                     ckg.getSelfCertificate(new X500Name("CN=Me"), 1000)
                 });
+                count++;
             }
         }
-        ks.store(null, null);
+
+        // Because of JDK-8185844, it has to reload the key store after
+        // deleting an entry.
+        for (int i = 0; i < count; i++) {
+            ks.deleteEntry(alias);
+            ks.load(null, null);
+        }
     }
 
     static class MySecureRandom extends SecureRandom {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/tools/jarsigner/compatibility/Compatibility.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,1183 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @summary This test is used to verify the compatibility on jarsigner cross
+ *     different JDK releases. It also can be used to check jar signing (w/
+ *     and w/o TSA) and verifying on some specific key algorithms and digest
+ *     algorithms.
+ *     Note that, this is a manual test. For more details about the test and
+ *     its usages, please look through README.
+ *
+ * @modules java.base/sun.security.pkcs
+ *          java.base/sun.security.timestamp
+ *          java.base/sun.security.tools.keytool
+ *          java.base/sun.security.util
+ *          java.base/sun.security.x509
+ * @library /test/lib /lib/testlibrary ../warnings
+ * @compile -source 1.6 -target 1.6 JdkUtils.java
+ * @run main/manual/othervm Compatibility
+ */
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import jdk.test.lib.process.OutputAnalyzer;
+import jdk.test.lib.process.ProcessTools;
+import jdk.test.lib.util.JarUtils;
+
+public class Compatibility {
+
+    private static final String TEST_JAR_NAME = "test.jar";
+
+    private static final String TEST_SRC = System.getProperty("test.src");
+    private static final String TEST_CLASSES = System.getProperty("test.classes");
+    private static final String TEST_JDK = System.getProperty("test.jdk");
+    private static final String TEST_JARSIGNER = jarsignerPath(TEST_JDK);
+
+    private static final String PROXY_HOST = System.getProperty("proxyHost");
+    private static final String PROXY_PORT = System.getProperty("proxyPort", "80");
+
+    // An alternative security properties file.
+    // The test provides a default one, which only contains two lines:
+    // jdk.certpath.disabledAlgorithms=MD2, MD5
+    // jdk.jar.disabledAlgorithms=MD2, MD5
+    private static final String JAVA_SECURITY = System.getProperty(
+            "javaSecurityFile", TEST_SRC + "/java.security");
+
+    private static final String PASSWORD = "testpass";
+    private static final String KEYSTORE = "testKeystore";
+
+    private static final String RSA = "RSA";
+    private static final String DSA = "DSA";
+    private static final String EC = "EC";
+    private static final String[] KEY_ALGORITHMS = new String[] {
+            RSA,
+            DSA,
+            EC};
+
+    private static final String SHA1 = "SHA-1";
+    private static final String SHA256 = "SHA-256";
+    private static final String SHA512 = "SHA-512";
+    private static final String DEFAULT = "DEFAULT";
+    private static final String[] DIGEST_ALGORITHMS = new String[] {
+            SHA1,
+            SHA256,
+            SHA512,
+            DEFAULT};
+
+    private static final boolean[] EXPIRED = new boolean[] {
+            false,
+            true};
+
+    private static final Calendar CALENDAR = Calendar.getInstance();
+    private static final DateFormat DATE_FORMAT
+            = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
+
+    // The certificate validity period in minutes. The default value is 1440
+    // minutes, namely 1 day.
+    private static final int CERT_VALIDITY
+            = Integer.valueOf(System.getProperty("certValidity", "1440"));
+    static {
+        if (CERT_VALIDITY < 1 || CERT_VALIDITY > 1440) {
+            throw new RuntimeException(
+                    "certValidity if out of range [1, 1440]: " + CERT_VALIDITY);
+        }
+    }
+
+    // If true, an additional verifying will be triggered after all of
+    // valid certificates expire. The default value is false.
+    public static final boolean DELAY_VERIFY
+            = Boolean.valueOf(System.getProperty("delayVerify", "false"));
+
+    private static long lastCertStartTime;
+
+    private static DetailsOutputStream detailsOutput;
+
+    public static void main(String[] args) throws Throwable {
+        // Backups stdout and stderr.
+        PrintStream origStdOut = System.out;
+        PrintStream origStdErr = System.err;
+
+        detailsOutput = new DetailsOutputStream();
+
+        // Redirects the system output to a custom one.
+        PrintStream printStream = new PrintStream(detailsOutput);
+        System.setOut(printStream);
+        System.setErr(printStream);
+
+        List<TsaInfo> tsaList = tsaInfoList();
+        if (tsaList.size() == 0) {
+            throw new RuntimeException("TSA service is mandatory.");
+        }
+
+        List<JdkInfo> jdkInfoList = jdkInfoList();
+        List<CertInfo> certList = createCertificates(jdkInfoList);
+        createJar();
+        List<SignItem> signItems = test(jdkInfoList, tsaList, certList);
+
+        boolean failed = generateReport(tsaList, signItems);
+
+        // Restores the original stdout and stderr.
+        System.setOut(origStdOut);
+        System.setErr(origStdErr);
+
+        if (failed) {
+            throw new RuntimeException("At least one test case failed. "
+                    + "Please check the failed row(s) in report.html "
+                    + "or failedReport.html.");
+        }
+    }
+
+    // Creates a jar file that contains an empty file.
+    private static void createJar() throws IOException {
+        String testFile = "test";
+        new File(testFile).createNewFile();
+        JarUtils.createJar(TEST_JAR_NAME, testFile);
+    }
+
+    // Creates a key store that includes a set of valid/expired certificates
+    // with various algorithms.
+    private static List<CertInfo> createCertificates(List<JdkInfo> jdkInfoList)
+            throws Throwable {
+        List<CertInfo> certList = new ArrayList<CertInfo>();
+        Set<String> expiredCertFilter = new HashSet<String>();
+
+        for(JdkInfo jdkInfo : jdkInfoList) {
+            for(String keyAlgorithm : KEY_ALGORITHMS) {
+                for(String digestAlgorithm : DIGEST_ALGORITHMS) {
+                    for(int keySize : keySizes(keyAlgorithm)) {
+                        for(boolean expired : EXPIRED) {
+                            // It creates only one expired certificate for one
+                            // key algorithm.
+                            if (expired
+                                    && !expiredCertFilter.add(keyAlgorithm)) {
+                                continue;
+                            }
+
+                            CertInfo certInfo = new CertInfo(
+                                    jdkInfo.version,
+                                    keyAlgorithm,
+                                    digestAlgorithm,
+                                    keySize,
+                                    expired);
+                            if (!certList.contains(certInfo)) {
+                                String alias = createCertificate(
+                                        jdkInfo.jdkPath, certInfo);
+                                if (alias != null) {
+                                    certList.add(certInfo);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return certList;
+    }
+
+    // Creates/Updates a key store that adds a certificate with specific algorithm.
+    private static String createCertificate(String jdkPath, CertInfo certInfo)
+            throws Throwable {
+        String alias = certInfo.alias();
+
+        List<String> arguments = new ArrayList<String>();
+        arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
+        arguments.add("-v");
+        arguments.add("-storetype");
+        arguments.add("jks");
+        arguments.add("-genkey");
+        arguments.add("-keyalg");
+        arguments.add(certInfo.keyAlgorithm);
+        String sigalg = sigalg(certInfo.digestAlgorithm, certInfo.keyAlgorithm);
+        if (sigalg != null) {
+            arguments.add("-sigalg");
+            arguments.add(sigalg);
+        }
+        if (certInfo.keySize != 0) {
+            arguments.add("-keysize");
+            arguments.add(certInfo.keySize + "");
+        }
+        arguments.add("-dname");
+        arguments.add("CN=Test");
+        arguments.add("-alias");
+        arguments.add(alias);
+        arguments.add("-keypass");
+        arguments.add(PASSWORD);
+        arguments.add("-storepass");
+        arguments.add(PASSWORD);
+
+        arguments.add("-startdate");
+        arguments.add(startDate(certInfo.expired));
+        arguments.add("-validity");
+        arguments.add("1");
+        arguments.add("-keystore");
+        arguments.add(KEYSTORE);
+
+        OutputAnalyzer outputAnalyzer = execTool(
+                jdkPath + "/bin/keytool",
+                arguments.toArray(new String[arguments.size()]));
+        if (outputAnalyzer.getExitValue() == 0
+                && !outputAnalyzer.getOutput().matches("[Ee]xception")) {
+            return alias;
+        } else {
+            return null;
+        }
+    }
+
+    private static String sigalg(String digestAlgorithm, String keyAlgorithm) {
+        if (digestAlgorithm == DEFAULT) {
+            return null;
+        }
+
+        String keyName = keyAlgorithm == EC ? "ECDSA" : keyAlgorithm;
+        return digestAlgorithm.replace("-", "") + "with" + keyName;
+    }
+
+    // The validity period of a certificate always be 1 day. For creating an
+    // expired certificate, the start date is the time before 1 day, then the
+    // certificate expires immediately. And for creating a valid certificate,
+    // the start date is the time before (1 day - CERT_VALIDITY minutes), then
+    // the certificate will expires in CERT_VALIDITY minutes.
+    private static String startDate(boolean expiredCert) {
+        CALENDAR.setTime(new Date());
+        CALENDAR.add(Calendar.DAY_OF_MONTH, -1);
+        if (!expiredCert) {
+            CALENDAR.add(Calendar.MINUTE, CERT_VALIDITY);
+        }
+        Date startDate = CALENDAR.getTime();
+        lastCertStartTime = startDate.getTime();
+        return DATE_FORMAT.format(startDate);
+    }
+
+    // Retrieves JDK info from the file which is specified by property jdkListFile,
+    // or from property jdkList if jdkListFile is not available.
+    private static List<JdkInfo> jdkInfoList() throws Throwable {
+        String[] jdkList = list("jdkList");
+        if (jdkList.length == 0) {
+            jdkList = new String[] { TEST_JDK };
+        }
+
+        List<JdkInfo> jdkInfoList = new ArrayList<JdkInfo>();
+        for (String jdkPath : jdkList) {
+            JdkInfo jdkInfo = new JdkInfo(jdkPath);
+            // The JDK version must be unique.
+            if (!jdkInfoList.contains(jdkInfo)) {
+                jdkInfoList.add(jdkInfo);
+            } else {
+                System.out.println("The JDK version is duplicate: " + jdkPath);
+            }
+        }
+        return jdkInfoList;
+    }
+
+    // Retrieves TSA info from the file which is specified by property tsaListFile,
+    // or from property tsaList if tsaListFile is not available.
+    private static List<TsaInfo> tsaInfoList() throws IOException {
+        String[] tsaList = list("tsaList");
+
+        List<TsaInfo> tsaInfoList = new ArrayList<TsaInfo>();
+        for (int i = 0; i < tsaList.length; i++) {
+            String[] values = tsaList[i].split(";digests=");
+
+            String[] digests = new String[0];
+            if (values.length == 2) {
+                digests = values[1].split(",");
+            }
+
+            TsaInfo bufTsa = new TsaInfo(i, values[0]);
+
+            for (String digest : digests) {
+                bufTsa.addDigest(digest);
+            }
+
+            tsaInfoList.add(bufTsa);
+        }
+
+        return tsaInfoList;
+    }
+
+    private static String[] list(String listProp)
+            throws IOException {
+        String listFileProp = listProp + "File";
+        String listFile = System.getProperty(listFileProp);
+        if (!isEmpty(listFile)) {
+            System.out.println(listFileProp + "=" + listFile);
+            List<String> list = new ArrayList<String>();
+            BufferedReader reader = new BufferedReader(
+                    new FileReader(listFile));
+            String line;
+            while ((line = reader.readLine()) != null) {
+                String item = line.trim();
+                if (!item.isEmpty()) {
+                    list.add(item);
+                }
+            }
+            reader.close();
+            return list.toArray(new String[list.size()]);
+        }
+
+        String list = System.getProperty(listProp);
+        System.out.println(listProp + "=" + list);
+        return !isEmpty(list) ? list.split("#") : new String[0];
+    }
+
+    private static boolean isEmpty(String str) {
+        return str == null || str.isEmpty();
+    }
+
+    // A JDK (signer) signs a jar with a variety of algorithms, and then all of
+    // JDKs (verifiers), including the signer itself, try to verify the signed
+    // jars respectively.
+    private static List<SignItem> test(List<JdkInfo> jdkInfoList,
+            List<TsaInfo> tsaInfoList, List<CertInfo> certList)
+            throws Throwable {
+        detailsOutput.transferPhase();
+        List<SignItem> signItems = signing(jdkInfoList, tsaInfoList, certList);
+
+        detailsOutput.transferPhase();
+        for (SignItem signItem : signItems) {
+            for (JdkInfo verifierInfo : jdkInfoList) {
+                // JDK 6 doesn't support EC
+                if (!verifierInfo.isJdk6()
+                        || signItem.certInfo.keyAlgorithm != EC) {
+                    verifying(signItem, VerifyItem.build(verifierInfo));
+                }
+            }
+        }
+
+        if (DELAY_VERIFY) {
+            detailsOutput.transferPhase();
+            System.out.print("Waiting for delay verifying");
+            long lastCertExpirationTime = lastCertStartTime + 24 * 60 * 60 * 1000;
+            while (System.currentTimeMillis() < lastCertExpirationTime) {
+                TimeUnit.SECONDS.sleep(30);
+                System.out.print(".");
+            }
+            System.out.println();
+
+            System.out.println("Delay verifying starts");
+            for (SignItem signItem : signItems) {
+                for (VerifyItem verifyItem : signItem.verifyItems) {
+                    verifying(signItem, verifyItem);
+                }
+            }
+        }
+
+        detailsOutput.transferPhase();
+
+        return signItems;
+    }
+
+    private static List<SignItem> signing(List<JdkInfo> jdkInfos,
+            List<TsaInfo> tsaList, List<CertInfo> certList) throws Throwable {
+        List<SignItem> signItems = new ArrayList<SignItem>();
+
+        Set<String> signFilter = new HashSet<String>();
+
+        for (JdkInfo signerInfo : jdkInfos) {
+            for (String keyAlgorithm : KEY_ALGORITHMS) {
+                // JDK 6 doesn't support EC
+                if (signerInfo.isJdk6() && keyAlgorithm == EC) {
+                    continue;
+                }
+
+                for (String digestAlgorithm : DIGEST_ALGORITHMS) {
+                    String sigalg = sigalg(digestAlgorithm, keyAlgorithm);
+                    // If the signature algorithm is not supported by the JDK,
+                    // it cannot try to sign jar with this algorithm.
+                    if (sigalg != null && !signerInfo.isSupportedSigalg(sigalg)) {
+                        continue;
+                    }
+
+                    // If the JDK doesn't support option -tsadigestalg, the
+                    // associated cases just be ignored.
+                    if (digestAlgorithm != DEFAULT
+                            && !signerInfo.supportsTsadigestalg) {
+                        continue;
+                    }
+
+                    for (int keySize : keySizes(keyAlgorithm)) {
+                        for (boolean expired : EXPIRED) {
+                            CertInfo certInfo = new CertInfo(
+                                    signerInfo.version,
+                                    keyAlgorithm,
+                                    digestAlgorithm,
+                                    keySize,
+                                    expired);
+                            if (!certList.contains(certInfo)) {
+                                continue;
+                            }
+
+                            String tsadigestalg = digestAlgorithm != DEFAULT
+                                                ? digestAlgorithm
+                                                : null;
+
+                            for (TsaInfo tsaInfo : tsaList) {
+                                // It has to ignore the digest algorithm, which
+                                // is not supported by the TSA server.
+                                if(!tsaInfo.isDigestSupported(tsadigestalg)) {
+                                    continue;
+                                }
+
+                                String tsaUrl = tsaInfo.tsaUrl;
+                                if (TsaFilter.filter(
+                                        signerInfo.version,
+                                        digestAlgorithm,
+                                        expired,
+                                        tsaInfo.index)) {
+                                    tsaUrl = null;
+                                }
+
+                                String signedJar = "JDK_"
+                                        + signerInfo.version + "-CERT_"
+                                        + certInfo
+                                        + (tsaUrl == null
+                                           ? ""
+                                           : "-TSA_" + tsaInfo.index);
+
+                                // It has to ignore the same jar signing.
+                                if (!signFilter.add(signedJar)) {
+                                    continue;
+                                }
+
+                                SignItem signItem = SignItem.build()
+                                        .certInfo(certInfo)
+                                        .version(signerInfo.version)
+                                        .signatureAlgorithm(sigalg)
+                                        .tsaDigestAlgorithm(
+                                                tsaUrl == null
+                                                ? null
+                                                : tsadigestalg)
+                                        .tsaIndex(
+                                                tsaUrl == null
+                                                ? -1
+                                                : tsaInfo.index)
+                                        .signedJar(signedJar);
+                                String signingId = signingId(signItem);
+                                detailsOutput.writeAnchorName(signingId,
+                                        "Signing: " + signingId);
+
+                                OutputAnalyzer signOA = signJar(
+                                        signerInfo.jarsignerPath,
+                                        sigalg,
+                                        tsadigestalg,
+                                        tsaUrl,
+                                        certInfo.alias(),
+                                        signedJar);
+                                Status signingStatus = signingStatus(signOA);
+                                signItem.status(signingStatus);
+
+                                if (signingStatus != Status.ERROR) {
+                                    // Using the testing JDK, which is specified
+                                    // by jtreg option "-jdk", to verify the
+                                    // signed jar and extract the signature
+                                    // algorithm and timestamp digest algorithm.
+                                    String output = verifyJar(TEST_JARSIGNER,
+                                            signedJar).getOutput();
+                                    signItem.extractedSignatureAlgorithm(
+                                            extract(output,
+                                                    " *Signature algorithm.*",
+                                                    ".*: |,.*"));
+                                    signItem.extractedTsaDigestAlgorithm(
+                                            extract(output,
+                                                    " *Timestamp digest algorithm.*",
+                                                    ".*: "));
+                                }
+
+                                signItems.add(signItem);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        return signItems;
+    }
+
+    private static void verifying(SignItem signItem, VerifyItem verifyItem)
+            throws Throwable {
+        boolean delayVerify = verifyItem.status == Status.NONE;
+        String verifyingId = verifyingId(signItem, verifyItem, !delayVerify);
+        detailsOutput.writeAnchorName(verifyingId, "Verifying: " + verifyingId);
+
+        OutputAnalyzer verifyOA = verifyJar(verifyItem.jdkInfo.jarsignerPath,
+                signItem.signedJar);
+        Status verifyingStatus = verifyingStatus(verifyOA);
+
+        // It checks if the default timestamp digest algorithm is SHA-256.
+        if (verifyingStatus != Status.ERROR
+                && signItem.tsaDigestAlgorithm == null) {
+            verifyingStatus = signItem.extractedTsaDigestAlgorithm != null
+                                    && !signItem.extractedTsaDigestAlgorithm.matches("SHA-?256")
+                            ? Status.ERROR
+                            : verifyingStatus;
+            if (verifyingStatus == Status.ERROR) {
+                System.out.println("The default tsa digest is not SHA-256: "
+                    + signItem.extractedTsaDigestAlgorithm);
+            }
+        }
+
+        if (delayVerify) {
+            signItem.addVerifyItem(verifyItem.status(verifyingStatus));
+        } else {
+            verifyItem.delayStatus(verifyingStatus);
+        }
+    }
+
+    // Return key sizes according to the specified key algorithm.
+    private static int[] keySizes(String keyAlgorithm) {
+        if (keyAlgorithm == RSA || keyAlgorithm == DSA) {
+            return new int[] { 1024, 2048, 0 };
+        } else if (keyAlgorithm == EC) {
+            return new int[] { 384, 571, 0 };
+        }
+
+        return null;
+    }
+
+    // Determines the status of signing.
+    private static Status signingStatus(OutputAnalyzer outputAnalyzer) {
+        if (outputAnalyzer.getExitValue() == 0) {
+            if (outputAnalyzer.getOutput().contains(Test.WARNING)) {
+                return Status.WARNING;
+            } else {
+                return Status.NORMAL;
+            }
+        } else {
+            return Status.ERROR;
+        }
+    }
+
+    // Determines the status of verifying.
+    private static Status verifyingStatus(OutputAnalyzer outputAnalyzer) {
+        if (outputAnalyzer.getExitValue() == 0) {
+            String output = outputAnalyzer.getOutput();
+            if (!output.contains(Test.JAR_VERIFIED)) {
+                return Status.ERROR;
+            } else if (output.contains(Test.WARNING)) {
+                return Status.WARNING;
+            } else {
+                return Status.NORMAL;
+            }
+        } else {
+            return Status.ERROR;
+        }
+    }
+
+    // Extracts string from text by specified patterns.
+    private static String extract(String text, String linePattern,
+            String replacePattern) {
+        Matcher lineMatcher = Pattern.compile(linePattern).matcher(text);
+        if (lineMatcher.find()) {
+            String line = lineMatcher.group(0);
+            return line.replaceAll(replacePattern, "");
+        } else {
+            return null;
+        }
+    }
+
+    // Using specified jarsigner to sign the pre-created jar with specified
+    // algorithms.
+    private static OutputAnalyzer signJar(String jarsignerPath, String sigalg,
+            String tsadigestalg, String tsa, String alias, String signedJar)
+            throws Throwable {
+        List<String> arguments = new ArrayList<String>();
+
+        if (PROXY_HOST != null && PROXY_PORT != null) {
+            arguments.add("-J-Dhttp.proxyHost=" + PROXY_HOST);
+            arguments.add("-J-Dhttp.proxyPort=" + PROXY_PORT);
+            arguments.add("-J-Dhttps.proxyHost=" + PROXY_HOST);
+            arguments.add("-J-Dhttps.proxyPort=" + PROXY_PORT);
+        }
+        arguments.add("-J-Djava.security.properties=" + JAVA_SECURITY);
+        arguments.add("-debug");
+        arguments.add("-verbose");
+        if (sigalg != null) {
+            arguments.add("-sigalg");
+            arguments.add(sigalg);
+        }
+        if (tsa != null) {
+            arguments.add("-tsa");
+            arguments.add(tsa);
+        }
+        if (tsadigestalg != null) {
+            arguments.add("-tsadigestalg");
+            arguments.add(tsadigestalg);
+        }
+        arguments.add("-keystore");
+        arguments.add(KEYSTORE);
+        arguments.add("-storepass");
+        arguments.add(PASSWORD);
+        arguments.add("-signedjar");
+        arguments.add(signedJar + ".jar");
+        arguments.add(TEST_JAR_NAME);
+        arguments.add(alias);
+
+        OutputAnalyzer outputAnalyzer = execTool(
+                jarsignerPath,
+                arguments.toArray(new String[arguments.size()]));
+        return outputAnalyzer;
+    }
+
+    // Using specified jarsigner to verify the signed jar.
+    private static OutputAnalyzer verifyJar(String jarsignerPath,
+            String signedJar) throws Throwable {
+        OutputAnalyzer outputAnalyzer = execTool(
+                jarsignerPath,
+                "-J-Djava.security.properties=" + JAVA_SECURITY,
+                "-debug",
+                "-verbose",
+                "-certs",
+                "-keystore", KEYSTORE,
+                "-verify", signedJar + ".jar");
+        return outputAnalyzer;
+    }
+
+    // Generates the test result report.
+    private static boolean generateReport(List<TsaInfo> tsaList,
+            List<SignItem> signItems) throws IOException {
+        System.out.println("Report is being generated...");
+
+        StringBuilder report = new StringBuilder();
+        report.append(HtmlHelper.startHtml());
+        report.append(HtmlHelper.startPre());
+        // Generates TSA URLs
+        report.append("TSA list:\n");
+        for(TsaInfo tsaInfo : tsaList) {
+            report.append(
+                    String.format("%d=%s%n", tsaInfo.index, tsaInfo.tsaUrl));
+        }
+        report.append(HtmlHelper.endPre());
+
+        report.append(HtmlHelper.startTable());
+        // Generates report headers.
+        List<String> headers = new ArrayList<String>();
+        headers.add("[Certificate]");
+        headers.add("[Signer JDK]");
+        headers.add("[Signature Algorithm]");
+        headers.add("[TSA Digest]");
+        headers.add("[TSA]");
+        headers.add("[Signing Status]");
+        headers.add("[Verifier JDK]");
+        headers.add("[Verifying Status]");
+        if (DELAY_VERIFY) {
+            headers.add("[Delay Verifying Status]");
+        }
+        headers.add("[Failed]");
+        report.append(HtmlHelper.htmlRow(
+                headers.toArray(new String[headers.size()])));
+
+        StringBuilder failedReport = new StringBuilder(report.toString());
+
+        boolean failed = false;
+
+        // Generates report rows.
+        for (SignItem signItem : signItems) {
+            for (VerifyItem verifyItem : signItem.verifyItems) {
+                String reportRow = reportRow(signItem, verifyItem);
+                report.append(reportRow);
+                boolean isFailedCase = isFailed(signItem, verifyItem);
+                if (isFailedCase) {
+                    failedReport.append(reportRow);
+                }
+                failed = failed || isFailedCase;
+            }
+        }
+
+        report.append(HtmlHelper.endTable());
+        report.append(HtmlHelper.endHtml());
+        generateFile("report.html", report.toString());
+        if (failed) {
+            failedReport.append(HtmlHelper.endTable());
+            failedReport.append(HtmlHelper.endPre());
+            failedReport.append(HtmlHelper.endHtml());
+            generateFile("failedReport.html", failedReport.toString());
+        }
+
+        System.out.println("Report is generated.");
+        return failed;
+    }
+
+    private static void generateFile(String path, String content)
+            throws IOException {
+        FileWriter writer = new FileWriter(new File(path));
+        writer.write(content);
+        writer.close();
+    }
+
+    private static String jarsignerPath(String jdkPath) {
+        return jdkPath + "/bin/jarsigner";
+    }
+
+    // Executes the specified function on JdkUtils by the specified JDK.
+    private static String execJdkUtils(String jdkPath, String method,
+            String... args) throws Throwable {
+        String[] cmd = new String[args.length + 5];
+        cmd[0] = jdkPath + "/bin/java";
+        cmd[1] = "-cp";
+        cmd[2] = TEST_CLASSES;
+        cmd[3] = JdkUtils.class.getName();
+        cmd[4] = method;
+        System.arraycopy(args, 0, cmd, 5, args.length);
+        return ProcessTools.executeCommand(cmd).getOutput();
+    }
+
+    // Executes the specified JDK tools, such as keytool and jarsigner, and
+    // ensures the output is in US English.
+    private static OutputAnalyzer execTool(String toolPath, String... args)
+            throws Throwable {
+        String[] cmd = new String[args.length + 4];
+        cmd[0] = toolPath;
+        cmd[1] = "-J-Duser.language=en";
+        cmd[2] = "-J-Duser.country=US";
+        cmd[3] = "-J-Djava.security.egd=file:/dev/./urandom";
+        System.arraycopy(args, 0, cmd, 4, args.length);
+        return ProcessTools.executeCommand(cmd);
+    }
+
+    private static class JdkInfo {
+
+        private final String jdkPath;
+        private final String jarsignerPath;
+        private final String version;
+        private final boolean supportsTsadigestalg;
+
+        private Map<String, Boolean> sigalgMap = new HashMap<String, Boolean>();
+
+        private JdkInfo(String jdkPath) throws Throwable {
+            this.jdkPath = jdkPath;
+            version = execJdkUtils(jdkPath, JdkUtils.M_JAVA_RUNTIME_VERSION);
+            if (version == null || version.trim().isEmpty()) {
+                throw new RuntimeException(
+                        "Cannot determine the JDK version: " + jdkPath);
+            }
+            jarsignerPath = jarsignerPath(jdkPath);
+            supportsTsadigestalg = execTool(jarsignerPath, "-help")
+                    .getOutput().contains("-tsadigestalg");
+        }
+
+        private boolean isSupportedSigalg(String sigalg) throws Throwable {
+            if (!sigalgMap.containsKey(sigalg)) {
+                boolean isSupported = "true".equalsIgnoreCase(
+                        execJdkUtils(
+                                jdkPath,
+                                JdkUtils.M_IS_SUPPORTED_SIGALG,
+                                sigalg));
+                sigalgMap.put(sigalg, isSupported);
+            }
+
+            return sigalgMap.get(sigalg);
+        }
+
+        private boolean isJdk6() {
+            return version.startsWith("1.6");
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result
+                    + ((version == null) ? 0 : version.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            JdkInfo other = (JdkInfo) obj;
+            if (version == null) {
+                if (other.version != null)
+                    return false;
+            } else if (!version.equals(other.version))
+                return false;
+            return true;
+        }
+    }
+
+    private static class TsaInfo {
+
+        private final int index;
+        private final String tsaUrl;
+        private Set<String> digestList = new HashSet<String>();
+
+        private TsaInfo(int index, String tsa) {
+            this.index = index;
+            this.tsaUrl = tsa;
+        }
+
+        private void addDigest(String digest) {
+            if (!ignore(digest)) {
+                digestList.add(digest);
+            }
+        }
+
+        private static boolean ignore(String digest) {
+            return !SHA1.equalsIgnoreCase(digest)
+                    && !SHA256.equalsIgnoreCase(digest)
+                    && !SHA512.equalsIgnoreCase(digest);
+        }
+
+        private boolean isDigestSupported(String digest) {
+            return digest == null || digestList.isEmpty()
+                    || digestList.contains(digest);
+        }
+    }
+
+    private static class CertInfo {
+
+        private final String jdkVersion;
+        private final String keyAlgorithm;
+        private final String digestAlgorithm;
+        private final int keySize;
+        private final boolean expired;
+
+        private CertInfo(String jdkVersion, String keyAlgorithm,
+                String digestAlgorithm, int keySize, boolean expired) {
+            this.jdkVersion = jdkVersion;
+            this.keyAlgorithm = keyAlgorithm;
+            this.digestAlgorithm = digestAlgorithm;
+            this.keySize = keySize;
+            this.expired = expired;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result
+                    + ((digestAlgorithm == null) ? 0 : digestAlgorithm.hashCode());
+            result = prime * result + (expired ? 1231 : 1237);
+            result = prime * result
+                    + ((jdkVersion == null) ? 0 : jdkVersion.hashCode());
+            result = prime * result
+                    + ((keyAlgorithm == null) ? 0 : keyAlgorithm.hashCode());
+            result = prime * result + keySize;
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj)
+                return true;
+            if (obj == null)
+                return false;
+            if (getClass() != obj.getClass())
+                return false;
+            CertInfo other = (CertInfo) obj;
+            if (digestAlgorithm == null) {
+                if (other.digestAlgorithm != null)
+                    return false;
+            } else if (!digestAlgorithm.equals(other.digestAlgorithm))
+                return false;
+            if (expired != other.expired)
+                return false;
+            if (jdkVersion == null) {
+                if (other.jdkVersion != null)
+                    return false;
+            } else if (!jdkVersion.equals(other.jdkVersion))
+                return false;
+            if (keyAlgorithm == null) {
+                if (other.keyAlgorithm != null)
+                    return false;
+            } else if (!keyAlgorithm.equals(other.keyAlgorithm))
+                return false;
+            if (keySize != other.keySize)
+                return false;
+            return true;
+        }
+
+        private String alias() {
+            return jdkVersion + "_" + toString();
+        }
+
+        @Override
+        public String toString() {
+            return keyAlgorithm + "_" + digestAlgorithm
+                    + (keySize == 0 ? "" : "_" + keySize)
+                    + (expired ? "_Expired" : "");
+        }
+    }
+
+    // It does only one timestamping for the same JDK, digest algorithm and
+    // TSA service with an arbitrary valid/expired certificate.
+    private static class TsaFilter {
+
+        private static final Set<Condition> SET = new HashSet<Condition>();
+
+        private static boolean filter(String signerVersion,
+                String digestAlgorithm, boolean expiredCert, int tsaIndex) {
+            return !SET.add(new Condition(signerVersion, digestAlgorithm,
+                    expiredCert, tsaIndex));
+        }
+
+        private static class Condition {
+
+            private final String signerVersion;
+            private final String digestAlgorithm;
+            private final boolean expiredCert;
+            private final int tsaIndex;
+
+            private Condition(String signerVersion, String digestAlgorithm,
+                    boolean expiredCert, int tsaIndex) {
+                this.signerVersion = signerVersion;
+                this.digestAlgorithm = digestAlgorithm;
+                this.expiredCert = expiredCert;
+                this.tsaIndex = tsaIndex;
+            }
+
+            @Override
+            public int hashCode() {
+                final int prime = 31;
+                int result = 1;
+                result = prime * result
+                        + ((digestAlgorithm == null) ? 0 : digestAlgorithm.hashCode());
+                result = prime * result + (expiredCert ? 1231 : 1237);
+                result = prime * result
+                        + ((signerVersion == null) ? 0 : signerVersion.hashCode());
+                result = prime * result + tsaIndex;
+                return result;
+            }
+
+            @Override
+            public boolean equals(Object obj) {
+                if (this == obj)
+                    return true;
+                if (obj == null)
+                    return false;
+                if (getClass() != obj.getClass())
+                    return false;
+                Condition other = (Condition) obj;
+                if (digestAlgorithm == null) {
+                    if (other.digestAlgorithm != null)
+                        return false;
+                } else if (!digestAlgorithm.equals(other.digestAlgorithm))
+                    return false;
+                if (expiredCert != other.expiredCert)
+                    return false;
+                if (signerVersion == null) {
+                    if (other.signerVersion != null)
+                        return false;
+                } else if (!signerVersion.equals(other.signerVersion))
+                    return false;
+                if (tsaIndex != other.tsaIndex)
+                    return false;
+                return true;
+            }
+        }}
+
+    private static enum Status {
+
+        // No action due to pre-action fails.
+        NONE,
+
+        // jar is signed/verified with error
+        ERROR,
+
+        // jar is signed/verified with warning
+        WARNING,
+
+        // jar is signed/verified without any warning and error
+        NORMAL
+    }
+
+    private static class SignItem {
+
+        private CertInfo certInfo;
+        private String version;
+        private String signatureAlgorithm;
+        // Signature algorithm that is extracted from verification output.
+        private String extractedSignatureAlgorithm;
+        private String tsaDigestAlgorithm;
+        // TSA digest algorithm that is extracted from verification output.
+        private String extractedTsaDigestAlgorithm;
+        private int tsaIndex;
+        private Status status;
+        private String signedJar;
+
+        private List<VerifyItem> verifyItems = new ArrayList<VerifyItem>();
+
+        private static SignItem build() {
+            return new SignItem();
+        }
+
+        private SignItem certInfo(CertInfo certInfo) {
+            this.certInfo = certInfo;
+            return this;
+        }
+
+        private SignItem version(String version) {
+            this.version = version;
+            return this;
+        }
+
+        private SignItem signatureAlgorithm(String signatureAlgorithm) {
+            this.signatureAlgorithm = signatureAlgorithm;
+            return this;
+        }
+
+        private SignItem extractedSignatureAlgorithm(
+                String extractedSignatureAlgorithm) {
+            this.extractedSignatureAlgorithm = extractedSignatureAlgorithm;
+            return this;
+        }
+
+        private SignItem tsaDigestAlgorithm(String tsaDigestAlgorithm) {
+            this.tsaDigestAlgorithm = tsaDigestAlgorithm;
+            return this;
+        }
+
+        private SignItem extractedTsaDigestAlgorithm(
+                String extractedTsaDigestAlgorithm) {
+            this.extractedTsaDigestAlgorithm = extractedTsaDigestAlgorithm;
+            return this;
+        }
+
+        private SignItem tsaIndex(int tsaIndex) {
+            this.tsaIndex = tsaIndex;
+            return this;
+        }
+
+        private SignItem status(Status status) {
+            this.status = status;
+            return this;
+        }
+
+        private SignItem signedJar(String signedJar) {
+            this.signedJar = signedJar;
+            return this;
+        }
+
+        private void addVerifyItem(VerifyItem verifyItem) {
+            verifyItems.add(verifyItem);
+        }
+    }
+
+    private static class VerifyItem {
+
+        private JdkInfo jdkInfo;
+        private Status status = Status.NONE;
+        private Status delayStatus = Status.NONE;
+
+        private static VerifyItem build(JdkInfo jdkInfo) {
+            VerifyItem verifyItem = new VerifyItem();
+            verifyItem.jdkInfo = jdkInfo;
+            return verifyItem;
+        }
+
+        private VerifyItem status(Status status) {
+            this.status = status;
+            return this;
+        }
+
+        private VerifyItem delayStatus(Status status) {
+            this.delayStatus = status;
+            return this;
+        }
+    }
+
+    // The identifier for a specific signing.
+    private static String signingId(SignItem signItem) {
+        return signItem.signedJar;
+    }
+
+    // The identifier for a specific verifying.
+    private static String verifyingId(SignItem signItem, VerifyItem verifyItem,
+            boolean delayVerify) {
+        return "S_" + signingId(signItem) + "-" + (delayVerify ? "DV" : "V")
+                + "_" + verifyItem.jdkInfo.version;
+    }
+
+    private static String reportRow(SignItem signItem, VerifyItem verifyItem) {
+        List<String> values = new ArrayList<String>();
+        values.add(signItem.certInfo.toString());
+        values.add(signItem.version);
+        values.add(null2Default(signItem.signatureAlgorithm,
+                signItem.extractedSignatureAlgorithm));
+        values.add(signItem.tsaIndex == -1
+                   ? ""
+                   : null2Default(signItem.tsaDigestAlgorithm,
+                        signItem.extractedTsaDigestAlgorithm));
+        values.add(signItem.tsaIndex == -1 ? "" : signItem.tsaIndex + "");
+        values.add(HtmlHelper.anchorLink(
+                PhaseOutputStream.fileName(PhaseOutputStream.Phase.SIGNING),
+                signingId(signItem),
+                signItem.status.toString()));
+        values.add(verifyItem.jdkInfo.version);
+        values.add(HtmlHelper.anchorLink(
+                PhaseOutputStream.fileName(PhaseOutputStream.Phase.VERIFYING),
+                verifyingId(signItem, verifyItem, false),
+                verifyItem.status.toString()));
+        if (DELAY_VERIFY) {
+            values.add(HtmlHelper.anchorLink(
+                    PhaseOutputStream.fileName(
+                            PhaseOutputStream.Phase.DELAY_VERIFYING),
+                    verifyingId(signItem, verifyItem, true),
+                    verifyItem.delayStatus.toString()));
+        }
+        values.add(isFailed(signItem, verifyItem) ? "X" : "");
+        return HtmlHelper.htmlRow(values.toArray(new String[values.size()]));
+    }
+
+    private static boolean isFailed(SignItem signItem,
+            VerifyItem verifyItem) {
+        return signItem.status == Status.ERROR
+                || verifyItem.status == Status.ERROR
+                || verifyItem.delayStatus == Status.ERROR;
+    }
+
+    // If a value is null, then displays the default value or N/A.
+    private static String null2Default(String value, String defaultValue) {
+        return value == null
+               ? DEFAULT + "(" + (defaultValue == null
+                                  ? "N/A"
+                                  : defaultValue) + ")"
+               : value;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/tools/jarsigner/compatibility/DetailsOutputStream.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/*
+ * A custom output stream that redirects the testing outputs to a file, called
+ * details.out. It also calls another output stream to save some outputs to
+ * other files.
+ */
+public class DetailsOutputStream extends FileOutputStream {
+
+    private PhaseOutputStream phaseOutputStream = new PhaseOutputStream();
+
+    public DetailsOutputStream() throws FileNotFoundException {
+        super("details.out", true);
+    }
+
+    public void transferPhase() throws IOException {
+        if (phaseOutputStream.isCorePhase()) {
+            phaseOutputStream.write(HtmlHelper.endHtml());
+            phaseOutputStream.write(HtmlHelper.endPre());
+        }
+
+        phaseOutputStream.transfer();
+
+        if (phaseOutputStream.isCorePhase()) {
+            phaseOutputStream.write(HtmlHelper.startHtml());
+            phaseOutputStream.write(HtmlHelper.startPre());
+        }
+    }
+
+    @Override
+    public void write(byte[] b) throws IOException {
+        super.write(b);
+        phaseOutputStream.write(b);
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        super.write(b);
+        phaseOutputStream.write(b);
+    }
+
+    @Override
+    public void write(byte b[], int off, int len) throws IOException {
+        super.write(b, off, len);
+        phaseOutputStream.write(b, off, len);
+    }
+
+    public void writeAnchorName(String name, String text) throws IOException {
+        super.write((text).getBytes());
+        super.write('\n');
+        phaseOutputStream.write(HtmlHelper.anchorName(name, text));
+        phaseOutputStream.write('\n');
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/tools/jarsigner/compatibility/HtmlHelper.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * A helper that is used for creating HTML elements.
+ */
+public class HtmlHelper {
+
+    private static final String STYLE
+            = "style=\"font-family: Courier New; "
+            + "font-size: 12px; "
+            + "white-space: pre-wrap\"";
+
+    public static String htmlRow(String... values) {
+        StringBuilder row = new StringBuilder();
+        row.append(startTr());
+        for (String value : values) {
+            row.append(startTd());
+            row.append(value);
+            row.append(endTd());
+        }
+        row.append(endTr());
+        return row.toString();
+    }
+
+    public static String startHtml() {
+        return startTag("html");
+    }
+
+    public static String endHtml() {
+        return endTag("html");
+    }
+
+    public static String startPre() {
+        return startTag("pre " + STYLE);
+    }
+
+    public static String endPre() {
+        return endTag("pre");
+    }
+
+    public static String startTable() {
+        return startTag("table " + STYLE);
+    }
+
+    public static String endTable() {
+        return endTag("table");
+    }
+
+    public static String startTr() {
+        return startTag("tr");
+    }
+
+    public static String endTr() {
+        return endTag("tr");
+    }
+
+    public static String startTd() {
+        return startTag("td");
+    }
+
+    public static String endTd() {
+        return endTag("td");
+    }
+
+    public static String startTag(String tag) {
+        return "<" + tag + ">";
+    }
+
+    public static String endTag(String tag) {
+        return "</" + tag + ">";
+    }
+
+    public static String anchorName(String name, String text) {
+        return "<a name=" + name + ">" + text + "</a>";
+    }
+
+    public static String anchorLink(String file, String anchorName,
+            String text) {
+        return "<a href=" + file + "#" + anchorName + ">" + text + "</a>";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/tools/jarsigner/compatibility/JdkUtils.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+
+/*
+ * This class is used for returning some specific JDK information.
+ */
+public class JdkUtils {
+
+    static final String M_JAVA_RUNTIME_VERSION = "javaRuntimeVersion";
+    static final String M_IS_SUPPORTED_SIGALG = "isSupportedSigalg";
+
+    // Returns the JDK build version.
+    static String javaRuntimeVersion() {
+        return System.getProperty("java.runtime.version");
+    }
+
+    // Checks if the specified signature algorithm is supported by the JDK.
+    static boolean isSupportedSigalg(String sigalg) {
+        boolean isSupported = false;
+        try {
+            isSupported = Signature.getInstance(sigalg) != null;
+        } catch (NoSuchAlgorithmException e) { }
+
+        if (!isSupported) {
+            System.out.println(sigalg + " is not supported yet.");
+        }
+
+        return isSupported;
+    }
+
+    public static void main(String[] args) {
+        if (M_JAVA_RUNTIME_VERSION.equals(args[0])) {
+            System.out.print(javaRuntimeVersion());
+        } else if (M_IS_SUPPORTED_SIGALG.equals(args[0])) {
+            System.out.print(isSupportedSigalg(args[1]));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/tools/jarsigner/compatibility/PhaseOutputStream.java	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/*
+ * A custom output stream that saves the testing details to different files
+ * according to the current testing phase.
+ */
+public class PhaseOutputStream extends OutputStream {
+
+    public enum Phase {
+        PRE_SIGNING,        // before jar signing
+        SIGNING,            // jar signing
+        VERIFYING,          // jar verifying
+        DELAY_VERIFYING,    // jar verifying after certificates expire
+        POST_VERIFYING;     // after jar verifying
+    }
+
+    private OutputStream signingOut = null;
+    private OutputStream verifyingOut = null;
+    private OutputStream delayVerifyingOut = null;
+
+    private Phase currentPhase = Phase.PRE_SIGNING;
+
+    public void transfer() {
+        switch (currentPhase) {
+        case PRE_SIGNING:
+            currentPhase = Phase.SIGNING;
+            break;
+        case SIGNING:
+            currentPhase = Phase.VERIFYING;
+            break;
+        case VERIFYING:
+            currentPhase = Compatibility.DELAY_VERIFY
+                    ? Phase.DELAY_VERIFYING
+                    : Phase.POST_VERIFYING;
+            break;
+        case DELAY_VERIFYING:
+            currentPhase = Phase.POST_VERIFYING;
+            break;
+        case POST_VERIFYING:
+            currentPhase = Phase.POST_VERIFYING;
+            break;
+        }
+    }
+
+    // The core phases are SIGNING, VERIFYING and DELAY_VERIFYING.
+    public boolean isCorePhase() {
+        return currentPhase != PhaseOutputStream.Phase.PRE_SIGNING
+                && currentPhase != PhaseOutputStream.Phase.POST_VERIFYING;
+    }
+
+    public Phase currentPhase() {
+        return currentPhase;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        OutputStream output = phaseOut();
+        if (output != null) {
+            output.write(b);
+        }
+    }
+
+    @Override
+    public void write(byte[] b) throws IOException {
+        OutputStream output = phaseOut();
+        if (output != null) {
+            output.write(b);
+        }
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        OutputStream output = phaseOut();
+        if (output != null) {
+            output.write(b, off, len);
+        }
+    }
+
+    public void write(String str) throws IOException {
+        write(str.getBytes());
+    }
+
+    private OutputStream phaseOut() throws FileNotFoundException {
+        switch (currentPhase) {
+        case SIGNING:
+            return signingOut == null
+                    ? signingOut = createOutput(Phase.SIGNING)
+                    : signingOut;
+        case VERIFYING:
+            return verifyingOut == null
+                    ? verifyingOut = createOutput(Phase.VERIFYING)
+                    : verifyingOut;
+        case DELAY_VERIFYING:
+            return delayVerifyingOut == null
+                    ? delayVerifyingOut = createOutput(Phase.DELAY_VERIFYING)
+                    : delayVerifyingOut;
+        default:
+            return null;
+        }
+    }
+
+    @Override
+    public void flush() throws IOException {
+        flush(signingOut);
+        flush(verifyingOut);
+        flush(delayVerifyingOut);
+    }
+
+    private void flush(OutputStream output) throws IOException {
+        if (output != null) {
+            output.flush();
+        }
+    }
+
+    @Override
+    public void close() throws IOException {
+        close(signingOut);
+        close(verifyingOut);
+        close(delayVerifyingOut);
+    }
+
+    private void close(OutputStream output) throws IOException {
+        if (output != null) {
+            output.close();
+        }
+    }
+
+    private static OutputStream createOutput(Phase phase)
+            throws FileNotFoundException {
+        return new FileOutputStream(fileName(phase), true);
+    }
+
+    public static String fileName(Phase phase) {
+        return phase.name() + ".html";
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/tools/jarsigner/compatibility/README	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,215 @@
+# Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+#
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+#
+# This code 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
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+#
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+
+##### Summary #####
+This test is used to verify the compatibility on jarsigner cross different JDK
+releases. It also can be used to check jar signing (w/ and w/o TSA) and verifying
+on some specific key algorithms and digest algorithms.
+
+##### Output #####
+The test will generate a report, at JTwork/scratch/report.html, to display the
+key parameters for signing and the status of signing and verifying. And it will
+generate another report, at JTwork/scratch/failedReport.html, to collect all of
+failed cases.
+
+Please note that, the test may output a great deal of logs if the jdk list and
+TSA list are big, and that would lead to jtreg output overflow. So, it redirects
+stdout and stderr to file JTwork/scratch/details.out.
+
+##### Report Columns #####
+Certificate
+    Certificate identifier. The identifier consists of specific attributes of
+    the certificate. Generally, the naming convention is:
+    KeyAlgorithm_DigestAlgorithm_[KeySize][_Expired]
+
+Signer JDK
+    The JDK version that signs jar.
+
+Signature Algorithm
+    The signature algorithm used by signing.
+
+TSA Digest
+    The timestamp digest algorithm used by signing.
+
+TSA
+    TSA URL index. All of TSA URLs and their indices can be found at the top
+    of this report.
+
+Signing Status
+    Signing process result status. The status are the followings:
+    [1]NONE, no action.
+    [2]NORMAL, no any error and warning.
+    [3]WARNING, no any error but some warnings raise.
+    [4]ERROR, some errors raise.
+
+Verifier JDK
+    The JDK version that verifies signed jars.
+
+Verifying Status
+    Verifying process result status. The status are the same as those for
+    "Status of Signing".
+
+Delay Verifying Status
+    Delay verifying process result status. The status are the same as those
+    for "Status of Signing".
+
+Failed
+    It highlights which case fails. The failed cases (rows) are marked with
+    letter X.
+
+##### Usages #####
+jtreg [-options] \
+    -jdk:<path/to/testing/JDK>
+    [-DproxyHost=<host> \
+     -DproxyPort=<port> \
+     -DtsaListFile=</url/to/tsaListFile> \
+     -DtsaList=</path/to/tsa1#/path/to/tsa2#/path/to/tsa3#...> \
+     -DjdkListFile=</path/to/jdkListFile> \
+     -DjdkList=</path/to/jdk1#/path/to/jdk2#/path/to/jdk3#...> \
+     -DjavaSecurityFile=</path/to/java/security/properties/file> \
+     -DdelayVerify=<true|false> \
+     -DcertValidity=<[1, 1440]>] \
+    <JDK_REPO>/jdk/test/sun/security/tools/jarsigner/compatibility/Compatibility.java
+
+Besides the common jtreg options, like -jdk, this test introduces a set of
+properties for receiving users' inputs and making the test more flexible. These
+properties are:
+proxyHost=<host>
+    This property indicates proxy host.
+
+proxyPort=<port>
+    This property indicates proxy port. The default value is 80.
+
+tsaListFile=</path/to/tsaListFile>
+    This property indicates a local file, which contains a set of TSA URLs and
+    the supported digest algorithms (by optional parameter digests). The format
+    of the file content looks like the below,
+    http://path/to/tsa1
+    http://path/to/tsa2;digests=SHA-1,SHA-256
+    https://path/to/tsa3
+    ...
+
+    If a TSA line does not list the supported digest algorithms, that means
+    the TSA supports SHA-1, SHA-256 and SHA-512. Because the test only focus
+    on SHA-1, SHA-256 and SHA-512. So, if other digest algorithms, like SHA-224
+    and SHA-384, are listed, they just be ignored.
+
+tsaList=</path/to/tsa1#/path/to/tsa2;digests=SHA-1,SHA-256#...>
+    This property directly lists a set of TSAs in command. "#" is the delimiter.
+    Note that, if both of tsaListFile and tsaList are specified, only property
+    jdkListFile is selected. If neither of tsaListFile and tsaList is specified,
+    the test will fails immediately.
+
+jdkListFile=</path/to/jdkListFile>
+    This property indicates a local file, which contains a set of local JDK
+    paths. The style of the file content looks like the below,
+    /path/to/jdk1
+    /path/to/jdk2
+    /path/to/jdk3
+    ...
+
+jdkList=</path/to/jdk1#/path/to/jdk2#/path/to/jdk3#...>
+    This property directly lists a set of local JDK paths in command. "#" is
+    the delimiter.
+    Note that, if both of jdkListFile and jdkList are specified, only property
+    jdkListFile is selected. If neither of jdkListFile nor jdkList is specified,
+    the testing JDK, which is specified by jtreg option -jdk will be used as
+    the only one JDK in the JDK list.
+
+javaSecurityFile=</path/to/java/security/properties/file>
+    This property indicates an alternative java security properties file. The
+    default file is the path of file java.scurity that is distributed with
+    this test.
+
+delayVerify=<true|false>
+    This property indicates if doing an additional verifying after all of valid
+    certificates expire. The default value is false.
+
+certValidity=<[1, 1440]>
+    This property indicates the remaining validity period in minutes for valid
+    certificates. The value range is [1, 1440]. The default value is 1440.
+    Note that, if delayVerify is false, this property doesn't take effect.
+
+The testing JDK, which is specified by jtreg option "-jdk", should include the
+fix for JDK-8163304. Otherwise, the signature algorithm and timestamp digest
+algorithm cannot be extracted from verification output. And this JDK should
+support as many as possible signature algorithms. Anyway the latest JDK build
+is always recommended.
+
+##### Examples #####
+$ cat /path/to/jdkList
+/path/to/jdk6u171-b05
+/path/to/jdk7u161-b05
+/path/to/jdk8u144-b01
+/path/to/jdk9-179 
+
+$ cat /path/to/tsaList
+http://timestamp.comodoca.com/rfc3161
+http://sha256timestamp.ws.symantec.com/sha256/timestamp
+http://tsa.starfieldtech.com
+http://timestamp.entrust.net/TSS/RFC3161sha1TS;digests=SHA-1,SHA-256
+http://timestamp.entrust.net/TSS/RFC3161sha2TS;digests=SHA-1,SHA-256
+http://rfc3161timestamp.globalsign.com/advanced;digests=SHA-256,SHA-512
+http://rfc3161timestamp.globalsign.com/standard
+http://timestamp.globalsign.com/scripts/timstamp.dll
+http://timestamp.globalsign.com/?signature=sha2;digests=SHA-256,SHA-512
+http://timestamp.digicert.com
+http://time.certum.pl
+http://tsa.swisssign.net
+http://zeitstempel.dfn.de
+https://tsp.iaik.tugraz.at/tsp/TspRequest
+
+$ jtreg -va -nr -timeout:100 \
+    -jdk:/path/to/latest/jdk \
+    -DproxyHost=<proxy> -DproxyPort=<port> \
+    -DjdkListFile=/path/to/jdkList \
+    -DtsaListFile=/path/to/tsaList \
+    -DdelayVerify=true -DcertValidity=60 \
+    <JDK_REPO>/jdk/test/sun/security/tools/jarsigner/compatibility/Compatibility.java
+
+The above is a comprehensive usage example. File "jdkList" lists the paths of
+testing JDK builds, and file "tsaList" lists the URLs of TSA services. Some TSAs,
+like http://timestamp.entrust.net/TSS/RFC3161sha1TS, specify the supported digest
+algorithms. Other TSAs, which don't specify parameter digests, are regarded to
+support SHA-1, SHA-256 and SHA-512. The test uses a proxy to access TSA services.
+And it enables delay verifying and set the certificate validity period to 60
+minutes. So, after the first verification is done, the test will wait for all
+of valid certificates expire and then does verification again.
+
+If don't want to provide such JDK list and TSA list files, the test allows to
+specify JDKs and TSAs (via properties jdkList and tsaList respectively) in the
+command directly, like the below style,
+$ jtreg -va -nr -timeout:100 \
+    -jdk:/path/to/latest/jdk \
+    -DproxyHost=<proxy> -DproxyPort=<port> \
+    -DjdkList=/path/to/jdk6u171-b05#/path/to/jdk7u161-b05#/path/to/jdk8u144-b01#/path/to/jdk9-179 \
+    -DtsaList=http://timestamp.comodoca.com/rfc3161#http://timestamp.entrust.net/TSS/RFC3161sha1TS;digests=SHA-1,SHA-256 \
+    -DdelayVerify=true -DcertValidity=60 \
+    <JDK_REPO>/jdk/test/sun/security/tools/jarsigner/compatibility/Compatibility.java
+
+Furthermore, here introduces one of the simplest usages. It doesn't specify any
+JDK list, so the testing JDK, which is specified by jtreg option "-jdk", will
+be tested. And it doesn't apply delay verifying, and no proxy is used, and use
+only one TSA. Now, the command is pretty simple and looks like the followings,
+$ jtreg -va -nr -timeout:100 \
+    -jdk:/path/to/latest/jdk \
+    -DtsaList=http://timestamp.comodoca.com/rfc3161 \
+    <JDK_REPO>/jdk/test/sun/security/tools/jarsigner/compatibility/Compatibility.java
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/sun/security/tools/jarsigner/compatibility/java.security	Wed Aug 16 11:31:56 2017 -0700
@@ -0,0 +1,2 @@
+jdk.certpath.disabledAlgorithms=MD2, MD5
+jdk.jar.disabledAlgorithms=MD2, MD5
\ No newline at end of file