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.ResponseXmlAttributes.APP_ID; 20 import static com.android.imsserviceentitlement.ts43.Ts43Constants.ResponseXmlNode.APPLICATION; 21 22 import android.text.TextUtils; 23 import android.util.ArrayMap; 24 import android.util.Log; 25 26 import androidx.annotation.Nullable; 27 28 import com.android.imsserviceentitlement.debug.DebugUtils; 29 30 import org.w3c.dom.Document; 31 import org.w3c.dom.Element; 32 import org.w3c.dom.NamedNodeMap; 33 import org.w3c.dom.Node; 34 import org.w3c.dom.NodeList; 35 import org.xml.sax.InputSource; 36 import org.xml.sax.SAXException; 37 38 import java.io.IOException; 39 import java.io.StringReader; 40 import java.util.Map; 41 import java.util.Optional; 42 43 import javax.xml.parsers.DocumentBuilder; 44 import javax.xml.parsers.DocumentBuilderFactory; 45 import javax.xml.parsers.ParserConfigurationException; 46 47 /** Wrap the raw content and parse it into nodes. */ 48 public class XmlDoc { 49 private static final String TAG = "IMSSE-XmlDoc"; 50 51 private static final String NODE_CHARACTERISTIC = "characteristic"; 52 private static final String NODE_PARM = "parm"; 53 private static final String PARM_NAME = "name"; 54 private static final String PARM_VALUE = "value"; 55 56 private final Map<String, Map<String, String>> mNodesMap = new ArrayMap<>(); 57 XmlDoc(String responseBody)58 public XmlDoc(String responseBody) { 59 parseXmlResponse(responseBody); 60 } 61 62 /** Returns param value for given node and key. */ get(String node, String key, @Nullable String appId)63 public Optional<String> get(String node, String key, @Nullable String appId) { 64 Map<String, String> paramsMap = mNodesMap.get(combineKeyWithAppId(node, appId)); 65 return Optional.ofNullable(paramsMap == null ? null : paramsMap.get(key)); 66 } 67 combineKeyWithAppId(String node, @Nullable String appId)68 private String combineKeyWithAppId(String node, @Nullable String appId) { 69 return APPLICATION.equals(node) && !TextUtils.isEmpty(appId) ? node + "_" + appId : node; 70 } 71 72 /** 73 * Parses the response body as per format defined in TS.43 2.7.2 New Characteristics for 74 * XML-Based Document. 75 */ parseXmlResponse(String responseBody)76 private void parseXmlResponse(String responseBody) { 77 if (responseBody == null) { 78 return; 79 } 80 81 // Workaround: some server doesn't escape "&" in XML response and that will cause XML parser 82 // failure later. 83 // This is a quick impl of escaping w/o intorducing a ton of new dependencies. 84 responseBody = responseBody.replace("&", "&").replace("&amp;", "&"); 85 86 try { 87 InputSource inputSource = new InputSource(new StringReader(responseBody)); 88 DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance(); 89 DocumentBuilder docBuilder = builderFactory.newDocumentBuilder(); 90 Document doc = docBuilder.parse(inputSource); 91 doc.getDocumentElement().normalize(); 92 93 if (DebugUtils.isPiiLoggable()) { 94 Log.d( 95 TAG, 96 "parseXmlResponseForNode() Root element: " 97 + doc.getDocumentElement().getNodeName()); 98 } 99 100 NodeList nodeList = doc.getElementsByTagName(NODE_CHARACTERISTIC); 101 for (int i = 0; i < nodeList.getLength(); i++) { 102 NamedNodeMap map = nodeList.item(i).getAttributes(); 103 if (DebugUtils.isPiiLoggable()) { 104 Log.d( 105 TAG, 106 "parseAuthenticateResponse() node name=" 107 + nodeList.item(i).getNodeName() 108 + " node value=" 109 + map.item(0).getNodeValue()); 110 } 111 Map<String, String> paramsMap = new ArrayMap<>(); 112 Element element = (Element) nodeList.item(i); 113 paramsMap.putAll(parseParams(element.getElementsByTagName(NODE_PARM))); 114 mNodesMap.put( 115 combineKeyWithAppId(map.item(0).getNodeValue(), paramsMap.get(APP_ID)), 116 paramsMap); 117 } 118 } catch (ParserConfigurationException | IOException | SAXException e) { 119 Log.e(TAG, "Failed to parse XML node. " + e); 120 } 121 } 122 parseParams(NodeList nodeList)123 private static Map<String, String> parseParams(NodeList nodeList) { 124 Map<String, String> nameValue = new ArrayMap<>(); 125 for (int i = 0; i < nodeList.getLength(); i++) { 126 Node node = nodeList.item(i); 127 NamedNodeMap map = node.getAttributes(); 128 String name = ""; 129 String value = ""; 130 for (int j = 0; j < map.getLength(); j++) { 131 if (PARM_NAME.equals(map.item(j).getNodeName())) { 132 name = map.item(j).getNodeValue(); 133 } else if (PARM_VALUE.equals(map.item(j).getNodeName())) { 134 value = map.item(j).getNodeValue(); 135 } 136 } 137 if (TextUtils.isEmpty(name) || TextUtils.isEmpty(value)) { 138 continue; 139 } 140 nameValue.put(name, value); 141 142 if (DebugUtils.isPiiLoggable()) { 143 Log.d(TAG, "parseParams() put name '" + name + "' with value " + value); 144 } 145 } 146 return nameValue; 147 } 148 } 149