Bug 1316008: Use explicit charset encoding draft
authorAdrian Zatreanu <adrianzatreanu1@gmail.com>
Tue, 29 Nov 2016 20:42:08 +0200
changeset 453440 47a9e5e9c232a1d00098eeaedac84401163383eb
parent 453266 2785aaf276ba29fb2e1f5607d90d441fee42efb4
child 540470 44cab356fd86e9f4e632784284a16aae8114b1f8
push id39676
push userbmo:adrianzatreanu1@gmail.com
push dateFri, 23 Dec 2016 13:32:48 +0000
bugs1316008
milestone53.0a1
Bug 1316008: Use explicit charset encoding MozReview-Commit-ID: 3y2CKQZrLtl
mobile/android/base/java/org/mozilla/gecko/ANRReporter.java
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/SuggestClient.java
mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java
mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
mobile/android/base/java/org/mozilla/gecko/search/SearchEngineManager.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java
mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/Logger.java
mobile/android/services/src/main/java/org/mozilla/gecko/browserid/JSONWebTokenUtils.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.java
mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/utils/StringUtils.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/utils/Zipper.java
mobile/android/stumbler/stumbler_sources.mozbuild
--- a/mobile/android/base/java/org/mozilla/gecko/ANRReporter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ANRReporter.java
@@ -17,16 +17,17 @@ import java.io.OutputStream;
 import java.io.Reader;
 import java.util.Locale;
 import java.util.UUID;
 import java.util.regex.Pattern;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Handler;
 import android.os.Looper;
