1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.libraries.entitlement.utils; 17 18 import android.text.TextUtils; 19 import android.util.ArrayMap; 20 import android.util.Log; 21 22 import androidx.annotation.NonNull; 23 import androidx.annotation.Nullable; 24 25 import com.google.common.collect.ImmutableList; 26 27 import org.w3c.dom.Document; 28 import org.w3c.dom.NamedNodeMap; 29 import org.w3c.dom.Node; 30 import org.w3c.dom.NodeList; 31 import org.xml.sax.InputSource; 32 import org.xml.sax.SAXException; 33 34 import java.io.IOException; 35 import java.io.StringReader; 36 import java.util.ArrayList; 37 import java.util.List; 38 import java.util.Locale; 39 import java.util.Map; 40 import java.util.Objects; 41 42 import javax.xml.parsers.DocumentBuilder; 43 import javax.xml.parsers.DocumentBuilderFactory; 44 import javax.xml.parsers.ParserConfigurationException; 45 46 /** Wraps the TS.43 XML raw string and parses it into nodes. */ 47 public final class Ts43XmlDoc { 48 private static final String TAG = "Ts43XmlDoc"; 49 private static final String NODE_CHARACTERISTIC = "characteristic"; 50 private static final String NODE_PARM = "parm"; 51 private static final String PARM_NAME = "name"; 52 private static final String PARM_VALUE = "value"; 53 54 /** Type names of characteristics. */ 55 public static final class CharacteristicType { CharacteristicType()56 private CharacteristicType() { 57 } 58 59 public static final String APPLICATION = "APPLICATION"; 60 public static final String PRIMARY_CONFIGURATION = "PrimaryConfiguration"; 61 public static final String COMPANION_CONFIGURATIONS = "CompanionConfigurations"; 62 public static final String COMPANION_CONFIGURATION = "CompanionConfiguration"; 63 public static final String ENTERPRISE_CONFIGURATION = "EnterpriseConfiguration"; 64 public static final String USER = "USER"; 65 public static final String TOKEN = "TOKEN"; 66 public static final String DOWNLOAD_INFO = "DownloadInfo"; 67 public static final String MSG = "MSG"; 68 } 69 70 /** Names of parameters. */ 71 public static final class Parm { Parm()72 private Parm() { 73 } 74 75 public static final String TOKEN = "token"; 76 public static final String APP_ID = "AppID"; 77 public static final String VERSION = "version"; 78 public static final String VALIDITY = "validity"; 79 public static final String OPERATION_RESULT = "OperationResult"; 80 public static final String GENERAL_ERROR_URL = "GeneralErrorURL"; 81 public static final String GENERAL_ERROR_USER_DATA = "GeneralErrorUserData"; 82 public static final String GENERAL_ERROR_TEXT = "GeneralErrorText"; 83 public static final String PRIMARY_APP_ELIGIBILITY = "PrimaryAppEligibility"; 84 public static final String COMPANION_APP_ELIGIBILITY = "CompanionAppEligibility"; 85 public static final String ENTERPRISE_APP_ELIGIBILITY = "EnterpriseAppEligibility"; 86 public static final String NOT_ENABLED_URL = "NotEnabledURL"; 87 public static final String NOT_ENABLED_USER_DATA = "NotEnabledUserData"; 88 public static final String NOT_ENABLED_CONTENTS_TYPE = "NotEnabledContentsType"; 89 public static final String COMPANION_DEVICE_SERVICES = "CompanionDeviceServices"; 90 public static final String TEMPORARY_TOKEN = "TemporaryToken"; 91 public static final String TEMPORARY_TOKEN_EXPIRY = "TemporaryTokenExpiry"; 92 public static final String MSISDN = "msisdn"; 93 public static final String ICCID = "ICCID"; 94 public static final String SERVICE_STATUS = "ServiceStatus"; 95 public static final String POLLING_INTERVAL = "PollingInterval"; 96 public static final String SUBSCRIPTION_RESULT = "SubscriptionResult"; 97 public static final String SUBSCRIPTION_SERVICE_URL = "SubscriptionServiceURL"; 98 public static final String SUBSCRIPTION_SERVICE_USER_DATA = "SubscriptionServiceUserData"; 99 public static final String SUBSCRIPTION_SERVICE_CONTENTS_TYPE = 100 "SubscriptionServiceContentsType"; 101 public static final String PROFILE_ACTIVATION_CODE = "ProfileActivationCode"; 102 public static final String PROFILE_ICCID = "ProfileIccid"; 103 public static final String PROFILE_SMDP_ADDRESS = "ProfileSmdpAddress"; 104 public static final String OPERATION_TARGETS = "OperationTargets"; 105 public static final String MESSAGE = "Message"; 106 public static final String ACCEPT_BUTTON = "Accept_btn"; 107 public static final String ACCEPT_BUTTON_LABEL = "Accept_btn_label"; 108 public static final String REJECT_BUTTON = "Reject_btn"; 109 public static final String REJECT_BUTTON_LABEL = "Reject_btn_label"; 110 public static final String ACCEPT_FREETEXT = "Accept_freetext"; 111 } 112 113 /** Parameter values of XML response content. */ 114 public static final class ParmValues { ParmValues()115 private ParmValues() { 116 } 117 118 public static final String OPERATION_RESULT_SUCCESS = "1"; 119 public static final String OPERATION_RESULT_ERROR_GENERAL = "100"; 120 public static final String OPERATION_RESULT_ERROR_INVALID_OPERATION = "101"; 121 public static final String OPERATION_RESULT_ERROR_INVALID_PARAMETER = "102"; 122 public static final String OPERATION_RESULT_WARNING_NOT_SUPPORTED_OPERATION = "103"; 123 public static final String OPERATION_RESULT_ERROR_INVALID_MSG_RESPONSE = "104"; 124 public static final String PRIMARY_APP_ELIGIBILITY_ENABLED = "1"; 125 public static final String SERVICE_STATUS_ACTIVATED = "1"; 126 public static final String SERVICE_STATUS_ACTIVATING = "2"; 127 public static final String SERVICE_STATUS_DEACTIVATED = "3"; 128 public static final String SERVICE_STATUS_DEACTIVATED_NO_REUSE = "4"; 129 public static final String SUBSCRIPTION_RESULT_CONTINUE_TO_WEBSHEET = "1"; 130 public static final String SUBSCRIPTION_RESULT_DOWNLOAD_PROFILE = "2"; 131 public static final String SUBSCRIPTION_RESULT_DONE = "3"; 132 public static final String SUBSCRIPTION_RESULT_DELAYED_DOWNLOAD = "4"; 133 public static final String SUBSCRIPTION_RESULT_DISMISS = "5"; 134 public static final String SUBSCRIPTION_RESULT_DELETE_PROFILE_IN_USE = "6"; 135 public static final String SUBSCRIPTION_RESULT_REDOWNLOADABLE_PROFILE_IS_MANDATORY = "7"; 136 public static final String SUBSCRIPTION_RESULT_REQUIRES_USER_INPUT = "8"; 137 public static final String CONTENTS_TYPE_XML = "xml"; 138 public static final String CONTENTS_TYPE_JSON = "json"; 139 public static final String DISABLED = "0"; 140 public static final String ENABLED = "1"; 141 public static final String INCOMPATIBLE = "2"; 142 } 143 144 /** 145 * Maps characteristics to a map of parameters. Key is the characteristic type. Value is 146 * parameter 147 * name and value. Example: {"APPLICATION" -> {"AppId" -> "ap2009", "OperationResult" -> "1"}, 148 * "APPLICATION|PrimaryConfiguration" -> {"ICCID" -> "123", "ServiceStatus" -> "2", 149 * "PollingInterval" -> "1"} } 150 */ 151 private final Map<String, Map<String, String>> mCharacteristicsMap = new ArrayMap<>(); 152 Ts43XmlDoc(String responseBody)153 public Ts43XmlDoc(String responseBody) { 154 parseXmlResponse(responseBody); 155 } 156 157 /** Returns {@code true} if a node structure exists for a given characteristicTypes. */ contains(ImmutableList<String> characteristicTypes)158 public boolean contains(ImmutableList<String> characteristicTypes) { 159 return mCharacteristicsMap.containsKey(TextUtils.join("|", characteristicTypes)); 160 } 161 162 /** 163 * Returns param value for given characteristicType and parameterName, or {@code null} if not 164 * found. 165 */ 166 @Nullable get(ImmutableList<String> characteristicTypes, String parameterName)167 public String get(ImmutableList<String> characteristicTypes, String parameterName) { 168 Map<String, String> parmMap = mCharacteristicsMap.get( 169 TextUtils.join("|", characteristicTypes)); 170 return parmMap == null ? null : parmMap.get(parameterName.toLowerCase(Locale.ROOT)); 171 } 172 173 /** 174 * Parses the response body as per format defined in TS.43 New Characteristics for XML-Based 175 * Document. 176 */ parseXmlResponse(String responseBody)177 private void parseXmlResponse(String responseBody) { 178 if (responseBody == null) { 179 return; 180 } 181 // Workaround: some server doesn't escape "&" in XML response and that will cause XML parser 182 // failure later. 183 // This is a quick impl of escaping w/o introducing a ton of new dependencies. 184 responseBody = responseBody.replace("&", "&").replace("&amp;", "&"); 185 try { 186 InputSource inputSource = new InputSource(new StringReader(responseBody)); 187 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); 188 DocumentBuilder docBuilder = builderFactory.newDocumentBuilder(); 189 Document doc = docBuilder.parse(inputSource); 190 doc.getDocumentElement().normalize(); 191 NodeList nodeList = doc.getDocumentElement().getChildNodes(); 192 for (int i = 0; i < nodeList.getLength(); i++) { 193 parseNode(new ArrayList<>(), Objects.requireNonNull(nodeList.item(i))); 194 } 195 } catch (ParserConfigurationException | IOException | SAXException e) { 196 // Nodes that failed to parse won't be stored in nodesMap 197 Log.w(TAG, "e=" + e); 198 } 199 } 200 201 @SuppressWarnings("AndroidJdkLibsChecker") // java.util.Map#getOrDefault 202 /* Parses characteristics and parm values into characteristicsMap. */ parseNode(List<String> characteristics, Node node)203 private void parseNode(List<String> characteristics, Node node) { 204 String nodeName = node.getNodeName(); 205 NamedNodeMap attributes = node.getAttributes(); 206 if (attributes == null) { 207 return; 208 } 209 if (nodeName.equals(NODE_CHARACTERISTIC)) { 210 Node typeNode = attributes.getNamedItem("type"); 211 if (typeNode == null) { 212 return; 213 } 214 characteristics.add(Objects.requireNonNull(typeNode.getNodeValue())); 215 NodeList children = node.getChildNodes(); 216 for (int i = 0; i < children.getLength(); i++) { 217 parseNode(characteristics, Objects.requireNonNull(children.item(i))); 218 } 219 characteristics.remove(characteristics.size() - 1); 220 } else if (nodeName.equals(NODE_PARM)) { 221 Node parmNameNode = attributes.getNamedItem(PARM_NAME); 222 Node parmValueNode = attributes.getNamedItem(PARM_VALUE); 223 if (parmNameNode == null || parmValueNode == null) { 224 return; 225 } 226 String characteristicKey = TextUtils.join("|", characteristics); 227 Map<String, String> parmMap = 228 mCharacteristicsMap.getOrDefault(characteristicKey, new ArrayMap<>()); 229 parmMap.put( 230 Objects.requireNonNull(parmNameNode.getNodeValue().toLowerCase(Locale.ROOT)), 231 Objects.requireNonNull(parmValueNode.getNodeValue())); 232 mCharacteristicsMap.put(characteristicKey, parmMap); 233 } 234 } 235 236 @NonNull 237 @Override toString()238 public String toString() { 239 return "Ts43XmlDoc: " + mCharacteristicsMap; 240 } 241 } 242