1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import static java.nio.charset.StandardCharsets.UTF_8;
  25 
  26 import java.io.ByteArrayInputStream;
  27 import java.io.ByteArrayOutputStream;
  28 import java.io.IOException;
  29 import java.util.jar.Attributes;
  30 import java.util.jar.Manifest;
  31 import java.util.jar.Attributes.Name;
  32 import java.lang.reflect.Field;
  33 
  34 import org.testng.annotations.Test;
  35 import static org.testng.Assert.*;
  36 
  37 /**
  38  * @test
  39  * @bug 8066619
  40  * @modules java.base/java.util.jar:+open
  41  * @run testng/othervm NullAndEmptyKeysAndValues
  42  * @summary Tests manifests with {@code null} and empty string {@code ""}
  43  * values as section name, header name, or value in both main and named
  44  * attributes sections.
  45  */
  46 /*
  47  * Note to future maintainer:
  48  * In order to actually being able to test all the cases where key and values
  49  * are null normal manifest and attributes manipulation through their public
  50  * api is not sufficient but then there were these null checks there before
  51  * which may or may not have had their reason and this way it's ensured that
  52  * the behavior does not change with that respect.
  53  * Once module isolation is enforced some test cases will not any longer be
  54  * possible and those now tested situations will be guaranteed not to occur
  55  * any longer at all at which point the corresponding tests can be removed
  56  * safely without replacement unless of course another way is found inject the
  57  * tested null values.
  58  * Another trick to access package private class members could be to use
  59  * deserialization or adding a new class to the same package on the classpath.
  60  * Here is not important how the values are set to null because it shows that
  61  * the behavior remains unchanged.
  62  */
  63 public class NullAndEmptyKeysAndValues {
  64 
  65     static final String SOME_KEY = "some-key";
  66     static final String SOME_VALUE = "some value";
  67     static final String NULL_TEXT = "null";
  68     static final String EMPTY_STR = "";
  69     static final Name EMPTY_NAME = new Name("tmp") {{
  70         try {
  71             Field name = Name.class.getDeclaredField("name");
  72             name.setAccessible(true);
  73             name.set(this, EMPTY_STR);
  74         } catch (Exception e) {
  75             throw new RuntimeException(e.getMessage(), e);
  76         }
  77     }};
  78 
  79     @Test
  80     public void testMainAttributesHeaderNameNull() throws Exception {
  81         Manifest mf = new Manifest();
  82         Field attr = mf.getClass().getDeclaredField("attr");
  83         attr.setAccessible(true);
  84         Attributes mainAtts = new Attributes() {{
  85             super.put(null, SOME_VALUE);
  86         }};
  87         attr.set(mf, mainAtts);
  88         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
  89         assertThrows(NullPointerException.class, () -> writeAndRead(mf));
  90     }
  91 
  92     @Test
  93     public void testMainAttributesHeaderNameEmpty() throws Exception {
  94         Manifest mf = new Manifest();
  95         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
  96         mf.getMainAttributes().put(EMPTY_NAME, SOME_VALUE);
  97         assertThrows(IOException.class, () -> writeAndRead(mf));
  98     }
  99 
 100     @Test
 101     public void testMainAttributesHeaderValueNull() throws Exception {
 102         Manifest mf = new Manifest();
 103         Field attr = mf.getClass().getDeclaredField("attr");
 104         attr.setAccessible(true);
 105         Attributes mainAtts = new Attributes() {{
 106             map.put(new Name(SOME_KEY), null);
 107         }};
 108         attr.set(mf, mainAtts);
 109         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
 110         mf = writeAndRead(mf);
 111         assertEquals(mf.getMainAttributes().getValue(SOME_KEY), NULL_TEXT);
 112     }
 113 
 114     @Test
 115     public void testMainAttributesHeaderValueEmpty() throws Exception {
 116         Manifest mf = new Manifest();
 117         Field attr = mf.getClass().getDeclaredField("attr");
 118         attr.setAccessible(true);
 119         Attributes mainAtts = new Attributes() {{
 120             map.put(new Name(SOME_KEY), EMPTY_STR);
 121         }};
 122         attr.set(mf, mainAtts);
 123         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
 124         mf = writeAndRead(mf);
 125         assertEquals(mf.getMainAttributes().getValue(SOME_KEY), EMPTY_STR);
 126     }
 127 
 128     @Test
 129     public void testSectionNameNull() throws IOException {
 130         Manifest mf = new Manifest();
 131         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
 132         mf.getEntries().put(null, new Attributes());
 133         mf = writeAndRead(mf);
 134         assertNotNull(mf.getEntries().get(NULL_TEXT));
 135     }
 136 
 137     @Test
 138     public void testSectionNameEmpty() throws IOException {
 139         Manifest mf = new Manifest();
 140         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
 141         mf.getEntries().put(EMPTY_STR, new Attributes());
 142         mf = writeAndRead(mf);
 143         assertNotNull(mf.getEntries().get(EMPTY_STR));
 144     }
 145 
 146     @Test
 147     public void testNamedSectionHeaderNameNull() throws IOException {
 148         Manifest mf = new Manifest();
 149         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
 150         mf.getEntries().put(SOME_KEY, new Attributes() {{
 151             map.put(null, SOME_VALUE);
 152         }});
 153         assertThrows(NullPointerException.class, () -> writeAndRead(mf));
 154     }
 155 
 156     @Test
 157     public void testNamedSectionHeaderNameEmpty() throws IOException {
 158         Manifest mf = new Manifest();
 159         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
 160         mf.getEntries().put(SOME_KEY, new Attributes() {{
 161             map.put(EMPTY_NAME, SOME_VALUE);
 162         }});
 163         assertThrows(IOException.class, () -> writeAndRead(mf));
 164     }
 165 
 166     @Test
 167     public void testNamedSectionHeaderValueNull() throws IOException {
 168         Manifest mf = new Manifest();
 169         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
 170         mf.getEntries().put(SOME_KEY, new Attributes() {{
 171             map.put(new Name(SOME_KEY), null);
 172         }});
 173         mf = writeAndRead(mf);
 174         assertEquals(mf.getEntries().get(SOME_KEY).getValue(SOME_KEY),
 175                 NULL_TEXT);
 176     }
 177 
 178     @Test
 179     public void testNamedSectionHeaderValueEmpty() throws IOException {
 180         Manifest mf = new Manifest();
 181         mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0");
 182         mf.getEntries().put(SOME_KEY, new Attributes() {{
 183             map.put(new Name(SOME_KEY), EMPTY_STR);
 184         }});
 185         mf = writeAndRead(mf);
 186         assertEquals(mf.getEntries().get(SOME_KEY).getValue(SOME_KEY),
 187                 EMPTY_STR);
 188     }
 189 
 190     static Manifest writeAndRead(Manifest mf) throws IOException {
 191         ByteArrayOutputStream out = new ByteArrayOutputStream();
 192         mf.write(out);
 193         byte[] mfBytes = out.toByteArray();
 194         System.out.println("-".repeat(72));
 195         System.out.print(new String(mfBytes, UTF_8));
 196         System.out.println("-".repeat(72));
 197         ByteArrayInputStream in = new ByteArrayInputStream(mfBytes);
 198         return new Manifest(in);
 199     }
 200 
 201 }