15f96702f582050c1598136ed2a748f76b981c94eChad Brubakerpackage android.security.net.config;
25f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
35f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.content.Context;
45f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.content.res.Resources;
55f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.content.res.XmlResourceParser;
632d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubakerimport android.os.Build;
75f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.util.ArraySet;
85f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.util.Base64;
95f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport android.util.Pair;
1032d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubakerimport com.android.internal.annotations.VisibleForTesting;
115f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport com.android.internal.util.XmlUtils;
125f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
135f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport org.xmlpull.v1.XmlPullParser;
145f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport org.xmlpull.v1.XmlPullParserException;
155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
165f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.io.IOException;
175f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.text.ParseException;
185f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.text.SimpleDateFormat;
195f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.ArrayList;
205f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Collection;
215f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Date;
225f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.List;
235f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Locale;
245f96702f582050c1598136ed2a748f76b981c94eChad Brubakerimport java.util.Set;
255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
265f96702f582050c1598136ed2a748f76b981c94eChad Brubaker/**
275f96702f582050c1598136ed2a748f76b981c94eChad Brubaker * {@link ConfigSource} based on an XML configuration file.
285f96702f582050c1598136ed2a748f76b981c94eChad Brubaker *
295f96702f582050c1598136ed2a748f76b981c94eChad Brubaker * @hide
305f96702f582050c1598136ed2a748f76b981c94eChad Brubaker */
315f96702f582050c1598136ed2a748f76b981c94eChad Brubakerpublic class XmlConfigSource implements ConfigSource {
3208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    private static final int CONFIG_BASE = 0;
3308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    private static final int CONFIG_DOMAIN = 1;
3408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    private static final int CONFIG_DEBUG = 2;
3508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker
365f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private final Object mLock = new Object();
375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private final int mResourceId;
3808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    private final boolean mDebugBuild;
3932d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker    private final int mTargetSdkVersion;
405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
415f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private boolean mInitialized;
425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private NetworkSecurityConfig mDefaultConfig;
435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private Set<Pair<Domain, NetworkSecurityConfig>> mDomainMap;
445f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private Context mContext;
455f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
4632d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker    @VisibleForTesting
475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    public XmlConfigSource(Context context, int resourceId) {
4808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        this(context, resourceId, false);
4908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    }
5008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker
5132d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker    @VisibleForTesting
5208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    public XmlConfigSource(Context context, int resourceId, boolean debugBuild) {
5332d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker        this(context, resourceId, debugBuild, Build.VERSION_CODES.CUR_DEVELOPMENT);
5432d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker    }
5532d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker
5632d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker    public XmlConfigSource(Context context, int resourceId, boolean debugBuild,
5732d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker            int targetSdkVersion) {
585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        mResourceId = resourceId;
595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        mContext = context;
6008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        mDebugBuild = debugBuild;
6132d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker        mTargetSdkVersion = targetSdkVersion;
625f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
645f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    public Set<Pair<Domain, NetworkSecurityConfig>> getPerDomainConfigs() {
655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        ensureInitialized();
665f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        return mDomainMap;
675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    public NetworkSecurityConfig getDefaultConfig() {
705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        ensureInitialized();
715f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        return mDefaultConfig;
725f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
735f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
7408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    private static final String getConfigString(int configType) {
7508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        switch (configType) {
7608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            case CONFIG_BASE:
7708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                return "base-config";
7808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            case CONFIG_DOMAIN:
7908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                return "domain-config";
8008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            case CONFIG_DEBUG:
8108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                return "debug-overrides";
8208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            default:
8308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                throw new IllegalArgumentException("Unknown config type: " + configType);
8408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        }
8508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    }
8608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker
875f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private void ensureInitialized() {
885f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        synchronized (mLock) {
895f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            if (mInitialized) {
905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                return;
915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            }
925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            try (XmlResourceParser parser = mContext.getResources().getXml(mResourceId)) {
935f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                parseNetworkSecurityConfig(parser);
945f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                mContext = null;
955f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                mInitialized = true;
965f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } catch (Resources.NotFoundException | XmlPullParserException | IOException
975f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    | ParserException e) {
985f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                throw new RuntimeException("Failed to parse XML configuration from "
995f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                        + mContext.getResources().getResourceEntryName(mResourceId), e);
1005f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            }
1015f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1025f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
1035f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
1045f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private Pin parsePin(XmlResourceParser parser)
1055f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throws IOException, XmlPullParserException, ParserException {
1065f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        String digestAlgorithm = parser.getAttributeValue(null, "digest");
1075f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (!Pin.isSupportedDigestAlgorithm(digestAlgorithm)) {
1085f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "Unsupported pin digest algorithm: "
1095f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    + digestAlgorithm);
1105f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1115f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (parser.next() != XmlPullParser.TEXT) {
1125f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "Missing pin digest");
1135f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1147cc736da82b814b383daaa59609372917fd004cdChad Brubaker        String digest = parser.getText().trim();
1155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        byte[] decodedDigest = null;
1165f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        try {
1175f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            decodedDigest = Base64.decode(digest, 0);
1185f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        } catch (IllegalArgumentException e) {
1195f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "Invalid pin digest", e);
1205f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1215f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        int expectedLength = Pin.getDigestLength(digestAlgorithm);
1225f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (decodedDigest.length != expectedLength) {
1235f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "digest length " + decodedDigest.length
1245f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    + " does not match expected length for " + digestAlgorithm + " of "
1255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    + expectedLength);
1265f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1275f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (parser.next() != XmlPullParser.END_TAG) {
1285f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "pin contains additional elements");
1295f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1305f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        return new Pin(digestAlgorithm, decodedDigest);
1315f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
1325f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
1335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private PinSet parsePinSet(XmlResourceParser parser)
1345f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throws IOException, XmlPullParserException, ParserException {
1355f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        String expirationDate = parser.getAttributeValue(null, "expiration");
1365f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        long expirationTimestampMilis = Long.MAX_VALUE;
1375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (expirationDate != null) {
1385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            try {
1395f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
1405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                sdf.setLenient(false);
1415f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                Date date = sdf.parse(expirationDate);
1425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                if (date == null) {
1435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    throw new ParserException(parser, "Invalid expiration date in pin-set");
1445f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                }
1455f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                expirationTimestampMilis = date.getTime();
1465f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } catch (ParseException e) {
1475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                throw new ParserException(parser, "Invalid expiration date in pin-set", e);
1485f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            }
1495f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1505f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
1515f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        int outerDepth = parser.getDepth();
1525f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        Set<Pin> pins = new ArraySet<>();
1535f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1545f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            String tagName = parser.getName();
1555f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            if (tagName.equals("pin")) {
1565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                pins.add(parsePin(parser));
1575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } else {
1585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                XmlUtils.skipCurrentTag(parser);
1595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            }
1605f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1615f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        return new PinSet(pins, expirationTimestampMilis);
1625f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
1635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
1645f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private Domain parseDomain(XmlResourceParser parser, Set<String> seenDomains)
1655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throws IOException, XmlPullParserException, ParserException {
1665f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        boolean includeSubdomains =
1675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                parser.getAttributeBooleanValue(null, "includeSubdomains", false);
1685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (parser.next() != XmlPullParser.TEXT) {
1695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "Domain name missing");
1705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1717cc736da82b814b383daaa59609372917fd004cdChad Brubaker        String domain = parser.getText().trim().toLowerCase(Locale.US);
1725f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (parser.next() != XmlPullParser.END_TAG) {
1735f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "domain contains additional elements");
1745f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1755f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        // Domains are matched using a most specific match, so don't allow duplicates.
1765f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        // includeSubdomains isn't relevant here, both android.com + subdomains and android.com
1775f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        // match for android.com equally. Do not allow any duplicates period.
1785f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (!seenDomains.add(domain)) {
1795f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, domain + " has already been specified");
1805f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1815f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        return new Domain(domain, includeSubdomains);
1825f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
1835f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
18408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    private CertificatesEntryRef parseCertificatesEntry(XmlResourceParser parser,
18508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            boolean defaultOverridePins)
1865f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throws IOException, XmlPullParserException, ParserException {
18708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        boolean overridePins =
18808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                parser.getAttributeBooleanValue(null, "overridePins", defaultOverridePins);
1895f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        int sourceId = parser.getAttributeResourceValue(null, "src", -1);
1905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        String sourceString = parser.getAttributeValue(null, "src");
1915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        CertificateSource source = null;
1925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (sourceString == null) {
1935f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "certificates element missing src attribute");
1945f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
1955f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (sourceId != -1) {
1965f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            // TODO: Cache ResourceCertificateSources by sourceId
1975f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            source = new ResourceCertificateSource(sourceId, mContext);
1985f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        } else if ("system".equals(sourceString)) {
1995f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            source = SystemCertificateSource.getInstance();
2005f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        } else if ("user".equals(sourceString)) {
2015f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            source = UserCertificateSource.getInstance();
2025f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        } else {
2035f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "Unknown certificates src. "
2045f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    + "Should be one of system|user|@resourceVal");
2055f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
2065f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        XmlUtils.skipCurrentTag(parser);
2075f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        return new CertificatesEntryRef(source, overridePins);
2085f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
2095f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
21008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    private Collection<CertificatesEntryRef> parseTrustAnchors(XmlResourceParser parser,
21108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            boolean defaultOverridePins)
2125f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throws IOException, XmlPullParserException, ParserException {
2135f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        int outerDepth = parser.getDepth();
2145f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        List<CertificatesEntryRef> anchors = new ArrayList<>();
2155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
2165f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            String tagName = parser.getName();
2175f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            if (tagName.equals("certificates")) {
21808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                anchors.add(parseCertificatesEntry(parser, defaultOverridePins));
2195f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } else {
2205f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                XmlUtils.skipCurrentTag(parser);
2215f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            }
2225f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
2235f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        return anchors;
2245f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
2255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
226bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker    private List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> parseConfigEntry(
227bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            XmlResourceParser parser, Set<String> seenDomains,
22808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            NetworkSecurityConfig.Builder parentBuilder, int configType)
2295f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throws IOException, XmlPullParserException, ParserException {
230bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker        List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>();
2315f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        NetworkSecurityConfig.Builder builder = new NetworkSecurityConfig.Builder();
232bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker        builder.setParent(parentBuilder);
2335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        Set<Domain> domains = new ArraySet<>();
2345f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        boolean seenPinSet = false;
2355f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        boolean seenTrustAnchors = false;
23608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        boolean defaultOverridePins = configType == CONFIG_DEBUG;
2375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        String configName = parser.getName();
2385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        int outerDepth = parser.getDepth();
239bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker        // Add this builder now so that this builder occurs before any of its children. This
240bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker        // makes the final build pass easier.
241bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker        builders.add(new Pair<>(builder, domains));
2425f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        // Parse config attributes. Only set values that are present, config inheritence will
2435f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        // handle the rest.
2445f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        for (int i = 0; i < parser.getAttributeCount(); i++) {
2455f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            String name = parser.getAttributeName(i);
2465f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            if ("hstsEnforced".equals(name)) {
2475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                builder.setHstsEnforced(
2485f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                        parser.getAttributeBooleanValue(i,
2495f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                                NetworkSecurityConfig.DEFAULT_HSTS_ENFORCED));
2505f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } else if ("cleartextTrafficPermitted".equals(name)) {
2515f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                builder.setCleartextTrafficPermitted(
2525f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                        parser.getAttributeBooleanValue(i,
2535f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                                NetworkSecurityConfig.DEFAULT_CLEARTEXT_TRAFFIC_PERMITTED));
2545f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            }
2555f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
2565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        // Parse the config elements.
2575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
2585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            String tagName = parser.getName();
2595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            if ("domain".equals(tagName)) {
26008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                if (configType != CONFIG_DOMAIN) {
26108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                    throw new ParserException(parser,
26208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                            "domain element not allowed in " + getConfigString(configType));
2635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                }
2645f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                Domain domain = parseDomain(parser, seenDomains);
2655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                domains.add(domain);
2665f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } else if ("trust-anchors".equals(tagName)) {
2675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                if (seenTrustAnchors) {
2685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    throw new ParserException(parser,
2695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                            "Multiple trust-anchor elements not allowed");
2705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                }
27108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                builder.addCertificatesEntryRefs(
27208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                        parseTrustAnchors(parser, defaultOverridePins));
2735f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                seenTrustAnchors = true;
2745f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } else if ("pin-set".equals(tagName)) {
27508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                if (configType != CONFIG_DOMAIN) {
2765f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    throw new ParserException(parser,
27708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                            "pin-set element not allowed in " + getConfigString(configType));
2785f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                }
2795f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                if (seenPinSet) {
2805f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    throw new ParserException(parser, "Multiple pin-set elements not allowed");
2815f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                }
2825f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                builder.setPinSet(parsePinSet(parser));
2835f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                seenPinSet = true;
284bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            } else if ("domain-config".equals(tagName)) {
28508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                if (configType != CONFIG_DOMAIN) {
286bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker                    throw new ParserException(parser,
28708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                            "Nested domain-config not allowed in " + getConfigString(configType));
288bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker                }
28908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                builders.addAll(parseConfigEntry(parser, seenDomains, builder, configType));
2905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } else {
2915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                XmlUtils.skipCurrentTag(parser);
2925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            }
2935f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
29408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        if (configType == CONFIG_DOMAIN && domains.isEmpty()) {
2955f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throw new ParserException(parser, "No domain elements in domain-config");
2965f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
297bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker        return builders;
2985f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
2995f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
30008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    private void addDebugAnchorsIfNeeded(NetworkSecurityConfig.Builder debugConfigBuilder,
30108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            NetworkSecurityConfig.Builder builder) {
30208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        if (debugConfigBuilder == null || !debugConfigBuilder.hasCertificatesEntryRefs()) {
30308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            return;
30408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        }
30508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        // Don't add trust anchors if not already present, the builder will inherit the anchors
30608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        // from its parent, and that's where the trust anchors should be added.
30708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        if (!builder.hasCertificatesEntryRefs()) {
30808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            return;
30908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        }
31008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker
31108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        builder.addCertificatesEntryRefs(debugConfigBuilder.getCertificatesEntryRefs());
31208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker    }
31308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker
3145f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    private void parseNetworkSecurityConfig(XmlResourceParser parser)
3155f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            throws IOException, XmlPullParserException, ParserException {
3165f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        Set<String> seenDomains = new ArraySet<>();
3175f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        List<Pair<NetworkSecurityConfig.Builder, Set<Domain>>> builders = new ArrayList<>();
3185f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        NetworkSecurityConfig.Builder baseConfigBuilder = null;
31908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        NetworkSecurityConfig.Builder debugConfigBuilder = null;
3205f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        boolean seenDebugOverrides = false;
3215f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        boolean seenBaseConfig = false;
3225f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
3235f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        XmlUtils.beginDocument(parser, "network-security-config");
3245f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        int outerDepth = parser.getDepth();
3255f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
3265f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            if ("base-config".equals(parser.getName())) {
3275f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                if (seenBaseConfig) {
3285f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                    throw new ParserException(parser, "Only one base-config allowed");
3295f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                }
3305f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                seenBaseConfig = true;
33108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                baseConfigBuilder =
33208d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                        parseConfigEntry(parser, seenDomains, null, CONFIG_BASE).get(0).first;
3335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } else if ("domain-config".equals(parser.getName())) {
33408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                builders.addAll(
33508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                        parseConfigEntry(parser, seenDomains, baseConfigBuilder, CONFIG_DOMAIN));
33608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            } else if ("debug-overrides".equals(parser.getName())) {
33708d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                if (seenDebugOverrides) {
33808d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                    throw new ParserException(parser, "Only one debug-overrides allowed");
33908d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                }
34008d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                if (mDebugBuild) {
34108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                    debugConfigBuilder =
342567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                            parseConfigEntry(parser, null, null, CONFIG_DEBUG).get(0).first;
34308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                } else {
34408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                    XmlUtils.skipCurrentTag(parser);
34508d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                }
34608d36202daeb3e668911c9902edb61b6894f822eChad Brubaker                seenDebugOverrides = true;
3475f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            } else {
3485f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                XmlUtils.skipCurrentTag(parser);
3495f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            }
3505f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
351567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        // If debug is true and there was no debug-overrides in the file check for an extra
352567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        // _debug resource.
353567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        if (mDebugBuild && debugConfigBuilder == null) {
354567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker            debugConfigBuilder = parseDebugOverridesResource();
355567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        }
3565f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
3575f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        // Use the platform default as the parent of the base config for any values not provided
3585f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        // there. If there is no base config use the platform default.
3595f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        NetworkSecurityConfig.Builder platformDefaultBuilder =
36032d2a1024f75f7e917f2aca18d34322a46d36bcbChad Brubaker                NetworkSecurityConfig.getDefaultBuilder(mTargetSdkVersion);
36108d36202daeb3e668911c9902edb61b6894f822eChad Brubaker        addDebugAnchorsIfNeeded(debugConfigBuilder, platformDefaultBuilder);
3625f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        if (baseConfigBuilder != null) {
3635f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            baseConfigBuilder.setParent(platformDefaultBuilder);
36408d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            addDebugAnchorsIfNeeded(debugConfigBuilder, baseConfigBuilder);
3655f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        } else {
3665f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            baseConfigBuilder = platformDefaultBuilder;
3675f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
3685f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        // Build the per-domain config mapping.
3695f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        Set<Pair<Domain, NetworkSecurityConfig>> configs = new ArraySet<>();
3705f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
3715f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        for (Pair<NetworkSecurityConfig.Builder, Set<Domain>> entry : builders) {
3725f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            NetworkSecurityConfig.Builder builder = entry.first;
3735f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            Set<Domain> domains = entry.second;
374bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            // Set the parent of configs that do not have a parent to the base-config. This can
375bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            // happen if the base-config comes after a domain-config in the file.
376bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            // Note that this is safe with regards to children because of the order that
377bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            // parseConfigEntry returns builders, the parent is always before the children. The
378bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            // children builders will not have build called until _after_ their parents have their
379bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            // parent set so everything is consistent.
380bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            if (builder.getParent() == null) {
381bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker                builder.setParent(baseConfigBuilder);
382bd173c28fcded629da722c6669f1b6478cdcd94fChad Brubaker            }
38308d36202daeb3e668911c9902edb61b6894f822eChad Brubaker            addDebugAnchorsIfNeeded(debugConfigBuilder, builder);
3845f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            NetworkSecurityConfig config = builder.build();
3855f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            for (Domain domain : domains) {
3865f96702f582050c1598136ed2a748f76b981c94eChad Brubaker                configs.add(new Pair<>(domain, config));
3875f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            }
3885f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
3895f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        mDefaultConfig = baseConfigBuilder.build();
3905f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        mDomainMap = configs;
3915f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
3925f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
393567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker    private NetworkSecurityConfig.Builder parseDebugOverridesResource()
394567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker            throws IOException, XmlPullParserException, ParserException {
395567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        Resources resources = mContext.getResources();
396567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        String packageName = resources.getResourcePackageName(mResourceId);
397567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        String entryName = resources.getResourceEntryName(mResourceId);
398567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        int resId = resources.getIdentifier(entryName + "_debug", "xml", packageName);
399567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        // No debug-overrides resource was found, nothing to parse.
400567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        if (resId == 0) {
401567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker            return null;
402567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        }
403567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        NetworkSecurityConfig.Builder debugConfigBuilder = null;
404567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        // Parse debug-overrides out of the _debug resource.
405567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        try (XmlResourceParser parser = resources.getXml(resId)) {
406567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker            XmlUtils.beginDocument(parser, "network-security-config");
407567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker            int outerDepth = parser.getDepth();
408567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker            boolean seenDebugOverrides = false;
409567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker            while (XmlUtils.nextElementWithin(parser, outerDepth)) {
410567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                if ("debug-overrides".equals(parser.getName())) {
411567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                    if (seenDebugOverrides) {
412567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                        throw new ParserException(parser, "Only one debug-overrides allowed");
413567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                    }
414567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                    if (mDebugBuild) {
415567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                        debugConfigBuilder =
416567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                                parseConfigEntry(parser, null, null, CONFIG_DEBUG).get(0).first;
417567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                    } else {
418567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                        XmlUtils.skipCurrentTag(parser);
419567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                    }
420567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                    seenDebugOverrides = true;
421567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                } else {
422567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                    XmlUtils.skipCurrentTag(parser);
423567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker                }
424567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker            }
425567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        }
426567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker
427567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker        return debugConfigBuilder;
428567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker    }
429567f6f24747c80b4ab362a22985576c4f8a418fdChad Brubaker
4305f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    public static class ParserException extends Exception {
4315f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
4325f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        public ParserException(XmlPullParser parser, String message, Throwable cause) {
4335f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            super(message + " at: " + parser.getPositionDescription(), cause);
4345f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
4355f96702f582050c1598136ed2a748f76b981c94eChad Brubaker
4365f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        public ParserException(XmlPullParser parser, String message) {
4375f96702f582050c1598136ed2a748f76b981c94eChad Brubaker            this(parser, message, null);
4385f96702f582050c1598136ed2a748f76b981c94eChad Brubaker        }
4395f96702f582050c1598136ed2a748f76b981c94eChad Brubaker    }
4405f96702f582050c1598136ed2a748f76b981c94eChad Brubaker}
441