1 /* 2 * Copyright (c) 1997, 2012, 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}openjdk-redirect.html?v=8&path=/technotes/guides/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 entries = new HashMap(); 55 56 /** 57 * Constructs a new, empty Manifest. 58 */ Manifest()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 occured 67 */ Manifest(InputStream is)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 */ Manifest(Manifest man)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 */ getMainAttributes()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 */ getEntries()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 */ getAttributes(String name)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 */ clear()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 */ write(OutputStream out)146 public void write(OutputStream out) throws IOException { 147 DataOutputStream dos = new DataOutputStream(out); 148 // Write out the main attributes for the manifest 149 attr.writeMain(dos); 150 // Now write out the pre-entry attributes 151 Iterator it = entries.entrySet().iterator(); 152 while (it.hasNext()) { 153 Map.Entry e = (Map.Entry)it.next(); 154 StringBuffer buffer = new StringBuffer("Name: "); 155 String value = (String)e.getKey(); 156 if (value != null) { 157 byte[] vb = value.getBytes("UTF8"); 158 value = new String(vb, 0, 0, vb.length); 159 } 160 buffer.append(value); 161 buffer.append("\r\n"); 162 make72Safe(buffer); 163 dos.writeBytes(buffer.toString()); 164 ((Attributes)e.getValue()).write(dos); 165 } 166 dos.flush(); 167 } 168 169 /** 170 * Adds line breaks to enforce a maximum 72 bytes per line. 171 */ make72Safe(StringBuffer line)172 static void make72Safe(StringBuffer line) { 173 int length = line.length(); 174 if (length > 72) { 175 int index = 70; 176 while (index < length - 2) { 177 line.insert(index, "\r\n "); 178 index += 72; 179 length += 3; 180 } 181 } 182 return; 183 } 184 185 /** 186 * Reads the Manifest from the specified InputStream. The entry 187 * names and attributes read will be merged in with the current 188 * manifest entries. 189 * 190 * @param is the input stream 191 * @exception IOException if an I/O error has occurred 192 */ read(InputStream is)193 public void read(InputStream is) throws IOException { 194 // Buffered input stream for reading manifest data 195 FastInputStream fis = new FastInputStream(is); 196 // Line buffer 197 byte[] lbuf = new byte[512]; 198 // Read the main attributes for the manifest 199 attr.read(fis, lbuf); 200 // Total number of entries, attributes read 201 int ecount = 0, acount = 0; 202 // Average size of entry attributes 203 int asize = 2; 204 // Now parse the manifest entries 205 int len; 206 String name = null; 207 boolean skipEmptyLines = true; 208 byte[] lastline = null; 209 210 while ((len = fis.readLine(lbuf)) != -1) { 211 if (lbuf[--len] != '\n') { 212 throw new IOException("manifest line too long"); 213 } 214 if (len > 0 && lbuf[len-1] == '\r') { 215 --len; 216 } 217 if (len == 0 && skipEmptyLines) { 218 continue; 219 } 220 skipEmptyLines = false; 221 222 if (name == null) { 223 name = parseName(lbuf, len); 224 if (name == null) { 225 throw new IOException("invalid manifest format"); 226 } 227 if (fis.peek() == ' ') { 228 // name is wrapped 229 lastline = new byte[len - 6]; 230 System.arraycopy(lbuf, 6, lastline, 0, len - 6); 231 continue; 232 } 233 } else { 234 // continuation line 235 byte[] buf = new byte[lastline.length + len - 1]; 236 System.arraycopy(lastline, 0, buf, 0, lastline.length); 237 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1); 238 if (fis.peek() == ' ') { 239 // name is wrapped 240 lastline = buf; 241 continue; 242 } 243 name = new String(buf, 0, buf.length, "UTF8"); 244 lastline = null; 245 } 246 Attributes attr = getAttributes(name); 247 if (attr == null) { 248 attr = new Attributes(asize); 249 entries.put(name, attr); 250 } 251 attr.read(fis, lbuf); 252 ecount++; 253 acount += attr.size(); 254 //XXX: Fix for when the average is 0. When it is 0, 255 // you get an Attributes object with an initial 256 // capacity of 0, which tickles a bug in HashMap. 257 asize = Math.max(2, acount / ecount); 258 259 name = null; 260 skipEmptyLines = true; 261 } 262 } 263 parseName(byte[] lbuf, int len)264 private String parseName(byte[] lbuf, int len) { 265 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' && 266 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' && 267 lbuf[4] == ':' && lbuf[5] == ' ') { 268 try { 269 return new String(lbuf, 6, len - 6, "UTF8"); 270 } 271 catch (Exception e) { 272 } 273 } 274 return null; 275 } 276 toLower(int c)277 private int toLower(int c) { 278 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c; 279 } 280 281 /** 282 * Returns true if the specified Object is also a Manifest and has 283 * the same main Attributes and entries. 284 * 285 * @param o the object to be compared 286 * @return true if the specified Object is also a Manifest and has 287 * the same main Attributes and entries 288 */ equals(Object o)289 public boolean equals(Object o) { 290 if (o instanceof Manifest) { 291 Manifest m = (Manifest)o; 292 return attr.equals(m.getMainAttributes()) && 293 entries.equals(m.getEntries()); 294 } else { 295 return false; 296 } 297 } 298 299 /** 300 * Returns the hash code for this Manifest. 301 */ hashCode()302 public int hashCode() { 303 return attr.hashCode() + entries.hashCode(); 304 } 305 306 /** 307 * Returns a shallow copy of this Manifest. The shallow copy is 308 * implemented as follows: 309 * <pre> 310 * public Object clone() { return new Manifest(this); } 311 * </pre> 312 * @return a shallow copy of this Manifest 313 */ clone()314 public Object clone() { 315 return new Manifest(this); 316 } 317 318 /* 319 * A fast buffered input stream for parsing manifest files. 320 */ 321 static class FastInputStream extends FilterInputStream { 322 private byte buf[]; 323 private int count = 0; 324 private int pos = 0; 325 FastInputStream(InputStream in)326 FastInputStream(InputStream in) { 327 this(in, 8192); 328 } 329 FastInputStream(InputStream in, int size)330 FastInputStream(InputStream in, int size) { 331 super(in); 332 buf = new byte[size]; 333 } 334 read()335 public int read() throws IOException { 336 if (pos >= count) { 337 fill(); 338 if (pos >= count) { 339 return -1; 340 } 341 } 342 return buf[pos++] & 0xff; 343 } 344 read(byte[] b, int off, int len)345 public int read(byte[] b, int off, int len) throws IOException { 346 int avail = count - pos; 347 if (avail <= 0) { 348 if (len >= buf.length) { 349 return in.read(b, off, len); 350 } 351 fill(); 352 avail = count - pos; 353 if (avail <= 0) { 354 return -1; 355 } 356 } 357 if (len > avail) { 358 len = avail; 359 } 360 System.arraycopy(buf, pos, b, off, len); 361 pos += len; 362 return len; 363 } 364 365 /* 366 * Reads 'len' bytes from the input stream, or until an end-of-line 367 * is reached. Returns the number of bytes read. 368 */ readLine(byte[] b, int off, int len)369 public int readLine(byte[] b, int off, int len) throws IOException { 370 byte[] tbuf = this.buf; 371 int total = 0; 372 while (total < len) { 373 int avail = count - pos; 374 if (avail <= 0) { 375 fill(); 376 avail = count - pos; 377 if (avail <= 0) { 378 return -1; 379 } 380 } 381 int n = len - total; 382 if (n > avail) { 383 n = avail; 384 } 385 int tpos = pos; 386 int maxpos = tpos + n; 387 while (tpos < maxpos && tbuf[tpos++] != '\n') ; 388 n = tpos - pos; 389 System.arraycopy(tbuf, pos, b, off, n); 390 off += n; 391 total += n; 392 pos = tpos; 393 if (tbuf[tpos-1] == '\n') { 394 break; 395 } 396 } 397 return total; 398 } 399 peek()400 public byte peek() throws IOException { 401 if (pos == count) 402 fill(); 403 if (pos == count) 404 return -1; // nothing left in buffer 405 return buf[pos]; 406 } 407 readLine(byte[] b)408 public int readLine(byte[] b) throws IOException { 409 return readLine(b, 0, b.length); 410 } 411 skip(long n)412 public long skip(long n) throws IOException { 413 if (n <= 0) { 414 return 0; 415 } 416 long avail = count - pos; 417 if (avail <= 0) { 418 return in.skip(n); 419 } 420 if (n > avail) { 421 n = avail; 422 } 423 pos += n; 424 return n; 425 } 426 available()427 public int available() throws IOException { 428 return (count - pos) + in.available(); 429 } 430 close()431 public void close() throws IOException { 432 if (in != null) { 433 in.close(); 434 in = null; 435 buf = null; 436 } 437 } 438 fill()439 private void fill() throws IOException { 440 count = pos = 0; 441 int n = in.read(buf, 0, buf.length); 442 if (n > 0) { 443 count = n; 444 } 445 } 446 } 447 } 448