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 per-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 byte c = lbuf[--len]; 209 if (c != '\n' && c != '\r') { 210 throw new IOException("manifest line too long"); 211 } 212 if (len > 0 && lbuf[len-1] == '\r') { 213 --len; 214 } 215 if (len == 0 && skipEmptyLines) { 216 continue; 217 } 218 skipEmptyLines = false; 219 220 if (name == null) { 221 name = parseName(lbuf, len); 222 if (name == null) { 223 throw new IOException("invalid manifest format"); 224 } 225 if (fis.peek() == ' ') { 226 // name is wrapped 227 lastline = new byte[len - 6]; 228 System.arraycopy(lbuf, 6, lastline, 0, len - 6); 229 continue; 230 } 231 } else { 232 // continuation line 233 byte[] buf = new byte[lastline.length + len - 1]; 234 System.arraycopy(lastline, 0, buf, 0, lastline.length); 235 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 236 if (fis.peek() == ' ') { 237 // name is wrapped 238 lastline = buf; 239 continue; 240 } 241 name = new String(buf, 0, buf.length, "UTF8"); 242 lastline = null; 243 } 244 Attributes attr = getAttributes(name); 245 if (attr == null) { 246 attr = new Attributes(asize); 247 entries.put(name, attr); 248 } 249 attr.read(fis, lbuf); 250 ecount++; 251 acount += attr.size(); 252 //XXX: Fix for when the average is 0. When it is 0, 253 // you get an Attributes object with an initial 254 // capacity of 0, which tickles a bug in HashMap. 255 asize = Math.max(2, acount / ecount); 256 257 name = null; 258 skipEmptyLines = true; 259 } 260 } 261 262 private String parseName(byte[] lbuf, int len) { 263 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' && 264 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' && 265 lbuf[4] == ':' && lbuf[5] == ' ') { 266 try { 267 return new String(lbuf, 6, len - 6, "UTF8"); 268 } 269 catch (Exception e) { 270 } 271 } 272 return null; 273 } 274 275 private int toLower(int c) { 276 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c; 277 } 278 279 /** 280 * Returns true if the specified Object is also a Manifest and has 281 * the same main Attributes and entries. 282 * 283 * @param o the object to be compared 284 * @return true if the specified Object is also a Manifest and has 285 * the same main Attributes and entries 286 */ 287 public boolean equals(Object o) { 288 if (o instanceof Manifest) { 289 Manifest m = (Manifest)o; 290 return attr.equals(m.getMainAttributes()) && 291 entries.equals(m.getEntries()); 292 } else { 293 return false; 294 } 295 } 296 297 /** 298 * Returns the hash code for this Manifest. 299 */ 300 public int hashCode() { 301 return attr.hashCode() + entries.hashCode(); 302 } 303 304 /** 305 * Returns a shallow copy of this Manifest. The shallow copy is 306 * implemented as follows: 307 * <pre> 308 * public Object clone() { return new Manifest(this); } 309 * </pre> 310 * @return a shallow copy of this Manifest 311 */ 312 public Object clone() { 313 return new Manifest(this); 314 } 315 316 /* 317 * A fast buffered input stream for parsing manifest files. 318 */ 319 static class FastInputStream extends FilterInputStream { 320 private byte buf[]; 321 private int count = 0; 322 private int pos = 0; 323 324 FastInputStream(InputStream in) { 325 this(in, 8192); 326 } 327 328 FastInputStream(InputStream in, int size) { 329 super(in); 330 buf = new byte[size]; 331 } 332 333 public int read() throws IOException { 334 if (pos >= count) { 335 fill(); 336 if (pos >= count) { 337 return -1; 338 } 339 } 340 return Byte.toUnsignedInt(buf[pos++]); 341 } 342 343 public int read(byte[] b, int off, int len) throws IOException { 344 int avail = count - pos; 345 if (avail <= 0) { 346 if (len >= buf.length) { 347 return in.read(b, off, len); 348 } 349 fill(); 350 avail = count - pos; 351 if (avail <= 0) { 352 return -1; 353 } 354 } 355 if (len > avail) { 356 len = avail; 357 } 358 System.arraycopy(buf, pos, b, off, len); 359 pos += len; 360 return len; 361 } 362 363 /* 364 * Reads 'len' bytes from the input stream, or until an end-of-line 365 * is reached. Returns the number of bytes read. 366 */ 367 public int readLine(byte[] b, int off, int len) throws IOException { 368 byte[] tbuf = this.buf; 369 int total = 0; 370 while (total < len) { 371 int avail = count - pos; 372 if (avail <= 0) { 373 fill(); 374 avail = count - pos; 375 if (avail <= 0) { 376 return -1; 377 } 378 } 379 int n = len - total; 380 if (n > avail) { 381 n = avail; 382 } 383 int tpos = pos; 384 int maxpos = tpos + n; 385 byte c = 0; 386 // jar.spec.newline: CRLF | LF | CR (not followed by LF) 387 while (tpos < maxpos && (c = tbuf[tpos++]) != '\n' && c != '\r'); 388 if (c == '\r' && tpos < maxpos && tbuf[tpos] == '\n') { 389 tpos++; 390 } 391 n = tpos - pos; 392 System.arraycopy(tbuf, pos, b, off, n); 393 off += n; 394 total += n; 395 pos = tpos; 396 c = tbuf[tpos-1]; 397 if (c == '\n') { 398 break; 399 } 400 if (c == '\r') { 401 if (count == pos) { 402 // try to see if there is a trailing LF 403 fill(); 404 if (pos < count && tbuf[pos] == '\n') { 405 if (total < len) { 406 b[off++] = '\n'; 407 total++; 408 } else { 409 // we should always have big enough lbuf but 410 // just in case we don't, replace the last CR 411 // with LF. 412 b[off - 1] = '\n'; 413 } 414 pos++; 415 } 416 } 417 break; 418 } 419 } 420 return total; 421 } 422 423 public byte peek() throws IOException { 424 if (pos == count) 425 fill(); 426 if (pos == count) 427 return -1; // nothing left in buffer 428 return buf[pos]; 429 } 430 431 public int readLine(byte[] b) throws IOException { 432 return readLine(b, 0, b.length); 433 } 434 435 public long skip(long n) throws IOException { 436 if (n <= 0) { 437 return 0; 438 } 439 long avail = count - pos; 440 if (avail <= 0) { 441 return in.skip(n); 442 } 443 if (n > avail) { 444 n = avail; 445 } 446 pos += n; 447 return n; 448 } 449 450 public int available() throws IOException { 451 return (count - pos) + in.available(); 452 } 453 454 public void close() throws IOException { 455 if (in != null) { 456 in.close(); 457 in = null; 458 buf = null; 459 } 460 } 461 462 private void fill() throws IOException { 463 count = pos = 0; 464 int n = in.read(buf, 0, buf.length); 465 if (n > 0) { 466 count = n; 467 } 468 } 469 } 470 }