proguard.io.JarWriter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of proguard-core Show documentation
Show all versions of proguard-core Show documentation
ProGuardCORE is a free library to read, analyze, modify, and write Java class files.
/*
* ProGuardCORE -- library to process Java bytecode.
*
* Copyright (c) 2002-2020 Guardsquare NV
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package proguard.io;
import proguard.util.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
/**
* This {@link DataEntryWriter} sends data entries to another given data entry writer,
* automatically adding a manifest file.
*
* You'll typically wrap a {@link ZipWriter} or one of its extensions:
*
* new JarWriter(new ZipWriter(...))
*
*
* @author Eric Lafortune
*/
public class JarWriter implements DataEntryWriter
{
public static final String DEFAULT_DIGEST_ALGORITHM = "SHA-256";
private static final String FORCED_DIGEST_ALGORITHM = System.getProperty("digest.algorithm");
private static final String MANIFEST_MF = "META-INF/MANIFEST.MF";
private static final StringMatcher EXCLUDED_FILE_NAME_MATCHER =
new ListParser(new FileNameParser()).parse("META-INF/MANIFEST.MF,META-INF/*.SF,META-INF/*.DSA,META-INF/*.RSA,META-INF/SIG-*");
protected final String[] digestAlgorithms;
protected final String creator;
private final String manifestFileName;
private final StringFunction manifestEntryNameFunction;
protected final DataEntryWriter zipEntryWriter;
protected final DataEntryWriter manifestEntryWriter;
protected DataEntry currentManifestEntry;
private PrintWriter manifestWriter;
/**
* Creates a new JarWriter wth default manifest digest "SHA-256" and
* manifest file name "META-INF/MANIFEST.MF".
* @param zipEntryWriter the data entry writer that can provide output
* streams for the jar entries.
*/
public JarWriter(DataEntryWriter zipEntryWriter)
{
this(new String[] { DEFAULT_DIGEST_ALGORITHM },
zipEntryWriter);
}
/**
* Creates a new JarWriter wth default manifest file name
* "META-INF/MANIFEST.MF".
* @param digestAlgorithms the manifest digest algorithms, e.g. "SHA-256".
* @param zipEntryWriter the data entry writer that can provide output
* streams for the jar entries.
*/
public JarWriter(String[] digestAlgorithms,
DataEntryWriter zipEntryWriter)
{
this(digestAlgorithms,
null,
zipEntryWriter);
}
/**
* Creates a new JarWriter wth default manifest file name
* "META-INF/MANIFEST.MF".
* @param digestAlgorithms the manifest digest algorithms, e.g. "SHA-256".
* @param creator the creator to mention in the manifest file.
* @param zipEntryWriter the data entry writer that can provide output
* streams for the jar entries.
*/
public JarWriter(String[] digestAlgorithms,
String creator,
DataEntryWriter zipEntryWriter)
{
this(digestAlgorithms,
creator,
MANIFEST_MF,
StringFunction.IDENTITY_FUNCTION,
zipEntryWriter,
zipEntryWriter);
}
/**
* Creates a new JarWriter.
* @param digestAlgorithms the manifest digest algorithms, e.g.
* "SHA-256".
* @param creator the creator to mention in the manifest
* file.
* @param manifestFileName the manifest file name, e.g.
* "META-INF/MANIFEST.MF".
* @param manifestEntryNameFunction the function to transform entry
* names in the manifest (not in the jar).
* @param zipEntryWriter the data entry writer that can provide
* output streams for the jar entries.
* @param manifestEntryWriter the data entry writer that can provide
* an output stream for the manifest file.
*/
public JarWriter(String[] digestAlgorithms,
String creator,
String manifestFileName,
StringFunction manifestEntryNameFunction,
DataEntryWriter zipEntryWriter,
DataEntryWriter manifestEntryWriter)
{
this.digestAlgorithms = FORCED_DIGEST_ALGORITHM != null ?
new String[] { FORCED_DIGEST_ALGORITHM } :
digestAlgorithms;
this.creator = creator;
this.manifestFileName = manifestFileName;
this.manifestEntryNameFunction = manifestEntryNameFunction;
this.zipEntryWriter = zipEntryWriter;
this.manifestEntryWriter = manifestEntryWriter;
}
// Implementations for DataEntryWriter.
@Override
public boolean createDirectory(DataEntry dataEntry) throws IOException
{
return zipEntryWriter.createDirectory(dataEntry);
}
@Override
public boolean sameOutputStream(DataEntry dataEntry1, DataEntry dataEntry2)
throws IOException
{
return zipEntryWriter.sameOutputStream(dataEntry1, dataEntry2);
}
@Override
public OutputStream createOutputStream(DataEntry dataEntry) throws IOException
{
String dataEntryName = dataEntry.getName();
finishIfNecessary(dataEntry);
// Don't sign or even include the signature-related files.
if (EXCLUDED_FILE_NAME_MATCHER.matches(dataEntryName))
{
return null;
}
setUp(dataEntry);
// Get the output stream, with prepared manifest and digest entries.
OutputStream outputStream = zipEntryWriter.createOutputStream(dataEntry);
// Digest the output stream (if any), except for the manifest itself
// (when the manifest writer isn't open yet). We need to create new
// digest instances, because multiple files can be open at the same
// time.
return
outputStream == null ||
manifestWriter == null ? outputStream :
new MyMultiDigestOutputStream(
manifestEntryNameFunction.transform(dataEntryName),
createMessageDigests(digestAlgorithms),
manifestWriter,
outputStream);
}
@Override
public void close() throws IOException
{
finish();
zipEntryWriter.close();
//manifestEntryWriter.close();
}
@Override
public void println(PrintWriter pw, String prefix)
{
pw.println(prefix + "JarWriter");
zipEntryWriter.println(pw, prefix + " ");
}
/**
* Sets up streams and writers for capturing digests and signatures for
* a given parent entry.
*/
private void setUp(DataEntry dataEntry) throws IOException
{
if (currentManifestEntry == null)
{
currentManifestEntry =
new RenamedDataEntry(dataEntry, manifestFileName);
openManifestFiles();
}
}
/**
* Prepares streams and writers for capturing digests of a parent entry.
*/
protected void openManifestFiles() throws IOException
{
// Create the manifest entry, so it comes first in the central
// directory of the zip. Its local file header and actual contents
// will come last, since it will be closed last.
OutputStream manifestOutputStream =
createManifestOutputStream(currentManifestEntry);
if (manifestOutputStream != null)
{
// Write out a main section for the manifest file.
manifestWriter = printWriter(manifestOutputStream);
manifestWriter.println("Manifest-Version: 1.0");
if (creator != null)
{
manifestWriter.println("Created-By: " + creator);
}
manifestWriter.println();
manifestWriter.flush();
}
}
/**
* Creates an output stream for the specified manifest file.
*/
protected OutputStream createManifestOutputStream(DataEntry manifestEntry)
throws IOException
{
return manifestEntryWriter.createOutputStream(manifestEntry);
}
/**
* Writes out the collected manifest file for the current jar, if we're
* entering a new jar with this data entry.
*/
protected void finishIfNecessary(DataEntry dataEntry) throws IOException
{
// Would the new manifest entry end up in a different jar?
if (currentManifestEntry != null &&
!zipEntryWriter.sameOutputStream(currentManifestEntry, new RenamedDataEntry(dataEntry, manifestFileName)))
{
finish();
}
}
/**
* Writes out the collected manifest file before closing the jar, if any.
*/
protected void finish() throws IOException
{
if (manifestWriter != null)
{
manifestWriter.close();
currentManifestEntry = null;
manifestWriter = null;
}
}
// Small utility methods.
/**
* Creates a convenience writer.
* @param outputStream the underlying output stream.
*/
protected PrintWriter printWriter(OutputStream outputStream)
throws IOException
{
return new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));
}
/**
* Creates message digests for the specified algorithms.
*/
private MessageDigest[] createMessageDigests(String[] digestAlgorithms)
{
try
{
MessageDigest[] manifestDigests =
new MessageDigest[digestAlgorithms.length];
for (int index = 0; index < digestAlgorithms.length; index++)
{
manifestDigests[index] =
MessageDigest.getInstance(digestAlgorithms[index]);
}
return manifestDigests;
}
catch (Exception e)
{
throw new UnsupportedOperationException(e.getMessage(), e);
}
}
// Helper classes.
/**
* This FilterOutputStream automatically appends a file digest entry
* to a given manifest writer, when the stream is closed.
*/
protected static class MyMultiDigestOutputStream
extends FilterOutputStream
{
private final String entryName;
private final MessageDigest[] manifestDigests;
private final PrintWriter manifestWriter;
public MyMultiDigestOutputStream(String entryName,
MessageDigest[] manifestDigests,
PrintWriter manifestWriter,
OutputStream outputStream)
{
super(outputStream);
this.entryName = entryName;
this.manifestDigests = manifestDigests;
this.manifestWriter = manifestWriter;
}
// Overridden methods for OutputStream.
@Override
public void write(int b) throws IOException
{
out.write(b);
// Update the digests.
for (int index = 0; index < manifestDigests.length; index++)
{
manifestDigests[index].update((byte) b);
}
}
@Override
public void write(byte[] bytes, int off, int len) throws IOException
{
out.write(bytes, off, len);
// Update the digests.
for (int index = 0; index < manifestDigests.length; index++)
{
manifestDigests[index].update(bytes, off, len);
}
}
@Override
public void close() throws IOException
{
super.close();
// Write out the digests.
appendDigests(entryName);
}
/**
* Appends the collected digests and signatures to the proper writer.
*/
private void appendDigests(String entryName)
{
manifestWriter.println("Name: " + entryName);
for (int index = 0; index < manifestDigests.length; index++)
{
MessageDigest manifestDigest = manifestDigests[index];
// Digest the written data entry for the manifest file.
manifestWriter.println(manifestDigest.getAlgorithm() + "-Digest: " +
Base64Util.encode(manifestDigest.digest()));
}
manifestWriter.println();
manifestWriter.flush();
}
}
/**
* Provides a simple test for this class, creating a signed apk file (only
* v1) with the given name and a few aligned/compressed/uncompressed
* zip entries.
* Arguments:
* jar_filename
* Verify the jar with:
* jarsigner -verify -verbose /tmp/test.jar
*/
public static void main(String[] args)
{
try
{
// Get the arguments.
String jarFileName = args[0];
// Start writing out the jar file.
DataEntryWriter writer =
new JarWriter(
new ZipWriter(new FixedStringMatcher("file1.txt"), 4, 0,
new FixedFileWriter(new File(jarFileName))));
PrintWriter printWriter =
new PrintWriter(writer.createOutputStream(new DummyDataEntry(null, "file1.txt", 0L, false)));
printWriter.println("Hello, world!");
printWriter.close();
printWriter =
new PrintWriter(writer.createOutputStream(new DummyDataEntry(null, "file2.txt", 0L, false)));
printWriter.println("Hello again, world!");
printWriter.close();
writer.close();
}
catch (Exception e)
{
e.printStackTrace();
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy