/* * Copyright (C) 2012 The Android Open Source Project * * 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 com.android.imap2; import android.content.Context; import android.os.Build; import android.telephony.TelephonyManager; import android.util.Base64; import android.util.Log; import com.android.emailcommon.Device; import com.android.emailcommon.Logging; import com.android.emailcommon.VendorPolicyLoader; import com.google.common.annotations.VisibleForTesting; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.regex.Pattern; public class ImapId { private static String sImapId; /** * Return, or create and return, an string suitable for use in an IMAP ID message. * This is constructed similarly to the way the browser sets up its user-agent strings. * See RFC 2971 for more details. The output of this command will be a series of key-value * pairs delimited by spaces (there is no point in returning a structured result because * this will be sent as-is to the IMAP server). No tokens, parenthesis or "ID" are included, * because some connections may append additional values. * * The following IMAP ID keys may be included: * name Android package name of the program * os "android" * os-version "version; model; build-id" * vendor Vendor of the client/server * x-android-device-model Model (only revealed if release build) * x-android-net-operator Mobile network operator (if known) * AGUID A device+account UID * * In addition, a vendor policy .apk can append key/value pairs. * * @param userName the username of the account * @param host the host (server) of the account * @param capabilities a list of the capabilities from the server * @return a String for use in an IMAP ID message. */ public static String getImapId(Context context, String userName, String host, String capabilities) { // The first section is global to all IMAP connections, and generates the fixed // values in any IMAP ID message synchronized (ImapId.class) { if (sImapId == null) { TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); String networkOperator = tm.getNetworkOperatorName(); if (networkOperator == null) networkOperator = ""; sImapId = makeCommonImapId(context.getPackageName(), Build.VERSION.RELEASE, Build.VERSION.CODENAME, Build.MODEL, Build.ID, Build.MANUFACTURER, networkOperator); } } // This section is per Store, and adds in a dynamic elements like UID's. // We don't cache the result of this work, because the caller does anyway. StringBuilder id = new StringBuilder(sImapId); // Optionally add any vendor-supplied id keys String vendorId = VendorPolicyLoader.getInstance(context).getImapIdValues(userName, host, capabilities); if (vendorId != null) { id.append(' '); id.append(vendorId); } // Generate a UID that mixes a "stable" device UID with the email address try { String devUID = Device.getConsistentDeviceId(context); MessageDigest messageDigest; messageDigest = MessageDigest.getInstance("SHA-1"); messageDigest.update(userName.getBytes()); messageDigest.update(devUID.getBytes()); byte[] uid = messageDigest.digest(); String hexUid = Base64.encodeToString(uid, Base64.NO_WRAP); id.append(" \"AGUID\" \""); id.append(hexUid); id.append('\"'); } catch (NoSuchAlgorithmException e) { Log.d(Logging.LOG_TAG, "couldn't obtain SHA-1 hash for device UID"); } return id.toString(); } /** * Helper function that actually builds the static part of the IMAP ID string. This is * separated from getImapId for testability. There is no escaping or encoding in IMAP ID so * any rogue chars must be filtered here. * * @param packageName context.getPackageName() * @param version Build.VERSION.RELEASE * @param codeName Build.VERSION.CODENAME * @param model Build.MODEL * @param id Build.ID * @param vendor Build.MANUFACTURER * @param networkOperator TelephonyManager.getNetworkOperatorName() * @return the static (never changes) portion of the IMAP ID */ @VisibleForTesting static String makeCommonImapId(String packageName, String version, String codeName, String model, String id, String vendor, String networkOperator) { // Before building up IMAP ID string, pre-filter the input strings for "legal" chars // This is using a fairly arbitrary char set intended to pass through most reasonable // version, model, and vendor strings: a-z A-Z 0-9 - _ + = ; : . , / // The most important thing is *not* to pass parens, quotes, or CRLF, which would break // the format of the IMAP ID list. Pattern p = Pattern.compile("[^a-zA-Z0-9-_\\+=;:\\.,/ ]"); packageName = p.matcher(packageName).replaceAll(""); version = p.matcher(version).replaceAll(""); codeName = p.matcher(codeName).replaceAll(""); model = p.matcher(model).replaceAll(""); id = p.matcher(id).replaceAll(""); vendor = p.matcher(vendor).replaceAll(""); networkOperator = p.matcher(networkOperator).replaceAll(""); // "name" "com.android.email" StringBuffer sb = new StringBuffer("\"name\" \""); sb.append(packageName); sb.append("\""); // "os" "android" sb.append(" \"os\" \"android\""); // "os-version" "version; build-id" sb.append(" \"os-version\" \""); if (version.length() > 0) { sb.append(version); } else { // default to "1.0" sb.append("1.0"); } // add the build ID or build # if (id.length() > 0) { sb.append("; "); sb.append(id); } sb.append("\""); // "vendor" "the vendor" if (vendor.length() > 0) { sb.append(" \"vendor\" \""); sb.append(vendor); sb.append("\""); } // "x-android-device-model" the device model (on release builds only) if ("REL".equals(codeName)) { if (model.length() > 0) { sb.append(" \"x-android-device-model\" \""); sb.append(model); sb.append("\""); } } // "x-android-mobile-net-operator" "name of network operator" if (networkOperator.length() > 0) { sb.append(" \"x-android-mobile-net-operator\" \""); sb.append(networkOperator); sb.append("\""); } return sb.toString(); } }