mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-27 23:04:50 +02:00
8066619: Fix deprecation warnings in java.util.jar
Reviewed-by: rriggs, lancea
This commit is contained in:
parent
203f6ad99a
commit
1dae61a374
5 changed files with 560 additions and 57 deletions
|
@ -36,6 +36,8 @@ import java.util.Set;
|
||||||
|
|
||||||
import sun.util.logging.PlatformLogger;
|
import sun.util.logging.PlatformLogger;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Attributes class maps Manifest attribute names to associated string
|
* The Attributes class maps Manifest attribute names to associated string
|
||||||
* values. Valid attribute names are case-insensitive, are restricted to
|
* values. Valid attribute names are case-insensitive, are restricted to
|
||||||
|
@ -298,25 +300,16 @@ public class Attributes implements Map<Object,Object>, Cloneable {
|
||||||
* Writes the current attributes to the specified data output stream.
|
* Writes the current attributes to the specified data output stream.
|
||||||
* XXX Need to handle UTF8 values and break up lines longer than 72 bytes
|
* XXX Need to handle UTF8 values and break up lines longer than 72 bytes
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
void write(DataOutputStream out) throws IOException {
|
||||||
void write(DataOutputStream os) throws IOException {
|
StringBuilder buffer = new StringBuilder(72);
|
||||||
for (Entry<Object, Object> e : entrySet()) {
|
for (Entry<Object, Object> e : entrySet()) {
|
||||||
StringBuffer buffer = new StringBuffer(
|
buffer.setLength(0);
|
||||||
((Name) e.getKey()).toString());
|
buffer.append(e.getKey().toString());
|
||||||
buffer.append(": ");
|
buffer.append(": ");
|
||||||
|
buffer.append(e.getValue());
|
||||||
String value = (String) e.getValue();
|
Manifest.println72(out, buffer.toString());
|
||||||
if (value != null) {
|
}
|
||||||
byte[] vb = value.getBytes("UTF8");
|
Manifest.println(out); // empty line after individual section
|
||||||
value = new String(vb, 0, 0, vb.length);
|
|
||||||
}
|
|
||||||
buffer.append(value);
|
|
||||||
|
|
||||||
Manifest.make72Safe(buffer);
|
|
||||||
buffer.append("\r\n");
|
|
||||||
os.writeBytes(buffer.toString());
|
|
||||||
}
|
|
||||||
os.writeBytes("\r\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -326,9 +319,9 @@ public class Attributes implements Map<Object,Object>, Cloneable {
|
||||||
*
|
*
|
||||||
* XXX Need to handle UTF8 values and break up lines longer than 72 bytes
|
* XXX Need to handle UTF8 values and break up lines longer than 72 bytes
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
void writeMain(DataOutputStream out) throws IOException {
|
||||||
void writeMain(DataOutputStream out) throws IOException
|
StringBuilder buffer = new StringBuilder(72);
|
||||||
{
|
|
||||||
// write out the *-Version header first, if it exists
|
// write out the *-Version header first, if it exists
|
||||||
String vername = Name.MANIFEST_VERSION.toString();
|
String vername = Name.MANIFEST_VERSION.toString();
|
||||||
String version = getValue(vername);
|
String version = getValue(vername);
|
||||||
|
@ -338,7 +331,11 @@ public class Attributes implements Map<Object,Object>, Cloneable {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version != null) {
|
if (version != null) {
|
||||||
out.writeBytes(vername+": "+version+"\r\n");
|
buffer.append(vername);
|
||||||
|
buffer.append(": ");
|
||||||
|
buffer.append(version);
|
||||||
|
out.write(buffer.toString().getBytes(UTF_8));
|
||||||
|
Manifest.println(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
// write out all attributes except for the version
|
// write out all attributes except for the version
|
||||||
|
@ -346,34 +343,24 @@ public class Attributes implements Map<Object,Object>, Cloneable {
|
||||||
for (Entry<Object, Object> e : entrySet()) {
|
for (Entry<Object, Object> e : entrySet()) {
|
||||||
String name = ((Name) e.getKey()).toString();
|
String name = ((Name) e.getKey()).toString();
|
||||||
if ((version != null) && !(name.equalsIgnoreCase(vername))) {
|
if ((version != null) && !(name.equalsIgnoreCase(vername))) {
|
||||||
|
buffer.setLength(0);
|
||||||
StringBuffer buffer = new StringBuffer(name);
|
buffer.append(name);
|
||||||
buffer.append(": ");
|
buffer.append(": ");
|
||||||
|
buffer.append(e.getValue());
|
||||||
String value = (String) e.getValue();
|
Manifest.println72(out, buffer.toString());
|
||||||
if (value != null) {
|
|
||||||
byte[] vb = value.getBytes("UTF8");
|
|
||||||
value = new String(vb, 0, 0, vb.length);
|
|
||||||
}
|
|
||||||
buffer.append(value);
|
|
||||||
|
|
||||||
Manifest.make72Safe(buffer);
|
|
||||||
buffer.append("\r\n");
|
|
||||||
out.writeBytes(buffer.toString());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out.writeBytes("\r\n");
|
|
||||||
|
Manifest.println(out); // empty line after main attributes section
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Reads attributes from the specified input stream.
|
* Reads attributes from the specified input stream.
|
||||||
* XXX Need to handle UTF8 values.
|
|
||||||
*/
|
*/
|
||||||
void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
|
void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
|
||||||
read(is, lbuf, null, 0);
|
read(is, lbuf, null, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
int read(Manifest.FastInputStream is, byte[] lbuf, String filename, int lineNumber) throws IOException {
|
int read(Manifest.FastInputStream is, byte[] lbuf, String filename, int lineNumber) throws IOException {
|
||||||
String name = null, value;
|
String name = null, value;
|
||||||
byte[] lastline = null;
|
byte[] lastline = null;
|
||||||
|
@ -409,7 +396,7 @@ public class Attributes implements Map<Object,Object>, Cloneable {
|
||||||
lastline = buf;
|
lastline = buf;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
value = new String(buf, 0, buf.length, "UTF8");
|
value = new String(buf, 0, buf.length, UTF_8);
|
||||||
lastline = null;
|
lastline = null;
|
||||||
} else {
|
} else {
|
||||||
while (lbuf[i++] != ':') {
|
while (lbuf[i++] != ':') {
|
||||||
|
@ -422,13 +409,13 @@ public class Attributes implements Map<Object,Object>, Cloneable {
|
||||||
throw new IOException("invalid header field ("
|
throw new IOException("invalid header field ("
|
||||||
+ Manifest.getErrorPosition(filename, lineNumber) + ")");
|
+ Manifest.getErrorPosition(filename, lineNumber) + ")");
|
||||||
}
|
}
|
||||||
name = new String(lbuf, 0, 0, i - 2);
|
name = new String(lbuf, 0, i - 2, UTF_8);
|
||||||
if (is.peek() == ' ') {
|
if (is.peek() == ' ') {
|
||||||
lastline = new byte[len - i];
|
lastline = new byte[len - i];
|
||||||
System.arraycopy(lbuf, i, lastline, 0, len - i);
|
System.arraycopy(lbuf, i, lastline, 0, len - i);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
value = new String(lbuf, i, len - i, "UTF8");
|
value = new String(lbuf, i, len - i, UTF_8);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if ((putValue(name, value) != null) && (!lineContinued)) {
|
if ((putValue(name, value) != null) && (!lineContinued)) {
|
||||||
|
|
|
@ -35,6 +35,8 @@ import java.util.Map;
|
||||||
|
|
||||||
import sun.security.util.SecurityProperties;
|
import sun.security.util.SecurityProperties;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Manifest class is used to maintain Manifest entry names and their
|
* The Manifest class is used to maintain Manifest entry names and their
|
||||||
* associated Attributes. There are main Manifest Attributes as well as
|
* associated Attributes. There are main Manifest Attributes as well as
|
||||||
|
@ -197,31 +199,28 @@ public class Manifest implements Cloneable {
|
||||||
* @exception IOException if an I/O error has occurred
|
* @exception IOException if an I/O error has occurred
|
||||||
* @see #getMainAttributes
|
* @see #getMainAttributes
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public void write(OutputStream out) throws IOException {
|
public void write(OutputStream out) throws IOException {
|
||||||
DataOutputStream dos = new DataOutputStream(out);
|
DataOutputStream dos = new DataOutputStream(out);
|
||||||
// Write out the main attributes for the manifest
|
// Write out the main attributes for the manifest
|
||||||
attr.writeMain(dos);
|
attr.writeMain(dos);
|
||||||
// Now write out the per-entry attributes
|
// Now write out the per-entry attributes
|
||||||
|
StringBuilder buffer = entries.isEmpty() ? null : new StringBuilder(72);
|
||||||
for (Map.Entry<String, Attributes> e : entries.entrySet()) {
|
for (Map.Entry<String, Attributes> e : entries.entrySet()) {
|
||||||
StringBuffer buffer = new StringBuffer("Name: ");
|
buffer.setLength(0);
|
||||||
String value = e.getKey();
|
buffer.append("Name: ");
|
||||||
if (value != null) {
|
buffer.append(e.getKey());
|
||||||
byte[] vb = value.getBytes("UTF8");
|
println72(dos, buffer.toString());
|
||||||
value = new String(vb, 0, 0, vb.length);
|
|
||||||
}
|
|
||||||
buffer.append(value);
|
|
||||||
make72Safe(buffer);
|
|
||||||
buffer.append("\r\n");
|
|
||||||
dos.writeBytes(buffer.toString());
|
|
||||||
e.getValue().write(dos);
|
e.getValue().write(dos);
|
||||||
}
|
}
|
||||||
dos.flush();
|
dos.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds line breaks to enforce a maximum 72 bytes per line.
|
* Adds line breaks to enforce a maximum of 72 bytes per line.
|
||||||
|
*
|
||||||
|
* @deprecation Replaced with {@link #println72}.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "13")
|
||||||
static void make72Safe(StringBuffer line) {
|
static void make72Safe(StringBuffer line) {
|
||||||
int length = line.length();
|
int length = line.length();
|
||||||
int index = 72;
|
int index = 72;
|
||||||
|
@ -230,7 +229,38 @@ public class Manifest implements Cloneable {
|
||||||
index += 74; // + line width + line break ("\r\n")
|
index += 74; // + line width + line break ("\r\n")
|
||||||
length += 3; // + line break ("\r\n") and space
|
length += 3; // + line break ("\r\n") and space
|
||||||
}
|
}
|
||||||
return;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes {@code line} to {@code out} with line breaks and continuation
|
||||||
|
* spaces within the limits of 72 bytes of contents per line followed
|
||||||
|
* by a line break.
|
||||||
|
*/
|
||||||
|
static void println72(OutputStream out, String line) throws IOException {
|
||||||
|
if (!line.isEmpty()) {
|
||||||
|
byte[] lineBytes = line.getBytes(UTF_8);
|
||||||
|
int length = lineBytes.length;
|
||||||
|
// first line can hold one byte more than subsequent lines which
|
||||||
|
// start with a continuation line break space
|
||||||
|
out.write(lineBytes[0]);
|
||||||
|
int pos = 1;
|
||||||
|
while (length - pos > 71) {
|
||||||
|
out.write(lineBytes, pos, 71);
|
||||||
|
pos += 71;
|
||||||
|
println(out);
|
||||||
|
out.write(' ');
|
||||||
|
}
|
||||||
|
out.write(lineBytes, pos, length - pos);
|
||||||
|
}
|
||||||
|
println(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a line break to {@code out}.
|
||||||
|
*/
|
||||||
|
static void println(OutputStream out) throws IOException {
|
||||||
|
out.write('\r');
|
||||||
|
out.write('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
static String getErrorPosition(String filename, final int lineNumber) {
|
static String getErrorPosition(String filename, final int lineNumber) {
|
||||||
|
@ -304,7 +334,7 @@ public class Manifest implements Cloneable {
|
||||||
lastline = buf;
|
lastline = buf;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
name = new String(buf, 0, buf.length, "UTF8");
|
name = new String(buf, 0, buf.length, UTF_8);
|
||||||
lastline = null;
|
lastline = null;
|
||||||
}
|
}
|
||||||
Attributes attr = getAttributes(name);
|
Attributes attr = getAttributes(name);
|
||||||
|
@ -330,7 +360,7 @@ public class Manifest implements Cloneable {
|
||||||
toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
|
toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
|
||||||
lbuf[4] == ':' && lbuf[5] == ' ') {
|
lbuf[4] == ':' && lbuf[5] == ' ') {
|
||||||
try {
|
try {
|
||||||
return new String(lbuf, 6, len - 6, "UTF8");
|
return new String(lbuf, 6, len - 6, UTF_8);
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
}
|
}
|
||||||
|
|
201
test/jdk/java/util/jar/Attributes/NullAndEmptyKeysAndValues.java
Normal file
201
test/jdk/java/util/jar/Attributes/NullAndEmptyKeysAndValues.java
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, 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.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
import java.util.jar.Attributes.Name;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8066619
|
||||||
|
* @modules java.base/java.util.jar:+open
|
||||||
|
* @run testng/othervm NullAndEmptyKeysAndValues
|
||||||
|
* @summary Tests manifests with {@code null} and empty string {@code ""}
|
||||||
|
* values as section name, header name, or value in both main and named
|
||||||
|
* attributes sections.
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
* Note to future maintainer:
|
||||||
|
* In order to actually being able to test all the cases where key and values
|
||||||
|
* are null normal manifest and attributes manipulation through their public
|
||||||
|
* api is not sufficient but then there were these null checks there before
|
||||||
|
* which may or may not have had their reason and this way it's ensured that
|
||||||
|
* the behavior does not change with that respect.
|
||||||
|
* Once module isolation is enforced some test cases will not any longer be
|
||||||
|
* possible and those now tested situations will be guaranteed not to occur
|
||||||
|
* any longer at all at which point the corresponding tests can be removed
|
||||||
|
* safely without replacement unless of course another way is found inject the
|
||||||
|
* tested null values.
|
||||||
|
* Another trick to access package private class members could be to use
|
||||||
|
* deserialization or adding a new class to the same package on the classpath.
|
||||||
|
* Here is not important how the values are set to null because it shows that
|
||||||
|
* the behavior remains unchanged.
|
||||||
|
*/
|
||||||
|
public class NullAndEmptyKeysAndValues {
|
||||||
|
|
||||||
|
static final String SOME_KEY = "some-key";
|
||||||
|
static final String SOME_VALUE = "some value";
|
||||||
|
static final String NULL_TEXT = "null";
|
||||||
|
static final String EMPTY_STR = "";
|
||||||
|
static final Name EMPTY_NAME = new Name("tmp") {{
|
||||||
|
try {
|
||||||
|
Field name = Name.class.getDeclaredField("name");
|
||||||
|
name.setAccessible(true);
|
||||||
|
name.set(this, EMPTY_STR);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainAttributesHeaderNameNull() throws Exception {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
Field attr = mf.getClass().getDeclaredField("attr");
|
||||||
|
attr.setAccessible(true);
|
||||||
|
Attributes mainAtts = new Attributes() {{
|
||||||
|
super.put(null, SOME_VALUE);
|
||||||
|
}};
|
||||||
|
attr.set(mf, mainAtts);
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
assertThrows(NullPointerException.class, () -> writeAndRead(mf));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainAttributesHeaderNameEmpty() throws Exception {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf.getMainAttributes().put(EMPTY_NAME, SOME_VALUE);
|
||||||
|
assertThrows(IOException.class, () -> writeAndRead(mf));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainAttributesHeaderValueNull() throws Exception {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
Field attr = mf.getClass().getDeclaredField("attr");
|
||||||
|
attr.setAccessible(true);
|
||||||
|
Attributes mainAtts = new Attributes() {{
|
||||||
|
map.put(new Name(SOME_KEY), null);
|
||||||
|
}};
|
||||||
|
attr.set(mf, mainAtts);
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf = writeAndRead(mf);
|
||||||
|
assertEquals(mf.getMainAttributes().getValue(SOME_KEY), NULL_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainAttributesHeaderValueEmpty() throws Exception {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
Field attr = mf.getClass().getDeclaredField("attr");
|
||||||
|
attr.setAccessible(true);
|
||||||
|
Attributes mainAtts = new Attributes() {{
|
||||||
|
map.put(new Name(SOME_KEY), EMPTY_STR);
|
||||||
|
}};
|
||||||
|
attr.set(mf, mainAtts);
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf = writeAndRead(mf);
|
||||||
|
assertEquals(mf.getMainAttributes().getValue(SOME_KEY), EMPTY_STR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSectionNameNull() throws IOException {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf.getEntries().put(null, new Attributes());
|
||||||
|
mf = writeAndRead(mf);
|
||||||
|
assertNotNull(mf.getEntries().get(NULL_TEXT));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSectionNameEmpty() throws IOException {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf.getEntries().put(EMPTY_STR, new Attributes());
|
||||||
|
mf = writeAndRead(mf);
|
||||||
|
assertNotNull(mf.getEntries().get(EMPTY_STR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNamedSectionHeaderNameNull() throws IOException {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf.getEntries().put(SOME_KEY, new Attributes() {{
|
||||||
|
map.put(null, SOME_VALUE);
|
||||||
|
}});
|
||||||
|
assertThrows(NullPointerException.class, () -> writeAndRead(mf));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNamedSectionHeaderNameEmpty() throws IOException {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf.getEntries().put(SOME_KEY, new Attributes() {{
|
||||||
|
map.put(EMPTY_NAME, SOME_VALUE);
|
||||||
|
}});
|
||||||
|
assertThrows(IOException.class, () -> writeAndRead(mf));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNamedSectionHeaderValueNull() throws IOException {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf.getEntries().put(SOME_KEY, new Attributes() {{
|
||||||
|
map.put(new Name(SOME_KEY), null);
|
||||||
|
}});
|
||||||
|
mf = writeAndRead(mf);
|
||||||
|
assertEquals(mf.getEntries().get(SOME_KEY).getValue(SOME_KEY),
|
||||||
|
NULL_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNamedSectionHeaderValueEmpty() throws IOException {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf.getEntries().put(SOME_KEY, new Attributes() {{
|
||||||
|
map.put(new Name(SOME_KEY), EMPTY_STR);
|
||||||
|
}});
|
||||||
|
mf = writeAndRead(mf);
|
||||||
|
assertEquals(mf.getEntries().get(SOME_KEY).getValue(SOME_KEY),
|
||||||
|
EMPTY_STR);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Manifest writeAndRead(Manifest mf) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
mf.write(out);
|
||||||
|
byte[] mfBytes = out.toByteArray();
|
||||||
|
System.out.println("-".repeat(72));
|
||||||
|
System.out.print(new String(mfBytes, UTF_8));
|
||||||
|
System.out.println("-".repeat(72));
|
||||||
|
ByteArrayInputStream in = new ByteArrayInputStream(mfBytes);
|
||||||
|
return new Manifest(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
212
test/jdk/java/util/jar/Manifest/ValueUtf8Coding.java
Normal file
212
test/jdk/java/util/jar/Manifest/ValueUtf8Coding.java
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, 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.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.Attributes.Name;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8066619
|
||||||
|
* @run testng ValueUtf8Coding
|
||||||
|
* @summary Tests encoding and decoding manifest header values to and from
|
||||||
|
* UTF-8 with the complete Unicode character set.
|
||||||
|
*/ /*
|
||||||
|
* see also "../tools/launcher/UnicodeTest.java" for manifest attributes
|
||||||
|
* parsed during launch
|
||||||
|
*/
|
||||||
|
public class ValueUtf8Coding {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum number of bytes of UTF-8 encoded characters in one header value.
|
||||||
|
* <p>
|
||||||
|
* There are too many different Unicode code points (more than one million)
|
||||||
|
* to fit all into one manifest value. The specifications state:
|
||||||
|
* <q>Implementations should support 65535-byte (not character) header
|
||||||
|
* values, and 65535 headers per file. They might run out of memory,
|
||||||
|
* but there should not be hard-coded limits below these values.</q>
|
||||||
|
*
|
||||||
|
* @see <a
|
||||||
|
* href="{@docRoot}/../specs/jar/jar.html#Notes_on_Manifest_and_Signature_Files">
|
||||||
|
* Notes on Manifest and Signature Files</a>
|
||||||
|
*/
|
||||||
|
static final int SUPPORTED_VALUE_LENGTH = 65535;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns {@code true} if {@code codePoint} is known not to be a supported
|
||||||
|
* character in manifest header values. Explicitly forbidden in manifest
|
||||||
|
* header values are according to a statement from the specifications:
|
||||||
|
* <q>otherchar: any UTF-8 character except NUL, CR and LF</q>.
|
||||||
|
* {@code NUL} ({@code 0x0}), however, works just fine and might have been
|
||||||
|
* used and might still be.
|
||||||
|
*
|
||||||
|
* @see <a href="{@docRoot}/../specs/jar/jar.html#Section-Specification">
|
||||||
|
* Jar File Specification</a>
|
||||||
|
*/
|
||||||
|
static boolean isUnsupportedManifestValueCharacter(int codePoint) {
|
||||||
|
return codePoint == '\r' /* CR */ || codePoint == '\n' /* LF */;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a list of strings with all Unicode characters except those
|
||||||
|
* explicitly invalid in manifest header values.
|
||||||
|
* Each string is filled with as many characters as fit into
|
||||||
|
* {@link #SUPPORTED_VALUE_LENGTH} bytes with UTF-8 encoding except the
|
||||||
|
* last string which contains the remaining characters. Each of those
|
||||||
|
* strings becomes a header value the number of which 65535 should be
|
||||||
|
* supported per file.
|
||||||
|
*
|
||||||
|
* @see <a
|
||||||
|
* href="{@docRoot}/../specs/jar/jar.html#Notes_on_Manifest_and_Signature_Files">
|
||||||
|
* Notes on Manifest and Signature Files</a>
|
||||||
|
*/
|
||||||
|
static List<String> produceValuesWithAllUnicodeCharacters() {
|
||||||
|
ArrayList<String> values = new ArrayList<>();
|
||||||
|
byte[] valueBuf = new byte[SUPPORTED_VALUE_LENGTH];
|
||||||
|
int pos = 0;
|
||||||
|
for (int codePoint = Character.MIN_CODE_POINT;
|
||||||
|
codePoint <= Character.MAX_CODE_POINT; codePoint++) {
|
||||||
|
if (isUnsupportedManifestValueCharacter(codePoint)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] charBuf = Character.toString(codePoint).getBytes(UTF_8);
|
||||||
|
if (pos + charBuf.length > valueBuf.length) {
|
||||||
|
values.add(new String(valueBuf, 0, pos, UTF_8));
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
System.arraycopy(charBuf, 0, valueBuf, pos, charBuf.length);
|
||||||
|
pos += charBuf.length;
|
||||||
|
}
|
||||||
|
if (pos > 0) {
|
||||||
|
values.add(new String(valueBuf, 0, pos, UTF_8));
|
||||||
|
}
|
||||||
|
// minimum number of headers supported is the same as the minimum size
|
||||||
|
// of each header value in bytes
|
||||||
|
assertTrue(values.size() <= SUPPORTED_VALUE_LENGTH);
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns simple, valid, short, and distinct manifest header names.
|
||||||
|
* The returned name cannot collide with "{@code Manifest-Version}" because
|
||||||
|
* the returned string does not contain "{@code -}".
|
||||||
|
*/
|
||||||
|
static Name azName(int seed) {
|
||||||
|
StringBuffer name = new StringBuffer();
|
||||||
|
do {
|
||||||
|
name.insert(0, (char) (seed % 26 + (seed < 26 ? 'A' : 'a')));
|
||||||
|
seed = seed / 26 - 1;
|
||||||
|
} while (seed >= 0);
|
||||||
|
return new Name(name.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes and reads a manifest with the complete Unicode character set.
|
||||||
|
* The characters are grouped into manifest header values with about as
|
||||||
|
* many bytes as allowed each, utilizing a single big manifest.
|
||||||
|
* <p>
|
||||||
|
* This test assumes that a manifest is encoded and decoded correctly if
|
||||||
|
* writing and then reading it again results in a manifest with identical
|
||||||
|
* values as the original. The test is not about other aspects of writing
|
||||||
|
* and reading manifests than only that, given the fact and the way it
|
||||||
|
* works for some characters such as the most widely and often used ones,
|
||||||
|
* it also works for the complete Unicode character set just the same.
|
||||||
|
* <p>
|
||||||
|
* Only header values are tested. The set of allowed characters for header
|
||||||
|
* names are much more limited and are a different topic entirely and most
|
||||||
|
* simple ones are used here as necessary just to get valid and different
|
||||||
|
* ones (see {@link #azName}).
|
||||||
|
* <p>
|
||||||
|
* Because the current implementation under test uses different portions
|
||||||
|
* of code depending on where the value occurs to read or write, each
|
||||||
|
* character is tested in each of the three positions:<ul>
|
||||||
|
* <li>main attribute header,</li>
|
||||||
|
* <li>named section name, and</li>
|
||||||
|
* <li>named sections header values</li>
|
||||||
|
* </ul>
|
||||||
|
* Implementation of writing the main section headers in
|
||||||
|
* {@link Attributes#writeMain(java.io.DataOutputStream)} differs from the
|
||||||
|
* one writing named section headers in
|
||||||
|
* {@link Attributes#write(java.io.DataOutputStream)} regarding the special
|
||||||
|
* order of {@link Name#MANIFEST_VERSION} and
|
||||||
|
* {@link Name#SIGNATURE_VERSION} and also
|
||||||
|
* {@link Manifest#read(java.io.InputStream)} at least potentially reads
|
||||||
|
* main sections differently than reading named sections names headers in
|
||||||
|
* {@link Attributes#read(Manifest.FastInputStream, byte[])}.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testCompleteUnicodeCharacterSet() throws IOException {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
|
||||||
|
List<String> values = produceValuesWithAllUnicodeCharacters();
|
||||||
|
for (int i = 0; i < values.size(); i++) {
|
||||||
|
Name name = azName(i);
|
||||||
|
String value = values.get(i);
|
||||||
|
|
||||||
|
mf.getMainAttributes().put(name, value);
|
||||||
|
Attributes attributes = new Attributes();
|
||||||
|
mf.getEntries().put(value, attributes);
|
||||||
|
attributes.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
mf = writeAndRead(mf);
|
||||||
|
|
||||||
|
for (int i = 0; i < values.size(); i++) {
|
||||||
|
String value = values.get(i);
|
||||||
|
Name name = azName(i);
|
||||||
|
|
||||||
|
assertEquals(mf.getMainAttributes().getValue(name), value,
|
||||||
|
"main attributes header value");
|
||||||
|
Attributes attributes = mf.getAttributes(value);
|
||||||
|
assertNotNull(attributes, "named section");
|
||||||
|
assertEquals(attributes.getValue(name), value,
|
||||||
|
"named section attributes value");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Manifest writeAndRead(Manifest mf) throws IOException {
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
mf.write(out);
|
||||||
|
byte[] mfBytes = out.toByteArray();
|
||||||
|
|
||||||
|
System.out.println("-".repeat(72));
|
||||||
|
System.out.print(new String(mfBytes, UTF_8));
|
||||||
|
System.out.println("-".repeat(72));
|
||||||
|
|
||||||
|
ByteArrayInputStream in = new ByteArrayInputStream(mfBytes);
|
||||||
|
return new Manifest(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
73
test/jdk/java/util/jar/Manifest/WriteBinaryStructure.java
Normal file
73
test/jdk/java/util/jar/Manifest/WriteBinaryStructure.java
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2018, 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.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.jar.Attributes;
|
||||||
|
import java.util.jar.Attributes.Name;
|
||||||
|
import java.util.jar.Manifest;
|
||||||
|
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
* @bug 8066619
|
||||||
|
* @run testng WriteBinaryStructure
|
||||||
|
* @summary Tests that jar manifests are written in a particular structure
|
||||||
|
*/
|
||||||
|
public class WriteBinaryStructure {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMainAttributes() throws IOException {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
mf.getMainAttributes().put(new Name("Key"), "Value");
|
||||||
|
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||||
|
mf.write(buf);
|
||||||
|
assertEquals(buf.toByteArray(), (
|
||||||
|
"Manifest-Version: 1.0\r\n" +
|
||||||
|
"Key: Value\r\n" +
|
||||||
|
"\r\n").getBytes(UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIndividualSection() throws IOException {
|
||||||
|
Manifest mf = new Manifest();
|
||||||
|
mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
|
||||||
|
Attributes attributes = new Attributes();
|
||||||
|
mf.getEntries().put("Individual-Section-Name", attributes);
|
||||||
|
attributes.put(new Name("Key"), "Value");
|
||||||
|
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||||
|
mf.write(buf);
|
||||||
|
assertEquals(buf.toByteArray(), (
|
||||||
|
"Manifest-Version: 1.0\r\n" +
|
||||||
|
"\r\n" +
|
||||||
|
"Name: Individual-Section-Name\r\n" +
|
||||||
|
"Key: Value\r\n" +
|
||||||
|
"\r\n").getBytes(UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue