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