From 2e665e2771054fafdfb1e8d3c2b0ad80d61b7234 Mon Sep 17 00:00:00 2001
From: Andrew Gaul <andrew@gaul.org>
Date: Wed, 10 Apr 2013 21:47:16 -0700
Subject: [PATCH] Improve stream handling

Close streams on error paths, ensure flush, and use consistent buffer
size.  Also add and use stream utilities.
---
 src/org/fdroid/fdroid/Downloader.java     | 38 +++++++--------
 src/org/fdroid/fdroid/Hasher.java         |  9 ++--
 src/org/fdroid/fdroid/RepoXMLHandler.java | 59 ++++++++++-------------
 src/org/fdroid/fdroid/UpdateService.java  | 27 +++++------
 src/org/fdroid/fdroid/Utils.java          | 55 +++++++++++++++++++++
 5 files changed, 114 insertions(+), 74 deletions(-)
 create mode 100644 src/org/fdroid/fdroid/Utils.java

diff --git a/src/org/fdroid/fdroid/Downloader.java b/src/org/fdroid/fdroid/Downloader.java
index e378e4f74..88ce4f4d0 100644
--- a/src/org/fdroid/fdroid/Downloader.java
+++ b/src/org/fdroid/fdroid/Downloader.java
@@ -21,10 +21,10 @@
 package org.fdroid.fdroid;
 
 import android.util.Log;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.URL;
 
 public class Downloader extends Thread {
@@ -94,6 +94,8 @@ public class Downloader extends Thread {
 
     public void run() {
 
+        InputStream input = null;
+        OutputStream output = null;
         String apkname = curapk.apkName;
         localfile = new File(DB.getDataPath(), apkname);
         try {
@@ -127,30 +129,23 @@ public class Downloader extends Thread {
                 status = Status.RUNNING;
             }
 
-            BufferedInputStream getit = new BufferedInputStream(new URL(
-                    remotefile).openStream(), 8192);
-
-            FileOutputStream saveit = new FileOutputStream(localfile);
-            BufferedOutputStream bout = new BufferedOutputStream(saveit, 1024);
-            byte data[] = new byte[1024];
-
-            int totalRead = 0;
-            int bytesRead = getit.read(data, 0, 1024);
-            while (bytesRead != -1) {
+            input = new URL(remotefile).openStream();
+            output = new FileOutputStream(localfile);
+            byte data[] = new byte[Utils.BUFFER_SIZE];
+            while (true) {
                 if (isInterrupted()) {
                     Log.d("FDroid", "Download cancelled!");
                     break;
                 }
-                bout.write(data, 0, bytesRead);
-                totalRead += bytesRead;
-                synchronized (this) {
-                    progress = totalRead;
+                int count = input.read(data);
+                if (count == -1) {
+                    break;
+                }
+                output.write(data, 0, count);
+                synchronized (this) {
+                    progress += count;
                 }
-                bytesRead = getit.read(data, 0, 1024);
             }
-            bout.close();
-            getit.close();
-            saveit.close();
 
             if (isInterrupted()) {
                 localfile.delete();
@@ -182,6 +177,9 @@ public class Downloader extends Thread {
                 status = Status.ERROR;
                 return;
             }
+        } finally {
+            Utils.closeQuietly(output);
+            Utils.closeQuietly(input);
         }
 
         Log.d("FDroid", "Download finished: " + localfile);
diff --git a/src/org/fdroid/fdroid/Hasher.java b/src/org/fdroid/fdroid/Hasher.java
index 90ebd761e..750c115f7 100644
--- a/src/org/fdroid/fdroid/Hasher.java
+++ b/src/org/fdroid/fdroid/Hasher.java
@@ -62,15 +62,16 @@ public class Hasher {
         else if (file != null) {
             byte[] buffer = new byte[1024];
             int read = 0;
+            InputStream input = null;
             try {
-                InputStream is = new BufferedInputStream(
-                        new FileInputStream(file));
-                while ((read = is.read(buffer)) > 0) {
+                input = new BufferedInputStream(new FileInputStream(file));
+                while ((read = input.read(buffer)) > 0) {
                     digest.update(buffer, 0, read);
                 }
-                is.close();
             } catch (Exception e) {
                 return hashCache = "";
+            } finally {
+                Utils.closeQuietly(input);
             }
         } else {
             digest.update(array);
diff --git a/src/org/fdroid/fdroid/RepoXMLHandler.java b/src/org/fdroid/fdroid/RepoXMLHandler.java
index 4f2e60c13..f5d8b91bd 100644
--- a/src/org/fdroid/fdroid/RepoXMLHandler.java
+++ b/src/org/fdroid/fdroid/RepoXMLHandler.java
@@ -19,8 +19,6 @@
 
 package org.fdroid.fdroid;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileOutputStream;
@@ -265,23 +263,16 @@ public class RepoXMLHandler extends DefaultHandler {
         int totalBytes = 0;
         int code = uc.getResponseCode();
         if (code == 200) {
-
-            FileOutputStream f = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
-
-            BufferedInputStream getit = new BufferedInputStream(
-                    new URL(url).openStream());
-            BufferedOutputStream bout = new BufferedOutputStream(f, 1024);
-            byte data[] = new byte[1024];
-
-            int readed = getit.read(data, 0, 1024);
-            while (readed != -1) {
-                totalBytes += readed;
-                bout.write(data, 0, readed);
-                readed = getit.read(data, 0, 1024);
+            InputStream input = null;
+            OutputStream output = null;
+            try {
+                input = new URL(url).openStream();
+                output = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
+                Utils.copy(input, output);
+            } finally {
+                Utils.closeQuietly(output);
+                Utils.closeQuietly(input);
             }
-            bout.close();
-            getit.close();
-            f.close();
 
             String et = uc.getHeaderField("ETag");
             if (et != null)
@@ -322,33 +313,33 @@ public class RepoXMLHandler extends DefaultHandler {
                         repo.lastetag, newetag);
                 if (code == 200) {
                     String jarpath = ctx.getFilesDir() + "/tempindex.jar";
-                    JarFile jar;
+                    JarFile jar = null;
                     JarEntry je;
+                    Certificate[] certs;
                     try {
                         jar = new JarFile(jarpath, true);
                         je = (JarEntry) jar.getEntry("index.xml");
                         File efile = new File(ctx.getFilesDir(),
                                 "/tempindex.xml");
-                        InputStream in = new BufferedInputStream(
-                                jar.getInputStream(je), 8192);
-                        OutputStream out = new BufferedOutputStream(
-                                new FileOutputStream(efile), 8192);
-                        byte[] buffer = new byte[8192];
-                        while (true) {
-                            int nBytes = in.read(buffer);
-                            if (nBytes <= 0)
-                                break;
-                            out.write(buffer, 0, nBytes);
+                        InputStream input = null;
+                        OutputStream output = null;
+                        try {
+                            input = jar.getInputStream(je);
+                            output = new FileOutputStream(efile);
+                            Utils.copy(input, output);
+                        } finally {
+                            Utils.closeQuietly(output);
+                            Utils.closeQuietly(input);
                         }
-                        out.flush();
-                        out.close();
-                        in.close();
+                        certs = je.getCertificates();
                     } catch (SecurityException e) {
                         Log.e("FDroid", "Invalid hash for index file");
                         return "Invalid hash for index file";
+                    } finally {
+                        if (jar != null) {
+                            jar.close();
+                        }
                     }
-                    Certificate[] certs = je.getCertificates();
-                    jar.close();
                     if (certs == null) {
                         Log.d("FDroid", "No signature found in index");
                         return "No signature found in index";
diff --git a/src/org/fdroid/fdroid/UpdateService.java b/src/org/fdroid/fdroid/UpdateService.java
index 2962a5b39..2a3f498be 100644
--- a/src/org/fdroid/fdroid/UpdateService.java
+++ b/src/org/fdroid/fdroid/UpdateService.java
@@ -18,10 +18,10 @@
 
 package org.fdroid.fdroid;
 
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.util.Vector;
@@ -290,21 +290,16 @@ public class UpdateService extends IntentService {
             URL u = new URL(server + "/icons/" + app.icon);
             HttpURLConnection uc = (HttpURLConnection) u.openConnection();
             if (uc.getResponseCode() == 200) {
-                BufferedInputStream getit = new BufferedInputStream(
-                        uc.getInputStream());
-                FileOutputStream saveit = new FileOutputStream(f);
-                BufferedOutputStream bout = new BufferedOutputStream(saveit,
-                        1024);
-                byte data[] = new byte[1024];
-
-                int readed = getit.read(data, 0, 1024);
-                while (readed != -1) {
-                    bout.write(data, 0, readed);
-                    readed = getit.read(data, 0, 1024);
+                InputStream input = null;
+                OutputStream output = null;
+                try {
+                    input = uc.getInputStream();
+                    output = new FileOutputStream(f);
+                    Utils.copy(input, output);
+                } finally {
+                    Utils.closeQuietly(output);
+                    Utils.closeQuietly(input);
                 }
-                bout.close();
-                getit.close();
-                saveit.close();
             }
         } catch (Exception e) {
 
diff --git a/src/org/fdroid/fdroid/Utils.java b/src/org/fdroid/fdroid/Utils.java
new file mode 100644
index 000000000..647395004
--- /dev/null
+++ b/src/org/fdroid/fdroid/Utils.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2010-12  Ciaran Gultnieks, ciaran@ciarang.com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.fdroid.fdroid;
+
+import java.io.Closeable;
+import java.io.InputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+public final class Utils {
+    private Utils() {
+    }
+
+    public static final int BUFFER_SIZE = 4096;
+
+    public static void copy(InputStream input, OutputStream output)
+            throws IOException {
+        byte[] buffer = new byte[BUFFER_SIZE];
+        while (true) {
+            int count = input.read(buffer);
+            if (count == -1) {
+                break;
+            }
+            output.write(buffer, 0, count);
+        }
+        output.flush();
+    }
+
+    public static void closeQuietly(Closeable closeable) {
+        if (closeable == null) {
+            return;
+        }
+        try {
+            closeable.close();
+        } catch (IOException ioe) {
+            // ignore
+        }
+    }
+}