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("&", "&amp;").replace("&amp;amp;", "&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