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