Bug 1168407 - Pre: Add GeckoJarReader.extractStream. r=rnewman

The use case is to ship a classes.dex file in a Gecko add-on.  This
makes it easy to extract such a file from an add-on to a temporary
location.  (Sadly, the Android Dex loading classes expect files, not
streams.)
This commit is contained in:
Nick Alexander 2015-06-15 16:21:55 -07:00
parent 858076ed5f
commit c88c530673
2 changed files with 137 additions and 4 deletions

View File

@ -5,21 +5,23 @@
package org.mozilla.gecko.util;
import android.content.Context;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.mozglue.NativeZip;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.util.Log;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.mozglue.GeckoLoader;
import org.mozilla.gecko.mozglue.NativeZip;
import org.mozilla.gecko.mozglue.RobocopTarget;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Stack;
@ -107,6 +109,68 @@ public final class GeckoJarReader {
return new NativeZip(fileUrl.getPath());
}
@RobocopTarget
/**
* Extract a (possibly nested) file from an archive and write it to a temporary file.
*
* @param context Android context.
* @param url to open. Can include jar: to "reach into" nested archives.
* @param dir to write temporary file to.
* @return a <code>File</code>, if one could be written; otherwise null.
* @throws IOException if an error occured.
*/
public static File extractStream(Context context, String url, File dir, String suffix) throws IOException {
InputStream input = null;
try {
try {
final URI fileURI = new URI(url);
// We don't check the scheme because we want to catch bare files, not just file:// URIs.
// If we let bare files through, we'd try to open them as ZIP files later -- and crash in native code.
if (fileURI != null && fileURI.getPath() != null) {
final File inputFile = new File(fileURI.getPath());
if (inputFile != null && inputFile.exists()) {
input = new FileInputStream(inputFile);
}
}
} catch (URISyntaxException e) {
// Not a file:// URI.
}
if (input == null) {
// No luck with file:// URI; maybe some other URI?
input = getStream(context, url);
}
if (input == null) {
// Not found!
return null;
}
// n.b.: createTempFile does not in fact delete the file.
final File file = File.createTempFile("extractStream", suffix, dir);
OutputStream output = null;
try {
output = new FileOutputStream(file);
byte[] buf = new byte[8192];
int len;
while ((len = input.read(buf)) >= 0) {
output.write(buf, 0, len);
}
return file;
} finally {
if (output != null) {
output.close();
}
}
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
Log.w(LOGTAG, "Got exception closing stream; ignoring.", e);
}
}
}
}
@RobocopTarget
public static InputStream getStream(Context context, String url) {
Stack<String> jarUrls = parseUrl(url);

View File

@ -3,10 +3,14 @@
package org.mozilla.tests.browser.junit3;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Stack;
import android.test.InstrumentationTestCase;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.util.FileUtils;
import org.mozilla.gecko.util.GeckoJarReader;
import android.content.Context;
@ -46,4 +50,69 @@ public class TestJarReader extends InstrumentationTestCase {
stream = GeckoJarReader.getStream(context, "jar:" + url + "!/chrome/chrome/content/branding/favicon32.png");
assertNull(stream);
}
protected void assertExtractStream(String url) throws IOException {
final File file = GeckoJarReader.extractStream(getInstrumentation().getTargetContext(), url, getInstrumentation().getContext().getCacheDir(), ".test");
assertNotNull(file);
try {
assertTrue(file.getName().endsWith("temp"));
final String contents = FileUtils.getFileContents(file);
assertNotNull(contents);
assertTrue(contents.length() > 0);
} finally {
file.delete();
}
}
public void testExtractStream() throws IOException {
String appPath = getInstrumentation().getTargetContext().getPackageResourcePath();
assertNotNull(appPath);
// We don't have a lot of good files to choose from. package-name.txt isn't included in Gradle APKs.
assertExtractStream("jar:file://" + appPath + "!/resources.arsc");
final String url = GeckoJarReader.getJarURL(getInstrumentation().getTargetContext(), "chrome.manifest");
assertExtractStream(url);
// Now use an extracted copy of chrome.manifest to test further.
final File file = GeckoJarReader.extractStream(getInstrumentation().getTargetContext(), url, getInstrumentation().getContext().getCacheDir(), ".test");
assertNotNull(file);
try {
assertExtractStream("file://" + file.getAbsolutePath()); // file:// URI.
assertExtractStream(file.getAbsolutePath()); // Vanilla path.
} finally {
file.delete();
}
}
protected void assertExtractStreamFails(String url) throws IOException {
final File file = GeckoJarReader.extractStream(getInstrumentation().getTargetContext(), url, getInstrumentation().getContext().getCacheDir(), ".test");
assertNull(file);
}
public void testExtractStreamFailureCases() throws IOException {
String appPath = getInstrumentation().getTargetContext().getPackageResourcePath();
assertNotNull(appPath);
// First, a bad APK.
assertExtractStreamFails("jar:file://" + appPath + "BAD!/resources.arsc");
// Second, a bad file in the APK.
assertExtractStreamFails("jar:file://" + appPath + "!/BADresources.arsc");
// Now a bad file in the omnijar.
final String badUrl = GeckoJarReader.getJarURL(getInstrumentation().getTargetContext(), "BADchrome.manifest");
assertExtractStreamFails(badUrl);
// Now use an extracted copy of chrome.manifest to test further.
final String goodUrl = GeckoJarReader.getJarURL(getInstrumentation().getTargetContext(), "chrome.manifest");
final File file = GeckoJarReader.extractStream(getInstrumentation().getTargetContext(), goodUrl, getInstrumentation().getContext().getCacheDir(), ".test");
assertNotNull(file);
try {
assertExtractStreamFails("file://" + file.getAbsolutePath() + "BAD"); // Bad file:// URI.
assertExtractStreamFails(file.getAbsolutePath() + "BAD"); //Bad vanilla path.
} finally {
file.delete();
}
}
}