Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / gnu / java / util / jar / JarUtils.java
blobc35daec55efdf1d63f34eabb585dc1aeb3aafcb3
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)
9 any later version.
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
19 02110-1301 USA.
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
24 combination.
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;
50 import java.util.Map;
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;
56 /**
57 * Utility methods for reading and writing JAR <i>Manifest</i> and
58 * <i>Manifest-like</i> files.
59 * <p>
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";
71 /**
72 * The original string representation of the manifest version attribute name.
73 */
74 public static final String MANIFEST_VERSION = "Manifest-Version";
76 /**
77 * The original string representation of the signature version attribute
78 * name.
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")
88 + " ("
89 + SystemProperties.getProperty("java.vendor")
90 + ")";
92 // default 0-arguments constructor
94 // Methods for reading Manifest files from InputStream ----------------------
96 public static void
97 readMFManifest(Attributes attr, Map entries, InputStream in)
98 throws IOException
100 BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8"));
101 readMainSection(attr, br);
102 readIndividualSections(entries, br);
105 public static void
106 readSFManifest(Attributes attr, Map entries, InputStream in)
107 throws IOException
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);
132 s = br.readLine();
136 private static void readMainSection(Attributes attr, BufferedReader br)
137 throws IOException
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)
149 throws IOException
151 String s = br.readLine();
152 while (s != null && (! s.equals("")))
154 Attributes attr = readSectionName(s, br, entries);
155 read_attributes(attr, br);
156 s = br.readLine();
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)
166 throws IOException
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)
182 throws IOException
184 String s = br.readLine();
185 if (s == null)
186 throw new JarException("unexpected end of file");
188 return expectHeader(header, br, s);
191 private static void read_attributes(Attributes attr, BufferedReader br)
192 throws IOException
194 String s = br.readLine();
195 while (s != null && (! s.equals("")))
197 readAttribute(attr, s, br);
198 s = br.readLine();
202 private static void
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)
220 throws IOException
222 boolean try_next = true;
223 while (try_next)
225 // Lets see if there is something on the next line
226 br.mark(1);
227 if (br.read() == ' ')
228 s += br.readLine();
229 else
231 br.reset();
232 try_next = false;
235 return s;
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);
246 return attr;
248 catch (IOException ioe)
250 throw new JarException("Section should start with a Name header: "
251 + ioe.getMessage());
255 private static String expectHeader(String header, BufferedReader br, String s)
256 throws IOException
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 --------------------
276 public static void
277 writeMFManifest(Attributes attr, Map entries, OutputStream stream)
278 throws IOException
280 BufferedOutputStream out = stream instanceof BufferedOutputStream
281 ? (BufferedOutputStream) stream
282 : new BufferedOutputStream(stream, 4096);
283 writeVersionInfo(attr, out);
284 Iterator i;
285 Map.Entry e;
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);
293 out.write(CRLF);
295 Iterator j;
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);
306 out.write(CRLF);
309 out.flush();
312 public static void
313 writeSFManifest(Attributes attr, Map entries, OutputStream stream)
314 throws IOException
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);
321 Iterator i;
322 Map.Entry e;
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))
328 continue;
330 writeHeader(name.toString(), (String) e.getValue(), out);
332 out.write(CRLF);
334 Iterator j;
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);
345 out.write(CRLF);
348 out.flush();
351 private static void writeVersionInfo(Attributes attr, OutputStream out)
352 throws IOException
354 // First check if there is already a version attribute set
355 String version = attr.getValue(Name.MANIFEST_VERSION);
356 if (version == null)
357 version = DEFAULT_MF_VERSION;
359 writeHeader(Name.MANIFEST_VERSION.toString(), version, out);
362 private static void writeAttributeEntry(Map.Entry entry, OutputStream out)
363 throws IOException
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'"
372 + name);
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.
382 * <pre>
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).
386 * </pre>
388 * and
390 * <pre>
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).
393 * </pre>
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)
401 throws IOException
403 String target = name + ": ";
404 byte[] b = target.getBytes("UTF-8");
405 if (b.length > 72)
406 throw new IOException("Attribute's name already longer than 70 bytes");
408 if (b.length == 72)
410 out.write(b);
411 out.write(CRLF);
412 target = " " + value;
414 else
415 target = target + value;
417 int n;
418 while (true)
420 b = target.getBytes("UTF-8");
421 if (b.length < 73)
423 out.write(b);
424 break;
427 // find an appropriate character position to break on
428 n = 72;
429 while (true)
431 b = target.substring(0, n).getBytes("UTF-8");
432 if (b.length < 73)
433 break;
435 n--;
436 if (n < 1)
437 throw new IOException("Header is unbreakable and longer than 72 bytes");
440 out.write(b);
441 out.write(CRLF);
442 target = " " + target.substring(n);
445 out.write(CRLF);