@@ -150,17 +151,18 @@ public final class ANRReporter extends B
         try {
             // getprop [prop-name [default-value]]
             Process propProc = (new ProcessBuilder())
                 .command("/system/bin/getprop", "dalvik.vm.stack-trace-file")
                 .redirectErrorStream(true)
                 .start();
             try {
                 BufferedReader buf = new BufferedReader(
-                    new InputStreamReader(propProc.getInputStream()), TRACES_LINE_SIZE);
+                    new InputStreamReader(
+                            propProc.getInputStream(), StringUtils.UTF_8), TRACES_LINE_SIZE);
                 String propVal = buf.readLine();
                 if (DEBUG) {
                     Log.d(LOGTAG, "getprop returned " + String.valueOf(propVal));
                 }
                 // getprop can return empty string when the prop value is empty
                 // or prop is undefined, treat both cases the same way
                 if (propVal != null && propVal.length() != 0) {
                     tracesFile = new File(propVal);
@@ -213,17 +215,18 @@ public final class ANRReporter extends B
             if (!AppConstants.MANGLED_ANDROID_PACKAGE_NAME.equals(pkgName)) {
                 mangledPattern = Pattern.compile(Pattern.quote(
                     AppConstants.MANGLED_ANDROID_PACKAGE_NAME) + END_OF_PACKAGE_NAME);
             }
             if (DEBUG) {
                 Log.d(LOGTAG, "trying to match package: " + pkgName);
             }
             BufferedReader traces = new BufferedReader(
-                new FileReader(tracesFile), TRACES_BLOCK_SIZE);
+                    new InputStreamReader(new FileInputStream(
+                            tracesFile), StringUtils.UTF_8), TRACES_BLOCK_SIZE);
             try {
                 for (int count = 0; count < LINES_TO_IDENTIFY_TRACES; count++) {
                     String line = traces.readLine();
                     if (DEBUG) {
                         Log.d(LOGTAG, "identifying line: " + String.valueOf(line));
                     }
                     if (line == null) {
                         if (DEBUG) {
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -2041,20 +2041,20 @@ public class BrowserApp extends GeckoApp
                 .execute(new IconCallback() {
                     @Override
                     public void onIconResponse(IconResponse response) {
                         ByteArrayOutputStream out = null;
                         Base64OutputStream b64 = null;
 
                         try {
                             out = new ByteArrayOutputStream();
-                            out.write("data:image/png;base64,".getBytes());
+                            out.write("data:image/png;base64,".getBytes(StringUtils.UTF_8));
                             b64 = new Base64OutputStream(out, Base64.NO_WRAP);
                             response.getBitmap().compress(Bitmap.CompressFormat.PNG, 100, b64);
-                            callback.sendSuccess(new String(out.toByteArray()));
+                            callback.sendSuccess(new String(out.toByteArray(), StringUtils.UTF_8));
                         } catch (IOException e) {
                             Log.w(LOGTAG, "Failed to convert to base64 data URI");
                             callback.sendError("Failed to convert favicon to a base64 data URI");
                         } finally {
                             try {
                                 if (out != null) {
                                     out.close();
                                 }
--- a/mobile/android/base/java/org/mozilla/gecko/SuggestClient.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SuggestClient.java
@@ -2,29 +2,31 @@
  * 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.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLEncoder;
 import java.util.ArrayList;
 
 import org.json.JSONArray;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.util.HardwareUtils;
 
 import android.content.Context;
 import android.text.TextUtils;
 import android.util.Log;
 import org.mozilla.gecko.util.NetworkUtils;
+import org.mozilla.gecko.util.StringUtils;
 
 /**
  * Use network-based search suggestions.
  */
 public class SuggestClient {
     private static final String LOGTAG = "GeckoSuggestClient";
 
     // This should go through GeckoInterface to get the UA, but the search activity
@@ -129,14 +131,14 @@ public class SuggestClient {
 
         mPrevQuery = query;
         mPrevResults = suggestions;
         return suggestions;
     }
 
     private String convertStreamToString(java.io.InputStream is) {
         try {
-            return new java.util.Scanner(is).useDelimiter("\\A").next();
+            return new java.util.Scanner(new InputStreamReader(is, StringUtils.UTF_8)).useDelimiter("\\A").next();
         } catch (java.util.NoSuchElementException e) {
             return "";
         }
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LoginsProvider.java
@@ -16,16 +16,17 @@ import android.net.Uri;
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 import android.util.Base64;
 
 import org.mozilla.gecko.db.BrowserContract.DeletedLogins;
 import org.mozilla.gecko.db.BrowserContract.Logins;
 import org.mozilla.gecko.db.BrowserContract.LoginsDisabledHosts;
 import org.mozilla.gecko.sync.Utils;
+import org.mozilla.gecko.util.StringUtils;
 
 import java.io.UnsupportedEncodingException;
 import java.security.GeneralSecurityException;
 import java.util.HashMap;
 
 import javax.crypto.Cipher;
 import javax.crypto.NullCipher;
 
@@ -502,17 +503,18 @@ public class LoginsProvider extends Shar
             debug("encryption failed : " + e);
             throw new IllegalStateException("Logins encryption failed", e);
         }
     }
 
     private String decrypt(@NonNull String initialValue) {
         try {
             final Cipher cipher = getCipher(Cipher.DECRYPT_MODE);
-            return new String(cipher.doFinal(Base64.decode(initialValue.getBytes("UTF-8"), Base64.URL_SAFE)));
+            return new String(cipher.doFinal(Base64.decode(
+                    initialValue.getBytes("UTF-8"), Base64.URL_SAFE)), StringUtils.UTF_8);
         } catch (Exception e) {
             debug("Decryption failed : " + e);
             throw new IllegalStateException("Logins decryption failed", e);
         }
     }
 
     private Cipher getCipher(int mode) throws UnsupportedEncodingException, GeneralSecurityException {
         return new NullCipher();
--- a/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/BrowserSearch.java
@@ -545,17 +545,18 @@ public class BrowserSearch extends HomeF
     }
 
     LinkedHashSet<String> domains = null;
     private LinkedHashSet<String> getDomains() {
         if (domains == null) {
             domains = new LinkedHashSet<String>(500);
             BufferedReader buf = null;
             try {
-                buf = new BufferedReader(new InputStreamReader(getResources().openRawResource(R.raw.topdomains)));
+                buf = new BufferedReader(new InputStreamReader(
+                        getResources().openRawResource(R.raw.topdomains), StringUtils.UTF_8));
                 String res = null;
 
                 do {
                     res = buf.readLine();
                     if (res != null) {
                         domains.add(res);
                     }
                 } while (res != null);
--- a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
@@ -24,16 +24,18 @@ import android.os.HandlerThread;
 import android.media.DeniedByServerException;
 import android.media.MediaCrypto;
 import android.media.MediaCryptoException;
 import android.media.MediaDrm;
 import android.media.MediaDrmException;
 import android.media.NotProvisionedException;
 import android.util.Log;
 
+import org.mozilla.gecko.util.StringUtils;
+
 public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
     protected final String LOGTAG;
     private static final String INVALID_SESSION_ID = "Invalid";
     private static final String WIDEVINE_KEY_SYSTEM = "com.widevine.alpha";
     private static final boolean DEBUG = false;
     private static final UUID WIDEVINE_SCHEME_UUID =
         new UUID(0xedef8ba979d64aceL, 0xa3c827dcd51d21edL);
     // MediaDrm.KeyStatus information listener is supported on M+, adding a
@@ -168,17 +170,18 @@ public class GeckoMediaDrmBridgeV21 impl
                              promiseId,
                              sessionId.array(),
                              request.getData());
             onSessionMessage(sessionId.array(),
                              LICENSE_REQUEST_INITIAL,
                              request.getData());
             mSessionMIMETypes.put(sessionId, initDataType);
             mSessionIds.add(sessionId);
-            if (DEBUG) Log.d(LOGTAG, " StringID : " + new String(sessionId.array()) + " is put into mSessionIds ");
+            if (DEBUG) Log.d(LOGTAG, " StringID : " + new String(
+                    sessionId.array(), StringUtils.UTF_8) + " is put into mSessionIds ");
         } catch (android.media.NotProvisionedException e) {
             if (DEBUG) Log.d(LOGTAG, "Device not provisioned:" + e.getMessage());
             if (sessionId != null) {
                 // The promise of this createSession will be either resolved
                 // or rejected after provisioning.
                 mDrm.closeSession(sessionId.array());
             }
             savePendingCreateSessionData(createSessionToken, promiseId,
@@ -192,17 +195,17 @@ public class GeckoMediaDrmBridgeV21 impl
                               String sessionId,
                               byte[] response) {
         if (DEBUG) Log.d(LOGTAG, "updateSession(), sessionId = " + sessionId);
         if (mDrm == null) {
             onRejectPromise(promiseId, "MediaDrm instance doesn't exist !!");
             return;
         }
 
-        ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes());
+        ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes(StringUtils.UTF_8));
         if (!sessionExists(session)) {
             onRejectPromise(promiseId, "Invalid session during updateSession.");
             return;
         }
 
         try {
             final byte [] keySetId = mDrm.provideKeyResponse(session.array(), response);
             if (DEBUG) {
@@ -227,17 +230,17 @@ public class GeckoMediaDrmBridgeV21 impl
     @Override
     public void closeSession(int promiseId, String sessionId) {
         if (DEBUG) Log.d(LOGTAG, "closeSession()");
         if (mDrm == null) {
             onRejectPromise(promiseId, "MediaDrm instance doesn't exist !!");
             return;
         }
 
-        ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes());
+        ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes(StringUtils.UTF_8));
         mSessionIds.remove(session);
         mDrm.closeSession(session.array());
         onSessionClosed(promiseId, session.array());
     }
 
     @Override
     public void release() {
         if (DEBUG) Log.d(LOGTAG, "release()");
@@ -373,20 +376,20 @@ public class GeckoMediaDrmBridgeV21 impl
                 case MediaDrm.EVENT_PROVISION_REQUIRED:
                     if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_PROVISION_REQUIRED");
                     break;
                 case MediaDrm.EVENT_KEY_REQUIRED:
                     if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_KEY_REQUIRED");
                     // No need to handle here if we're not in privacy mode.
                     break;
                 case MediaDrm.EVENT_KEY_EXPIRED:
-                    if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_KEY_EXPIRED, sessionId=" + new String(session.array()));
+                    if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_KEY_EXPIRED, sessionId=" + new String(session.array(), StringUtils.UTF_8));
                     break;
                 case MediaDrm.EVENT_VENDOR_DEFINED:
-                    if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_VENDOR_DEFINED, sessionId=" + new String(session.array()));
+                    if (DEBUG) Log.d(LOGTAG, "MediaDrm.EVENT_VENDOR_DEFINED, sessionId=" + new String(session.array(), StringUtils.UTF_8));
                     break;
                 default:
                     if (DEBUG) Log.d(LOGTAG, "Invalid DRM event " + event);
                     return;
             }
         }
     }
 
@@ -451,25 +454,25 @@ public class GeckoMediaDrmBridgeV21 impl
                 urlConnection.setRequestProperty("Content-Type", "application/json");
 
                 // Execute HTTP Post Request
                 urlConnection.connect();
 
                 int responseCode = urlConnection.getResponseCode();
                 if (responseCode == HttpURLConnection.HTTP_OK) {
                     BufferedReader in =
-                      new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
+                      new BufferedReader(new InputStreamReader(urlConnection.getInputStream(), StringUtils.UTF_8));
                     String inputLine;
                     StringBuffer response = new StringBuffer();
 
                     while ((inputLine = in.readLine()) != null) {
                         response.append(inputLine);
                     }
                     in.close();
-                    mResponseBody = String.valueOf(response).getBytes();
+                    mResponseBody = String.valueOf(response).getBytes(StringUtils.UTF_8);
                     if (DEBUG) Log.d(LOGTAG, "Provisioning, response received.");
                     if (mResponseBody != null) Log.d(LOGTAG, "response length=" + mResponseBody.length);
                 } else {
                     Log.d(LOGTAG, "Provisioning, server returned HTTP error code :" + responseCode);
                 }
             } catch (IOException e) {
                 Log.e(LOGTAG, "Got exception during posting provisioning request ...", e);
             }
@@ -585,17 +588,18 @@ public class GeckoMediaDrmBridgeV21 impl
                 if (DEBUG) Log.d(LOGTAG, "Cannot open session for MediaCrypto");
                 return false;
             }
 
             if (MediaCrypto.isCryptoSchemeSupported(mSchemeUUID)) {
                 final byte [] cryptoSessionId = mCryptoSessionId.array();
                 mCrypto = new MediaCrypto(mSchemeUUID, cryptoSessionId);
                 mSessionIds.add(mCryptoSessionId);
-                if (DEBUG) Log.d(LOGTAG, "MediaCrypto successfully created! - SId " + INVALID_SESSION_ID + ", " + new String(cryptoSessionId));
+                if (DEBUG) Log.d(LOGTAG, "MediaCrypto successfully created! - SId " + INVALID_SESSION_ID +
+                        ", " + new String(cryptoSessionId, StringUtils.UTF_8));
                 return true;
             } else {
                 if (DEBUG) Log.d(LOGTAG, "Cannot create MediaCrypto for unsupported scheme.");
                 return false;
             }
         } catch (android.media.MediaCryptoException e) {
             if (DEBUG) Log.d(LOGTAG, "Cannot create MediaCrypto:" + e.getMessage());
             release();
--- a/mobile/android/base/java/org/mozilla/gecko/search/SearchEngineManager.java
+++ b/mobile/android/base/java/org/mozilla/gecko/search/SearchEngineManager.java
@@ -18,16 +18,17 @@ import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoJarReader;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.RawResource;
+import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -388,17 +389,17 @@ public class SearchEngineManager impleme
                 final String message = "{}";
 
                 urlConnection.setDoOutput(true);
                 urlConnection.setConnectTimeout(10000);
                 urlConnection.setReadTimeout(10000);
                 urlConnection.setRequestMethod("POST");
                 urlConnection.setRequestProperty("User-Agent", USER_AGENT);
                 urlConnection.setRequestProperty("Content-Type", "application/json");
-                urlConnection.setFixedLengthStreamingMode(message.getBytes().length);
+                urlConnection.setFixedLengthStreamingMode(message.getBytes(StringUtils.UTF_8).length);
 
                 final OutputStream out = urlConnection.getOutputStream();
                 out.write(message.getBytes());
                 out.close();
 
                 responseText = getHttpResponse(urlConnection);
             } finally {
                 urlConnection.disconnect();
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/StringUtils.java
@@ -4,27 +4,34 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.util;
 
 import android.net.Uri;
 import android.support.annotation.NonNull;
 import android.text.TextUtils;
 
+import java.nio.charset.Charset;
 import java.util.Collections;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Set;
 
 public class StringUtils {
     private static final String LOGTAG = "GeckoStringUtils";
 
     private static final String FILTER_URL_PREFIX = "filter://";
     private static final String USER_ENTERED_URL_PREFIX = "user-entered:";
 
+
+    /**
+     * The UTF-8 charset.
+     */
+    public static final Charset UTF_8 = Charset.forName("UTF-8");
+
     /*
      * This method tries to guess if the given string could be a search query or URL,
      * and returns a previous result if there is ambiguity
      *
      * Search examples:
      *  foo
      *  foo bar.com
      *  foo http://bar.com
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/Logger.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/common/log/Logger.java
@@ -1,26 +1,28 @@
 /* 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.background.common.log;
 
+import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.Set;
 
 import org.mozilla.gecko.background.common.GlobalConstants;
 import org.mozilla.gecko.background.common.log.writers.AndroidLevelCachingLogWriter;
 import org.mozilla.gecko.background.common.log.writers.AndroidLogWriter;
 import org.mozilla.gecko.background.common.log.writers.LogWriter;
 import org.mozilla.gecko.background.common.log.writers.PrintLogWriter;
 import org.mozilla.gecko.background.common.log.writers.SimpleTagLogWriter;
 import org.mozilla.gecko.background.common.log.writers.ThreadLocalTagLogWriter;
+import org.mozilla.gecko.util.StringUtils;
 
 import android.util.Log;
 
 /**
  * Logging helper class. Serializes all log operations (by synchronizing).
  */
 public class Logger {
   public static final String LOGGER_TAG = "Logger";
@@ -121,17 +123,18 @@ public class Logger {
 
   /**
    * Start writing log output to stdout.
    * <p>
    * Use <code>resetLogging</code> to stop logging to stdout.
    */
   public static synchronized void startLoggingToConsole() {
     setThreadLogTag("Test");
-    startLoggingTo(new PrintLogWriter(new PrintWriter(System.out, true)));
+    startLoggingTo(new PrintLogWriter(new PrintWriter(
+            new OutputStreamWriter(System.out, StringUtils.UTF_8), true)));
   }
 
   // Synchronized version for other classes to use.
   public static synchronized boolean shouldLogVerbose(String logTag) {
     for (LogWriter logWriter : logWriters) {
       if (logWriter.shouldLogVerbose(logTag)) {
         return true;
       }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/JSONWebTokenUtils.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/browserid/JSONWebTokenUtils.java
@@ -144,18 +144,20 @@ public class JSONWebTokenUtils {
    *         certificate is well-formed.
    */
   public static ExtendedJSONObject parseCertificate(String input) {
     try {
       String[] parts = input.split("\\.");
       if (parts.length != 3) {
         return null;
       }
-      String cHeader = new String(Base64.decodeBase64(parts[0]));
-      String cPayload = new String(Base64.decodeBase64(parts[1]));
+      String cHeader = new String(Base64.decodeBase64(parts[0]),
+              org.mozilla.gecko.util.StringUtils.UTF_8);
+      String cPayload = new String(Base64.decodeBase64(parts[1]),
+              org.mozilla.gecko.util.StringUtils.UTF_8);
       String cSignature = Utils.byte2Hex(Base64.decodeBase64(parts[2]));
       ExtendedJSONObject o = new ExtendedJSONObject();
       o.put("header", new ExtendedJSONObject(cHeader));
       o.put("payload", new ExtendedJSONObject(cPayload));
       o.put("signature", cSignature);
       return o;
     } catch (Exception e) {
       return null;
@@ -198,18 +200,20 @@ public class JSONWebTokenUtils {
         return null;
       }
       String certificate = parts[0];
       String assertion = parts[1];
       parts = assertion.split("\\.");
       if (parts.length != 3) {
         return null;
       }
-      String aHeader = new String(Base64.decodeBase64(parts[0]));
-      String aPayload = new String(Base64.decodeBase64(parts[1]));
+      String aHeader = new String(Base64.decodeBase64(parts[0]),
+              org.mozilla.gecko.util.StringUtils.UTF_8);
+      String aPayload = new String(Base64.decodeBase64(parts[1]),
+              org.mozilla.gecko.util.StringUtils.UTF_8);
       String aSignature = Utils.byte2Hex(Base64.decodeBase64(parts[2]));
       // We do all the assertion parsing *before* dumping the certificate in
       // case there's a malformed assertion.
       ExtendedJSONObject o = new ExtendedJSONObject();
       o.put("header", new ExtendedJSONObject(aHeader));
       o.put("payload", new ExtendedJSONObject(aPayload));
       o.put("signature", aSignature);
       o.put("certificate", certificate);
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CollectionKeys.java
@@ -10,16 +10,17 @@ import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map.Entry;
 import java.util.Set;
 
 import org.json.simple.JSONArray;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
+import org.mozilla.gecko.util.StringUtils;
 
 public class CollectionKeys {
   private KeyBundle                  defaultKeyBundle     = null;
   private final HashMap<String, KeyBundle> collectionKeyBundles = new HashMap<String, KeyBundle>();
 
   /**
    * Randomly generate a basic CollectionKeys object.
    * @throws CryptoException
@@ -64,18 +65,18 @@ public class CollectionKeys {
     String hmacKeyStr = (String) array.get(1);
     return KeyBundle.fromBase64EncodedKeys(encKeyStr, hmacKeyStr);
   }
 
   @SuppressWarnings("unchecked")
   private static JSONArray keyBundleToArray(KeyBundle bundle) {
     // Generate JSON.
     JSONArray keysArray = new JSONArray();
-    keysArray.add(new String(Base64.encodeBase64(bundle.getEncryptionKey())));
-    keysArray.add(new String(Base64.encodeBase64(bundle.getHMACKey())));
+    keysArray.add(new String(Base64.encodeBase64(bundle.getEncryptionKey()), StringUtils.UTF_8));
+    keysArray.add(new String(Base64.encodeBase64(bundle.getHMACKey()), StringUtils.UTF_8));
     return keysArray;
   }
 
   private ExtendedJSONObject asRecordContents() throws NoCollectionKeysSetException {
     ExtendedJSONObject json = new ExtendedJSONObject();
     json.put("id", "keys");
     json.put("collection", "crypto");
     json.put("default", keyBundleToArray(this.defaultKeyBundle()));
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/CryptoRecord.java
@@ -11,16 +11,17 @@ import org.json.simple.JSONObject;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.sync.crypto.CryptoException;
 import org.mozilla.gecko.sync.crypto.CryptoInfo;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.crypto.MissingCryptoInputException;
 import org.mozilla.gecko.sync.crypto.NoKeyBundleException;
 import org.mozilla.gecko.sync.repositories.domain.Record;
 import org.mozilla.gecko.sync.repositories.domain.RecordParseException;
+import org.mozilla.gecko.util.StringUtils;
 
 /**
  * A Sync crypto record has:
  *
  * <ul>
  * <li>a collection of fields which are not encrypted (id and collection);</il>
  * <li>a set of metadata fields (index, modified, ttl);</il>
  * <li>a payload, which is encrypted and decrypted on request.</il>
@@ -199,17 +200,17 @@ public class CryptoRecord extends Record
     return this;
   }
 
   public CryptoRecord encrypt() throws CryptoException, UnsupportedEncodingException {
     if (this.keyBundle == null) {
       throw new NoKeyBundleException();
     }
     String cleartext = payload.toJSONString();
-    byte[] cleartextBytes = cleartext.getBytes("UTF-8");
+    byte[] cleartextBytes = cleartext.getBytes(StringUtils.UTF_8);
     CryptoInfo info = CryptoInfo.encrypt(cleartextBytes, keyBundle);
     String message = new String(Base64.encodeBase64(info.getMessage()));
     String iv      = new String(Base64.encodeBase64(info.getIV()));
     String hmac    = Utils.byte2Hex(info.getHMAC());
     ExtendedJSONObject ciphertext = new ExtendedJSONObject();
     ciphertext.put(KEY_CIPHERTEXT, message);
     ciphertext.put(KEY_HMAC, hmac);
     ciphertext.put(KEY_IV, iv);
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/Utils.java
@@ -26,33 +26,34 @@ import java.util.TreeMap;
 import java.util.concurrent.Executor;
 
 import org.json.simple.JSONArray;
 import org.mozilla.apache.commons.codec.binary.Base32;
 import org.mozilla.apache.commons.codec.binary.Base64;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.nativecode.NativeCrypto;
 import org.mozilla.gecko.sync.setup.Constants;
+import org.mozilla.gecko.util.StringUtils;
 
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.Bundle;
 
 public class Utils {
 
   private static final String LOG_TAG = "Utils";
 
   private static final SecureRandom sharedSecureRandom = new SecureRandom();
 
   // See <http://developer.android.com/reference/android/content/Context.html#getSharedPreferences%28java.lang.String,%20int%29>
   public static final int SHARED_PREFERENCES_MODE = 0;
 
   public static String generateGuid() {
     byte[] encodedBytes = Base64.encodeBase64(generateRandomBytes(9), false);
-    return new String(encodedBytes).replace("+", "-").replace("/", "_");
+    return new String(encodedBytes, StringUtils.UTF_8).replace("+", "-").replace("/", "_");
   }
 
   /**
    * Helper to generate secure random bytes.
    *
    * @param length
    *        Number of bytes to generate.
    */
@@ -488,17 +489,17 @@ public class Utils {
     }
 
     FileInputStream fis = null;
     InputStreamReader isr = null;
     BufferedReader br = null;
 
     try {
       fis = context.openFileInput(filename);
-      isr = new InputStreamReader(fis);
+      isr = new InputStreamReader(fis, StringUtils.UTF_8);
       br = new BufferedReader(isr);
       StringBuilder sb = new StringBuilder();
       String line;
       while ((line = br.readLine()) != null) {
         sb.append(line);
       }
       return sb.toString();
     } catch (Exception e) {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/MozResponse.java
@@ -13,16 +13,17 @@ import java.util.Scanner;
 
 import org.json.simple.JSONArray;
 import org.json.simple.parser.JSONParser;
 import org.json.simple.parser.ParseException;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.NonArrayJSONException;
 import org.mozilla.gecko.sync.NonObjectJSONException;
+import org.mozilla.gecko.util.StringUtils;
 
 import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.HttpEntity;
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.HttpStatus;
 import ch.boye.httpclientandroidlib.impl.cookie.DateParseException;
 import ch.boye.httpclientandroidlib.impl.cookie.DateUtils;
 
@@ -74,17 +75,17 @@ public class MozResponse {
       return body;
     }
     final HttpEntity entity = this.response.getEntity();
     if (entity == null) {
       body = null;
       return null;
     }
 
-    InputStreamReader is = new InputStreamReader(entity.getContent());
+    InputStreamReader is = new InputStreamReader(entity.getContent(), StringUtils.UTF_8);
     // Oh, Java, you are so evil.
     body = new Scanner(is).useDelimiter("\\A").next();
     return body;
   }
 
   /**
    * Return the body as a <b>non-null</b> <code>ExtendedJSONObject</code>.
    *
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/net/SyncStorageCollectionRequest.java
@@ -6,16 +6,17 @@ package org.mozilla.gecko.sync.net;
 
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.net.URI;
 
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.util.StringUtils;
 
 import ch.boye.httpclientandroidlib.Header;
 import ch.boye.httpclientandroidlib.HttpEntity;
 import ch.boye.httpclientandroidlib.HttpResponse;
 import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
 import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
 
 /**
@@ -98,17 +99,17 @@ public class SyncStorageCollectionReques
       // it processes. Bug 721887.
 
       // Line-by-line processing, then invoke success.
       SyncStorageCollectionRequestDelegate delegate = (SyncStorageCollectionRequestDelegate) this.request.delegate;
       InputStream content = null;
       BufferedReader br = null;
       try {
         content = entity.getContent();
-        br = new BufferedReader(new InputStreamReader(content), FETCH_BUFFER_SIZE);
+        br = new BufferedReader(new InputStreamReader(content, StringUtils.UTF_8), FETCH_BUFFER_SIZE);
         String line;
 
         // This relies on connection timeouts at the HTTP layer.
         while (!aborting &&
                null != (line = br.readLine())) {
           try {
             delegate.handleRequestProgress(line);
           } catch (Exception ex) {
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java
@@ -1,17 +1,19 @@
 /* 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.mozstumbler.service.stumblerthread.datahandling;
 
 import android.content.Context;
 import android.util.Log;
+
 import org.mozilla.mozstumbler.service.AppGlobals;
+import org.mozilla.mozstumbler.service.utils.StringUtils;
 import org.mozilla.mozstumbler.service.utils.Zipper;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.util.ArrayList;
 import java.util.Properties;
@@ -96,17 +98,17 @@ public class DataStorageManager {
     public QueuedCounts getQueuedCounts() {
         int reportCount = mFileList.mReportCount + mCurrentReports.reports.size();
         int wifiCount = mFileList.mWifiCount + mCurrentReports.wifiCount;
         int cellCount = mFileList.mCellCount + mCurrentReports.cellCount;
         long bytes = 0;
 
         if (mCurrentReports.reports.size() > 0) {
             try {
-                bytes = Zipper.zipData(finalizeReports(mCurrentReports.reports).getBytes()).length;
+                bytes = Zipper.zipData(finalizeReports(mCurrentReports.reports).getBytes(StringUtils.UTF_8)).length;
             } catch (IOException ex) {
                 Log.e(LOG_TAG, "Zip error in getQueuedCounts()", ex);
             }
 
             if (mFileList.mReportCount > 0) {
                 bytes += mFileList.mFilesOnDiskBytes;
             }
         }
@@ -296,17 +298,17 @@ public class DataStorageManager {
         if (dirEmpty && currentReportsCount < 1) {
             return null;
         }
 
         mReportBatchIterator = new ReportBatchIterator(mFileList);
 
         if (currentReportsCount > 0) {
             final String filename = MEMORY_BUFFER_NAME;
-            final byte[] data = Zipper.zipData(finalizeReports(mCurrentReports.reports).getBytes());
+            final byte[] data = Zipper.zipData(finalizeReports(mCurrentReports.reports).getBytes(StringUtils.UTF_8));
             final int wifiCount = mCurrentReports.wifiCount;
             final int cellCount = mCurrentReports.cellCount;
             clearCurrentReports();
             final ReportBatch result = new ReportBatch(filename, data, currentReportsCount, wifiCount, cellCount);
             mCurrentReportsSendBuffer = result;
             return result;
         } else {
             return getNextBatch();
@@ -407,17 +409,17 @@ public class DataStorageManager {
         return result;
     }
 
     public synchronized void saveCurrentReportsToDisk() throws IOException {
         saveCurrentReportsSendBufferToDisk();
         if (mCurrentReports.reports.size() < 1) {
             return;
         }
-        final byte[] bytes = Zipper.zipData(finalizeReports(mCurrentReports.reports).getBytes());
+        final byte[] bytes = Zipper.zipData(finalizeReports(mCurrentReports.reports).getBytes(StringUtils.UTF_8));
         saveToDisk(bytes, mCurrentReports.reports.size(), mCurrentReports.wifiCount, mCurrentReports.cellCount);
         clearCurrentReports();
     }
 
     public synchronized void insert(String report, int wifiCount, int cellCount) throws IOException {
         notifyStorageIsEmpty(false);
 
         if (mFlushMemoryBuffersToDiskTimer != null) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/utils/StringUtils.java
@@ -0,0 +1,15 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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.mozstumbler.service.utils;
+
+import java.nio.charset.Charset;
+
+public class StringUtils {
+    /**
+     * The UTF-8 charset.
+     */
+    public static final Charset UTF_8 = Charset.forName("UTF-8");
+}
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/utils/Zipper.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/utils/Zipper.java
@@ -28,17 +28,17 @@ public class Zipper {
         return output;
     }
 
     public static String unzipData(byte[] data) throws IOException {
         StringBuilder result = new StringBuilder();
         final ByteArrayInputStream bs = new ByteArrayInputStream(data);
         GZIPInputStream gstream = new GZIPInputStream(bs);
         try {
-            InputStreamReader reader = new InputStreamReader(gstream);
+            InputStreamReader reader = new InputStreamReader(gstream, StringUtils.UTF_8);
             BufferedReader in = new BufferedReader(reader);
             String read;
             while ((read = in.readLine()) != null) {
                 result.append(read);
             }
         } finally {
             gstream.close();
             bs.close();
--- a/mobile/android/stumbler/stumbler_sources.mozbuild
+++ b/mobile/android/stumbler/stumbler_sources.mozbuild
@@ -26,11 +26,12 @@ stumbler_sources = [
     'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/ScanManager.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/WifiScanner.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/StumblerService.java',
     'java/org/mozilla/mozstumbler/service/uploadthread/AsyncUploader.java',
     'java/org/mozilla/mozstumbler/service/uploadthread/UploadAlarmReceiver.java',
     'java/org/mozilla/mozstumbler/service/utils/AbstractCommunicator.java',
     'java/org/mozilla/mozstumbler/service/utils/NetworkUtils.java',
     'java/org/mozilla/mozstumbler/service/utils/PersistentIntentService.java',
+    'java/org/mozilla/mozstumbler/service/utils/StringUtils.java',
     'java/org/mozilla/mozstumbler/service/utils/TelemetryWrapper.java',
     'java/org/mozilla/mozstumbler/service/utils/Zipper.java',
 ]