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 }