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.IOException;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.LinkedHashMap;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Set;
36
37 import sun.util.logging.PlatformLogger;
38
39 /**
40 * The Attributes class maps Manifest attribute names to associated string
41 * values. Valid attribute names are case-insensitive, are restricted to
42 * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
43 * characters in length. There must be a colon and a SPACE after the name;
44 * the combined length will not exceed 72 characters.
45 * Attribute values can contain any characters and
46 * will be UTF8-encoded when written to the output stream. See the
47 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
48 * for more information about valid attribute names and values.
49 *
50 * <p>This map and its views have a predictable iteration order, namely the
51 * order that keys were inserted into the map, as with {@link LinkedHashMap}.
52 *
53 * @author David Connelly
54 * @see Manifest
55 * @since 1.2
56 */
57 public class Attributes implements Map<Object,Object>, Cloneable {
58 /**
281 return map.hashCode();
282 }
283
284 /**
285 * Returns a copy of the Attributes, implemented as follows:
286 * <pre>
287 * public Object clone() { return new Attributes(this); }
288 * </pre>
289 * Since the attribute names and values are themselves immutable,
290 * the Attributes returned can be safely modified without affecting
291 * the original.
292 */
293 public Object clone() {
294 return new Attributes(this);
295 }
296
297 /*
298 * Writes the current attributes to the specified data output stream.
299 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
300 */
301 @SuppressWarnings("deprecation")
302 void write(DataOutputStream os) throws IOException {
303 for (Entry<Object, Object> e : entrySet()) {
304 StringBuffer buffer = new StringBuffer(
305 ((Name) e.getKey()).toString());
306 buffer.append(": ");
307
308 String value = (String) e.getValue();
309 if (value != null) {
310 byte[] vb = value.getBytes("UTF8");
311 value = new String(vb, 0, 0, vb.length);
312 }
313 buffer.append(value);
314
315 Manifest.make72Safe(buffer);
316 buffer.append("\r\n");
317 os.writeBytes(buffer.toString());
318 }
319 os.writeBytes("\r\n");
320 }
321
322 /*
323 * Writes the current attributes to the specified data output stream,
324 * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION
325 * attributes first.
326 *
327 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
328 */
329 @SuppressWarnings("deprecation")
330 void writeMain(DataOutputStream out) throws IOException
331 {
332 // write out the *-Version header first, if it exists
333 String vername = Name.MANIFEST_VERSION.toString();
334 String version = getValue(vername);
335 if (version == null) {
336 vername = Name.SIGNATURE_VERSION.toString();
337 version = getValue(vername);
338 }
339
340 if (version != null) {
341 out.writeBytes(vername+": "+version+"\r\n");
342 }
343
344 // write out all attributes except for the version
345 // we wrote out earlier
346 for (Entry<Object, Object> e : entrySet()) {
347 String name = ((Name) e.getKey()).toString();
348 if ((version != null) && !(name.equalsIgnoreCase(vername))) {
349
350 StringBuffer buffer = new StringBuffer(name);
351 buffer.append(": ");
352
353 String value = (String) e.getValue();
354 if (value != null) {
355 byte[] vb = value.getBytes("UTF8");
356 value = new String(vb, 0, 0, vb.length);
357 }
358 buffer.append(value);
359
360 Manifest.make72Safe(buffer);
361 buffer.append("\r\n");
362 out.writeBytes(buffer.toString());
363 }
364 }
365 out.writeBytes("\r\n");
366 }
367
368 /*
369 * Reads attributes from the specified input stream.
370 * XXX Need to handle UTF8 values.
371 */
372 void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
373 read(is, lbuf, null, 0);
374 }
375
376 @SuppressWarnings("deprecation")
377 int read(Manifest.FastInputStream is, byte[] lbuf, String filename, int lineNumber) throws IOException {
378 String name = null, value;
379 byte[] lastline = null;
380
381 int len;
382 while ((len = is.readLine(lbuf)) != -1) {
383 boolean lineContinued = false;
384 byte c = lbuf[--len];
385 lineNumber++;
386
387 if (c != '\n' && c != '\r') {
388 throw new IOException("line too long ("
389 + Manifest.getErrorPosition(filename, lineNumber) + ")");
390 }
391 if (len > 0 && lbuf[len-1] == '\r') {
392 --len;
393 }
394 if (len == 0) {
395 break;
396 }
397 int i = 0;
398 if (lbuf[0] == ' ') {
399 // continuation of previous line
400 if (name == null) {
401 throw new IOException("misplaced continuation line ("
402 + Manifest.getErrorPosition(filename, lineNumber) + ")");
403 }
404 lineContinued = true;
405 byte[] buf = new byte[lastline.length + len - 1];
406 System.arraycopy(lastline, 0, buf, 0, lastline.length);
407 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
408 if (is.peek() == ' ') {
409 lastline = buf;
410 continue;
411 }
412 value = new String(buf, 0, buf.length, "UTF8");
413 lastline = null;
414 } else {
415 while (lbuf[i++] != ':') {
416 if (i >= len) {
417 throw new IOException("invalid header field ("
418 + Manifest.getErrorPosition(filename, lineNumber) + ")");
419 }
420 }
421 if (lbuf[i++] != ' ') {
422 throw new IOException("invalid header field ("
423 + Manifest.getErrorPosition(filename, lineNumber) + ")");
424 }
425 name = new String(lbuf, 0, 0, i - 2);
426 if (is.peek() == ' ') {
427 lastline = new byte[len - i];
428 System.arraycopy(lbuf, i, lastline, 0, len - i);
429 continue;
430 }
431 value = new String(lbuf, i, len - i, "UTF8");
432 }
433 try {
434 if ((putValue(name, value) != null) && (!lineContinued)) {
435 PlatformLogger.getLogger("java.util.jar").warning(
436 "Duplicate name in Manifest: " + name
437 + ".\n"
438 + "Ensure that the manifest does not "
439 + "have duplicate entries, and\n"
440 + "that blank lines separate "
441 + "individual sections in both your\n"
442 + "manifest and in the META-INF/MANIFEST.MF "
443 + "entry in the jar file.");
444 }
445 } catch (IllegalArgumentException e) {
446 throw new IOException("invalid header field name: " + name
447 + " (" + Manifest.getErrorPosition(filename, lineNumber) + ")");
448 }
449 }
450 return lineNumber;
451 }
|
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.IOException;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.LinkedHashMap;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.Set;
36
37 import sun.util.logging.PlatformLogger;
38
39 import static java.nio.charset.StandardCharsets.UTF_8;
40
41 /**
42 * The Attributes class maps Manifest attribute names to associated string
43 * values. Valid attribute names are case-insensitive, are restricted to
44 * the ASCII characters in the set [0-9a-zA-Z_-], and cannot exceed 70
45 * characters in length. There must be a colon and a SPACE after the name;
46 * the combined length will not exceed 72 characters.
47 * Attribute values can contain any characters and
48 * will be UTF8-encoded when written to the output stream. See the
49 * <a href="{@docRoot}/../specs/jar/jar.html">JAR File Specification</a>
50 * for more information about valid attribute names and values.
51 *
52 * <p>This map and its views have a predictable iteration order, namely the
53 * order that keys were inserted into the map, as with {@link LinkedHashMap}.
54 *
55 * @author David Connelly
56 * @see Manifest
57 * @since 1.2
58 */
59 public class Attributes implements Map<Object,Object>, Cloneable {
60 /**
283 return map.hashCode();
284 }
285
286 /**
287 * Returns a copy of the Attributes, implemented as follows:
288 * <pre>
289 * public Object clone() { return new Attributes(this); }
290 * </pre>
291 * Since the attribute names and values are themselves immutable,
292 * the Attributes returned can be safely modified without affecting
293 * the original.
294 */
295 public Object clone() {
296 return new Attributes(this);
297 }
298
299 /*
300 * Writes the current attributes to the specified data output stream.
301 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
302 */
303 void write(DataOutputStream out) throws IOException {
304 StringBuilder buffer = new StringBuilder(72);
305 for (Entry<Object, Object> e : entrySet()) {
306 buffer.setLength(0);
307 buffer.append(e.getKey().toString());
308 buffer.append(": ");
309 buffer.append(e.getValue());
310 Manifest.println72(out, buffer.toString());
311 }
312 Manifest.println(out); // empty line after individual section
313 }
314
315 /*
316 * Writes the current attributes to the specified data output stream,
317 * make sure to write out the MANIFEST_VERSION or SIGNATURE_VERSION
318 * attributes first.
319 *
320 * XXX Need to handle UTF8 values and break up lines longer than 72 bytes
321 */
322 void writeMain(DataOutputStream out) throws IOException {
323 StringBuilder buffer = new StringBuilder(72);
324
325 // write out the *-Version header first, if it exists
326 String vername = Name.MANIFEST_VERSION.toString();
327 String version = getValue(vername);
328 if (version == null) {
329 vername = Name.SIGNATURE_VERSION.toString();
330 version = getValue(vername);
331 }
332
333 if (version != null) {
334 buffer.append(vername);
335 buffer.append(": ");
336 buffer.append(version);
337 out.write(buffer.toString().getBytes(UTF_8));
338 Manifest.println(out);
339 }
340
341 // write out all attributes except for the version
342 // we wrote out earlier
343 for (Entry<Object, Object> e : entrySet()) {
344 String name = ((Name) e.getKey()).toString();
345 if ((version != null) && !(name.equalsIgnoreCase(vername))) {
346 buffer.setLength(0);
347 buffer.append(name);
348 buffer.append(": ");
349 buffer.append(e.getValue());
350 Manifest.println72(out, buffer.toString());
351 }
352 }
353
354 Manifest.println(out); // empty line after main attributes section
355 }
356
357 /*
358 * Reads attributes from the specified input stream.
359 */
360 void read(Manifest.FastInputStream is, byte[] lbuf) throws IOException {
361 read(is, lbuf, null, 0);
362 }
363
364 int read(Manifest.FastInputStream is, byte[] lbuf, String filename, int lineNumber) throws IOException {
365 String name = null, value;
366 byte[] lastline = null;
367
368 int len;
369 while ((len = is.readLine(lbuf)) != -1) {
370 boolean lineContinued = false;
371 byte c = lbuf[--len];
372 lineNumber++;
373
374 if (c != '\n' && c != '\r') {
375 throw new IOException("line too long ("
376 + Manifest.getErrorPosition(filename, lineNumber) + ")");
377 }
378 if (len > 0 && lbuf[len-1] == '\r') {
379 --len;
380 }
381 if (len == 0) {
382 break;
383 }
384 int i = 0;
385 if (lbuf[0] == ' ') {
386 // continuation of previous line
387 if (name == null) {
388 throw new IOException("misplaced continuation line ("
389 + Manifest.getErrorPosition(filename, lineNumber) + ")");
390 }
391 lineContinued = true;
392 byte[] buf = new byte[lastline.length + len - 1];
393 System.arraycopy(lastline, 0, buf, 0, lastline.length);
394 System.arraycopy(lbuf, 1, buf, lastline.length, len - 1);
395 if (is.peek() == ' ') {
396 lastline = buf;
397 continue;
398 }
399 value = new String(buf, 0, buf.length, UTF_8);
400 lastline = null;
401 } else {
402 while (lbuf[i++] != ':') {
403 if (i >= len) {
404 throw new IOException("invalid header field ("
405 + Manifest.getErrorPosition(filename, lineNumber) + ")");
406 }
407 }
408 if (lbuf[i++] != ' ') {
409 throw new IOException("invalid header field ("
410 + Manifest.getErrorPosition(filename, lineNumber) + ")");
411 }
412 name = new String(lbuf, 0, i - 2, UTF_8);
413 if (is.peek() == ' ') {
414 lastline = new byte[len - i];
415 System.arraycopy(lbuf, i, lastline, 0, len - i);
416 continue;
417 }
418 value = new String(lbuf, i, len - i, UTF_8);
419 }
420 try {
421 if ((putValue(name, value) != null) && (!lineContinued)) {
422 PlatformLogger.getLogger("java.util.jar").warning(
423 "Duplicate name in Manifest: " + name
424 + ".\n"
425 + "Ensure that the manifest does not "
426 + "have duplicate entries, and\n"
427 + "that blank lines separate "
428 + "individual sections in both your\n"
429 + "manifest and in the META-INF/MANIFEST.MF "
430 + "entry in the jar file.");
431 }
432 } catch (IllegalArgumentException e) {
433 throw new IOException("invalid header field name: " + name
434 + " (" + Manifest.getErrorPosition(filename, lineNumber) + ")");
435 }
436 }
437 return lineNumber;
438 }
|