Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/openjdk-multiarch-jdk8u
Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/share/classes/sun/tools/jar/Main.java
38918 views
1
/*
2
* Copyright (c) 1996, 2013, 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 sun.tools.jar;
27
28
import java.io.*;
29
import java.nio.file.Path;
30
import java.nio.file.Files;
31
import java.util.*;
32
import java.util.zip.*;
33
import java.util.jar.*;
34
import java.util.jar.Pack200.*;
35
import java.util.jar.Manifest;
36
import java.text.MessageFormat;
37
import sun.misc.JarIndex;
38
import static sun.misc.JarIndex.INDEX_NAME;
39
import static java.util.jar.JarFile.MANIFEST_NAME;
40
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
41
42
/**
43
* This class implements a simple utility for creating files in the JAR
44
* (Java Archive) file format. The JAR format is based on the ZIP file
45
* format, with optional meta-information stored in a MANIFEST entry.
46
*/
47
public
48
class Main {
49
String program;
50
PrintStream out, err;
51
String fname, mname, ename;
52
String zname = "";
53
String[] files;
54
String rootjar = null;
55
56
// An entryName(path)->File map generated during "expand", it helps to
57
// decide whether or not an existing entry in a jar file needs to be
58
// replaced, during the "update" operation.
59
Map<String, File> entryMap = new HashMap<String, File>();
60
61
// All files need to be added/updated.
62
Set<File> entries = new LinkedHashSet<File>();
63
64
// Directories specified by "-C" operation.
65
Set<String> paths = new HashSet<String>();
66
67
/*
68
* cflag: create
69
* uflag: update
70
* xflag: xtract
71
* tflag: table
72
* vflag: verbose
73
* flag0: no zip compression (store only)
74
* Mflag: DO NOT generate a manifest file (just ZIP)
75
* iflag: generate jar index
76
* nflag: Perform jar normalization at the end
77
* pflag: preserve/don't strip leading slash and .. component from file name
78
*
79
*/
80
boolean cflag, uflag, xflag, tflag, vflag, flag0, Mflag, iflag, nflag, pflag;
81
82
static final String MANIFEST_DIR = "META-INF/";
83
static final String VERSION = "1.0";
84
85
private static ResourceBundle rsrc;
86
87
/**
88
* If true, maintain compatibility with JDK releases prior to 6.0 by
89
* timestamping extracted files with the time at which they are extracted.
90
* Default is to use the time given in the archive.
91
*/
92
private static final boolean useExtractionTime =
93
Boolean.getBoolean("sun.tools.jar.useExtractionTime");
94
95
/**
96
* Initialize ResourceBundle
97
*/
98
static {
99
try {
100
rsrc = ResourceBundle.getBundle("sun.tools.jar.resources.jar");
101
} catch (MissingResourceException e) {
102
throw new Error("Fatal: Resource for jar is missing");
103
}
104
}
105
106
private String getMsg(String key) {
107
try {
108
return (rsrc.getString(key));
109
} catch (MissingResourceException e) {
110
throw new Error("Error in message file");
111
}
112
}
113
114
private String formatMsg(String key, String arg) {
115
String msg = getMsg(key);
116
String[] args = new String[1];
117
args[0] = arg;
118
return MessageFormat.format(msg, (Object[]) args);
119
}
120
121
private String formatMsg2(String key, String arg, String arg1) {
122
String msg = getMsg(key);
123
String[] args = new String[2];
124
args[0] = arg;
125
args[1] = arg1;
126
return MessageFormat.format(msg, (Object[]) args);
127
}
128
129
public Main(PrintStream out, PrintStream err, String program) {
130
this.out = out;
131
this.err = err;
132
this.program = program;
133
}
134
135
/**
136
* Creates a new empty temporary file in the same directory as the
137
* specified file. A variant of File.createTempFile.
138
*/
139
private static File createTempFileInSameDirectoryAs(File file)
140
throws IOException {
141
File dir = file.getParentFile();
142
if (dir == null)
143
dir = new File(".");
144
return File.createTempFile("jartmp", null, dir);
145
}
146
147
private boolean ok;
148
149
/**
150
* Starts main program with the specified arguments.
151
*/
152
public synchronized boolean run(String args[]) {
153
ok = true;
154
if (!parseArgs(args)) {
155
return false;
156
}
157
try {
158
if (cflag || uflag) {
159
if (fname != null) {
160
// The name of the zip file as it would appear as its own
161
// zip file entry. We use this to make sure that we don't
162
// add the zip file to itself.
163
zname = fname.replace(File.separatorChar, '/');
164
if (zname.startsWith("./")) {
165
zname = zname.substring(2);
166
}
167
}
168
}
169
if (cflag) {
170
Manifest manifest = null;
171
InputStream in = null;
172
173
if (!Mflag) {
174
if (mname != null) {
175
in = new FileInputStream(mname);
176
manifest = new Manifest(new BufferedInputStream(in));
177
} else {
178
manifest = new Manifest();
179
}
180
addVersion(manifest);
181
addCreatedBy(manifest);
182
if (isAmbiguousMainClass(manifest)) {
183
if (in != null) {
184
in.close();
185
}
186
return false;
187
}
188
if (ename != null) {
189
addMainClass(manifest, ename);
190
}
191
}
192
expand(null, files, false);
193
OutputStream out;
194
if (fname != null) {
195
out = new FileOutputStream(fname);
196
} else {
197
out = new FileOutputStream(FileDescriptor.out);
198
if (vflag) {
199
// Disable verbose output so that it does not appear
200
// on stdout along with file data
201
// error("Warning: -v option ignored");
202
vflag = false;
203
}
204
}
205
File tmpfile = null;
206
final OutputStream finalout = out;
207
final String tmpbase = (fname == null)
208
? "tmpjar"
209
: fname.substring(fname.indexOf(File.separatorChar) + 1);
210
if (nflag) {
211
tmpfile = createTemporaryFile(tmpbase, ".jar");
212
out = new FileOutputStream(tmpfile);
213
}
214
create(new BufferedOutputStream(out, 4096), manifest);
215
if (in != null) {
216
in.close();
217
}
218
out.close();
219
if (nflag) {
220
JarFile jarFile = null;
221
File packFile = null;
222
JarOutputStream jos = null;
223
try {
224
Packer packer = Pack200.newPacker();
225
Map<String, String> p = packer.properties();
226
p.put(Packer.EFFORT, "1"); // Minimal effort to conserve CPU
227
jarFile = new JarFile(tmpfile.getCanonicalPath());
228
packFile = createTemporaryFile(tmpbase, ".pack");
229
out = new FileOutputStream(packFile);
230
packer.pack(jarFile, out);
231
jos = new JarOutputStream(finalout);
232
Unpacker unpacker = Pack200.newUnpacker();
233
unpacker.unpack(packFile, jos);
234
} catch (IOException ioe) {
235
fatalError(ioe);
236
} finally {
237
if (jarFile != null) {
238
jarFile.close();
239
}
240
if (out != null) {
241
out.close();
242
}
243
if (jos != null) {
244
jos.close();
245
}
246
if (tmpfile != null && tmpfile.exists()) {
247
tmpfile.delete();
248
}
249
if (packFile != null && packFile.exists()) {
250
packFile.delete();
251
}
252
}
253
}
254
} else if (uflag) {
255
File inputFile = null, tmpFile = null;
256
FileInputStream in;
257
FileOutputStream out;
258
if (fname != null) {
259
inputFile = new File(fname);
260
tmpFile = createTempFileInSameDirectoryAs(inputFile);
261
in = new FileInputStream(inputFile);
262
out = new FileOutputStream(tmpFile);
263
} else {
264
in = new FileInputStream(FileDescriptor.in);
265
out = new FileOutputStream(FileDescriptor.out);
266
vflag = false;
267
}
268
InputStream manifest = (!Mflag && (mname != null)) ?
269
(new FileInputStream(mname)) : null;
270
expand(null, files, true);
271
boolean updateOk = update(in, new BufferedOutputStream(out),
272
manifest, null);
273
if (ok) {
274
ok = updateOk;
275
}
276
in.close();
277
out.close();
278
if (manifest != null) {
279
manifest.close();
280
}
281
if (ok && fname != null) {
282
// on Win32, we need this delete
283
inputFile.delete();
284
if (!tmpFile.renameTo(inputFile)) {
285
tmpFile.delete();
286
throw new IOException(getMsg("error.write.file"));
287
}
288
tmpFile.delete();
289
}
290
} else if (tflag) {
291
replaceFSC(files);
292
if (fname != null) {
293
list(fname, files);
294
} else {
295
InputStream in = new FileInputStream(FileDescriptor.in);
296
try {
297
list(new BufferedInputStream(in), files);
298
} finally {
299
in.close();
300
}
301
}
302
} else if (xflag) {
303
replaceFSC(files);
304
if (fname != null && files != null) {
305
extract(fname, files);
306
} else {
307
InputStream in = (fname == null)
308
? new FileInputStream(FileDescriptor.in)
309
: new FileInputStream(fname);
310
try {
311
extract(new BufferedInputStream(in), files);
312
} finally {
313
in.close();
314
}
315
}
316
} else if (iflag) {
317
genIndex(rootjar, files);
318
}
319
} catch (IOException e) {
320
fatalError(e);
321
ok = false;
322
} catch (Error ee) {
323
ee.printStackTrace();
324
ok = false;
325
} catch (Throwable t) {
326
t.printStackTrace();
327
ok = false;
328
}
329
out.flush();
330
err.flush();
331
return ok;
332
}
333
334
/**
335
* Parses command line arguments.
336
*/
337
boolean parseArgs(String args[]) {
338
/* Preprocess and expand @file arguments */
339
try {
340
args = CommandLine.parse(args);
341
} catch (FileNotFoundException e) {
342
fatalError(formatMsg("error.cant.open", e.getMessage()));
343
return false;
344
} catch (IOException e) {
345
fatalError(e);
346
return false;
347
}
348
/* parse flags */
349
int count = 1;
350
try {
351
String flags = args[0];
352
if (flags.startsWith("-")) {
353
flags = flags.substring(1);
354
}
355
for (int i = 0; i < flags.length(); i++) {
356
switch (flags.charAt(i)) {
357
case 'c':
358
if (xflag || tflag || uflag || iflag) {
359
usageError();
360
return false;
361
}
362
cflag = true;
363
break;
364
case 'u':
365
if (cflag || xflag || tflag || iflag) {
366
usageError();
367
return false;
368
}
369
uflag = true;
370
break;
371
case 'x':
372
if (cflag || uflag || tflag || iflag) {
373
usageError();
374
return false;
375
}
376
xflag = true;
377
break;
378
case 't':
379
if (cflag || uflag || xflag || iflag) {
380
usageError();
381
return false;
382
}
383
tflag = true;
384
break;
385
case 'M':
386
Mflag = true;
387
break;
388
case 'v':
389
vflag = true;
390
break;
391
case 'f':
392
fname = args[count++];
393
break;
394
case 'm':
395
mname = args[count++];
396
break;
397
case '0':
398
flag0 = true;
399
break;
400
case 'i':
401
if (cflag || uflag || xflag || tflag) {
402
usageError();
403
return false;
404
}
405
// do not increase the counter, files will contain rootjar
406
rootjar = args[count++];
407
iflag = true;
408
break;
409
case 'n':
410
nflag = true;
411
break;
412
case 'e':
413
ename = args[count++];
414
break;
415
case 'P':
416
pflag = true;
417
break;
418
default:
419
error(formatMsg("error.illegal.option",
420
String.valueOf(flags.charAt(i))));
421
usageError();
422
return false;
423
}
424
}
425
} catch (ArrayIndexOutOfBoundsException e) {
426
usageError();
427
return false;
428
}
429
if (!cflag && !tflag && !xflag && !uflag && !iflag) {
430
error(getMsg("error.bad.option"));
431
usageError();
432
return false;
433
}
434
/* parse file arguments */
435
int n = args.length - count;
436
if (n > 0) {
437
int k = 0;
438
String[] nameBuf = new String[n];
439
try {
440
for (int i = count; i < args.length; i++) {
441
if (args[i].equals("-C")) {
442
/* change the directory */
443
String dir = args[++i];
444
dir = (dir.endsWith(File.separator) ?
445
dir : (dir + File.separator));
446
dir = dir.replace(File.separatorChar, '/');
447
while (dir.indexOf("//") > -1) {
448
dir = dir.replace("//", "/");
449
}
450
paths.add(dir.replace(File.separatorChar, '/'));
451
nameBuf[k++] = dir + args[++i];
452
} else {
453
nameBuf[k++] = args[i];
454
}
455
}
456
} catch (ArrayIndexOutOfBoundsException e) {
457
usageError();
458
return false;
459
}
460
files = new String[k];
461
System.arraycopy(nameBuf, 0, files, 0, k);
462
} else if (cflag && (mname == null)) {
463
error(getMsg("error.bad.cflag"));
464
usageError();
465
return false;
466
} else if (uflag) {
467
if ((mname != null) || (ename != null)) {
468
/* just want to update the manifest */
469
return true;
470
} else {
471
error(getMsg("error.bad.uflag"));
472
usageError();
473
return false;
474
}
475
}
476
return true;
477
}
478
479
/**
480
* Expands list of files to process into full list of all files that
481
* can be found by recursively descending directories.
482
*/
483
void expand(File dir, String[] files, boolean isUpdate) {
484
if (files == null) {
485
return;
486
}
487
for (int i = 0; i < files.length; i++) {
488
File f;
489
if (dir == null) {
490
f = new File(files[i]);
491
} else {
492
f = new File(dir, files[i]);
493
}
494
if (f.isFile()) {
495
if (entries.add(f)) {
496
if (isUpdate)
497
entryMap.put(entryName(f.getPath()), f);
498
}
499
} else if (f.isDirectory()) {
500
if (entries.add(f)) {
501
if (isUpdate) {
502
String dirPath = f.getPath();
503
dirPath = (dirPath.endsWith(File.separator)) ? dirPath :
504
(dirPath + File.separator);
505
entryMap.put(entryName(dirPath), f);
506
}
507
expand(f, f.list(), isUpdate);
508
}
509
} else {
510
error(formatMsg("error.nosuch.fileordir", String.valueOf(f)));
511
ok = false;
512
}
513
}
514
}
515
516
/**
517
* Creates a new JAR file.
518
*/
519
void create(OutputStream out, Manifest manifest)
520
throws IOException
521
{
522
ZipOutputStream zos = new JarOutputStream(out);
523
if (flag0) {
524
zos.setMethod(ZipOutputStream.STORED);
525
}
526
if (manifest != null) {
527
if (vflag) {
528
output(getMsg("out.added.manifest"));
529
}
530
ZipEntry e = new ZipEntry(MANIFEST_DIR);
531
e.setTime(System.currentTimeMillis());
532
e.setSize(0);
533
e.setCrc(0);
534
zos.putNextEntry(e);
535
e = new ZipEntry(MANIFEST_NAME);
536
e.setTime(System.currentTimeMillis());
537
if (flag0) {
538
crc32Manifest(e, manifest);
539
}
540
zos.putNextEntry(e);
541
manifest.write(zos);
542
zos.closeEntry();
543
}
544
for (File file: entries) {
545
addFile(zos, file);
546
}
547
zos.close();
548
}
549
550
private char toUpperCaseASCII(char c) {
551
return (c < 'a' || c > 'z') ? c : (char) (c + 'A' - 'a');
552
}
553
554
/**
555
* Compares two strings for equality, ignoring case. The second
556
* argument must contain only upper-case ASCII characters.
557
* We don't want case comparison to be locale-dependent (else we
558
* have the notorious "turkish i bug").
559
*/
560
private boolean equalsIgnoreCase(String s, String upper) {
561
assert upper.toUpperCase(java.util.Locale.ENGLISH).equals(upper);
562
int len;
563
if ((len = s.length()) != upper.length())
564
return false;
565
for (int i = 0; i < len; i++) {
566
char c1 = s.charAt(i);
567
char c2 = upper.charAt(i);
568
if (c1 != c2 && toUpperCaseASCII(c1) != c2)
569
return false;
570
}
571
return true;
572
}
573
574
/**
575
* Updates an existing jar file.
576
*/
577
boolean update(InputStream in, OutputStream out,
578
InputStream newManifest,
579
JarIndex jarIndex) throws IOException
580
{
581
ZipInputStream zis = new ZipInputStream(in);
582
ZipOutputStream zos = new JarOutputStream(out);
583
ZipEntry e = null;
584
boolean foundManifest = false;
585
boolean updateOk = true;
586
587
if (jarIndex != null) {
588
addIndex(jarIndex, zos);
589
}
590
591
// put the old entries first, replace if necessary
592
while ((e = zis.getNextEntry()) != null) {
593
String name = e.getName();
594
595
boolean isManifestEntry = equalsIgnoreCase(name, MANIFEST_NAME);
596
597
if ((jarIndex != null && equalsIgnoreCase(name, INDEX_NAME))
598
|| (Mflag && isManifestEntry)) {
599
continue;
600
} else if (isManifestEntry && ((newManifest != null) ||
601
(ename != null))) {
602
foundManifest = true;
603
if (newManifest != null) {
604
// Don't read from the newManifest InputStream, as we
605
// might need it below, and we can't re-read the same data
606
// twice.
607
FileInputStream fis = new FileInputStream(mname);
608
boolean ambiguous = isAmbiguousMainClass(new Manifest(fis));
609
fis.close();
610
if (ambiguous) {
611
return false;
612
}
613
}
614
615
// Update the manifest.
616
Manifest old = new Manifest(zis);
617
if (newManifest != null) {
618
old.read(newManifest);
619
}
620
if (!updateManifest(old, zos)) {
621
return false;
622
}
623
} else {
624
if (!entryMap.containsKey(name)) { // copy the old stuff
625
// do our own compression
626
ZipEntry e2 = new ZipEntry(name);
627
e2.setMethod(e.getMethod());
628
e2.setTime(e.getTime());
629
e2.setComment(e.getComment());
630
e2.setExtra(e.getExtra());
631
if (e.getMethod() == ZipEntry.STORED) {
632
e2.setSize(e.getSize());
633
e2.setCrc(e.getCrc());
634
}
635
zos.putNextEntry(e2);
636
copy(zis, zos);
637
} else { // replace with the new files
638
File f = entryMap.get(name);
639
addFile(zos, f);
640
entryMap.remove(name);
641
entries.remove(f);
642
}
643
}
644
}
645
646
// add the remaining new files
647
for (File f: entries) {
648
addFile(zos, f);
649
}
650
if (!foundManifest) {
651
if (newManifest != null) {
652
Manifest m = new Manifest(newManifest);
653
updateOk = !isAmbiguousMainClass(m);
654
if (updateOk) {
655
if (!updateManifest(m, zos)) {
656
updateOk = false;
657
}
658
}
659
} else if (ename != null) {
660
if (!updateManifest(new Manifest(), zos)) {
661
updateOk = false;
662
}
663
}
664
}
665
zis.close();
666
zos.close();
667
return updateOk;
668
}
669
670
private void addIndex(JarIndex index, ZipOutputStream zos)
671
throws IOException
672
{
673
ZipEntry e = new ZipEntry(INDEX_NAME);
674
e.setTime(System.currentTimeMillis());
675
if (flag0) {
676
CRC32OutputStream os = new CRC32OutputStream();
677
index.write(os);
678
os.updateEntry(e);
679
}
680
zos.putNextEntry(e);
681
index.write(zos);
682
zos.closeEntry();
683
}
684
685
private boolean updateManifest(Manifest m, ZipOutputStream zos)
686
throws IOException
687
{
688
addVersion(m);
689
addCreatedBy(m);
690
if (ename != null) {
691
addMainClass(m, ename);
692
}
693
ZipEntry e = new ZipEntry(MANIFEST_NAME);
694
e.setTime(System.currentTimeMillis());
695
if (flag0) {
696
crc32Manifest(e, m);
697
}
698
zos.putNextEntry(e);
699
m.write(zos);
700
if (vflag) {
701
output(getMsg("out.update.manifest"));
702
}
703
return true;
704
}
705
706
private static final boolean isWinDriveLetter(char c) {
707
return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'));
708
}
709
710
private String safeName(String name) {
711
if (!pflag) {
712
int len = name.length();
713
int i = name.lastIndexOf("../");
714
if (i == -1) {
715
i = 0;
716
} else {
717
i += 3; // strip any dot-dot components
718
}
719
if (File.separatorChar == '\\') {
720
// the spec requests no drive letter. skip if
721
// the entry name has one.
722
while (i < len) {
723
int off = i;
724
if (i + 1 < len &&
725
name.charAt(i + 1) == ':' &&
726
isWinDriveLetter(name.charAt(i))) {
727
i += 2;
728
}
729
while (i < len && name.charAt(i) == '/') {
730
i++;
731
}
732
if (i == off) {
733
break;
734
}
735
}
736
} else {
737
while (i < len && name.charAt(i) == '/') {
738
i++;
739
}
740
}
741
if (i != 0) {
742
name = name.substring(i);
743
}
744
}
745
return name;
746
}
747
748
private String entryName(String name) {
749
name = name.replace(File.separatorChar, '/');
750
String matchPath = "";
751
for (String path : paths) {
752
if (name.startsWith(path)
753
&& (path.length() > matchPath.length())) {
754
matchPath = path;
755
}
756
}
757
name = name.substring(matchPath.length());
758
name = safeName(name);
759
// the old implementaton doesn't remove
760
// "./" if it was led by "/" (?)
761
if (name.startsWith("./")) {
762
name = name.substring(2);
763
}
764
return name;
765
}
766
767
private void addVersion(Manifest m) {
768
Attributes global = m.getMainAttributes();
769
if (global.getValue(Attributes.Name.MANIFEST_VERSION) == null) {
770
global.put(Attributes.Name.MANIFEST_VERSION, VERSION);
771
}
772
}
773
774
private void addCreatedBy(Manifest m) {
775
Attributes global = m.getMainAttributes();
776
if (global.getValue(new Attributes.Name("Created-By")) == null) {
777
String javaVendor = System.getProperty("java.vendor");
778
String jdkVersion = System.getProperty("java.version");
779
global.put(new Attributes.Name("Created-By"), jdkVersion + " (" +
780
javaVendor + ")");
781
}
782
}
783
784
private void addMainClass(Manifest m, String mainApp) {
785
Attributes global = m.getMainAttributes();
786
787
// overrides any existing Main-Class attribute
788
global.put(Attributes.Name.MAIN_CLASS, mainApp);
789
}
790
791
private boolean isAmbiguousMainClass(Manifest m) {
792
if (ename != null) {
793
Attributes global = m.getMainAttributes();
794
if ((global.get(Attributes.Name.MAIN_CLASS) != null)) {
795
error(getMsg("error.bad.eflag"));
796
usageError();
797
return true;
798
}
799
}
800
return false;
801
}
802
803
/**
804
* Adds a new file entry to the ZIP output stream.
805
*/
806
void addFile(ZipOutputStream zos, File file) throws IOException {
807
String name = file.getPath();
808
boolean isDir = file.isDirectory();
809
if (isDir) {
810
name = name.endsWith(File.separator) ? name :
811
(name + File.separator);
812
}
813
name = entryName(name);
814
815
if (name.equals("") || name.equals(".") || name.equals(zname)) {
816
return;
817
} else if ((name.equals(MANIFEST_DIR) || name.equals(MANIFEST_NAME))
818
&& !Mflag) {
819
if (vflag) {
820
output(formatMsg("out.ignore.entry", name));
821
}
822
return;
823
}
824
825
long size = isDir ? 0 : file.length();
826
827
if (vflag) {
828
out.print(formatMsg("out.adding", name));
829
}
830
ZipEntry e = new ZipEntry(name);
831
e.setTime(file.lastModified());
832
if (size == 0) {
833
e.setMethod(ZipEntry.STORED);
834
e.setSize(0);
835
e.setCrc(0);
836
} else if (flag0) {
837
crc32File(e, file);
838
}
839
zos.putNextEntry(e);
840
if (!isDir) {
841
copy(file, zos);
842
}
843
zos.closeEntry();
844
/* report how much compression occurred. */
845
if (vflag) {
846
size = e.getSize();
847
long csize = e.getCompressedSize();
848
out.print(formatMsg2("out.size", String.valueOf(size),
849
String.valueOf(csize)));
850
if (e.getMethod() == ZipEntry.DEFLATED) {
851
long ratio = 0;
852
if (size != 0) {
853
ratio = ((size - csize) * 100) / size;
854
}
855
output(formatMsg("out.deflated", String.valueOf(ratio)));
856
} else {
857
output(getMsg("out.stored"));
858
}
859
}
860
}
861
862
/**
863
* A buffer for use only by copy(InputStream, OutputStream).
864
* Not as clean as allocating a new buffer as needed by copy,
865
* but significantly more efficient.
866
*/
867
private byte[] copyBuf = new byte[8192];
868
869
/**
870
* Copies all bytes from the input stream to the output stream.
871
* Does not close or flush either stream.
872
*
873
* @param from the input stream to read from
874
* @param to the output stream to write to
875
* @throws IOException if an I/O error occurs
876
*/
877
private void copy(InputStream from, OutputStream to) throws IOException {
878
int n;
879
while ((n = from.read(copyBuf)) != -1)
880
to.write(copyBuf, 0, n);
881
}
882
883
/**
884
* Copies all bytes from the input file to the output stream.
885
* Does not close or flush the output stream.
886
*
887
* @param from the input file to read from
888
* @param to the output stream to write to
889
* @throws IOException if an I/O error occurs
890
*/
891
private void copy(File from, OutputStream to) throws IOException {
892
InputStream in = new FileInputStream(from);
893
try {
894
copy(in, to);
895
} finally {
896
in.close();
897
}
898
}
899
900
/**
901
* Copies all bytes from the input stream to the output file.
902
* Does not close the input stream.
903
*
904
* @param from the input stream to read from
905
* @param to the output file to write to
906
* @throws IOException if an I/O error occurs
907
*/
908
private void copy(InputStream from, File to) throws IOException {
909
OutputStream out = new FileOutputStream(to);
910
try {
911
copy(from, out);
912
} finally {
913
out.close();
914
}
915
}
916
917
/**
918
* Computes the crc32 of a Manifest. This is necessary when the
919
* ZipOutputStream is in STORED mode.
920
*/
921
private void crc32Manifest(ZipEntry e, Manifest m) throws IOException {
922
CRC32OutputStream os = new CRC32OutputStream();
923
m.write(os);
924
os.updateEntry(e);
925
}
926
927
/**
928
* Computes the crc32 of a File. This is necessary when the
929
* ZipOutputStream is in STORED mode.
930
*/
931
private void crc32File(ZipEntry e, File f) throws IOException {
932
CRC32OutputStream os = new CRC32OutputStream();
933
copy(f, os);
934
if (os.n != f.length()) {
935
throw new JarException(formatMsg(
936
"error.incorrect.length", f.getPath()));
937
}
938
os.updateEntry(e);
939
}
940
941
void replaceFSC(String files[]) {
942
if (files != null) {
943
for (int i = 0; i < files.length; i++) {
944
files[i] = files[i].replace(File.separatorChar, '/');
945
}
946
}
947
}
948
949
@SuppressWarnings("serial")
950
Set<ZipEntry> newDirSet() {
951
return new HashSet<ZipEntry>() {
952
public boolean add(ZipEntry e) {
953
return ((e == null || useExtractionTime) ? false : super.add(e));
954
}};
955
}
956
957
void updateLastModifiedTime(Set<ZipEntry> zes) throws IOException {
958
for (ZipEntry ze : zes) {
959
long lastModified = ze.getTime();
960
if (lastModified != -1) {
961
String name = safeName(ze.getName().replace(File.separatorChar, '/'));
962
if (name.length() != 0) {
963
File f = new File(name.replace('/', File.separatorChar));
964
f.setLastModified(lastModified);
965
}
966
}
967
}
968
}
969
970
/**
971
* Extracts specified entries from JAR file.
972
*/
973
void extract(InputStream in, String files[]) throws IOException {
974
ZipInputStream zis = new ZipInputStream(in);
975
ZipEntry e;
976
// Set of all directory entries specified in archive. Disallows
977
// null entries. Disallows all entries if using pre-6.0 behavior.
978
Set<ZipEntry> dirs = newDirSet();
979
while ((e = zis.getNextEntry()) != null) {
980
if (files == null) {
981
dirs.add(extractFile(zis, e));
982
} else {
983
String name = e.getName();
984
for (String file : files) {
985
if (name.startsWith(file)) {
986
dirs.add(extractFile(zis, e));
987
break;
988
}
989
}
990
}
991
}
992
993
// Update timestamps of directories specified in archive with their
994
// timestamps as given in the archive. We do this after extraction,
995
// instead of during, because creating a file in a directory changes
996
// that directory's timestamp.
997
updateLastModifiedTime(dirs);
998
}
999
1000
/**
1001
* Extracts specified entries from JAR file, via ZipFile.
1002
*/
1003
void extract(String fname, String files[]) throws IOException {
1004
ZipFile zf = new ZipFile(fname);
1005
Set<ZipEntry> dirs = newDirSet();
1006
Enumeration<? extends ZipEntry> zes = zf.entries();
1007
while (zes.hasMoreElements()) {
1008
ZipEntry e = zes.nextElement();
1009
if (files == null) {
1010
dirs.add(extractFile(zf.getInputStream(e), e));
1011
} else {
1012
String name = e.getName();
1013
for (String file : files) {
1014
if (name.startsWith(file)) {
1015
dirs.add(extractFile(zf.getInputStream(e), e));
1016
break;
1017
}
1018
}
1019
}
1020
}
1021
zf.close();
1022
updateLastModifiedTime(dirs);
1023
}
1024
1025
/**
1026
* Extracts next entry from JAR file, creating directories as needed. If
1027
* the entry is for a directory which doesn't exist prior to this
1028
* invocation, returns that entry, otherwise returns null.
1029
*/
1030
ZipEntry extractFile(InputStream is, ZipEntry e) throws IOException {
1031
ZipEntry rc = null;
1032
// The spec requres all slashes MUST be forward '/', it is possible
1033
// an offending zip/jar entry may uses the backwards slash in its
1034
// name. It might cause problem on Windows platform as it skips
1035
// our "safe" check for leading slahs and dot-dot. So replace them
1036
// with '/'.
1037
String name = safeName(e.getName().replace(File.separatorChar, '/'));
1038
if (name.length() == 0) {
1039
return rc; // leading '/' or 'dot-dot' only path
1040
}
1041
File f = new File(name.replace('/', File.separatorChar));
1042
if (e.isDirectory()) {
1043
if (f.exists()) {
1044
if (!f.isDirectory()) {
1045
throw new IOException(formatMsg("error.create.dir",
1046
f.getPath()));
1047
}
1048
} else {
1049
if (!f.mkdirs()) {
1050
throw new IOException(formatMsg("error.create.dir",
1051
f.getPath()));
1052
} else {
1053
rc = e;
1054
}
1055
}
1056
1057
if (vflag) {
1058
output(formatMsg("out.create", name));
1059
}
1060
} else {
1061
if (f.getParent() != null) {
1062
File d = new File(f.getParent());
1063
if (!d.exists() && !d.mkdirs() || !d.isDirectory()) {
1064
throw new IOException(formatMsg(
1065
"error.create.dir", d.getPath()));
1066
}
1067
}
1068
try {
1069
copy(is, f);
1070
} finally {
1071
if (is instanceof ZipInputStream)
1072
((ZipInputStream)is).closeEntry();
1073
else
1074
is.close();
1075
}
1076
if (vflag) {
1077
if (e.getMethod() == ZipEntry.DEFLATED) {
1078
output(formatMsg("out.inflated", name));
1079
} else {
1080
output(formatMsg("out.extracted", name));
1081
}
1082
}
1083
}
1084
if (!useExtractionTime) {
1085
long lastModified = e.getTime();
1086
if (lastModified != -1) {
1087
f.setLastModified(lastModified);
1088
}
1089
}
1090
return rc;
1091
}
1092
1093
/**
1094
* Lists contents of JAR file.
1095
*/
1096
void list(InputStream in, String files[]) throws IOException {
1097
ZipInputStream zis = new ZipInputStream(in);
1098
ZipEntry e;
1099
while ((e = zis.getNextEntry()) != null) {
1100
/*
1101
* In the case of a compressed (deflated) entry, the entry size
1102
* is stored immediately following the entry data and cannot be
1103
* determined until the entry is fully read. Therefore, we close
1104
* the entry first before printing out its attributes.
1105
*/
1106
zis.closeEntry();
1107
printEntry(e, files);
1108
}
1109
}
1110
1111
/**
1112
* Lists contents of JAR file, via ZipFile.
1113
*/
1114
void list(String fname, String files[]) throws IOException {
1115
ZipFile zf = new ZipFile(fname);
1116
Enumeration<? extends ZipEntry> zes = zf.entries();
1117
while (zes.hasMoreElements()) {
1118
printEntry(zes.nextElement(), files);
1119
}
1120
zf.close();
1121
}
1122
1123
/**
1124
* Outputs the class index table to the INDEX.LIST file of the
1125
* root jar file.
1126
*/
1127
void dumpIndex(String rootjar, JarIndex index) throws IOException {
1128
File jarFile = new File(rootjar);
1129
Path jarPath = jarFile.toPath();
1130
Path tmpPath = createTempFileInSameDirectoryAs(jarFile).toPath();
1131
try {
1132
if (update(Files.newInputStream(jarPath),
1133
Files.newOutputStream(tmpPath),
1134
null, index)) {
1135
try {
1136
Files.move(tmpPath, jarPath, REPLACE_EXISTING);
1137
} catch (IOException e) {
1138
throw new IOException(getMsg("error.write.file"), e);
1139
}
1140
}
1141
} finally {
1142
Files.deleteIfExists(tmpPath);
1143
}
1144
}
1145
1146
private HashSet<String> jarPaths = new HashSet<String>();
1147
1148
/**
1149
* Generates the transitive closure of the Class-Path attribute for
1150
* the specified jar file.
1151
*/
1152
List<String> getJarPath(String jar) throws IOException {
1153
List<String> files = new ArrayList<String>();
1154
files.add(jar);
1155
jarPaths.add(jar);
1156
1157
// take out the current path
1158
String path = jar.substring(0, Math.max(0, jar.lastIndexOf('/') + 1));
1159
1160
// class path attribute will give us jar file name with
1161
// '/' as separators, so we need to change them to the
1162
// appropriate one before we open the jar file.
1163
JarFile rf = new JarFile(jar.replace('/', File.separatorChar));
1164
1165
if (rf != null) {
1166
Manifest man = rf.getManifest();
1167
if (man != null) {
1168
Attributes attr = man.getMainAttributes();
1169
if (attr != null) {
1170
String value = attr.getValue(Attributes.Name.CLASS_PATH);
1171
if (value != null) {
1172
StringTokenizer st = new StringTokenizer(value);
1173
while (st.hasMoreTokens()) {
1174
String ajar = st.nextToken();
1175
if (!ajar.endsWith("/")) { // it is a jar file
1176
ajar = path.concat(ajar);
1177
/* check on cyclic dependency */
1178
if (! jarPaths.contains(ajar)) {
1179
files.addAll(getJarPath(ajar));
1180
}
1181
}
1182
}
1183
}
1184
}
1185
}
1186
}
1187
rf.close();
1188
return files;
1189
}
1190
1191
/**
1192
* Generates class index file for the specified root jar file.
1193
*/
1194
void genIndex(String rootjar, String[] files) throws IOException {
1195
List<String> jars = getJarPath(rootjar);
1196
int njars = jars.size();
1197
String[] jarfiles;
1198
1199
if (njars == 1 && files != null) {
1200
// no class-path attribute defined in rootjar, will
1201
// use command line specified list of jars
1202
for (int i = 0; i < files.length; i++) {
1203
jars.addAll(getJarPath(files[i]));
1204
}
1205
njars = jars.size();
1206
}
1207
jarfiles = jars.toArray(new String[njars]);
1208
JarIndex index = new JarIndex(jarfiles);
1209
dumpIndex(rootjar, index);
1210
}
1211
1212
/**
1213
* Prints entry information, if requested.
1214
*/
1215
void printEntry(ZipEntry e, String[] files) throws IOException {
1216
if (files == null) {
1217
printEntry(e);
1218
} else {
1219
String name = e.getName();
1220
for (String file : files) {
1221
if (name.startsWith(file)) {
1222
printEntry(e);
1223
return;
1224
}
1225
}
1226
}
1227
}
1228
1229
/**
1230
* Prints entry information.
1231
*/
1232
void printEntry(ZipEntry e) throws IOException {
1233
if (vflag) {
1234
StringBuilder sb = new StringBuilder();
1235
String s = Long.toString(e.getSize());
1236
for (int i = 6 - s.length(); i > 0; --i) {
1237
sb.append(' ');
1238
}
1239
sb.append(s).append(' ').append(new Date(e.getTime()).toString());
1240
sb.append(' ').append(e.getName());
1241
output(sb.toString());
1242
} else {
1243
output(e.getName());
1244
}
1245
}
1246
1247
/**
1248
* Prints usage message.
1249
*/
1250
void usageError() {
1251
error(getMsg("usage"));
1252
}
1253
1254
/**
1255
* A fatal exception has been caught. No recovery possible
1256
*/
1257
void fatalError(Exception e) {
1258
e.printStackTrace();
1259
}
1260
1261
/**
1262
* A fatal condition has been detected; message is "s".
1263
* No recovery possible
1264
*/
1265
void fatalError(String s) {
1266
error(program + ": " + s);
1267
}
1268
1269
/**
1270
* Print an output message; like verbose output and the like
1271
*/
1272
protected void output(String s) {
1273
out.println(s);
1274
}
1275
1276
/**
1277
* Print an error message; like something is broken
1278
*/
1279
protected void error(String s) {
1280
err.println(s);
1281
}
1282
1283
/**
1284
* Main routine to start program.
1285
*/
1286
public static void main(String args[]) {
1287
Main jartool = new Main(System.out, System.err, "jar");
1288
System.exit(jartool.run(args) ? 0 : 1);
1289
}
1290
1291
/**
1292
* An OutputStream that doesn't send its output anywhere, (but could).
1293
* It's here to find the CRC32 of an input file, necessary for STORED
1294
* mode in ZIP.
1295
*/
1296
private static class CRC32OutputStream extends java.io.OutputStream {
1297
final CRC32 crc = new CRC32();
1298
long n = 0;
1299
1300
CRC32OutputStream() {}
1301
1302
public void write(int r) throws IOException {
1303
crc.update(r);
1304
n++;
1305
}
1306
1307
public void write(byte[] b, int off, int len) throws IOException {
1308
crc.update(b, off, len);
1309
n += len;
1310
}
1311
1312
/**
1313
* Updates a ZipEntry which describes the data read by this
1314
* output stream, in STORED mode.
1315
*/
1316
public void updateEntry(ZipEntry e) {
1317
e.setMethod(ZipEntry.STORED);
1318
e.setSize(n);
1319
e.setCrc(crc.getValue());
1320
}
1321
}
1322
1323
/**
1324
* Attempt to create temporary file in the system-provided temporary folder, if failed attempts
1325
* to create it in the same folder as the file in parameter (if any)
1326
*/
1327
private File createTemporaryFile(String tmpbase, String suffix) {
1328
File tmpfile = null;
1329
1330
try {
1331
tmpfile = File.createTempFile(tmpbase, suffix);
1332
} catch (IOException | SecurityException e) {
1333
// Unable to create file due to permission violation or security exception
1334
}
1335
if (tmpfile == null) {
1336
// Were unable to create temporary file, fall back to temporary file in the same folder
1337
if (fname != null) {
1338
try {
1339
File tmpfolder = new File(fname).getAbsoluteFile().getParentFile();
1340
tmpfile = File.createTempFile(fname, ".tmp" + suffix, tmpfolder);
1341
} catch (IOException ioe) {
1342
// Last option failed - fall gracefully
1343
fatalError(ioe);
1344
}
1345
} else {
1346
// No options left - we can not compress to stdout without access to the temporary folder
1347
fatalError(new IOException(getMsg("error.create.tempfile")));
1348
}
1349
}
1350
return tmpfile;
1351
}
1352
}
1353
1354