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.DataOutputStream;
29 import java.io.FilterInputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.util.HashMap;
34 import java.util.Map;
35
36 import sun.security.util.SecurityProperties;
37
38 /**
39 * The Manifest class is used to maintain Manifest entry names and their
40 * associated Attributes. There are main Manifest Attributes as well as
41 * per-entry Attributes. For information on the Manifest format, please
42 * see the
43 * <a href="{@docRoot}/../specs/jar/jar.html">
44 * Manifest format specification</a>.
45 *
46 * @author David Connelly
47 * @see Attributes
48 * @since 1.2
49 */
50 public class Manifest implements Cloneable {
51
52 // manifest main attributes
53 private final Attributes attr = new Attributes();
54
55 // manifest entries
56 private final Map<String, Attributes> entries = new HashMap<>();
57
180 return result;
181 }
182
183 /**
184 * Clears the main Attributes as well as the entries in this Manifest.
185 */
186 public void clear() {
187 attr.clear();
188 entries.clear();
189 }
190
191 /**
192 * Writes the Manifest to the specified OutputStream.
193 * Attributes.Name.MANIFEST_VERSION must be set in
194 * MainAttributes prior to invoking this method.
195 *
196 * @param out the output stream
197 * @exception IOException if an I/O error has occurred
198 * @see #getMainAttributes
199 */
200 @SuppressWarnings("deprecation")
201 public void write(OutputStream out) throws IOException {
202 DataOutputStream dos = new DataOutputStream(out);
203 // Write out the main attributes for the manifest
204 attr.writeMain(dos);
205 // Now write out the per-entry attributes
206 for (Map.Entry<String, Attributes> e : entries.entrySet()) {
207 StringBuffer buffer = new StringBuffer("Name: ");
208 String value = e.getKey();
209 if (value != null) {
210 byte[] vb = value.getBytes("UTF8");
211 value = new String(vb, 0, 0, vb.length);
212 }
213 buffer.append(value);
214 make72Safe(buffer);
215 buffer.append("\r\n");
216 dos.writeBytes(buffer.toString());
217 e.getValue().write(dos);
218 }
219 dos.flush();
220 }
221
222 /**
223 * Adds line breaks to enforce a maximum 72 bytes per line.
224 */
225 static void make72Safe(StringBuffer line) {
226 int length = line.length();
227 int index = 72;
228 while (index < length) {
229 line.insert(index, "\r\n ");
230 index += 74; // + line width + line break ("\r\n")
231 length += 3; // + line break ("\r\n") and space
232 }
233 return;
234 }
235
236 static String getErrorPosition(String filename, final int lineNumber) {
237 if (filename == null ||
238 !SecurityProperties.INCLUDE_JAR_NAME_IN_EXCEPTIONS) {
239 return "line " + lineNumber;
240 }
241 return "manifest of " + filename + ":" + lineNumber;
242 }
243
244 /**
245 * Reads the Manifest from the specified InputStream. The entry
246 * names and attributes read will be merged in with the current
247 * manifest entries.
248 *
249 * @param is the input stream
250 * @exception IOException if an I/O error has occurred
251 */
252 public void read(InputStream is) throws IOException {
253 // Buffered input stream for reading manifest data
287 if (name == null) {
288 throw new IOException("invalid manifest format"
289 + getErrorPosition(jarFilename, lineNumber) + ")");
290 }
291 if (fis.peek() == ' ') {
292 // name is wrapped
293 lastline = new byte[len - 6];
294 System.arraycopy(lbuf, 6, lastline, 0, len - 6);
295 continue;
296 }
297 } else {
298 // continuation line
299 byte[] buf = new byte[lastline.length + len - 1];
300 System.arraycopy(lastline, 0, buf, 0, lastline.length);
301 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
302 if (fis.peek() == ' ') {
303 // name is wrapped
304 lastline = buf;
305 continue;
306 }
307 name = new String(buf, 0, buf.length, "UTF8");
308 lastline = null;
309 }
310 Attributes attr = getAttributes(name);
311 if (attr == null) {
312 attr = new Attributes(asize);
313 entries.put(name, attr);
314 }
315 lineNumber = attr.read(fis, lbuf, jarFilename, lineNumber);
316 ecount++;
317 acount += attr.size();
318 //XXX: Fix for when the average is 0. When it is 0,
319 // you get an Attributes object with an initial
320 // capacity of 0, which tickles a bug in HashMap.
321 asize = Math.max(2, acount / ecount);
322
323 name = null;
324 skipEmptyLines = true;
325 }
326 }
327
328 private String parseName(byte[] lbuf, int len) {
329 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
330 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
331 lbuf[4] == ':' && lbuf[5] == ' ') {
332 try {
333 return new String(lbuf, 6, len - 6, "UTF8");
334 }
335 catch (Exception e) {
336 }
337 }
338 return null;
339 }
340
341 private int toLower(int c) {
342 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
343 }
344
345 /**
346 * Returns true if the specified Object is also a Manifest and has
347 * the same main Attributes and entries.
348 *
349 * @param o the object to be compared
350 * @return true if the specified Object is also a Manifest and has
351 * the same main Attributes and entries
352 */
353 public boolean equals(Object o) {
|
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.DataOutputStream;
29 import java.io.FilterInputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.util.HashMap;
34 import java.util.Map;
35
36 import sun.security.util.SecurityProperties;
37
38 import static java.nio.charset.StandardCharsets.UTF_8;
39
40 /**
41 * The Manifest class is used to maintain Manifest entry names and their
42 * associated Attributes. There are main Manifest Attributes as well as
43 * per-entry Attributes. For information on the Manifest format, please
44 * see the
45 * <a href="{@docRoot}/../specs/jar/jar.html">
46 * Manifest format specification</a>.
47 *
48 * @author David Connelly
49 * @see Attributes
50 * @since 1.2
51 */
52 public class Manifest implements Cloneable {
53
54 // manifest main attributes
55 private final Attributes attr = new Attributes();
56
57 // manifest entries
58 private final Map<String, Attributes> entries = new HashMap<>();
59
182 return result;
183 }
184
185 /**
186 * Clears the main Attributes as well as the entries in this Manifest.
187 */
188 public void clear() {
189 attr.clear();
190 entries.clear();
191 }
192
193 /**
194 * Writes the Manifest to the specified OutputStream.
195 * Attributes.Name.MANIFEST_VERSION must be set in
196 * MainAttributes prior to invoking this method.
197 *
198 * @param out the output stream
199 * @exception IOException if an I/O error has occurred
200 * @see #getMainAttributes
201 */
202 public void write(OutputStream out) throws IOException {
203 DataOutputStream dos = new DataOutputStream(out);
204 // Write out the main attributes for the manifest
205 attr.writeMain(dos);
206 // Now write out the per-entry attributes
207 StringBuilder buffer = entries.isEmpty() ? null : new StringBuilder(72);
208 for (Map.Entry<String, Attributes> e : entries.entrySet()) {
209 buffer.setLength(0);
210 buffer.append("Name: ");
211 buffer.append(e.getKey());
212 println72(dos, buffer.toString());
213 e.getValue().write(dos);
214 }
215 dos.flush();
216 }
217
218 /**
219 * Adds line breaks to enforce a maximum of 72 bytes per line.
220 *
221 * @deprecation Replaced with {@link #println72}.
222 */
223 @Deprecated(since = "13")
224 static void make72Safe(StringBuffer line) {
225 int length = line.length();
226 int index = 72;
227 while (index < length) {
228 line.insert(index, "\r\n ");
229 index += 74; // + line width + line break ("\r\n")
230 length += 3; // + line break ("\r\n") and space
231 }
232 }
233
234 /**
235 * Writes {@code line} to {@code out} with line breaks and continuation
236 * spaces within the limits of 72 bytes of contents per line followed
237 * by a line break.
238 */
239 static void println72(OutputStream out, String line) throws IOException {
240 if (!line.isEmpty()) {
241 byte[] lineBytes = line.getBytes(UTF_8);
242 int length = lineBytes.length;
243 // first line can hold one byte more than subsequent lines which
244 // start with a continuation line break space
245 out.write(lineBytes[0]);
246 int pos = 1;
247 while (length - pos > 71) {
248 out.write(lineBytes, pos, 71);
249 pos += 71;
250 println(out);
251 out.write(' ');
252 }
253 out.write(lineBytes, pos, length - pos);
254 }
255 println(out);
256 }
257
258 /**
259 * Writes a line break to {@code out}.
260 */
261 static void println(OutputStream out) throws IOException {
262 out.write('\r');
263 out.write('\n');
264 }
265
266 static String getErrorPosition(String filename, final int lineNumber) {
267 if (filename == null ||
268 !SecurityProperties.INCLUDE_JAR_NAME_IN_EXCEPTIONS) {
269 return "line " + lineNumber;
270 }
271 return "manifest of " + filename + ":" + lineNumber;
272 }
273
274 /**
275 * Reads the Manifest from the specified InputStream. The entry
276 * names and attributes read will be merged in with the current
277 * manifest entries.
278 *
279 * @param is the input stream
280 * @exception IOException if an I/O error has occurred
281 */
282 public void read(InputStream is) throws IOException {
283 // Buffered input stream for reading manifest data
317 if (name == null) {
318 throw new IOException("invalid manifest format"
319 + getErrorPosition(jarFilename, lineNumber) + ")");
320 }
321 if (fis.peek() == ' ') {
322 // name is wrapped
323 lastline = new byte[len - 6];
324 System.arraycopy(lbuf, 6, lastline, 0, len - 6);
325 continue;
326 }
327 } else {
328 // continuation line
329 byte[] buf = new byte[lastline.length + len - 1];
330 System.arraycopy(lastline, 0, buf, 0, lastline.length);
331 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
332 if (fis.peek() == ' ') {
333 // name is wrapped
334 lastline = buf;
335 continue;
336 }
337 name = new String(buf, 0, buf.length, UTF_8);
338 lastline = null;
339 }
340 Attributes attr = getAttributes(name);
341 if (attr == null) {
342 attr = new Attributes(asize);
343 entries.put(name, attr);
344 }
345 lineNumber = attr.read(fis, lbuf, jarFilename, lineNumber);
346 ecount++;
347 acount += attr.size();
348 //XXX: Fix for when the average is 0. When it is 0,
349 // you get an Attributes object with an initial
350 // capacity of 0, which tickles a bug in HashMap.
351 asize = Math.max(2, acount / ecount);
352
353 name = null;
354 skipEmptyLines = true;
355 }
356 }
357
358 private String parseName(byte[] lbuf, int len) {
359 if (toLower(lbuf[0]) == 'n' && toLower(lbuf[1]) == 'a' &&
360 toLower(lbuf[2]) == 'm' && toLower(lbuf[3]) == 'e' &&
361 lbuf[4] == ':' && lbuf[5] == ' ') {
362 try {
363 return new String(lbuf, 6, len - 6, UTF_8);
364 }
365 catch (Exception e) {
366 }
367 }
368 return null;
369 }
370
371 private int toLower(int c) {
372 return (c >= 'A' && c <= 'Z') ? 'a' + (c - 'A') : c;
373 }
374
375 /**
376 * Returns true if the specified Object is also a Manifest and has
377 * the same main Attributes and entries.
378 *
379 * @param o the object to be compared
380 * @return true if the specified Object is also a Manifest and has
381 * the same main Attributes and entries
382 */
383 public boolean equals(Object o) {
|