1 /* 2 * Copyright (C) 2021 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 17 package com.android.imsserviceentitlement.utils; 18 19 import static com.android.imsserviceentitlement.ts43.Ts43Constants.AccessTypeValue.LTE; 20 import static com.android.imsserviceentitlement.ts43.Ts43Constants.AccessTypeValue.NGRAN; 21 import static com.android.imsserviceentitlement.ts43.Ts43Constants.HomeRoamingNwTypeValue.ALL; 22 import static com.android.imsserviceentitlement.ts43.Ts43Constants.HomeRoamingNwTypeValue.HOME; 23 import static com.android.imsserviceentitlement.ts43.Ts43Constants.HomeRoamingNwTypeValue.ROAMING; 24 import static com.android.imsserviceentitlement.ts43.Ts43Constants.ResponseXmlAttributes.ACCESS_TYPE; 25 import static com.android.imsserviceentitlement.ts43.Ts43Constants.ResponseXmlAttributes.APP_ID; 26 import static com.android.imsserviceentitlement.ts43.Ts43Constants.ResponseXmlAttributes.HOME_ROAMING_NW_TYPE; 27 import static com.android.imsserviceentitlement.ts43.Ts43Constants.ResponseXmlAttributes.RAT_VOICE_ENTITLE_INFO_DETAILS; 28 import static com.android.imsserviceentitlement.ts43.Ts43Constants.ResponseXmlNode.APPLICATION; 29 import static com.android.imsserviceentitlement.ts43.Ts43Constants.ResponseXmlNode.TOKEN; 30 import static com.android.imsserviceentitlement.ts43.Ts43Constants.ResponseXmlNode.VERS; 31 32 import android.text.TextUtils; 33 import android.util.ArrayMap; 34 import android.util.Log; 35 36 import com.android.imsserviceentitlement.debug.DebugUtils; 37 import com.android.libraries.entitlement.ServiceEntitlement; 38 39 import org.w3c.dom.Document; 40 import org.w3c.dom.Element; 41 import org.w3c.dom.NamedNodeMap; 42 import org.w3c.dom.Node; 43 import org.w3c.dom.NodeList; 44 import org.xml.sax.InputSource; 45 import org.xml.sax.SAXException; 46 47 import java.io.IOException; 48 import java.io.StringReader; 49 import java.util.Map; 50 import java.util.Optional; 51 52 import javax.xml.parsers.DocumentBuilder; 53 import javax.xml.parsers.DocumentBuilderFactory; 54 import javax.xml.parsers.ParserConfigurationException; 55 56 /** Wrap the raw content and parse it into nodes. */ 57 public class XmlDoc { 58 private static final String TAG = "IMSSE-XmlDoc"; 59 60 private static final String NODE_CHARACTERISTIC = "characteristic"; 61 private static final String NODE_PARM = "parm"; 62 private static final String PARM_NAME = "name"; 63 private static final String PARM_VALUE = "value"; 64 65 private final Map<String, Map<String, String>> mNodesMap = new ArrayMap<>(); 66 XmlDoc(String responseBody)67 public XmlDoc(String responseBody) { 68 parseXmlResponse(responseBody); 69 } 70 getFromToken(String key)71 public Optional<String> getFromToken(String key) { 72 Map<String, String> paramsMap = mNodesMap.get(TOKEN); 73 return Optional.ofNullable(paramsMap == null ? null : paramsMap.get(key)); 74 } 75 getFromVersion(String key)76 public Optional<String> getFromVersion(String key) { 77 Map<String, String> paramsMap = mNodesMap.get(VERS); 78 return Optional.ofNullable(paramsMap == null ? null : paramsMap.get(key)); 79 } 80 getFromVowifi(String key)81 public Optional<String> getFromVowifi(String key) { 82 Map<String, String> paramsMap = mNodesMap.get(ServiceEntitlement.APP_VOWIFI); 83 return Optional.ofNullable(paramsMap == null ? null : paramsMap.get(key)); 84 } 85 getFromVolte(String key)86 public Optional<String> getFromVolte(String key) { 87 Map<String, String> paramsMap = mNodesMap.get(ServiceEntitlement.APP_VOLTE); 88 paramsMap = paramsMap == null ? mNodesMap.get(LTE + ALL) : paramsMap; 89 paramsMap = paramsMap == null ? mNodesMap.get(LTE + HOME) : paramsMap; 90 return Optional.ofNullable(paramsMap == null ? null : paramsMap.get(key)); 91 } 92 getFromSmsoverip(String key)93 public Optional<String> getFromSmsoverip(String key) { 94 Map<String, String> paramsMap = mNodesMap.get(ServiceEntitlement.APP_SMSOIP); 95 return Optional.ofNullable(paramsMap == null ? null : paramsMap.get(key)); 96 } 97 getFromVonrHome(String key)98 public Optional<String> getFromVonrHome(String key) { 99 Map<String, String> paramsMap = mNodesMap.get(NGRAN + ALL); 100 paramsMap = paramsMap == null ? mNodesMap.get(NGRAN + HOME) : paramsMap; 101 return Optional.ofNullable(paramsMap == null ? null : paramsMap.get(key)); 102 } 103 getFromVonrRoaming(String key)104 public Optional<String> getFromVonrRoaming(String key) { 105 Map<String, String> paramsMap = mNodesMap.get(NGRAN + ALL); 106 paramsMap = paramsMap == null ? mNodesMap.get(NGRAN + ROAMING) : paramsMap; 107 return Optional.ofNullable(paramsMap == null ? null : paramsMap.get(key)); 108 } 109 110 /** 111 * Parses the response body as per format defined in TS.43 2.7.2 New Characteristics for 112 * XML-Based Document. 113 */ parseXmlResponse(String responseBody)114 private void parseXmlResponse(String responseBody) { 115 if (responseBody == null) { 116 return; 117 } 118 119 if (DebugUtils.isPiiLoggable()) { 120 Log.d(TAG, "Raw Response Body: " + responseBody); 121 } 122 // Workaround: some server doesn't escape "&" in XML response and that will cause XML parser 123 // failure later. 124 // This is a quick impl of escaping w/o intorducing a ton of new dependencies. 125 responseBody = responseBody.replace("&", "&").replace("&amp;", "&"); 126 127 try { 128 InputSource inputSource = new InputSource(new StringReader(responseBody)); 129 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); 130 DocumentBuilder docBuilder = builderFactory.newDocumentBuilder(); 131 Document doc = docBuilder.parse(inputSource); 132 doc.getDocumentElement().normalize(); 133 134 if (DebugUtils.isPiiLoggable()) { 135 Log.d( 136 TAG, 137 "parseXmlResponseForNode() Root element: " 138 + doc.getDocumentElement().getNodeName()); 139 } 140 141 NodeList nodeList = doc.getElementsByTagName(NODE_CHARACTERISTIC); 142 for (int i = 0; i < nodeList.getLength(); i++) { 143 NamedNodeMap map = nodeList.item(i).getAttributes(); 144 if (DebugUtils.isPiiLoggable()) { 145 Log.d( 146 TAG, 147 "parseAuthenticateResponse() node name=" 148 + nodeList.item(i).getNodeName() 149 + " node value=" 150 + map.item(0).getNodeValue()); 151 } 152 Element element = (Element) nodeList.item(i); 153 if (element.getElementsByTagName(NODE_CHARACTERISTIC).getLength() != 0) { 154 continue; 155 } 156 157 Map<String, String> paramsMap = new ArrayMap<>(); 158 String characteristicType = map.item(0).getNodeValue(); 159 String key; 160 paramsMap.putAll(parseParams(element.getElementsByTagName(NODE_PARM))); 161 if (APPLICATION.equals(characteristicType)) { 162 key = paramsMap.get(APP_ID); 163 } else if (RAT_VOICE_ENTITLE_INFO_DETAILS.equals(characteristicType)) { 164 key = paramsMap.get(ACCESS_TYPE) + paramsMap.get(HOME_ROAMING_NW_TYPE); 165 } else { // VERS or TOKEN 166 key = characteristicType; 167 } 168 mNodesMap.put(key, paramsMap); 169 } 170 } catch (ParserConfigurationException | IOException | SAXException e) { 171 Log.e(TAG, "Failed to parse XML node. " + e); 172 } 173 } 174 parseParams(NodeList nodeList)175 private static Map<String, String> parseParams(NodeList nodeList) { 176 Map<String, String> nameValue = new ArrayMap<>(); 177 for (int i = 0; i < nodeList.getLength(); i++) { 178 Node node = nodeList.item(i); 179 NamedNodeMap map = node.getAttributes(); 180 String name = ""; 181 String value = ""; 182 for (int j = 0; j < map.getLength(); j++) { 183 if (PARM_NAME.equals(map.item(j).getNodeName())) { 184 name = map.item(j).getNodeValue(); 185 } else if (PARM_VALUE.equals(map.item(j).getNodeName())) { 186 value = map.item(j).getNodeValue(); 187 } 188 } 189 if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) { 190 continue; 191 } 192 nameValue.put(name, value); 193 194 if (DebugUtils.isPiiLoggable()) { 195 Log.d(TAG, "parseParams() put name '" + name + "' with value " + value); 196 } 197 } 198 return nameValue; 199 } 200 } 201