Bug 709250 - Add the ability to read nested jar files. r=mfinkle,lucasr
authorWes Johnston <wjohnston@mozilla.com>
Thu, 22 Mar 2012 11:11:49 -0700
changeset 93369 6bf86d4a94ea018218a3fbe5724bc2a067a66ea4
parent 93368 0bc32fccce4cfee3173dcc0e733ca30f3e9b9fa3
child 93370 7b54766189f3bf6ad26b62742b030a4e10a46dc3
push idunknown
push userunknown
push dateunknown
reviewersmfinkle, lucasr
bugs709250
milestone14.0a1
Bug 709250 - Add the ability to read nested jar files. r=mfinkle,lucasr
mobile/android/base/Favicons.java
mobile/android/base/GeckoJarReader.java
mobile/android/base/Makefile.in
--- a/mobile/android/base/Favicons.java
+++ b/mobile/android/base/Favicons.java
@@ -46,16 +46,17 @@ import android.database.sqlite.SQLiteOpe
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.AsyncTask;
 import android.util.Log;
 
 import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
+import java.io.InputStream;
 import java.io.IOException;
 import java.net.URLConnection;
 import java.net.HttpURLConnection;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -289,17 +290,27 @@ public class Favicons {
                 int offset = 0;
                 while ((pos = contentStream.read(bytes, offset, length - offset)) > 0)
                     offset += pos;
                 if (length == offset) {
                     byteStream = new ByteArrayInputStream(bytes);
                     image = (BitmapDrawable) Drawable.createFromStream(byteStream, "src");
                 }
             } catch (Exception e) {
-                Log.d(LOGTAG, "Error downloading favicon: " + e);
+                // Trying to read icons from nested jar files will fail
+                if (mFaviconUrl.startsWith("jar:jar:")) {
+                    InputStream stream = GeckoJarReader.getStream(mFaviconUrl);
+                    if (stream != null) {
+                        image = new BitmapDrawable(stream);
+                    } else {
+                        Log.d(LOGTAG, "Error getting favicon from jar: " + e);
+                    }
+                } else {
+                    Log.d(LOGTAG, "Error downloading favicon: " + e);
+                }
             } finally {
                 if (urlConnection != null && urlConnection instanceof HttpURLConnection) {
                     HttpURLConnection httpConnection = (HttpURLConnection) urlConnection;
                     httpConnection.disconnect();
                 }
 
                 try {
                     if (contentStream != null)
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/GeckoJarReader.java
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko;
+
+import java.io.File;
+import java.net.URL;
+import java.util.EmptyStackException;
+import java.util.Stack;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipEntry;
+import java.io.InputStream;
+import java.io.IOException;
+
+import android.util.Log;
+
+/* Reads out of a multiple level deep jar file such as
+ *  jar:jar:file:///data/app/org.mozilla.fennec.apk!/omni.ja!/chrome/chrome/content/branding/favicon32.png
+ */
+public class GeckoJarReader {
+    static private String LOGTAG = "GeckoJarReader";
+
+    static public InputStream getStream(String url) {
+        Stack<String> jarUrls = parseUrl(url);
+        ZipInputStream inputStream = null;
+
+        try {
+            // Load the initial jar file as a zip
+            URL fileUrl = new URL(jarUrls.pop());
+            File file = new File(fileUrl.getPath());
+            ZipFile zip = new ZipFile(file);
+            ZipEntry entry = null;
+
+            // loop through children jar files until we reach the innermost one
+            while (jarUrls.peek() != null) {
+                String fileName = jarUrls.pop();
+
+                if (inputStream != null) {
+                    entry = getEntryFromStream(inputStream, fileName);
+                } else {
+                    entry = zip.getEntry(fileName);
+                }
+
+                // if there is nothing else on the stack, this will throw and break us out of the loop
+                jarUrls.peek();
+
+                if (inputStream != null) {
+                    inputStream = new ZipInputStream(inputStream);
+                } else {
+                    inputStream = new ZipInputStream(zip.getInputStream(entry));
+                }
+  
+                if (entry == null) {
+                    Log.d(LOGTAG, "No Entry for " + fileName);
+                    return null;
+                }
+            }
+        } catch (EmptyStackException ex) {
+            Log.d(LOGTAG, "Reached Jar reader reached end of stack");
+        } catch (IOException ex) {
+            Log.e(LOGTAG, "Exception ", ex);
+        } catch (Exception ex) {
+            Log.e(LOGTAG, "Exception ", ex);
+        }
+
+        return inputStream;
+    }
+
+    /* Searches through a ZipInputStream for an entry with a given name */
+    static private ZipEntry getEntryFromStream(ZipInputStream zipStream, String entryName) {
+        ZipEntry entry = null;
+
+        try {
+            entry = zipStream.getNextEntry();
+            while(entry != null && !entry.getName().equals(entryName)) {
+                entry = zipStream.getNextEntry();
+            }
+        } catch (IOException ex) {
+            Log.e(LOGTAG, "Exception getting stream entry", ex);
+        }
+
+        return entry;
+    }
+
+    /* Returns a stack of strings breaking the url up into pieces. Each piece
+     * is assumed to point to a jar file except for the final one. Callers should
+     * pass in the url to parse, and null for the parent parameter (used for recursion)
+     * For example, jar:jar:file:///data/app/org.mozilla.fennec.apk!/omni.ja!/chrome/chrome/content/branding/favicon32.png
+     * will return:
+     *    file:///data/app/org.mozilla.fennec.apk
+     *    omni.ja
+     *    chrome/chrome/content/branding/favicon32.png
+     */
+    static private Stack<String> parseUrl(String url) {
+        return parseUrl(url, null);
+    }
+
+    static private Stack<String> parseUrl(String url, Stack<String> results) {
+        if (results == null) {
+            results = new Stack<String>();
+        }
+
+        if (url.startsWith("jar:")) {
+            int jarEnd = url.lastIndexOf("!");
+            String subStr = url.substring(4, jarEnd);
+            results.push(url.substring(jarEnd+2)); // remove the !/ characters
+            return parseUrl(subStr, results);
+        } else {
+            results.push(url);
+            return results;
+        }
+    }
+}
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -85,16 +85,17 @@ FENNEC_JAVA_FILES = \
   GeckoBatteryManager.java \
   GeckoBackgroundThread.java \
   GeckoConnectivityReceiver.java \
   GeckoEvent.java \
   GeckoEventListener.java \
   GeckoEventResponder.java \
   GeckoHalDefines.java \
   GeckoInputConnection.java \
+  GeckoJarReader.java \
 	GeckoMessageReceiver.java \
   GeckoPreferences.java \
   GeckoProfile.java \
   GeckoStateListDrawable.java \
   GeckoThread.java \
   GlobalHistory.java \
   LinkPreference.java \
   LinkTextView.java \