1 /* 2 * Copyright (c) 1997, 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. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package java.util.jar; 27 28 import java.io.FilterInputStream; 29 import java.io.DataOutputStream; 30 import java.io.InputStream; 31 import java.io.OutputStream; 32 import java.io.IOException; 33 import java.util.Map; 34 import java.util.HashMap; 35 import java.util.Iterator; 36 37 /** 38 * The Manifest class is used to maintain Manifest entry names and their 39 * associated Attributes. There are main Manifest Attributes as well as 40 * per-entry Attributes. For information on the Manifest format, please 41 * see the 42 * <a href="{@docRoot}/../specs/jar/jar.html"> 43 * Manifest format specification</a>. 44 * 45 * @author David Connelly 46 * @see Attributes 47 * @since 1.2 48 */ 49 public class Manifest implements Cloneable { 50 // manifest main attributes 51 private Attributes attr = new Attributes(); 52 53 // manifest entries 54 private Map<String, Attributes> entries = new HashMap<>(); 55 56 /** 57 * Constructs a new, empty Manifest. 58 */ 59 public Manifest() { 60 } 61 62 /** 63 * Constructs a new Manifest from the specified input stream. 64 * 65 * @param is the input stream containing manifest data 66 * @throws IOException if an I/O error has occurred 67 */ 68 public Manifest(InputStream is) throws IOException { 69 read(is); 70 } 71 72 /** 73 * Constructs a new Manifest that is a copy of the specified Manifest. 74 * 75 * @param man the Manifest to copy 76 */ 77 public Manifest(Manifest man) { 78 attr.putAll(man.getMainAttributes()); 79 entries.putAll(man.getEntries()); 80 } 81 82 /** 83 * Returns the main Attributes for the Manifest. 84 * @return the main Attributes for the Manifest 85 */ 86 public Attributes getMainAttributes() { 87 return attr; 88 } 89 90 /** 91 * Returns a Map of the entries contained in this Manifest. Each entry 92 * is represented by a String name (key) and associated Attributes (value). 93 * The Map permits the {@code null} key, but no entry with a null key is 94 * created by {@link #read}, nor is such an entry written by using {@link 95 * #write}. 96 * 97 * @return a Map of the entries contained in this Manifest 98 */ 99 public Map<String,Attributes> getEntries() { 100 return entries; 101 } 102 103 /** 104 * Returns the Attributes for the specified entry name. 105 * This method is defined as: 106 * <pre> 107 * return (Attributes)getEntries().get(name) 108 * </pre> 109 * Though {@code null} is a valid {@code name}, when 110 * {@code getAttributes(null)} is invoked on a {@code Manifest} 111 * obtained from a jar file, {@code null} will be returned. While jar 112 * files themselves do not allow {@code null}-named attributes, it is 113 * possible to invoke {@link #getEntries} on a {@code Manifest}, and 114 * on that result, invoke {@code put} with a null key and an 115 * arbitrary value. Subsequent invocations of 116 * {@code getAttributes(null)} will return the just-{@code put} 117 * value. 118 * <p> 119 * Note that this method does not return the manifest's main attributes; 120 * see {@link #getMainAttributes}. 121 * 122 * @param name entry name 123 * @return the Attributes for the specified entry name 124 */ 125 public Attributes getAttributes(String name) { 126 return getEntries().get(name); 127 } 128 129 /** 130 * Clears the main Attributes as well as the entries in this Manifest. 131 */ 132 public void clear() { 133 attr.clear(); 134 entries.clear(); 135 } 136 137 /** 138 * Writes the Manifest to the specified OutputStream. 139 * Attributes.Name.MANIFEST_VERSION must be set in 140 * MainAttributes prior to invoking this method. 141 * 142 * @param out the output stream 143 * @exception IOException if an I/O error has occurred 144 * @see #getMainAttributes 145 */ 146 @SuppressWarnings("deprecation") 147 public void write(OutputStream out) throws IOException { 148 DataOutputStream dos = new DataOutputStream(out); 149 // Write out the main attributes for the manifest 150 attr.writeMain(dos); 151 // Now write out the pre-entry attributes 152 for (Map.Entry<String, Attributes> e : entries.entrySet()) { 153 StringBuffer buffer = new StringBuffer("Name: "); 154 String value = e.getKey(); 155 if (value != null) { 156 byte[] vb = value.getBytes("UTF8"); 157 value = new String(vb, 0, 0, vb.length); 158 } 159 buffer.append(value); 160 make72Safe(buffer); 161 buffer.append("\r\n"); 162 dos.writeBytes(buffer.toString()); 163 e.getValue().write(dos); 164 } 165 dos.flush(); 166 } 167 168 /** 169 * Adds line breaks to enforce a maximum 72 bytes per line. 170 */ 171 static void make72Safe(StringBuffer line) { 172 int length = line.length(); 173 int index = 72; 174 while (index < length) { 175 line.insert(index, "\r\n "); 176 index += 74; // + line width + line break ("\r\n") 177 length += 3; // + line break ("\r\n") and space 178 } 179 return; 180 } 181 182 /** 183 * Reads the Manifest from the specified InputStream. The entry 184 * names and attributes read will be merged in with the current 185 * manifest entries. 186 * 187 * @param is the input stream 188 * @exception IOException if an I/O error has occurred 189 */ 190 public void read(InputStream is) throws IOException { 191 // Buffered input stream for reading manifest data 192 FastInputStream fis = new FastInputStream(is); 193 // Line buffer 194 byte[] lbuf = new byte[512]; 195 // Read the main attributes for the manifest 196 attr.read(fis, lbuf); 197 // Total number of entries, attributes read 198 int ecount = 0, acount = 0; 199 // Average size of entry attributes 200 int asize = 2; 201 // Now parse the manifest entries 202 int len; 203 String name = null; 204 boolean skipEmptyLines = true; 205 byte[] lastline = null; 206 207 while ((len = fis.readLine(lbuf)) != -1) { 208 if (lbuf[--len] != '\n') { 209 throw new IOException("manifest line too long"); 210 } 211 if (len > 0 && lbuf[len-1] == '\r') { 212 --len; 213 } 214 if (len == 0 && skipEmptyLines) { 215 continue; 216 } 217 skipEmptyLines = false; 218 219 if (name == null) { 220 name = parseName(lbuf, len); 221 if (name == null) { 222 throw new IOException("invalid manifest format"); 223 } 224 if (fis.peek() == ' ') { 225 // name is wrapped 226 lastline = new byte[len - 6]; 227 System.arraycopy(lbuf, 6, lastline, 0, len - 6); 228 continue; 229 } 230 } else { 231 // continuation line 232 byte[] buf = new byte[lastline.length + len - 1]; 233 System.arraycopy(lastline, 0, buf, 0, lastline.length); 234 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 235 if (fis.peek() == ' ') { 236 // name is wrapped 237 lastline = buf; 238 continue; 239 } 240 name = new String(buf, 0, buf.length, "UTF8"); 241 lastline = null; 242 } 243 Attributes attr = getAttributes(name); 244 if (attr == null) { 245 attr = new Attributes(asize); 246 entries.put(name, attr); 247 } 248 attr.read(fis, lbuf); 249 ecount++; 250 acount += attr.size(); 251 //XXX: Fix for when the average is 0. When it is 0, 252 // you get an Attributes object with an initial 253 // capacity of 0, which tickles a bug in HashMap. 254 asize = Math.max(2, acount / ecount); 255 256 name = null; 257 skipEmptyLines = true; 258 } 259 } 260 261 private String parseName(byte[] lbuf, int len) { 262 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' && 263 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' && 264 lbuf[4] == ':' && lbuf[5] == ' ') { 265 try { 266 return new String(lbuf, 6, len - 6, "UTF8"); 267 } 268 catch (Exception e) { 269 } 270 } 271 return null; 272 } 273 274 private int toLower(int c) { 275 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c; 276 } 277 278 /** 279 * Returns true if the specified Object is also a Manifest and has 280 * the same main Attributes and entries. 281 * 282 * @param o the object to be compared 283 * @return true if the specified Object is also a Manifest and has 284 * the same main Attributes and entries 285 */ 286 public boolean equals(Object o) { 287 if (o instanceof Manifest) { 288 Manifest m = (Manifest)o; 289 return attr.equals(m.getMainAttributes()) && 290 entries.equals(m.getEntries()); 291 } else { 292 return false; 293 } 294 } 295 296 /** 297 * Returns the hash code for this Manifest. 298 */ 299 public int hashCode() { 300 return attr.hashCode() + entries.hashCode(); 301 } 302 303 /** 304 * Returns a shallow copy of this Manifest. The shallow copy is 305 * implemented as follows: 306 * <pre> 307 * public Object clone() { return new Manifest(this); } 308 * </pre> 309 * @return a shallow copy of this Manifest 310 */ 311 public Object clone() { 312 return new Manifest(this); 313 } 314 315 /* 316 * A fast buffered input stream for parsing manifest files. 317 */ 318 static class FastInputStream extends FilterInputStream { 319 private byte buf[]; 320 private int count = 0; 321 private int pos = 0; 322 323 FastInputStream(InputStream in) { 324 this(in, 8192); 325 } 326 327 FastInputStream(InputStream in, int size) { 328 super(in); 329 buf = new byte[size]; 330 } 331 332 public int read() throws IOException { 333 if (pos >= count) { 334 fill(); 335 if (pos >= count) { 336 return -1; 337 } 338 } 339 return Byte.toUnsignedInt(buf[pos++]); 340 } 341 342 public int read(byte[] b, int off, int len) throws IOException { 343 int avail = count - pos; 344 if (avail <= 0) { 345 if (len >= buf.length) { 346 return in.read(b, off, len); 347 } 348 fill(); 349 avail = count - pos; 350 if (avail <= 0) { 351 return -1; 352 } 353 } 354 if (len > avail) { 355 len = avail; 356 } 357 System.arraycopy(buf, pos, b, off, len); 358 pos += len; 359 return len; 360 } 361 362 /* 363 * Reads 'len' bytes from the input stream, or until an end-of-line 364 * is reached. Returns the number of bytes read. 365 */ 366 public int readLine(byte[] b, int off, int len) throws IOException { 367 byte[] tbuf = this.buf; 368 int total = 0; 369 while (total < len) { 370 int avail = count - pos; 371 if (avail <= 0) { 372 fill(); 373 avail = count - pos; 374 if (avail <= 0) { 375 return -1; 376 } 377 } 378 int n = len - total; 379 if (n > avail) { 380 n = avail; 381 } 382 int tpos = pos; 383 int maxpos = tpos + n; 384 while (tpos < maxpos && tbuf[tpos++] != '\n') ; 385 n = tpos - pos; 386 System.arraycopy(tbuf, pos, b, off, n); 387 off += n; 388 total += n; 389 pos = tpos; 390 if (tbuf[tpos-1] == '\n') { 391 break; 392 } 393 } 394 return total; 395 } 396 397 public byte peek() throws IOException { 398 if (pos == count) 399 fill(); 400 if (pos == count) 401 return -1; // nothing left in buffer 402 return buf[pos]; 403 } 404 405 public int readLine(byte[] b) throws IOException { 406 return readLine(b, 0, b.length); 407 } 408 409 public long skip(long n) throws IOException { 410 if (n <= 0) { 411 return 0; 412 } 413 long avail = count - pos; 414 if (avail <= 0) { 415 return in.skip(n); 416 } 417 if (n > avail) { 418 n = avail; 419 } 420 pos += n; 421 return n; 422 } 423 424 public int available() throws IOException { 425 return (count - pos) + in.available(); 426 } 427 428 public void close() throws IOException { 429 if (in != null) { 430 in.close(); 431 in = null; 432 buf = null; 433 } 434 } 435 436 private void fill() throws IOException { 437 count = pos = 0; 438 int n = in.read(buf, 0, buf.length); 439 if (n > 0) { 440 count = n; 441 } 442 } 443 } 444 }