1 /* JarUtils.java -- Utility methods for reading/writing Manifest[-like] files
2 Copyright (C) 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package gnu
.java
.util
.jar
;
41 import gnu
.classpath
.SystemProperties
;
43 import java
.io
.BufferedOutputStream
;
44 import java
.io
.BufferedReader
;
45 import java
.io
.IOException
;
46 import java
.io
.InputStream
;
47 import java
.io
.InputStreamReader
;
48 import java
.io
.OutputStream
;
49 import java
.util
.Iterator
;
51 import java
.util
.jar
.Attributes
;
52 import java
.util
.jar
.JarException
;
53 import java
.util
.jar
.Attributes
.Name
;
54 import java
.util
.logging
.Logger
;
57 * Utility methods for reading and writing JAR <i>Manifest</i> and
58 * <i>Manifest-like</i> files.
60 * JAR-related files that resemble <i>Manifest</i> files are Signature files
61 * (with an <code>.SF</code> extension) found in signed JARs.
63 public abstract class JarUtils
65 private static final Logger log
= Logger
.getLogger(JarUtils
.class.getName());
66 public static final String META_INF
= "META-INF/";
67 public static final String DSA_SUFFIX
= ".DSA";
68 public static final String SF_SUFFIX
= ".SF";
69 public static final String NAME
= "Name";
72 * The original string representation of the manifest version attribute name.
74 public static final String MANIFEST_VERSION
= "Manifest-Version";
77 * The original string representation of the signature version attribute
80 public static final String SIGNATURE_VERSION
= "Signature-Version";
82 /** Platform-independent line-ending. */
83 public static final byte[] CRLF
= new byte[] { 0x0D, 0x0A };
84 private static final String DEFAULT_MF_VERSION
= "1.0";
85 private static final String DEFAULT_SF_VERSION
= "1.0";
86 private static final Name CREATED_BY
= new Name("Created-By");
87 private static final String CREATOR
= SystemProperties
.getProperty("java.version")
89 + SystemProperties
.getProperty("java.vendor")
92 // default 0-arguments constructor
94 // Methods for reading Manifest files from InputStream ----------------------
97 readMFManifest(Attributes attr
, Map entries
, InputStream in
)
100 BufferedReader br
= new BufferedReader(new InputStreamReader(in
, "UTF-8"));
101 readMainSection(attr
, br
);
102 readIndividualSections(entries
, br
);
106 readSFManifest(Attributes attr
, Map entries
, InputStream in
)
109 BufferedReader br
= new BufferedReader(new InputStreamReader(in
, "UTF-8"));
110 String version_header
= Name
.SIGNATURE_VERSION
.toString();
113 String version
= expectHeader(version_header
, br
);
114 attr
.putValue(SIGNATURE_VERSION
, version
);
115 if (! DEFAULT_SF_VERSION
.equals(version
))
116 log
.warning("Unexpected version number: " + version
117 + ". Continue (but may fail later)");
119 catch (IOException ioe
)
121 throw new JarException("Signature file MUST start with a "
122 + version_header
+ ": " + ioe
.getMessage());
124 read_attributes(attr
, br
);
126 // read individual sections
127 String s
= br
.readLine();
128 while (s
!= null && s
.length() > 0)
130 Attributes eAttr
= readSectionName(s
, br
, entries
);
131 read_attributes(eAttr
, br
);
136 private static void readMainSection(Attributes attr
, BufferedReader br
)
139 // According to the spec we should actually call read_version_info() here.
140 read_attributes(attr
, br
);
141 // Explicitly set Manifest-Version attribute if not set in Main
142 // attributes of Manifest.
143 // XXX (rsn): why 0.0 and not 1.0?
144 if (attr
.getValue(Name
.MANIFEST_VERSION
) == null)
145 attr
.putValue(MANIFEST_VERSION
, "0.0");
148 private static void readIndividualSections(Map entries
, BufferedReader br
)
151 String s
= br
.readLine();
152 while (s
!= null && (! s
.equals("")))
154 Attributes attr
= readSectionName(s
, br
, entries
);
155 read_attributes(attr
, br
);
161 * Pedantic method that requires the next attribute in the Manifest to be the
162 * "Manifest-Version". This follows the Manifest spec closely but reject some
163 * jar Manifest files out in the wild.
165 private static void readVersionInfo(Attributes attr
, BufferedReader br
)
168 String version_header
= Name
.MANIFEST_VERSION
.toString();
171 String value
= expectHeader(version_header
, br
);
172 attr
.putValue(MANIFEST_VERSION
, value
);
174 catch (IOException ioe
)
176 throw new JarException("Manifest should start with a " + version_header
177 + ": " + ioe
.getMessage());
181 private static String
expectHeader(String header
, BufferedReader br
)
184 String s
= br
.readLine();
186 throw new JarException("unexpected end of file");
188 return expectHeader(header
, br
, s
);
191 private static void read_attributes(Attributes attr
, BufferedReader br
)
194 String s
= br
.readLine();
195 while (s
!= null && (! s
.equals("")))
197 readAttribute(attr
, s
, br
);
203 readAttribute(Attributes attr
, String s
, BufferedReader br
) throws IOException
207 int colon
= s
.indexOf(": ");
208 String name
= s
.substring(0, colon
);
209 String value_start
= s
.substring(colon
+ 2);
210 String value
= readHeaderValue(value_start
, br
);
211 attr
.putValue(name
, value
);
213 catch (IndexOutOfBoundsException iobe
)
215 throw new JarException("Manifest contains a bad header: " + s
);
219 private static String
readHeaderValue(String s
, BufferedReader br
)
222 boolean try_next
= true;
225 // Lets see if there is something on the next line
227 if (br
.read() == ' ')
238 private static Attributes
239 readSectionName(String s
, BufferedReader br
, Map entries
) throws JarException
243 String name
= expectHeader(NAME
, br
, s
);
244 Attributes attr
= new Attributes();
245 entries
.put(name
, attr
);
248 catch (IOException ioe
)
250 throw new JarException("Section should start with a Name header: "
255 private static String
expectHeader(String header
, BufferedReader br
, String s
)
260 String name
= s
.substring(0, header
.length() + 1);
261 if (name
.equalsIgnoreCase(header
+ ":"))
263 String value_start
= s
.substring(header
.length() + 2);
264 return readHeaderValue(value_start
, br
);
267 catch (IndexOutOfBoundsException ignored
)
270 // If we arrive here, something went wrong
271 throw new JarException("unexpected '" + s
+ "'");
274 // Methods for writing Manifest files to an OutputStream --------------------
277 writeMFManifest(Attributes attr
, Map entries
, OutputStream stream
)
280 BufferedOutputStream out
= stream
instanceof BufferedOutputStream
281 ?
(BufferedOutputStream
) stream
282 : new BufferedOutputStream(stream
, 4096);
283 writeVersionInfo(attr
, out
);
286 for (i
= attr
.entrySet().iterator(); i
.hasNext();)
288 e
= (Map
.Entry
) i
.next();
289 // Don't print the manifest version again
290 if (! Name
.MANIFEST_VERSION
.equals(e
.getKey()))
291 writeAttributeEntry(e
, out
);
296 for (i
= entries
.entrySet().iterator(); i
.hasNext();)
298 e
= (Map
.Entry
) i
.next();
299 writeHeader(NAME
, e
.getKey().toString(), out
);
300 Attributes eAttr
= (Attributes
) e
.getValue();
301 for (j
= eAttr
.entrySet().iterator(); j
.hasNext();)
303 Map
.Entry e2
= (Map
.Entry
) j
.next();
304 writeAttributeEntry(e2
, out
);
313 writeSFManifest(Attributes attr
, Map entries
, OutputStream stream
)
316 BufferedOutputStream out
= stream
instanceof BufferedOutputStream
317 ?
(BufferedOutputStream
) stream
318 : new BufferedOutputStream(stream
, 4096);
319 writeHeader(Name
.SIGNATURE_VERSION
.toString(), DEFAULT_SF_VERSION
, out
);
320 writeHeader(CREATED_BY
.toString(), CREATOR
, out
);
323 for (i
= attr
.entrySet().iterator(); i
.hasNext();)
325 e
= (Map
.Entry
) i
.next();
326 Name name
= (Name
) e
.getKey();
327 if (Name
.SIGNATURE_VERSION
.equals(name
) || CREATED_BY
.equals(name
))
330 writeHeader(name
.toString(), (String
) e
.getValue(), out
);
335 for (i
= entries
.entrySet().iterator(); i
.hasNext();)
337 e
= (Map
.Entry
) i
.next();
338 writeHeader(NAME
, e
.getKey().toString(), out
);
339 Attributes eAttr
= (Attributes
) e
.getValue();
340 for (j
= eAttr
.entrySet().iterator(); j
.hasNext();)
342 Map
.Entry e2
= (Map
.Entry
) j
.next();
343 writeHeader(e2
.getKey().toString(), (String
) e2
.getValue(), out
);
351 private static void writeVersionInfo(Attributes attr
, OutputStream out
)
354 // First check if there is already a version attribute set
355 String version
= attr
.getValue(Name
.MANIFEST_VERSION
);
357 version
= DEFAULT_MF_VERSION
;
359 writeHeader(Name
.MANIFEST_VERSION
.toString(), version
, out
);
362 private static void writeAttributeEntry(Map
.Entry entry
, OutputStream out
)
365 String name
= entry
.getKey().toString();
366 String value
= entry
.getValue().toString();
367 if (name
.equalsIgnoreCase(NAME
))
368 throw new JarException("Attributes cannot be called 'Name'");
370 if (name
.startsWith("From"))
371 throw new JarException("Header cannot start with the four letters 'From'"
374 writeHeader(name
, value
, out
);
378 * The basic method for writing <code>Mainfest</code> attributes. This
379 * implementation respects the rule stated in the Jar Specification concerning
380 * the maximum allowed line length; i.e.
383 * No line may be longer than 72 bytes (not characters), in its UTF8-encoded
384 * form. If a value would make the initial line longer than this, it should
385 * be continued on extra lines (each starting with a single SPACE).
391 * Because header names cannot be continued, the maximum length of a header
392 * name is 70 bytes (there must be a colon and a SPACE after the name).
395 * @param name the name of the attribute.
396 * @param value the value of the attribute.
397 * @param out the output stream to write the attribute's name/value pair to.
398 * @throws IOException if an I/O related exception occurs during the process.
400 private static void writeHeader(String name
, String value
, OutputStream out
)
403 String target
= name
+ ": ";
404 byte[] b
= target
.getBytes("UTF-8");
406 throw new IOException("Attribute's name already longer than 70 bytes");
412 target
= " " + value
;
415 target
= target
+ value
;
420 b
= target
.getBytes("UTF-8");
427 // find an appropriate character position to break on
431 b
= target
.substring(0, n
).getBytes("UTF-8");
437 throw new IOException("Header is unbreakable and longer than 72 bytes");
442 target
= " " + target
.substring(n
);