| /* |
| * Copyright (C) 2023 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.content.res; |
| |
| import android.annotation.NonNull; |
| import android.annotation.StyleableRes; |
| |
| import com.android.internal.R; |
| |
| import org.xmlpull.v1.XmlPullParser; |
| import org.xmlpull.v1.XmlPullParserException; |
| |
| import java.util.ArrayDeque; |
| |
| /** |
| * Validates manifest files by ensuring that tag counts and the length of string attributes are |
| * restricted. |
| * |
| * {@hide} |
| */ |
| public class Validator { |
| |
| private final ArrayDeque<Element> mElements = new ArrayDeque<>(); |
| |
| private void cleanUp() { |
| while (!mElements.isEmpty()) { |
| mElements.pop().recycle(); |
| } |
| } |
| |
| /** |
| * Validates the elements and it's attributes as the XmlPullParser traverses the xml. |
| */ |
| public void validate(@NonNull XmlPullParser parser) throws XmlPullParserException { |
| int eventType = parser.getEventType(); |
| int depth = parser.getDepth(); |
| // The mElement size should equal to the parser depth-1 when the parser eventType is |
| // START_TAG. If depth - mElement.size() is larger than 1 then that means |
| // validation for the previous element was skipped so we should skip validation for all |
| // descendant elements as well |
| if (depth > mElements.size() + 1) { |
| return; |
| } |
| if (eventType == XmlPullParser.START_TAG) { |
| String tag = parser.getName(); |
| if (Element.shouldValidate(tag)) { |
| Element element = Element.obtain(tag); |
| Element parent = mElements.peek(); |
| if (parent != null && parent.hasChild(tag)) { |
| try { |
| parent.seen(element); |
| } catch (SecurityException e) { |
| cleanUp(); |
| throw e; |
| } |
| } |
| mElements.push(element); |
| } |
| } else if (eventType == XmlPullParser.END_TAG && depth == mElements.size()) { |
| mElements.pop().recycle(); |
| } else if (eventType == XmlPullParser.END_DOCUMENT) { |
| cleanUp(); |
| } |
| } |
| |
| /** |
| * Validates the resource string of a manifest tag attribute. |
| */ |
| public void validateResStrAttr(@NonNull XmlPullParser parser, @StyleableRes int index, |
| CharSequence stringValue) { |
| if (parser.getDepth() > mElements.size()) { |
| return; |
| } |
| mElements.peek().validateResStrAttr(index, stringValue); |
| if (index == R.styleable.AndroidManifestMetaData_value) { |
| validateComponentMetadata(stringValue.toString()); |
| } |
| } |
| |
| /** |
| * Validates the string of a manifest tag attribute by name. |
| */ |
| public void validateStrAttr(@NonNull XmlPullParser parser, String attrName, String attrValue) { |
| if (parser.getDepth() > mElements.size()) { |
| return; |
| } |
| mElements.peek().validateStrAttr(attrName, attrValue); |
| if (attrName.equals(Element.TAG_ATTR_VALUE)) { |
| validateComponentMetadata(attrValue); |
| } |
| } |
| |
| private void validateComponentMetadata(String attrValue) { |
| Element element = mElements.peek(); |
| // Meta-data values are evaluated on the parent element which is the next element in the |
| // mElements stack after the meta-data element. The top of the stack is always the current |
| // element being validated so check that the top element is meta-data. |
| if (element.mTag.equals(Element.TAG_META_DATA) && mElements.size() > 1) { |
| element = mElements.pop(); |
| mElements.peek().validateComponentMetadata(attrValue); |
| mElements.push(element); |
| } |
| } |
| } |